diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4ced76d76..21d16d195 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -5,10 +5,12 @@ IMPORTANT NOTES 1. Use GH flavored markdown when writing your description: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax -2. If all boxes in the PR Checklist cannot be checked, this PR should be marked as a draft. +2. Draft PRs should submitted for incomplete PRs if one of the following criteria can be met: + - Feedback or further discussion is required prior to completion of the contribution + - If all boxes in the PR Checklist cannot be checked, but feedback is desired prior to completion -3. DO NOT DELETE ANYTHING FROM THIS TEMPLATE. If a section does not apply to you, simply write - "N/A" in the description. +3. DO NOT DELETE REQUIRED SECTIONS FROM THIS TEMPLATE. Optional sections may be deleted for PRs that + are ready for review or you may simply write "N/A" in the description. 4. Code snippets to highlight new, modified, or problematic functionality are highly encouraged, though not required. Be sure to use proper code highlighting as demonstrated below. @@ -28,7 +30,8 @@ IMPORTANT NOTES [Add description of feature here] -## Section 1: Type of Contribution + +## Type of Contribution - [ ] Feature Enhancement - [ ] Framework @@ -41,7 +44,9 @@ IMPORTANT NOTES - [ ] CI Changes - [ ] Other (please describe): -## Section 2: Draft PR Checklist + + +## Draft PR Checklist - [ ] Open draft PR @@ -50,7 +55,9 @@ IMPORTANT NOTES - [ ] Describe requested feedback from reviewers on draft PR - [ ] Complete Section 8: New Model Checklist (if applicable) -### TODO: + +### TODO + - [ ] Step 1 - [ ] Step 2 @@ -62,60 +69,82 @@ IMPORTANT NOTES **Other feedback**: -## Section 3: General PR Checklist + +## General PR Checklist - [ ] PR description thoroughly describes the new feature, bug fix, etc. -- [ ] Added tests for new functionality or bug fixes -- [ ] Tests pass (If not, and this is expected, please elaborate in the Section 6: Test Results) +- [ ] New model checklist has been completed (new models only) +- [ ] Tests + - [ ] All tests pass locally + - [ ] Added tests for new functionality or bug fixes + - [ ] Modified tests have been outlined in the impacted areas section - [ ] Documentation - [ ] Docstrings are up-to-date - [ ] Related `docs/` files are up-to-date, or added when necessary - [ ] Documentation has been rebuilt successfully - [ ] Examples have been updated (if applicable) + - [ ] No changes required - [ ] `CHANGELOG.md` - [ ] At least one complete sentence has been provided to describe the changes made in this PR - [ ] After the above, a hyperlink has been provided to the PR using the following format: "A complete thought. [PR XYZ]((https://github.com/NatLabRockies/H2Integrate/pull/XYZ)", where `XYZ` should be replaced with the actual number. -## Section 4: Related Issues - + +## Related Issues + +[N/A or appropriately link related issues] -## Section 5: Impacted Areas of the Software + + +## Impacted Areas of the Software -### Section 5.1: New Files +### New Files - `path/to/file.extension` - `method1`: What and why something was changed in one sentence or less. -### Section 5.2: Modified Files +### Modified Files - `path/to/file.extension` - `method1`: What and why something was changed in one sentence or less. -## Section 6: Additional Supporting Information + +## Additional Supporting Information -## Section 7: Test Results, if applicable + +## Test Issues -## Section 8 (Optional): New Model Checklist + +## New Model Checklist - [ ] **Model Structure**: - [ ] Follows established naming conventions outlined in `docs/developer_guide/coding_guidelines.md` - - [ ] Configuration class is named `{ModelName}Config`, where `{ModelName}` is the complete model class name registered with `@register` + - [ ] Configuration class is named `{ModelName}Config`, where `{ModelName}` is the complete model class name - [ ] Used `attrs` class to define the `Config` to load in attributes for the model - [ ] If applicable: inherit from `BaseConfig` or `CostModelBaseConfig` - [ ] Added: `initialize()` method, `setup()` method, `compute()` method - [ ] If applicable: inherit from `PerformanceModelBaseClass` and `CostModelBaseClass` - [ ] **Integration**: Model has been properly integrated into H2Integrate - [ ] Add the new model to the appropriate `__init__.py` file so it can be imported from its subpackage - - [ ] Decorated the model class with `@register` from `h2integrate/core/supported_models.py` + - [ ] Import and apply the register decorator from `h2integrate/core/supported_models.py` to enable H2I to recognize the new model - [ ] **Tests**: Unit tests have been added for the new model - [ ] [Pytest-style unit tests](https://realpython.com/pytest-python-testing/) - [ ] Unit tests are in a "test" folder within the folder a new model was added to @@ -123,18 +152,18 @@ failing test cases. - [ ] **Example**: If applicable, a working example demonstrating the new model has been created - [ ] Input file comments - [ ] Run file comments - - [ ] Example has been tested and runs successfully in `test_all_examples.py` + - [ ] Example has been tested and runs successfully in `test_all_examples.py`, and its run script is named `run_*.py` - [ ] **Documentation**: - [ ] Write docstrings using the [Google style](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html) - [ ] Model documentation page added to the appropriate `docs/` section - [ ] Path to model documentation page is added to the `_toc.yml` - +## Release checklist + +- [ ] Update the version in `h2integrate/__init__.py` +- [ ] Verify the nightly Examples workflow is passing on `develop` - [ ] Verify docs builds correctly - [ ] Create a tag on the main branch in the NatLabRockies/H2Integrate repository and push - [ ] Ensure the Test PyPI build is successful - [ ] Create a release on the main branch ---> diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml new file mode 100644 index 000000000..90caa52b6 --- /dev/null +++ b/.github/workflows/examples.yml @@ -0,0 +1,92 @@ +name: Examples +on: + schedule: + # Nightly at 09:00 UTC (3 AM MDT, 2 AM MST) on the default branch (develop) + - cron: 0 9 * * * + workflow_dispatch: null + pull_request: + branches: + - main + - develop + types: + - opened + - ready_for_review + - review_requested + - enqueued + - auto_merge_enabled + paths: + - examples/** + - h2integrate/control/control_strategies/system_level/test/test_slc_examples.py + - .github/workflows/examples.yml +permissions: + contents: read +concurrency: + group: examples-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} +jobs: + examples: + strategy: + fail-fast: false + matrix: + python-version: + - '3.11' + - '3.13' + runs-on: ubuntu-latest + timeout-minutes: 180 + steps: + - uses: actions/checkout@v7 + - name: Set up Python ${{ matrix.python-version }} + uses: conda-incubator/setup-miniconda@v4 + with: + miniconda-version: latest + auto-update-conda: true + python-version: ${{ matrix.python-version }} + channels: conda-forge,defaults + auto-activate: true + activate-environment: h2integrate + - name: Install conda dependencies + shell: bash -el {0} + run: conda install -y -c conda-forge glpk highspy "coin-or-cbc>=2.10.12" wisdem + - name: Install H2Integrate + shell: bash -el {0} + env: + SKLEARN_ALLOW_DEPRECATED_SKLEARN_PACKAGE_INSTALL: true + run: | + python -m pip install --upgrade pip + pip install ".[develop,extras]" + - name: Create env file + run: touch .env + - name: Run example tests + shell: bash -el {0} + run: pytest . -m examples --durations=30 + report: + needs: examples + if: always() && github.event_name == 'schedule' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Open, update, or close the nightly failure issue + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + RESULT: ${{ needs.examples.result }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: |- + label="nightly-examples" + issue=$(gh issue list --state open --label "$label" --json number --jq '.[0].number // empty') + if [ "$RESULT" = "success" ]; then + if [ -n "$issue" ]; then + gh issue close "$issue" --comment "The nightly examples workflow passed again: $RUN_URL" + fi + elif [ -n "$issue" ]; then + gh issue comment "$issue" --body "Still failing ($RESULT) at $GITHUB_SHA: $RUN_URL" + else + body=$(printf 'The nightly examples workflow failed (%s) on develop at %s.\n\nRun: %s\n\n%s\n' \ + "$RESULT" "$GITHUB_SHA" "$RUN_URL" \ + "Reproduce locally with \`pytest . -m examples\`. Each failing nightly run comments here, and this issue closes automatically when the workflow passes again.") + gh label create "$label" --color B60205 --force \ + --description "Failures reported by the nightly examples workflow" + gh issue create --label "$label" \ + --title "Nightly examples workflow is failing on develop" --body "$body" + fi diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 095784fbb..b3deff7cf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: - id: yamlfix # Exclude YAML files that are used as inputs for testing or examples, or ones for desired schedule in legacy example inputs as they # expect misformatted YAML files. - exclude: ^(h2integrate/core/test/inputs/.*|examples/11_hybrid_energy_plant/.*|library/demand_profiles/.*|examples/34_plm_optimized_dispatch/demand_profiles/.*)$ + exclude: ^(h2integrate/core/test/inputs/.*|library/demand_profiles/.*|examples/34_plm_optimized_dispatch/demand_profiles/.*)$ - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. rev: v0.16.10 diff --git a/CHANGELOG.md b/CHANGELOG.md index fd82bccb8..d540a5809 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,32 @@ # Changelog + ## Unreleased [TBD] +- Add time-marching simulation capability (`n_timesteps_window`) to the h2integrate framework, with steppable example component models. [PR 857](https://github.com/NatLabRockies/H2Integrate/pull/857) - Added a documentation changelog and version indicator, and updated installation, environment variable, and contribution guidance. [PR 917](https://github.com/NatLabRockies/H2Integrate/pull/917) +- Added user references for PEM electrolysis, transport, FLORIS, configuration files, and additional registered models, with API and example links. [PR 918](https://github.com/NatLabRockies/H2Integrate/pull/918) - Updated `make_price_breakdown()` and `run_profast()` in `profast_tools.py` so that fixed cost items and feedstock cost items are properly labelled. [PR 930](https://github.com/NatLabRockies/H2Integrate/pull/930) -- Add data resampling capabilities via pandas methods. [PR 933](https://github.com/NatLabRockies/H2Integrate/pull/933) - - +- Updated the reverse osmosis desalination models to remove the base class, take time-varying `electricity_in` and `feedwater_in` inputs that limit `water_out`, and expose capacity and related config values as OpenMDAO inputs. [PR TBD](https://github.com/NatLabRockies/H2Integrate/pull/TBD) +- Add data resampling capabilities to resource models via pandas methods. [PR 933](https://github.com/NatLabRockies/H2Integrate/pull/933) +- Aligned ammonia feed consumption with the final ramped output (including positive ramp-down output below turndown), added SLC command limiting for ammonia, and aligned PEM hydrogen-linked outputs with clipped production. [PR 942](https://github.com/NatLabRockies/H2Integrate/pull/942) +- Refactored `H2IntegrateModel.connect_technologies()` into separate tech-to-tech, site-to-tech, tech-to-finance, dispatch-rule, and finalization methods with shared helpers. [PR 946](https://github.com/NatLabRockies/H2Integrate/pull/946) +- Moved tests that run full examples into a new `examples` test suite that runs nightly on `develop` and on pull requests that change `examples/`, instead of with the integration tests on every pull request. [PR TBD](https://github.com/NatLabRockies/H2Integrate/pull/TBD) + - Added `examples/test/test_example_scripts.py`, which runs every `run_*.py` example script from a temporary copy, and renamed the example 14 script to `run_hydrogen_dispatch.py`. + - Added the `Examples` GitHub Actions workflow, which opens or updates a `nightly-examples` issue when the nightly run fails and closes it when the run passes again. + - Fixed plotting code in examples 3 (DOC methanol), 18, 19, 23, and 30 that used outdated variable names or config sections, and updated example scripts that relied on `h2integrate.EXAMPLE_DIR` to resolve files relative to the script. + - Reused one example run across the SQL summary unit tests. +- Consolidated duplicate examples so that framework changes touch fewer example files. [PR TBD](https://github.com/NatLabRockies/H2Integrate/pull/TBD) + - Merged `21_iron_examples/iron_dri_nrri` into `21_iron_examples/iron_dri`, whose run script now compares the simple and NRRI iron mine models. + - Merged `38_time_marching_simulation` into `24_solar_battery_grid` as `run_time_marching.py`, since both used the same plant. + - Merged the `no_battery`, `yes_battery`, and `battery_with_controller` system-level control examples into `35_system_level_control/demand_following`. + - Merged `09_co2/ocean_alkalinity_enhancement_financials` into `09_co2/ocean_alkalinity_enhancement`. + - Removed `11_hybrid_energy_plant`, which is covered by examples 01 and 33, and moved its demand metric checks into the demand component unit tests. +- Add core framework support for non-annual simulation durations, including annual capacity-factor and replacement-count projections across the plant life. [PR 934](https://github.com/NatLabRockies/H2Integrate/pull/934) +- Moved resource resampling into `resample_tools.py`, used `contains_leap_day()` for leap-day detection in `time_tools.py` (fixing a malformed leap-day error message), and made the leap-day-excluded calendar regeneration exact instead of using an upper bound. [PR 964](https://github.com/NatLabRockies/H2Integrate/pull/964) ## 0.10.0 [October 12, 2026] ### New Features - - Add `PySAMWavePerformanceModel` and `WaveResource` to wrap PySAM MhkWave as an H2I performance model, replacing the HOPP wave module in example 09. [PR 825](https://github.com/NatLabRockies/H2Integrate/pull/825) - Added a new NLR wind resource model: HRRR MET Toolkit [PR 837](https://github.com/NatLabRockies/H2Integrate/pull/837) - Added a converter controller that implements a peak-load management heuristic. [PR 773](https://github.com/NatLabRockies/H2Integrate/pull/773) diff --git a/README.md b/README.md index 60c483eeb..0afbf7874 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![PyPI version](https://badge.fury.io/py/H2Integrate.svg)](https://badge.fury.io/py/H2Integrate) ![CI Tests](https://github.com/NatLabRockies/H2Integrate/actions/workflows/ci.yml/badge.svg) +[![Nightly Examples](https://github.com/NatLabRockies/H2Integrate/actions/workflows/examples.yml/badge.svg?branch=develop&event=schedule)](https://github.com/NatLabRockies/H2Integrate/actions/workflows/examples.yml) [![image](https://img.shields.io/pypi/pyversions/H2Integrate.svg)](https://pypi.python.org/pypi/H2Integrate) [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![DOI 10.5281/zenodo.17903150](https://zenodo.org/badge/DOI/10.5281/zenodo.17903150.svg)](https://zenodo.org/records/17903150) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 30b3ae111..b932f19e8 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -78,14 +78,15 @@ jupytext notebook.md --to .ipynb ## Tests Run tests from the repository root in an environment with the development dependencies installed. -Every test must use one of the `unit`, `regression`, `integration`, or `hpc` markers. Collection -fails for unmarked tests. Common commands are: +Every test must use one of the `unit`, `regression`, `integration`, `hpc`, or `examples` markers. +Collection fails for unmarked tests. Common commands are: ```bash pytest -m unit pytest -m regression pytest -m integration pytest -m hpc +pytest -m examples ``` To run one test file or test function, provide its path and optional node ID: @@ -96,9 +97,10 @@ pytest h2integrate/core/test/test_utilities.py::test_function_name ``` See the [Testing Guide](developer_guide/testing.md) for test conventions, marker details, and -additional commands. Pull requests run the automated test suite through -[GitHub Actions](https://github.com/NatLabRockies/H2Integrate/actions). The documentation build is -also checked on every pull request by [Read the Docs](https://h2integrate.readthedocs.io/). +additional commands. Pull requests run the unit, regression, and integration tests through +[GitHub Actions](https://github.com/NatLabRockies/H2Integrate/actions). The example tests run +nightly on `develop` and on pull requests that change files in `examples/`. The documentation build +is also checked on every pull request by [Read the Docs](https://h2integrate.readthedocs.io/). ## Code Review Process diff --git a/docs/_templates/autosummary/module.rst b/docs/_templates/autosummary/module.rst index 53840f6d2..7e93ba723 100644 --- a/docs/_templates/autosummary/module.rst +++ b/docs/_templates/autosummary/module.rst @@ -47,11 +47,8 @@ {% endblock %} {% block modules %} -{# Filter out pytest conftest modules; they cannot be imported outside a - pytest session (they use `from test.conftest import ...` which requires - the repo-root ``test/`` package to be on ``sys.path``) and so autosummary - raises "failed to import" warnings on them. #} -{% set filtered_modules = modules | reject('equalto', 'conftest') | list %} +{# Exclude pytest modules from the generated public API tree. #} +{% set filtered_modules = modules | reject('equalto', 'conftest') | reject('equalto', 'test') | list %} {% if filtered_modules %} .. rubric:: Modules diff --git a/docs/_toc.yml b/docs/_toc.yml index 94562e793..f9c67e7dc 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -13,6 +13,9 @@ parts: - file: user_guide/model_overview - file: user_guide/model_registry - file: user_guide/how_to_set_up_an_analysis + - file: user_guide/plant_config_reference + - file: user_guide/driver_config_reference + - file: user_guide/technology_config_reference - file: user_guide/connecting_technologies - file: user_guide/defining_sites_and_resources - file: user_guide/populate_tech_yaml @@ -23,9 +26,12 @@ parts: - file: user_guide/how_to_interface_with_user_defined_model - file: user_guide/how_to_run_several_cases_in_sequence - file: user_guide/run_size_modes + - file: user_guide/time_marching_simulation - file: user_guide/plotting_geospatial_data_with_geopandas - caption: Technology Models chapters: + - file: technology_models/transport + - file: technology_models/combiners_and_splitters - file: technology_models/feedstocks - file: technology_models/marine_hydrokinetic - file: technology_models/run_of_river @@ -34,6 +40,8 @@ parts: - file: technology_models/natural_gas - file: technology_models/nuclear - file: technology_models/wombat_electrolyzer_om + - file: technology_models/pem_electrolyzer + - file: technology_models/floris_wind_plant - file: technology_models/pvwattsv8_solar_pv.md - file: technology_models/windpower_wind_plant.md - file: technology_models/atb_costs_pv.md @@ -41,6 +49,8 @@ parts: - file: technology_models/co2.md - file: technology_models/methanol.md - file: technology_models/ammonia.md + - file: technology_models/nitrogen_asu + - file: technology_models/desalination - file: technology_models/paper_mill.md - file: technology_models/saf.md - file: technology_models/ammonia_synloop_dynamics @@ -52,6 +62,7 @@ parts: - file: technology_models/h2_htse.md - file: technology_models/iron_ewin.md - file: technology_models/iron_mine.md + - file: technology_models/iron_transport - file: technology_models/iron_dri.md - file: technology_models/steel_eaf.md - file: technology_models/steel_eaf_cmu.md diff --git a/docs/api.md b/docs/api.md index 5a8f46847..9a566c10b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -16,5 +16,7 @@ storage transporters tools + preprocess + postprocess demand reliability diff --git a/docs/control/system_level_control/slc_demand_following.md b/docs/control/system_level_control/slc_demand_following.md index 3dbd9b3b6..262cb0154 100644 --- a/docs/control/system_level_control/slc_demand_following.md +++ b/docs/control/system_level_control/slc_demand_following.md @@ -17,6 +17,7 @@ kernelspec: The demand following controller, `DemandFollowingControl`, aims to fully meet the demand and does not have any inputs related to cost. The N2 diagram below shows an example system using the demand following controller with wind, natural gas, and battery storage technologies. +The full example in `examples/35_system_level_control/demand_following` runs the same plant with no battery, with a battery, and with a battery that has its own open-loop controller. ```{code-cell} ipython3 :tags: [remove-input] @@ -30,9 +31,9 @@ from pathlib import Path from h2integrate import EXAMPLE_DIR from IPython.display import HTML, display -os.chdir(EXAMPLE_DIR / "35_system_level_control/battery_with_controller/") +os.chdir(EXAMPLE_DIR / "35_system_level_control/demand_following/") -h2i_model = H2IntegrateModel("wind_ng_demand.yaml") +h2i_model = H2IntegrateModel("wind_ng_battery_controller_demand.yaml") h2i_model.setup() om.n2( diff --git a/docs/control/technology_level_control/pyomo_controllers.md b/docs/control/technology_level_control/pyomo_controllers.md index 1927893ca..0d349fa9d 100644 --- a/docs/control/technology_level_control/pyomo_controllers.md +++ b/docs/control/technology_level_control/pyomo_controllers.md @@ -77,7 +77,20 @@ The optimized dispatch method is specified by setting the storage control to `O The optimized dispatch using Pyomo is implemented differently than the heuristic dispatch in order to be able to properly aggregate the individual Pyomo technology models into a cohesive Pyomo plant model for the optimization solver. The Pyomo plant model is from the perspective of the storage technology and is meant to track inflows of commodities and other parameters that might impact the dispatch of the storage from upstream technologies. Practically, this means that the Pyomo elements of the dispatch (including the individual technology models and the plant model) are not exposed to the main H2I code flow, and do not appear in the N2 diagram. The figure below shows a flow diagram of how the dispatch is implemented. The green blocks below represent what is represented in the N2 diagram of the system. The dispatch routine is currently self-contained within the storage technology of the system, though it includes solving an aggregated plant model in the optimization -```{note} Only the PySAM battery performance model can call Pyomo dispatch at this time. +```{note} +Optimized dispatch passes a `pyomo_dispatch_solver` callable to a storage performance model. +`StoragePerformanceBase` provides this interface, so optimized dispatch is not inherently limited +to the PySAM battery. To confirm that a storage model works with optimized dispatch: + +- Check that the storage `performance_model` is derived from `StoragePerformanceBase`; the models + listed in the [Storage Models](../../storage/storage_models_index.md) page are valid. +- Check that `tech_to_dispatch_connections` in `plant_config.yaml` lists the storage technology as the + dispatching technology, for example `[battery, battery]`. Only then does the storage model add the + `pyomo_dispatch_solver` input and run with feedback control. +- Check that the controller `commodity` matches the storage model `commodity`. + +[Example 30](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/30_pyomo_optimized_dispatch) +uses `PySAMBatteryPerformanceModel`. ``` ![](./figures/Pyomo_dispatch_figure.png) @@ -93,7 +106,25 @@ We have exposed the optimization cost (weighting) values to the user in this imp ``` For an example of how to use the optimized Pyomo control framework with the `OptimizedDispatchStorageController`, see -- `examples/30_pyomo_optimized_dispatch` +- [Example 30: Optimized Pyomo Dispatch](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/30_pyomo_optimized_dispatch) + +### Minimum operating-cost rules + +The optimized controller creates `PyomoDispatchGenericConverterMinOperatingCosts` blocks for +upstream converter technologies and a `PyomoRuleStorageMinOperatingCosts` block for the dispatching +storage technology. These registered Pyomo rule classes define the per-technology variables and +constraints used by the controller; the optimized controller constructs them internally rather than +requiring them to be added as performance models. + +```{eval-rst} +.. autoclass:: h2integrate.control.control_rules.converters.generic_converter_min_operating_cost.PyomoDispatchGenericConverterMinOperatingCosts + :members: initialize_parameters + :no-index: + +.. autoclass:: h2integrate.control.control_rules.storage.pyomo_storage_rule_min_operating_cost.PyomoRuleStorageMinOperatingCosts + :members: initialize_parameters + :no-index: +``` This controller only allows one incoming electricity stream and does not apply optimal dispatch of that stream back through the upstream technologies (no feedback). The dispatch can handle more than one generation technology, but the incoming electricity must be combined using an H2I combiner before going to the storage component, and the `cost_per_production`, which is defined in the storage technology section, needs to include the cost of production for all production technologies. This could be done using the following: diff --git a/docs/developer_guide/adding_a_new_technology.md b/docs/developer_guide/adding_a_new_technology.md index 38e3d7467..f7297ff19 100644 --- a/docs/developer_guide/adding_a_new_technology.md +++ b/docs/developer_guide/adding_a_new_technology.md @@ -80,6 +80,10 @@ from h2integrate.core.model_baseclass import PerformanceModelBaseClass class SolarPerformanceClass(PerformanceModelBaseClass): # (min, max) time step lengths (in seconds) compatible with this model _time_step_bounds = (3600, 3600) + # (min, max) simulation duration (in years) supported by this model. + # Defaults to annual-only (1.0, 1.0) on the baseclass; override to allow + # sub-annual or multi-year simulations. + _simulation_duration_bounds = (1.0, 1.0) # System-level control classifier; see the control classifier docs. _control_classifier = "flexible" @@ -142,6 +146,11 @@ class SolarPerformanceClass(PerformanceModelBaseClass): Every model must define the following class attributes. These are typically set on the category baseclass so that all subclasses inherit them, but they can also be set or overridden on individual model classes. - `_time_step_bounds` (tuple[int, int]): `(min, max)` simulation time-step lengths (in seconds) the model can run at. Use `(3600, 3600)` for hourly-only models and a wider range (e.g. `(300, 3600)`) for models that support sub-hourly time steps. The plant simulation `dt` must lie within every model's bounds. +- `_simulation_duration_bounds` (tuple[float, float]): `(min, max)` simulation durations (in 365-day years) the model can run over. Defaults to `(1.0, 1.0)` (annual-only) on the core baseclasses; override it (e.g. `(0.0, float("inf"))`) for models that support sub-annual or multi-year simulations. The configured duration in years must lie within every selected model's bounds, including technology-specific finance models. + +Widen bounds only after verifying the model at the additional timesteps or horizons. See the +{ref}`simulation settings ` for year and horizon rules, and +{ref}`annualization guidance ` for the base-class helpers. Performance models must also define the following class attributes: - `commodity` (str), `commodity_rate_units` (str), `commodity_amount_units` (str): set in `initialize()` (or before calling `super().setup()`). These define the commodity produced by the model and the units used for its rate (e.g. `"kW"`, `"kg/h"`) and cumulative amount (e.g. `"kW*h"`, `"kg"`). `PerformanceModelBaseClass.setup()` uses them to register all of the standard outputs and will raise `NotImplementedError` if any are missing. diff --git a/docs/developer_guide/class_structure.md b/docs/developer_guide/class_structure.md index 4705dbd2d..0017b0e38 100644 --- a/docs/developer_guide/class_structure.md +++ b/docs/developer_guide/class_structure.md @@ -57,6 +57,59 @@ directly) means the baseclass: - Reads `n_timesteps`, `dt`, `plant_life`, and `fraction_of_year_simulated` from `plant_config`. - Validates that `commodity`, `commodity_rate_units`, and `commodity_amount_units` are set on the subclass and registers all of the standard production outputs from those attributes. - Adds the command-value input and uncurtailed output for `flexible` models, and provides the `apply_curtailment()` helper. + - Provides `calculate_annual_cf_and_replacement_schedule()` to project annual capacity factors and a replacement schedule across the plant life from a simulated horizon of any length (optionally accounting for degradation). + - Declares `_time_step_bounds` (min, max in seconds) used to validate the configured simulation time step against the model. + - Declares `_simulation_duration_bounds` (min, max in years, default annual-only) used to validate the configured simulation duration against the model. + +Similarly, inheriting from `CostModelBaseClass` (rather than `om.ExplicitComponent` +directly) means the baseclass: + - Declares the standard `driver_config` / `plant_config` / `tech_config` options. + - Reads `n_timesteps`, `dt`, `plant_life`, and `fraction_of_year_simulated` from `plant_config`. + - Registers the standard cost outputs `CapEx`, `OpEx`, and `VarOpEx` consumed by the finance models, along with the discrete `cost_year` output and a `marginal_cost` output used for dispatch decisions. + - Provides `calculate_annual_varopex()` to annualize timestep-level variable operating costs across the plant life. + - Declares `_time_step_bounds` (min, max in seconds) used to validate the configured simulation time step against the model. + - Declares `_simulation_duration_bounds` (min, max in years, default annual-only) used to validate the configured simulation duration against the model. + +(annualization-methods)= +### Annualization + +For models supporting nonannual simulations, use the inherited annualization methods +to project annual outputs across plant life. Their pure calculation functions live in +`h2integrate/core/annualization.py`; model implementations should normally call the base-class +methods rather than assemble the private helpers themselves. + +A "plant year" here is always 365 days (31,536,000 s); a simulation shorter or longer than that is +split at plant-year boundaries and each segment is averaged over its actual duration. No built-in +model calls these methods yet because all models default to annual-only support (see +`_simulation_duration_bounds`); a model opts in by widening its bounds and calling them, for example: + +```python +# In a performance model's compute(); state of health (SOH) is optional +annual_capacity_factors, replacement_schedule = self.calculate_annual_cf_and_replacement_schedule( + performance_timeseries=outputs["electricity_out"], + rated_performance=self.config.rated_capacity, + state_of_health_timeseries=None, # or a timeseries of SOH fractions in [0, 1] + eol_soh=None, # end-of-life SOH threshold, required only when SOH is given +) + +# In a cost model's compute(); timestep-level variable costs in USD per timestep +annual_varopex = self.calculate_annual_varopex(varopex_timeseries) +``` + +Both results have one entry per plant year (`plant_life` entries). + +CF (capacity factor) and variable costs use duration-weighted annual means. Optional SOH supports +timestep-level completion of the trailing cycle and integer replacement counts per year. For arguments, +extrapolation options, and boundary behavior, see +{py:meth}`~h2integrate.core.model_baseclass.PerformanceModelBaseClass.calculate_annual_cf_and_replacement_schedule` +and {py:meth}`~h2integrate.core.model_baseclass.CostModelBaseClass.calculate_annual_varopex`. +The plant-level `plant.simulation.annual_extrapolation_method` allows the user to select `tile`, `final_sim_value`, +or `time_averaged_sim_value` when multiple simulated year segments must be extended across the +remaining plant life. Technology implementations can override it per call using +`no_degradation_extrapolation` for capacity-factor projection or `annual_extrapolation_method` +for variable OpEx projection. A finance subgroup using a custom timeseries may also override the +plant setting; otherwise, it inherits the plant-level value. See +{ref}`Plant lifetime and simulation `. ### Multiple layers of inheritance diff --git a/docs/developer_guide/technology_model_types.md b/docs/developer_guide/technology_model_types.md index 4e951c698..895c194e1 100644 --- a/docs/developer_guide/technology_model_types.md +++ b/docs/developer_guide/technology_model_types.md @@ -122,8 +122,7 @@ from h2integrate.core.model_baseclass import CostModelBaseClass class ReverseOsmosisCostModelConfig(BaseConfig): # Config values come from tech_config['model_inputs']['cost_parameters'] # or tech_config['model_inputs']['shared_parameters']. - freshwater_kg_per_hour: float = field(validator=validators.gt(0)) - freshwater_density: float = field(validator=validators.gt(0)) + freshwater_capacity_kg_per_hr: float = field(validator=validators.gt(0)) # cost_year is fixed because this model always reports 2013 USD. cost_year: int = field(default=2013, converter=int, validator=validators.in_([2013])) @@ -137,12 +136,15 @@ class ReverseOsmosisCostModel(CostModelBaseClass): super().setup() self.add_input( - "plant_capacity_kgph", val=0.0, units="kg/h", desc="Desired freshwater flow rate" + "rated_freshwater_production_capacity", + val=self.config.freshwater_capacity_kg_per_hr / 3600, + units="kg/s", + desc="Maximum freshwater production rate", ) def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - capex = 32894 * (self.config.freshwater_kg_per_hour / 3600) # USD - opex = 4841 * (self.config.freshwater_kg_per_hour / 3600) # USD/yr + capex = 32894 * inputs["rated_freshwater_production_capacity"] # USD + opex = 4841 * inputs["rated_freshwater_production_capacity"] # USD/yr outputs["CapEx"] = capex outputs["OpEx"] = opex ``` diff --git a/docs/developer_guide/testing.md b/docs/developer_guide/testing.md index d55df6822..7a13ee38d 100644 --- a/docs/developer_guide/testing.md +++ b/docs/developer_guide/testing.md @@ -50,7 +50,7 @@ def test_BaseConfig(subtests): ... # rest of the test ``` -Tests must be marked with at least one of `unit`, `regression`, `integration`, or `hpc`. The test configuration rejects unknown markers, and `test/conftest.py` fails collection for unmarked tests. +Tests must be marked with at least one of `unit`, `regression`, `integration`, `hpc`, or `examples`. The test configuration rejects unknown markers, and `test/conftest.py` fails collection for unmarked tests. ## Unit Tests @@ -100,9 +100,33 @@ systems. Run `pytest -m integration` to run only the integration test suite or `pytest -m "not integration"` to skip the integration tests. -For examples of integration tests, please see the `examples/test/test_all_examples.py` module -where multiple components are combined to test the models outputs for stability when using tools -in conjunction with each other. +For examples of integration tests, please see the +`h2integrate/control/control_strategies/storage/test/test_openloop_storage_integration.py` module, +where storage performance models and their controllers are connected and run together over a short +simulation. + +## Example Tests + +Tests that run a full example from the `examples/` folder are marked with `@pytest.mark.examples`. +These tests are the most expensive part of the test suite, so they do not run with the unit, +regression, and integration tests on every pull request. Instead, the +[Examples workflow](https://github.com/NatLabRockies/H2Integrate/actions/workflows/examples.yml) +runs them nightly on `develop`, on pull requests that change files in `examples/`, and on demand +from the GitHub Actions tab. When a nightly run fails, the workflow opens (or comments on) an issue +labeled `nightly-examples`, and it closes that issue once the examples pass again. + +There are two example test modules: + +- `examples/test/test_all_examples.py` builds and runs each example model and checks key outputs, + such as levelized costs and annual production, for stability. +- `examples/test/test_example_scripts.py` runs every `run_*.py` script in `examples/` from a + temporary copy of its folder, the same way a user would from the command line, to make sure + plotting and post-processing code keeps working. New example scripts must be named `run_*.py` + so they are picked up automatically. + +Run `pytest -m examples` to run the example tests locally. Because these tests do not run on every +pull request, run the example tests that use any model you change before requesting a review, for +example `pytest examples/test/test_all_examples.py -k steel`. ## Test coverage diff --git a/docs/intro.md b/docs/intro.md index 5b8d21c10..8b3d4d61a 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -40,6 +40,17 @@ Appropriate time step bounds are included as class attributes when non-hourly ti Check individual model docs and definitions for time step bounds for individual models. All models in a given simulation must be compatible with the specified time step. ``` +```{note} +Simulations are no longer restricted to exactly one year by the framework. A simulation may cover any +positive duration up to the plant life (a fraction of a year, a single year, or multiple +years), set via `n_timesteps` and `dt` in `plant_config`. Performance, cost, and finance +results are annualized across the plant life using the simulated horizon. Each model +declares the durations it supports with a `_simulation_duration_bounds` class attribute +(min, max in years); the default is annual-only, and currently available models keep that +default unless their documentation states otherwise. All models in a given simulation must be +compatible with the specified duration. +``` + For each technology there are 4 different types of models: control, performance, cost, and finance. These model categories allow for modular pieces to be brought in or re-used throughout H2Integrate, as well as ease of development and organization. Note that the only required models for a technology are performance and cost, while control and finance are optional. The figure below shows these four categories and some of the technologies included in H2Integrate. For a full list of models available, please see [Model Overview](user_guide/model_overview.md). ![A representation of a single technology model in H2Integrate](tech-model.png) diff --git a/docs/misc_resources/changelog.md b/docs/misc_resources/changelog.md index 3139cd42b..40918aa10 100644 --- a/docs/misc_resources/changelog.md +++ b/docs/misc_resources/changelog.md @@ -1,2 +1,8 @@ +# Changelog + +The release and development history is maintained in the repository's +[CHANGELOG.md](https://github.com/NatLabRockies/H2Integrate/blob/develop/CHANGELOG.md). + ```{include} ../../CHANGELOG.md +:start-line: 2 ``` diff --git a/docs/resource/resource_index.md b/docs/resource/resource_index.md index 7e9654842..e5ee3e3a3 100644 --- a/docs/resource/resource_index.md +++ b/docs/resource/resource_index.md @@ -42,9 +42,10 @@ If running a simulation that is longer than 1 year, then there are a few options ## Resampling resource data -API resource data may have a different native timestep from the simulation, but H2I does not resample it by default. You may explicitly choose a pandas interpolation method for upsampling ([`pandas.DataFrame.interpolate`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html)) or a pandas aggregation method for downsampling ([`pandas.DataFrame.agg`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.agg.html)) in the resource's `resource_parameters`. +API resource data may have a different native timestep from the simulation you would like to run. H2I does not resample resource data by default, but you can set explicit parameters in the input file to either upsample the data, downsample the data, or perform both upsampling and downsampling if you have nonuniform data time steps to obtain data for your desired timestep. +The options available are those enabled in the pandas interpolation method for upsampling ([`pandas.DataFrame.interpolate`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html)) or a pandas aggregation method for downsampling ([`pandas.DataFrame.agg`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.agg.html)) in the resource's `resource_parameters`. -Set `upsample_method` or `downsample_method` in `resource_parameters` to explicitly select the pandas operation. If the data timestep and simulation timestep already match, neither setting is needed. When resampling is requested, H2I emits a warning naming the source timestep, target timestep, direction, and method. If a mismatch requires resampling but no corresponding method is set, H2I raises an error. See {py:func}`h2integrate.resource.utilities.time_tools.resample_resource_data_to_dt` for method details. +Set `upsample_method` or `downsample_method` in `resource_parameters` to explicitly select the pandas operation. If the data timestep and simulation timestep already match, neither setting is needed. When resampling is requested, H2I emits a warning naming the source timestep, target timestep, direction, and method. If a mismatch requires resampling but no corresponding method is set, H2I raises an error. See {py:func}`h2integrate.resource.utilities.resample_tools.resample_resource_data_to_dt` for method details. ### Non-uniform timestep resampling @@ -59,7 +60,7 @@ simulation time step, applying downsampling and upsampling in the same run: import matplotlib.pyplot as plt import numpy as np import pandas as pd -from h2integrate.resource.utilities.time_tools import resample_resource_data_to_dt +from h2integrate.resource.utilities.resample_tools import resample_resource_data_to_dt # Mixed-resolution series: 15-min intervals followed by 2-hour intervals idx1 = pd.date_range("2020-01-01 00:00", "2020-01-01 03:00", freq="15min") diff --git a/docs/resource/solar_index.md b/docs/resource/solar_index.md index 7d6d42be4..8bdfaa862 100644 --- a/docs/resource/solar_index.md +++ b/docs/resource/solar_index.md @@ -16,6 +16,10 @@ - "MeteosatPrimeMeridianTMYSolarAPI" - [OpenMeteo Historical API](#solar_resource:openmeteo_historical) +Some API resource models support multi-year simulations when the requested resource data covers +the configured time range. See the [Resource Model Index](resource_index.md) for details on +multi-year resource handling. + ```{note} Please refer to the [Setting Environment Variables](../getting_started/environment_variables) doc page for information on setting up an NLR API key if you haven't yet. ``` diff --git a/docs/resource/wind_index.md b/docs/resource/wind_index.md index 3f835285e..2e528571c 100644 --- a/docs/resource/wind_index.md +++ b/docs/resource/wind_index.md @@ -6,6 +6,10 @@ - "HRRRMETToolkitWindAPI" - [**"OpenMeteoHistoricalWindResource"**](wind_resource:openmeteo_archive) +Some API resource models support multi-year simulations when the requested resource data covers +the configured time range. See the [Resource Model Index](resource_index.md) for details on +multi-year resource handling. + ```{note} Please refer to the [Setting Environment Variables](../getting_started/environment_variables) doc page for information on setting up an NLR API key if you haven't yet. diff --git a/docs/storage/storage_models_index.md b/docs/storage/storage_models_index.md index 47baa086c..4c1d60d96 100644 --- a/docs/storage/storage_models_index.md +++ b/docs/storage/storage_models_index.md @@ -11,6 +11,22 @@ H2I currently supports the following storage performance models: The following sections detail the inputs and outputs of the storage performance models. +### Automatic storage sizing + +`StorageAutoSizingModel` calculates the storage capacity and charge/discharge rate required to +balance a commodity production profile against demand, then simulates the storage operation. In the +`tech_config.yaml` set +`set_demand_as_avg_commodity_in: true` to use average input as demand, or set it to `false` and +provide `demand_profile`. The model requires either `round_trip_efficiency`, or both +`charge_efficiency` and `discharge_efficiency`. `min_soc_fraction` and `max_soc_fraction` bound the +usable state of charge. It supports one-hour time steps. + +```{eval-rst} +.. autoclass:: h2integrate.storage.simple_storage_auto_sizing.StorageSizingModelConfig + :members: + :no-index: +``` + ```{note} The inputs and outputs of storage performance models are generalized here for any commodity. If input and output names include the word `commodity`, the actual variable name would be the commodity defined for that storage model. For example, the `PySAMBatteryPerformanceModel` can only be used for the commodity `electricity`. Therefore, the `commodity_in` input to the `PySAMBatteryPerformanceModel` is actually named `electricity_in`. ``` @@ -61,3 +77,18 @@ The available storage cost models are: - `SaltCavernStorageCostModel` - `PipeStorageCostModel` - `MCHTOLStorageCostModel` + + ### ATB battery cost model + + `ATBBatteryCostModel` uses separate energy and power capital costs from the [NLR Annual Technology + Baseline utility-scale battery ATB](https://atb.nrel.gov/electricity/2024b/utility-scale_battery_storage). + It derives storage duration from capacity and charge rate, calculates + CapEx from the energy and power cost inputs, and calculates annual OpEx as a fraction of CapEx. + `commodity_rate_units` must be a power unit based on watts; the default amount unit is the rate unit + multiplied by hours. + + ```{eval-rst} + .. autoclass:: h2integrate.storage.battery.atb_battery_cost.ATBBatteryCostConfig + :members: + :no-index: + ``` diff --git a/docs/technology_models/ammonia.md b/docs/technology_models/ammonia.md index 3ea56dfa1..2e3ee64d2 100644 --- a/docs/technology_models/ammonia.md +++ b/docs/technology_models/ammonia.md @@ -6,6 +6,24 @@ Ammonia is a common fertilizer, and also has the potential to serve as a lower-c 2. The synloop ammonia model accepts stream-table measurements (or modeled values) from an ammonia synthesis loop as performance parameters. Its registered models are `AmmoniaSynLoopPerformanceModel` in `h2integrate/converters/ammonia/ammonia_synloop_performance.py` and `AmmoniaSynLoopCostModel` in `h2integrate/converters/ammonia/ammonia_synloop_cost.py`. The example in `examples/12_ammonia_synloop/` uses mostly the same parameters as those used by Reznicek et al., but with updated capital costs for the air separator and synthesis loop derived from an NETL baseline study of ammonia production by [Brasington et al.](https://doi.org/10.2172/1515254). +## Simple ammonia configuration + +`SimpleAmmoniaPerformanceModel` uses an annual plant capacity and capacity factor to produce +ammonia. It accepts a hydrogen feed profile and reports ammonia production and capacity factor. +`SimpleAmmoniaCostModel` estimates capital and operating cost and includes oxygen by-product +credits. Both models use one-hour time steps and are demonstrated in +[Example 02: Texas Ammonia](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/02_texas_ammonia). + +```{eval-rst} +.. autoclass:: h2integrate.converters.ammonia.simple_ammonia_model.SimpleAmmoniaPerformanceModelConfig + :members: + :no-index: + +.. autoclass:: h2integrate.converters.ammonia.simple_ammonia_model.SimpleAmmoniaCostModelConfig + :members: + :no-index: +``` + ## Synloop purge gas output The Synloop Ammonia Model exposes the purge gas exiting the synthesis loop as a **multivariable stream** called `process_gas_mixture`. This is a general-purpose stream type for process gas mixtures that can be reused by other components. It bundles seven constituent variables into a single connection type: diff --git a/docs/technology_models/ammonia_synloop_dynamics.md b/docs/technology_models/ammonia_synloop_dynamics.md index 26744d277..f5cd2384e 100644 --- a/docs/technology_models/ammonia_synloop_dynamics.md +++ b/docs/technology_models/ammonia_synloop_dynamics.md @@ -68,9 +68,10 @@ delays. The cold and warm multipliers are derived from the same post-ramping reference profile, so the pass order does not change the result. ```{note} -Feedstocks are consumed from the post-dynamic NH3 output profile. Zero-production -startup-delay hours consume nothing, while partial warm-start hours consume in -proportion to the partial NH3 output. +Feedstocks are consumed in proportion to the final post-dynamic NH3 output profile. +Zero-production startup-delay hours consume nothing, partial warm-start hours consume +in proportion to the partial NH3 output, and positive ramp-down output below the +turndown floor still consumes feedstock. ``` ## Worked example diff --git a/docs/technology_models/combiners_and_splitters.md b/docs/technology_models/combiners_and_splitters.md new file mode 100644 index 000000000..5c3331ebc --- /dev/null +++ b/docs/technology_models/combiners_and_splitters.md @@ -0,0 +1,195 @@ +# Combiners and Splitters + +Combiners merge several upstream streams into one, and splitters divide one upstream stream into +two. They are technologies declared in `tech_config.yaml`, and `plant_config.yaml` connects +other technologies to them as source or destination technologies in `technology_interconnections`. + +## Combiner + +`GenericCombinerPerformanceModel` sums one commodity from multiple sources into one output. It +accepts `commodity`, `commodity_rate_units`, and optional `in_streams` (defaults to 2). Set +`in_streams` to the number of incoming connections when combining more than two sources. + +```yaml +technologies: + electricity_combiner: + performance_model: + model: GenericCombinerPerformanceModel + model_inputs: + performance_parameters: + commodity: electricity + commodity_rate_units: kW + in_streams: 3 +``` + +The combined commodity profile and rated production are sums of the input streams. The output +capacity factor is a rated-production-weighted average; it is zero when total rated production is +zero. Each input must provide its commodity profile, rated production, and capacity factor. + +### Inputs and outputs + +For each input stream $i$ (numbered $1, 2, \ldots$, `in_streams`), the combiner has these inputs: + +- `{commodity}_in{i}`: time-series commodity profile from source $i$, in `commodity_rate_units`. +- `rated_{commodity}_production{i}`: rated (nameplate) production of source $i$, in + `commodity_rate_units` (scalar). +- `{commodity}_capacity_factor{i}`: annual capacity factor of source $i$ (unitless, with one + value per year of plant life). + +Its outputs are: + +- `{commodity}_out`: combined commodity time-series profile, the element-wise sum of all inputs. +- `rated_{commodity}_production`: total rated production, the sum of all input rated productions. +- `capacity_factor`: combined capacity factor (unitless, with one value per year of plant life). + +### Commodity output + +The combined output profile is the element-wise sum of all input profiles: + +$$ +\text{commodity\_out} = \sum_{i=1}^{N} \text{commodity\_in}_i +$$ + +where $N$ is `in_streams`. + +### Capacity factor calculation + +The combined capacity factor is a weighted average of the input capacity factors, weighted by each +stream's rated production: + +$$ +CF_{out} = \frac{\sum_{i=1}^{N} CF_i \cdot S_i}{\sum_{i=1}^{N} S_i} +$$ + +where $CF_i$ is the capacity factor and $S_i$ is the rated commodity production of input stream $i$. +This weighting ensures that larger sources contribute proportionally more to the combined capacity +factor. If the total rated production is zero, the output capacity factor is set to zero. + +In `plant_config.yaml`, connect each source to the combiner, then connect the combiner to whatever +uses the combined stream. The combiner's output is `{commodity}_out`. + +### Example: wind and natural gas + +[Example 23](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/23_solar_wind_ng_demand) +combines variable renewable generation with natural gas generation. The renewable generation is +first combined in `combiner`, and `fin_combiner` then adds the output of the natural gas plant so +that the total plant output can be used for finance calculations: + +```yaml +# tech_config.yaml +technologies: + fin_combiner: + performance_model: + model: GenericCombinerPerformanceModel + model_inputs: + performance_parameters: + commodity: electricity + commodity_rate_units: kW +``` + +```yaml +# plant_config.yaml +technology_interconnections: + - [combiner, fin_combiner, electricity, cable] + - [natural_gas_plant, fin_combiner, electricity, cable] +``` + +### Example: solar and battery + +[Example 24](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/24_solar_battery_grid) +combines the solar output with the battery output, so downstream technologies see the combined +electricity from both: + +```yaml +# tech_config.yaml +technologies: + bat_combiner: + performance_model: + model: GenericCombinerPerformanceModel + model_inputs: + performance_parameters: + commodity: electricity + commodity_rate_units: kW +``` + +```yaml +# plant_config.yaml +technology_interconnections: + - [solar, battery, electricity, cable] + - [solar, bat_combiner, electricity, cable] + - [battery, bat_combiner, electricity, cable] + - [bat_combiner, electrical_load_demand, electricity, cable] +``` + +## Splitter + +`GenericSplitterPerformanceModel` takes one input and produces exactly two outputs. `out1` is the +priority branch; `out2` receives the remainder. Configure `commodity` and `commodity_rate_units` +under `model_inputs.performance_parameters`, together with one of these modes: + +- `split_mode: fraction` requires `fraction_to_priority_tech`, between 0 and 1. `out1` receives that + fraction of the input, and `out2` receives the rest. +- `split_mode: prescribed_commodity` requires `prescribed_commodity_to_priority_tech`, in the + configured commodity rate units. `out1` receives the requested amount, limited to the available + nonnegative input; `out2` receives the remainder. + +```yaml +technologies: + electricity_splitter: + performance_model: + model: GenericSplitterPerformanceModel + model_inputs: + performance_parameters: + commodity: electricity + commodity_rate_units: kW + split_mode: fraction + fraction_to_priority_tech: 0.7 +``` + +Connect the two downstream technologies to the splitter in the same order as the intended `out1` +and `out2` branches. + +### Example: splitting wind electricity + +[Example 17](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/17_splitter_wind_doc_h2) +splits wind electricity between a direct ocean capture plant and an electrolyzer. The first +downstream technology listed receives `out1`, and the second receives `out2`: + +```yaml +# plant_config.yaml +technology_interconnections: + - [wind, electricity_splitter, electricity, cable] + - [electricity_splitter, doc, electricity, cable] + - [electricity_splitter, electrolyzer, electricity, cable] +``` + +## Multivariable gas streams + +`GasStreamCombinerPerformanceModel` combines supported multivariable gas streams. It sums each +stream's mass flow and computes mass-weighted averages for intensive properties such as temperature, +pressure, and composition. Set `commodity` and `in_streams` in its performance parameters. The +connection syntax and stream variable names are covered in +{ref}`Connecting Technologies `. + +## API reference + +```{eval-rst} +.. autoclass:: h2integrate.transporters.generic_combiner.GenericCombinerPerformanceConfig + :members: + :no-index: + +.. autoclass:: h2integrate.transporters.generic_splitter.GenericSplitterPerformanceConfig + :members: + :no-index: + +.. autoclass:: h2integrate.transporters.gas_stream_combiner.GasStreamCombinerPerformanceModelConfig + :members: + :no-index: +``` + +## Examples + +- [Example 17: Splitter, wind, and direct ocean capture](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/17_splitter_wind_doc_h2) +- [Example 23: Solar, wind, and natural gas with demand](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/23_solar_wind_ng_demand) +- [Example 24: Solar, battery, and grid](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/24_solar_battery_grid) +- [Example 32: Multivariable gas streams](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/32_multivariable_streams) diff --git a/docs/technology_models/desalination.md b/docs/technology_models/desalination.md new file mode 100644 index 000000000..7ac9fa864 --- /dev/null +++ b/docs/technology_models/desalination.md @@ -0,0 +1,42 @@ +# Reverse Osmosis Desalination + +`ReverseOsmosisPerformanceModel` represents a reverse osmosis plant that converts feedwater and +electricity into freshwater. At each timestep, freshwater production (`water_out`, m^3/h) is the +minimum of three limits: + +- the plant capacity, `freshwater_capacity_kg_per_hr` (kg/h) divided by `freshwater_density` + (kg/m^3) +- the available electricity, `electricity_in` (kW) divided by `specific_energy_consumption` (kWh/m^3) +- the available feedwater, `feedwater_in` (m^3/h) multiplied by `recovery_ratio` + +`electricity_in` and `feedwater_in` are inputs with one value per timestep, so they may vary over +the simulation. The model reports the resources actually used as `electricity_consumed` and +`feedwater_consumed`, along with the system `mass` and `footprint`. + +Choose `salinity: seawater` or `salinity: brackish` in the configuration. This sets the recovery +ratio and specific energy consumption: + +| Salinity | Recovery ratio | Specific energy consumption | +| ---------- | -------------- | --------------------------- | +| `seawater` | 0.5 | 4.0 kWh/m^3 | +| `brackish` | 0.75 | 1.5 kWh/m^3 | + +The capacity and freshwater density are also OpenMDAO inputs, so they can be varied by a design of +experiments or optimization. + +`ReverseOsmosisCostModel` estimates capital and operating costs from +`freshwater_capacity_kg_per_hr` using the model's reference costs. Its cost configuration is fixed +to 2013 USD. +Both models support time steps from 1 second to 1 hour. Rates (`water_out`, `electricity_in`, +`feedwater_in`, and the consumed quantities) are per hour regardless of time step, while +`total_water_produced` and `annual_water_produced` account for the time step length. + +```{eval-rst} +.. autoclass:: h2integrate.converters.water.desal.desalination.ReverseOsmosisPerformanceModelConfig + :members: + :no-index: + +.. autoclass:: h2integrate.converters.water.desal.desalination.ReverseOsmosisCostModelConfig + :members: + :no-index: +``` diff --git a/docs/technology_models/floris_wind_plant.md b/docs/technology_models/floris_wind_plant.md new file mode 100644 index 000000000..25d363bcb --- /dev/null +++ b/docs/technology_models/floris_wind_plant.md @@ -0,0 +1,60 @@ +# FLORIS Wind Plant + +`FlorisWindPlantPerformanceModel` wraps the [FLORIS](https://github.com/NatLabRockies/floris) wind farm +model to calculate wind-farm electrical output from wind resource data and turbine configuration. +It supports a single turbine design per farm and uses one-hour time steps. + +The model requires a FLORIS wake configuration and a turbine configuration [formatted for FLORIS](https://natlabrockies.github.io/floris/). The `floris_wake_config` is a dictionary containing the [FLORIS wake parameters](https://natlabrockies.github.io/floris/wake_models.html), with an example in the H2I library named [`floris_v4_default_template.yaml`](https://github.com/NatLabRockies/H2Integrate/blob/develop/library/floris_v4_default_template.yaml). The `floris_turbine_config` is a dictionary containing [FLORIS wind turbine design information](https://natlabrockies.github.io/floris/input_reference_turbine.html), with some examples provided in the [H2I library](https://github.com/NatLabRockies/H2Integrate/tree/develop/library), with filenames starting with `floris_turbine_`. Please see the [section below](turbine-file-preprocessing) on some tips to create the floris turbine input files. + + +The configurations can be included from YAML files as in [Example 26](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/26_floris). +The `layout` configuration selects a layout generator and its options; do not also set turbine +coordinates in `floris_wake_config.farm.layout_x` or `layout_y`, because the layout settings create +those coordinates. Multiple turbine designs in the same farm are not currently supported. + +Resource data is supplied through the plant's site resource configuration and +`site_to_tech_connections`. The model selects or averages the resource heights around the turbine +hub height using `resource_data_averaging_method` (`weighted_average`, `average`, or `nearest`). +Turbulence intensity can be taken from the resource data or supplied as a default (defined as a fraction, with a value between 0 and 1). Additional farm losses are applied based on the `operational_losses` input (which is represented as a percent, with a value between 0 and 100) + +FLORIS calculations can be expensive for larger wind farms and more complicated wake models, so caching is enabled by default. `cache_dir` selects the +cache location when caching is enabled. Cache entries are based on the model configuration and +simulation inputs; disable caching with `enable_caching: false` when debugging or when cache reuse +is not desired. + +(turbine-file-preprocessing)= +## Turbine file preprocessing + +The `h2integrate.preprocess.wind_turbine_file_tools` module can create turbine files from the +[turbine-models](https://github.com/NatLabRockies/turbine-models) library, so you do not have to +write them by hand: + +- `export_turbine_to_floris_format(turbine_name)` writes a FLORIS turbine YAML file (named + `floris_turbine_{turbine_name}.yaml` by default) to the H2I library folder, or to `output_folder` + if given. Use its path as the FLORIS turbine configuration for this model. +- `FlorisTurbineDefaults` holds the defaults (`TSR`, `ref_air_density`, `ref_tilt`, and the + cosine loss exponents) that fill in values FLORIS requires but are not provided by the turbine-models + library. Pass a `FlorisTurbineDefaults` object or a dictionary as `floris_defaults` to override them. +- `export_turbine_to_pysam_format(turbine_name)` does the same for the PySAM Windpower model used + by `PYSAMWindPlantPerformanceModel`. + +See [Turbine Models Library Pre-Processing](../misc_resources/turbine_models_library_preprocessing.md) +for a worked example that exports a turbine, updates the `tech_config`, and runs Example 26. + +```{eval-rst} +.. autofunction:: h2integrate.preprocess.wind_turbine_file_tools.export_turbine_to_floris_format + :no-index: + +.. autoclass:: h2integrate.preprocess.wind_turbine_file_tools.FlorisTurbineDefaults + :members: + :no-index: + +.. autofunction:: h2integrate.preprocess.wind_turbine_file_tools.export_turbine_to_pysam_format + :no-index: +``` + +```{eval-rst} +.. autoclass:: h2integrate.converters.wind.floris.FlorisWindPlantPerformanceModelConfig + :members: + :no-index: +``` diff --git a/docs/technology_models/geologic_hydrogen.md b/docs/technology_models/geologic_hydrogen.md index cbf02e677..bc22fd5e7 100644 --- a/docs/technology_models/geologic_hydrogen.md +++ b/docs/technology_models/geologic_hydrogen.md @@ -64,3 +64,8 @@ The modeling approach in this performance model is based on: The modeling approach in this cost model is based on: - Mathur et al. (Stanford): + +## Example + +[Example 04: Geologic Hydrogen](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/04_geo_h2) +contains separate configurations for natural and stimulated subsurface hydrogen models. diff --git a/docs/technology_models/h2_htse.md b/docs/technology_models/h2_htse.md index 764431541..ef8c9ca7d 100644 --- a/docs/technology_models/h2_htse.md +++ b/docs/technology_models/h2_htse.md @@ -91,6 +91,9 @@ The current implementation is intentionally simple and should be interpreted as ### API details For API details, see the [`HTSEPerformanceModel` and `HTSECostModel` API documentation](../_autosummary/h2integrate.converters.hydrogen.htse_electrolyzer). +The nuclear heat and electricity integration is demonstrated in +[Example 36: Nuclear Reactor and HTSE](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/36_nuclear_reactor_htse). + ## Cost Model To use this model, in your `tech_config.yaml` file, set: diff --git a/docs/technology_models/iron_transport.md b/docs/technology_models/iron_transport.md new file mode 100644 index 000000000..307c8b844 --- /dev/null +++ b/docs/technology_models/iron_transport.md @@ -0,0 +1,31 @@ +# Iron Transport Models + +`IronTransportPerformanceComponent` estimates the land and water distances needed to ship iron ore +to a Great Lakes port. It reads the mine location from `plant_config.sites.site` and uses built-in +port coordinates and shipping waypoints. Set `find_closest_ship_site: true` and +`shipment_site: "None"` to select the shortest modeled route, or set `find_closest_ship_site: false` +and choose `Duluth`, `Chicago`, `Cleveland`, or `Buffalo` explicitly. + +The performance component reports `land_transport_distance`, `water_transport_distance`, and +`total_transport_distance` in kilometers. `IronTransportCostComponent` uses these distances and the +`iron_ore_in` profile to calculate transport cost outputs. Its `transport_year` and `marginal_cost` +inputs are configured in the technology's cost parameters; the cost year is set from the plant's +`target_dollar_year`. + +Both models require hourly time steps. See the iron DRI and mine documentation for their use in +complete flowsheets. + +```{eval-rst} +.. autoclass:: h2integrate.converters.iron.iron_transport.IronTransportPerformanceComponentConfig + :members: + :no-index: + +.. autoclass:: h2integrate.converters.iron.iron_transport.IronTransportCostComponentConfig + :members: + :no-index: +``` + +## Examples + +The model is used by the iron mine, DRI, electrowinning, and mapping configurations in +[Example 21](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/21_iron_examples). diff --git a/docs/technology_models/methanol.md b/docs/technology_models/methanol.md index 86fee1ef8..4be885adf 100644 --- a/docs/technology_models/methanol.md +++ b/docs/technology_models/methanol.md @@ -10,3 +10,44 @@ Methanol is an essential precursor to many chemicals, and is also used as a fuel - [Nyari et al.](https://doi.org/10.1016/j.jcou.2020.101166) This modeling is further documented in a journal publication by [Martin et al.](https://doi.org/10.1021/acs.est.4c02589) + +## Registered models and configuration + +| Route | Performance model | Cost model | Finance model | +|---|---|---|---| +| Steam methane reforming | `SMRMethanolPlantPerformanceModel` | `SMRMethanolPlantCostModel` | `SMRMethanolPlantFinanceModel` | +| CO2 hydrogenation | `CO2HMethanolPlantPerformanceModel` | `CO2HMethanolPlantCostModel` | `CO2HMethanolPlantFinanceModel` | + +Both performance models use hourly time steps and ratio-based feedstock calculations, limited by +the configured `plant_capacity_kgpy` and available inputs. The SMR route consumes natural gas and +produces electricity as a co-product. The CO2 hydrogenation route consumes hydrogen, carbon +dioxide, natural gas, and electricity. Both report methanol production, emissions, and water +consumption. The cost models use total overnight, fixed operating, and variable operating cost +inputs, then calculate route-specific feedstock costs and credits. The finance models report +levelized cost of methanol. + +```{eval-rst} +.. autoclass:: h2integrate.converters.methanol.smr_methanol_plant.SMRMethanolPlantPerformanceModelConfig + :members: + :no-index: + +.. autoclass:: h2integrate.converters.methanol.smr_methanol_plant.SMRMethanolPlantCostModelConfig + :members: + :no-index: + +.. autoclass:: h2integrate.converters.methanol.smr_methanol_plant.SMRMethanolPlantFinanceModelConfig + :members: + :no-index: + +.. autoclass:: h2integrate.converters.methanol.co2h_methanol_plant.CO2HMethanolPlantPerformanceModelConfig + :members: + :no-index: + +.. autoclass:: h2integrate.converters.methanol.co2h_methanol_plant.CO2HMethanolPlantCostModelConfig + :members: + :no-index: + +.. autoclass:: h2integrate.converters.methanol.co2h_methanol_plant.CO2HMethanolPlantFinanceModelConfig + :members: + :no-index: +``` diff --git a/docs/technology_models/nitrogen_asu.md b/docs/technology_models/nitrogen_asu.md new file mode 100644 index 000000000..b4a620b77 --- /dev/null +++ b/docs/technology_models/nitrogen_asu.md @@ -0,0 +1,25 @@ +# Nitrogen Air Separation Unit + +`SimpleASUPerformanceModel` is a linear model for producing nitrogen from air. It can size from a +configured nitrogen demand, or use a rated nitrogen production or electrical power input. It also +calculates air feed, oxygen and argon co-products, and electricity use. Its `efficiency_kWh_pr_kg_N2` +is expressed in kWh per kg of nitrogen; the model documentation gives representative values of +0.119 for cryogenic separation and 0.29 for pressure swing adsorption. + +With `size_from_N2_demand: true`, connect `nitrogen_in` as the demand profile and the model reports +the required `electricity_in`. With it set to `false`, provide `electricity_in` and specify either +`rated_N2_kg_pr_hr` or `ASU_rated_power_kW`. The model supports one-hour time steps. + +`SimpleASUCostModel` calculates capital and operating expenses using a cost per nitrogen capacity +or a cost per power capacity. `capex_unit` must identify the basis; when `opex_usd_per_unit_per_year` +is nonzero, set `opex_unit` as well. + +```{eval-rst} +.. autoclass:: h2integrate.converters.nitrogen.simple_ASU.SimpleASUPerformanceConfig + :members: + :no-index: + +.. autoclass:: h2integrate.converters.nitrogen.simple_ASU.SimpleASUCostConfig + :members: + :no-index: +``` diff --git a/docs/technology_models/nuclear.md b/docs/technology_models/nuclear.md index edb20a264..1854db931 100644 --- a/docs/technology_models/nuclear.md +++ b/docs/technology_models/nuclear.md @@ -70,3 +70,8 @@ $$ ### API details For API details, see the [`SimpleThermalNuclearReactorPerformanceModel` and `SimpleThermalNuclearReactorCostModel` API documentation](../_autosummary/h2integrate.converters.nuclear.nuclear_plant_thermal). + +## Integrated nuclear and HTSE example + +[Example 36: Nuclear Reactor and HTSE](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/36_nuclear_reactor_htse) +connects thermal nuclear output to the high-temperature steam electrolyzer. diff --git a/docs/technology_models/pem_electrolyzer.md b/docs/technology_models/pem_electrolyzer.md new file mode 100644 index 000000000..b9dbeae93 --- /dev/null +++ b/docs/technology_models/pem_electrolyzer.md @@ -0,0 +1,55 @@ +# PEM Electrolyzer + +H2Integrate's `ECOElectrolyzerPerformanceModel` wraps the ECO-PEM model to convert electricity +into hydrogen and oxygen. It models electrolyzer clusters, turndown, degradation, and replacement +timing. Use it when an analysis needs an electrolyzer model with time-varying operation rather than +a fixed-rate conversion. + +## Registered models + +- `ECOElectrolyzerPerformanceModel` computes hydrogen and oxygen production, electricity and water + consumption, capacity factor, and the replacement schedule. +- `BasicElectrolyzerCostModel`, `SingliticoCostModel`, and `CustomElectrolyzerCostModel` provide + alternative capital-cost models. Choose the cost model whose assumptions and input data fit the + analysis; their configuration classes are documented below. + +The performance model requires a one-hour time step. `size_mode: normal` uses the configured +cluster count and rating. `resize_by_max_feedstock` can size from the maximum available electricity, +and `resize_by_max_commodity` can size from a connected hydrogen demand. The resize modes also +require `flow_used_for_sizing`; see [Sizing Modes](../user_guide/run_size_modes.md) for the +framework-level behavior. + +## Configuration + +The performance configuration controls cluster count and rating, onshore/offshore location, +turndown, degradation penalties, and operating hours to end of life. Cost inputs depend on the +selected cost model. + +```{eval-rst} +.. autoclass:: h2integrate.converters.hydrogen.pem_electrolyzer.ECOElectrolyzerPerformanceModelConfig + :members: + :no-index: + +.. autoclass:: h2integrate.converters.hydrogen.basic_cost_model.BasicElectrolyzerCostModelConfig + :members: + :no-index: + +.. autoclass:: h2integrate.converters.hydrogen.singlitico_cost_model.SingliticoCostModelConfig + :members: + :no-index: + +.. autoclass:: h2integrate.converters.hydrogen.custom_electrolyzer_cost_model.CustomElectrolyzerCostModelConfig + :members: + :no-index: +``` + +## Examples + +- [Example 25: Sizing Modes](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/25_sizing_modes) + demonstrates the electrolyzer's sizing modes. +- [Example 01: Onshore Steel](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/01_onshore_steel_mn) + and [Example 13: Dispatch for Electrolyzer](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/13_dispatch_for_electrolyzer) + show the model in larger systems. + +The separate [WOMBAT electrolyzer operations model](wombat_electrolyzer_om.md) can be used when +maintenance and downtime behavior are important. diff --git a/docs/technology_models/steel_eaf.md b/docs/technology_models/steel_eaf.md index 2f7d25967..9b050b90f 100644 --- a/docs/technology_models/steel_eaf.md +++ b/docs/technology_models/steel_eaf.md @@ -17,6 +17,23 @@ The models implemented in H2I are: - `HydrogenEAFPlantPerformanceComponent` - `HydrogenEAFPlantCostComponent` +## Simple steel plant + +The separate `SteelPerformanceModel` represents annual steel production using a configured plant +capacity and capacity factor. `SteelCostAndFinancialModel` calculates capital and operating costs +and reports levelized cost of steel (`LCOS`). Both use hourly time steps. These models are distinct +from the natural-gas and hydrogen EAF performance components described above. + +```{eval-rst} +.. autoclass:: h2integrate.converters.steel.steel.SteelPerformanceModelConfig + :members: + :no-index: + +.. autoclass:: h2integrate.converters.steel.steel.SteelCostAndFinancialModelConfig + :members: + :no-index: +``` + ```{note} The EAF model use sponge iron as an input rather than pig iron, which is lower in carbon content. The LBNL model calls the input to the EAF pig iron, but that's typically produced using a blast furnace rather than through the DRI process and has higher carbon impurities. ``` diff --git a/docs/technology_models/tidal.md b/docs/technology_models/tidal.md index d75dd5505..9ad77dc7d 100644 --- a/docs/technology_models/tidal.md +++ b/docs/technology_models/tidal.md @@ -48,3 +48,8 @@ where: This allows a generic power curve to be adapted to different device ratings. + +## Example + +[Example 31: Tidal](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/31_tidal) +shows the tidal resource, plant, and driver configurations. diff --git a/docs/technology_models/transport.md b/docs/technology_models/transport.md new file mode 100644 index 000000000..a8dfc65aa --- /dev/null +++ b/docs/technology_models/transport.md @@ -0,0 +1,54 @@ +# Transport Components + +Transport components connect technologies in `plant_config.yaml` through +`technology_interconnections`. The built-in cable, pipe, and generic transporter models pass their +input commodity profile through without losses. They do not model transport delays, pressure drop, +or other physical constraints. + +## Cables and pipes + +The registered transport types `cable` and `pipe` are selected in a plant interconnection, for +example: + +```yaml +technology_interconnections: + - [wind, battery, electricity, cable] + - [hydrogen_producer, hydrogen_storage, hydrogen, pipe] +``` + +`cable` transports electricity. `pipe` supports hydrogen, carbon dioxide, methanol, ammonia, +nitrogen, natural gas, wellhead gas, water, oxygen, lignin, and diesel. Both are no-cost +pass-through components; add a separate cost model if transport cost is needed. + +`GenericTransporterPerformanceModel` is a configurable pass-through for a commodity and its rate +units. It supports time steps from 1 second to $10^9$ seconds. + +Combiners and splitters are documented separately in +[Combiners and Splitters](combiners_and_splitters.md). + +## Transport cost models + +- `LinearDistanceCostModel` calculates the geodesic distance between source and destination + coordinates, then multiplies it by `capex_per_km` and `fixed_opex_per_km`. Connect site latitude + and longitude to its `source_*` and `dest_*` inputs. +- `LinearMassTransportCostModel` uses geodesic distance, an optional `circuity_ratio` (default 1), + and annual commodity throughput. Its input costs are `capex_per_mton_km` and + `fixed_opex_per_mton_km`. + +```{eval-rst} +.. autoclass:: h2integrate.transporters.generic_transporter.GenericTransporterPerformanceConfig + :members: + :no-index: + +.. autoclass:: h2integrate.transporters.linear_transport_cost.LinearTransportCostConfig + :members: + :no-index: + +.. autoclass:: h2integrate.transporters.linear_mass_transport_cost.LinearMassTransportCostConfig + :members: + :no-index: +``` + +## Examples + +- [Example 22: Site DOE with transport cost](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/22_site_doe) diff --git a/docs/technology_models/windpower_wind_plant.md b/docs/technology_models/windpower_wind_plant.md index 066936b3b..f07c6a3a7 100644 --- a/docs/technology_models/windpower_wind_plant.md +++ b/docs/technology_models/windpower_wind_plant.md @@ -145,3 +145,14 @@ The model automatically formats wind resource data from the H2I wind resource da - Wind direction in degrees east of north The model handles interpolation between available resource heights to match the specified hub height and uses the closest available data when exact matches are not available. + +## Turbine file preprocessing +The `h2integrate.preprocess.wind_turbine_file_tools` module can create turbine files from the +[turbine-models](https://github.com/NatLabRockies/turbine-models) library, so you do not have to +write them by hand: + +- `export_turbine_to_pysam_format(turbine_name)` writes a PySAM options turbine YAML file (named `pysam_options_{turbine_name}.yaml` by default) to the H2I library folder, or to `output_folder` +if given. Use its path as the `pysam_options` turbine configuration input for this model. + +See [Turbine Models Library Pre-Processing](../misc_resources/turbine_models_library_preprocessing.md) +for a worked example that exports a turbine, updates the `tech_config`, and runs [Example 26](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/26_floris). diff --git a/docs/user_guide/connecting_technologies.md b/docs/user_guide/connecting_technologies.md index cdfc1574e..b10e64b86 100644 --- a/docs/user_guide/connecting_technologies.md +++ b/docs/user_guide/connecting_technologies.md @@ -88,7 +88,7 @@ When the destination has a slice, it must (1) start at `0` (a non-zero start rai ### Internal connection logic -H2Integrate processes these connections in the `connect_technologies()` method of `h2integrate_model.py`. Here's what happens internally: +H2Integrate processes these connections in `h2integrate_model.py`: `connect_technologies()` handles `technology_interconnections`, `connect_sites_to_techs()` handles `site_to_tech_connections`, `connect_techs_to_finance()` wires technology costs into the finance groups, and `connect_dispatch_rules()` handles `tech_to_dispatch_connections`. For `technology_interconnections`, here's what happens internally: 1. **Transport component creation**: For 4-element connections, H2Integrate creates a transport component instance and adds it to the OpenMDAO model with a unique name like `{source}_to_{dest}_{transport_type}`. @@ -223,144 +223,10 @@ For the splitter outputs, variables are indexed as `wellhead_gas_mixture:_in`: Time-series commodity profile from source *i* (commodity units) - - `rated__production`: Rated (nameplate) production of source *i* (commodity units, scalar) - - `_capacity_factor`: Annual capacity factor of source *i* (unitless, shape = plant life) - -- **Outputs**: - - `_out`: Combined commodity time-series profile — element-wise sum of all inputs - - `rated__production`: Total rated production — sum of all input rated productions - - `capacity_factor`: Combined capacity factor (unitless, shape = plant life) - -### Commodity output - -The combined output profile is the element-wise sum of all input profiles: - -`_out = _in1 + _in2 + …` - -### Capacity factor calculation - -The combined capacity factor is a **weighted average** of the input capacity factors, weighted by each stream's rated production: - -`CF_out = (CF1 × S1 + CF2 × S2 + …) / (S1 + S2 + …)` - -where `CF_i` is the capacity factor and `S_i` is the rated commodity production of input stream *i*. This weighting ensures that larger sources contribute proportionally more to the combined capacity factor. If the total rated production is zero, the output capacity factor is set to zero. - -### Usage example - -```yaml -technology_interconnections: [ - ["wind_farm", "combiner", "electricity", "cable"], - ["solar_farm", "combiner", "electricity", "cable"], - ["combiner", "electrolyzer", "electricity", "cable"], -] -``` - -This configuration combines wind and solar power before sending it to an electrolyzer. - -## Generic splitter - -The generic splitter takes one commodity stream from a single source and splits it between two outputs. It offers two operating modes; the examples below use electricity. - -### Configuration - -Add the splitter to your `tech_config.yaml`: - -```yaml -technologies: - electricity_splitter: - performance_model: - model: "GenericSplitterPerformanceModel" - model_inputs: - performance_parameters: - commodity: "electricity" - commodity_rate_units: "kW" - split_mode: "fraction" # or "prescribed_commodity" - fraction_to_priority_tech: 0.7 -``` - -### Split modes - -#### Fraction mode (`split_mode: "fraction"`) - -In fraction mode, you specify what fraction of the input commodity goes to the first technology: - -```yaml -config: - split_mode: "fraction" - fraction_to_priority_tech: 0.3 # 30% to first tech, 70% to second -``` - -- **Input parameter**: `fraction_to_priority_tech` (0.0 to 1.0) -- **Behavior**: - - `electricity_out1 = electricity_in × fraction` - - `electricity_out2 = electricity_in × (1 - fraction)` - -#### Prescribed commodity mode (`split_mode: "prescribed_commodity"`) - -In prescribed commodity mode, you specify an amount of the commodity to send to the first technology: - -```yaml -performance_parameters: - split_mode: "prescribed_commodity" - prescribed_commodity_to_priority_tech: 200.0 # 200 kW to first tech -``` - -- **Input parameter**: `prescribed_commodity_to_priority_tech` (in `commodity_rate_units`) -- **Behavior**: - - `electricity_out1 = min(prescribed_amount, available_power)` - - `electricity_out2 = electricity_in - electricity_out1` -- **Smart limiting**: If you request more power than available, the first output gets all available power and the second output gets zero - -### Inputs and outputs - -- **Input**: - - `electricity_in`: Total power input (kW) - - Mode-specific input: `fraction_to_priority_tech` or `prescribed_commodity_to_priority_tech` -- **Outputs**: - - `electricity_out1`: Commodity sent to the priority technology (kW) - - `electricity_out2`: Remaining commodity sent to the other technology (kW) - - `electricity_consumed`: Total commodity consumed from the input (kW) - -### Usage example - -```yaml -technology_interconnections: [ - ["offshore_wind", "electricity_splitter", "electricity", "cable"], - ["electricity_splitter", "doc", "electricity", "cable"], # first output - ["electricity_splitter", "electrolyzer", "electricity", "cable"], # second output -] -``` - -This sends part of the offshore wind power to a direct ocean capture system and the remainder to an electrolyzer. - -```{note} -Each splitter has one input stream and exactly two output streams. Add a combiner upstream when the splitter must receive multiple sources. -``` +The `cable`, `pipe`, and generic transporter models are documented in +[Transport Components](../technology_models/transport.md), and the combiner and splitter models are +documented in [Combiners and Splitters](../technology_models/combiners_and_splitters.md). This page +focuses on how to express their connections in `technology_interconnections`. diff --git a/docs/user_guide/defining_sites_and_resources.md b/docs/user_guide/defining_sites_and_resources.md index fd118b956..2f4d434b0 100644 --- a/docs/user_guide/defining_sites_and_resources.md +++ b/docs/user_guide/defining_sites_and_resources.md @@ -23,6 +23,13 @@ sites: Further information on the available resource models can be found [here](https://h2integrate.readthedocs.io/en/latest/resource/resource_index.html) +Some API resource models support simulations spanning multiple years when the requested data and +simulation time range are compatible. See the [Resource Model Index](../resource/resource_index.md) +for details, and set `plant.simulation.n_timesteps` and `dt` to match the desired simulation range. + +Connected technology models must also support the requested time range. See the +{ref}`simulation settings ` for the fixed-year and leap-day rules. + ## Site-to-resource connections overview The `site_to_tech_connections` section in your plant configuration file defines how technologies are connected to site data and resource outputs for that site. This includes resource data such as wind or solar time series and site parameters such as latitude and longitude. @@ -103,7 +110,6 @@ sites: Some examples that define a single site without resource data are: - `examples/03_methanol/smr/plant_config_smr.yaml` -- `examples/11_hybrid_energy_plant/plant_config.yaml` ### Single site with a single resource If a single technology (named `"wind"` in this example) requires resource data, then the `sites` configuration and `site_to_tech_connections` may look like: diff --git a/docs/user_guide/driver_config_reference.md b/docs/user_guide/driver_config_reference.md new file mode 100644 index 000000000..9972db445 --- /dev/null +++ b/docs/user_guide/driver_config_reference.md @@ -0,0 +1,122 @@ +# Driver Configuration Reference + +The driver configuration controls output files, optional OpenMDAO reports, analysis drivers, +design variables, objectives, constraints, and recording. It is referenced from the top-level +configuration with `driver_config`. + +## General options + +| Field | Default | Purpose | +|---|---:|---| +| `general.folder_output` | `output` | Directory for generated output and recorder files. | +| `general.create_om_reports` | `true` | Create OpenMDAO reports, including N2 diagrams. Set to `false` to disable them. | + +## Analysis driver + +The `driver` mapping may contain `optimization` or `parameter_sweep`. At most one driver's `flag` +can be true. If neither is enabled, the model runs once using the configured values. Optimization +settings include the solver, tolerance, iteration limit, derivative options, and solver-specific +options. The `parameter_sweep` block selects a generator and its inputs, such as `num_samples`, +`levels`, or a CSV `filename`. See [Design Optimization](design_optimization_in_h2i.md) and +[Parameter Sweeps](parameter_sweep_in_h2i.md) for workflows and examples. + +An optimization driver config selects a solver, then lists design variables, an objective, and +optionally constraints. This is the driver config from +[Example 5](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/05_wind_h2_opt): + +```{literalinclude} ../../examples/05_wind_h2_opt/driver_config.yaml +:language: yaml +:linenos: true +``` + +A parameter sweep uses the `parameter_sweep` block instead. This is the driver config from +[Example 22](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/22_site_doe), which +evaluates the cases listed in a CSV file (`csvgen` generator): + +```{literalinclude} ../../examples/22_site_doe/driver_config.yaml +:language: yaml +:linenos: true +``` + +## Design variables, objective, and constraints + +`design_variables` and `constraints` are grouped by technology or finance-subgroup name, then by +OpenMDAO variable name. Set `flag` to enable each entry and provide its `units`; design variables +may also specify bounds and scaling, while constraints require at least one of `lower`, `upper`, or +`equals`. The single `objective` identifies the full OpenMDAO variable name to minimize. + +The top-level key under `design_variables` and `constraints` is a technology name from +`tech_config.yaml` (or a finance subgroup name from `plant_config.yaml`), and the key beneath it is +the variable name. Together they form the full OpenMDAO variable name, `{technology}.{variable}`. For +example, this design variable is `electrolyzer.n_clusters`, and this constraint is +`electrical_load_demand.percent_load_missed`: + +```yaml +design_variables: + electrolyzer: # technology name in tech_config.yaml + n_clusters: # variable name + flag: true + lower: 2. + upper: 40. + units: unitless +constraints: + electrical_load_demand: + percent_load_missed: + flag: true + upper: 5. + units: percent +``` + +To find the correct variable name, run the model once and look at the results table that is printed +at the end of the run. The `prom_name` column lists the full variable names, and the technology and +variable name are the parts before and after the first period (see +[Postprocessing Results](postprocessing_results.md)). Design variables are inputs to a model, such as +the parameters you set under `model_inputs` in `tech_config.yaml`, and constraints are usually model +outputs. [Design Optimization](design_optimization_in_h2i.md) has more detail. + +The objective is given as the full variable name, with an optional `ref`. The `ref` is the reference +value that the driver uses to scale the objective to a magnitude of 1, which can help the optimizer +converge. The driver always minimizes, so to maximize a result, use a negative `ref`. In the example +below, the `ref` value of 10.0 means that the optimizer scales the objective so that an LCOH of 10.0 +is treated as 1. This allows the user to control the scaling of the objective and helps provide a +more well-posed optimization problem. + +```yaml +objective: + name: finance_subgroup_hydrogen.LCOH + ref: 10.0 # use a negative value, such as -1.0, to maximize instead +``` + +## Recorder + +Set `recorder.flag` to enable an SQL recorder. The `file` name is written inside +`general.folder_output`; `overwrite_recorder` controls whether an existing file is replaced or a +unique suffix is used. `recorder_attachment` may be `driver` or `model` (the driver is recommended +for parallel runs). + +```yaml +recorder: + flag: true + file: cases.sql + overwrite_recorder: true + recorder_attachment: driver + includes: ['*'] + excludes: ['*resource_data'] +``` + +| Field | Purpose | +|---|---| +| `includes` | Glob patterns selecting variables to record. Defaults to `['*']`. | +| `excludes` | Glob patterns to omit. Defaults to `['*resource_data']`, which avoids storing large, constant resource profiles. | +| `record_inputs`, `record_outputs`, `record_residuals` | Select component-level data. | +| `options_excludes` | Component option names to omit when attached to the model. | +| `record_desvars`, `record_objectives`, `record_constraints`, `record_derivative` | Select driver data; used when attached to the driver. | + +```{note} +The `excludes` supersede the `includes`. A variable that matches both an `includes` pattern and +an `excludes` pattern is not recorded. For example, with `includes: ['*']` and +`excludes: ['*resource_data']`, everything except the resource data is recorded. +``` + +The schema is maintained in [`driver_schema.yaml`](../../h2integrate/core/inputs/driver_schema.yaml). +See [Recording and Loading Data](recording_and_loading_data.md) for working recorder examples. diff --git a/docs/user_guide/how_to_set_up_an_analysis.md b/docs/user_guide/how_to_set_up_an_analysis.md index 4e5848c24..1de759166 100644 --- a/docs/user_guide/how_to_set_up_an_analysis.md +++ b/docs/user_guide/how_to_set_up_an_analysis.md @@ -62,6 +62,7 @@ If you are running a basic analysis and not an optimization, the driver config f If you are running an optimization, the driver config file will contain additional keys to define the optimization settings, including design variables, constraints, and objective functions. Further details of more complex instances of the driver config file can be found in more advanced examples as they are developed. +See the [Driver Configuration Reference](driver_config_reference.md) for a description of every driver config field, and [Design Optimization](design_optimization_in_h2i.md) and [Parameter Sweeps](parameter_sweep_in_h2i.md) for complete workflows. ## Technology config file @@ -88,6 +89,7 @@ Model inputs vary by model. Configuration classes may provide defaults for some require others. Use [`populate_tech_yaml`](populate_tech_yaml.md) to generate a configuration template, and see the model documentation in the [API documentation](../api.md) for parameter details and valid values. The available models are registered with the `@register` decorator from `h2integrate/core/supported_models.py`; see the {ref}`technology models overview ` for the full list. +See the [Technology Configuration Reference](technology_config_reference.md) for a description of the technology config fields. ```{note} Every technology group also contains a controller that converts a `{commodity}_demand` signal into the `{commodity}_set_point` consumed by the performance model. If you do not specify a `control_strategy` for a technology, H2Integrate automatically inserts an implicit passthrough controller that simply maps demand to set-point. See the [technology-level control overview](../control/technology_level_control/technology_control_overview.md) for more details. @@ -115,6 +117,7 @@ If the list is length 4, then the third entry in the list is what's being passed If the list is length 3, then the third entry in the list is what is connected directly between the technologies. The `site_to_tech_connections` section defines how resources (like wind or solar data) are connected to the technologies that use them. +See the [Plant Configuration Reference](plant_config_reference.md) for a description of every plant config field. ```{note} For more information on how to define and interpret technology interconnections, see the {ref}`connecting_technologies:overview` page. diff --git a/docs/user_guide/plant_config_reference.md b/docs/user_guide/plant_config_reference.md new file mode 100644 index 000000000..b0675769f --- /dev/null +++ b/docs/user_guide/plant_config_reference.md @@ -0,0 +1,153 @@ +# Plant Configuration Reference + +The plant configuration describes site locations, simulation time, technology connections, and +system-level finance. It is referenced from the top-level configuration with `plant_config`. See +[Defining Sites and Connecting Resources](defining_sites_and_resources.md) for resource-specific +examples, and the [Technology Configuration Reference](technology_config_reference.md) and +[Driver Configuration Reference](driver_config_reference.md) for the other input files. + +## Top-level fields + +| Field | Purpose | +|---|---| +| `name`, `description` | Identify and describe the plant configuration. | +| `sites` | Named sites with latitude, longitude, optional elevation, and named resource models. | +| `plant` | Plant lifetime and simulation time settings. | +| `technology_interconnections` | Connections between technologies and optional transport components. | +| `site_to_tech_connections` | Connections from site data or resource outputs to technology inputs. | +| `tech_to_dispatch_connections` | Connections from technologies to dispatch rules or dispatch controllers. | +| `system_level_control` | Optional system-level controller configuration. | +| `finance_parameters` | Finance groups, subgroups, and cost-year adjustment settings. | + +The plant schema currently validates the fields documented in +[`plant_schema.yaml`](../../h2integrate/core/inputs/plant_schema.yaml). +## Sites + +Each site is identified by a user-chosen key. Latitude and longitude are required; elevation is +optional. A site's `resources` mapping may contain multiple named resource models. Each resource +entry specifies a registered `resource_model` and its `resource_parameters`; the resource key is a +user-chosen name, not the model class name. Use `site_to_tech_connections` to connect site location +or resource data to technologies. + +```yaml +sites: + site_A: # user-chosen site name + latitude: 35.2018863 + longitude: -101.945027 + resources: + wind_resource: # user-chosen resource name + resource_model: WTKNLRDeveloperAPIWindResource + resource_parameters: + resource_year: 2012 +``` + +Resource models have model-specific data ranges and time-step requirements. Some API-backed wind +and solar resources support multiple years; see the [resource index](../resource/resource_index.md) +and the wind and solar resource pages. + +(plant-simulation-settings)= +## Plant lifetime and simulation + +`plant.plant_life` is the project lifetime in years. `plant.simulation` controls the simulated +period and uses seconds for `dt`: + +```yaml +plant: + plant_life: 30 + simulation: + dt: 3600 + n_timesteps: 8760 + start_time: "01/01 00:30:00" + timezone: 0 +``` + +`dt` and `n_timesteps` are required by the schema. The default `dt` is 3600 seconds and the +default `n_timesteps` is 8760. `start_time` and `timezone` are optional; timezone is the UTC offset +for the start time. Every selected model in the system must support the configured timestep and simulation +duration, and the requested period must be compatible with the connected resource data. + +**One simulation year is exactly 8,760 hours (365 days, or 31,536,000 seconds); leap years +of 8,784 hours are not modeled.** Annualization assumes 365-day plant years, so keep resource +`include_leap_day: false` (the default) for any run that relies on it. + +Simulation duration is `n_timesteps * dt / 31_536_000` years and must be positive and no +longer than `plant_life`. Partial-year and multi-year runs require support from every selected +model; otherwise models default to annual-only support. At `dt: 3600`, for example, `4380` +timesteps represent half a year. + +`plant.simulation.annual_extrapolation_method` controls how annualized profiles are extended +beyond the simulated horizon. Choose `tile`, `final_sim_value`, or `time_averaged_sim_value`. +Set it when multiple simulated year segments must be extended beyond the simulation horizon to +cover plant life. It can be omitted for a single simulated year segment, where the methods +produce the same result, or when the simulation covers the plant life. The setting applies to +capacity factors and variable operating costs. A finance subgroup using a custom timeseries may +override it with its own `annual_extrapolation_method`; otherwise the plant-level value applies. +Technology implementations may also pass an explicit method to an annualization call to +override the plant-level value. + +```{note} +The subgroup override should generally match `plant.simulation.annual_extrapolation_method`. +The finance model divides variable costs by production, so using different methods for capacity +factor and variable operating costs distorts the resulting unit costs. Per-call overrides in +technology implementations should follow the same rule. +``` + +See the {ref}`model duration requirements ` and +{ref}`annualization guidance ` for developer details. + +`n_timesteps_window` is optional and enables a time-marching simulation that advances the plant +a window of timesteps at a time; see [Time-Marching Simulation](time_marching_simulation.md). + +## Technology and site connections + +`technology_interconnections` lists source, destination, commodity or stream, and optionally a +transporter. For example, `- [wind, battery, electricity, cable]` connects wind electricity to a +battery through a cable. See [Connecting Technologies](connecting_technologies.md) for connection +forms, [Transport Components](../technology_models/transport.md) for cable and pipe behavior, and +[Combiners and Splitters](../technology_models/combiners_and_splitters.md) for combiner and splitter +behavior. + +`site_to_tech_connections` connects a named site or resource to a technology. Resource data is +typically connected with a three-element entry such as +`[site.wind_resource, wind, wind_resource_data]`; site coordinates can also be mapped to technology +inputs. See [Defining Sites and Connecting Resources](defining_sites_and_resources.md) for the +supported forms and resource variable names. + +`tech_to_dispatch_connections` is used when dispatch rules need information from technologies +upstream of a dispatch controller. Each pair identifies a source technology and the technology +whose dispatch model consumes it. For example, optimized battery dispatch commonly connects both +the upstream generator and battery to the battery controller: + +```yaml +tech_to_dispatch_connections: + - [wind, battery] + - [battery, battery] +``` + +Declare the corresponding rule models in `tech_config.yaml`; the +[Pyomo Controllers](../control/technology_level_control/pyomo_controllers.md) guide explains the +heuristic and optimized patterns. + +## System-level control + +When present, `system_level_control` enables a plant-level controller. The strategy-specific +parameters are documented with each controller in the [System-Level Control](../control/system_level_control/system_level_control.md) +guide. For example, a demand-following configuration identifies the strategy and demand technology: + +```yaml +system_level_control: + control_strategy: DemandFollowingControl + demand_component: electrical_load_demand + solver_options: + solver_name: gauss_seidel + max_iter: 20 + convergence_tolerance: 1.0e-6 +``` + +## Finance parameters + +`finance_parameters` contains `finance_groups`, `finance_subgroups`, and +`cost_adjustment_parameters`. Groups define finance models and their inputs; subgroups select +technologies, a commodity stream, and one or more groups for an analysis. Cost adjustment converts +model costs to `target_dollar_year` using `cost_year_adjustment_inflation`. See +[Specifying Finance Parameters](specifying_finance_parameters.md) for the finance-group structure. diff --git a/docs/user_guide/postprocessing_results.md b/docs/user_guide/postprocessing_results.md index 11b0ad922..53a66b3cb 100644 --- a/docs/user_guide/postprocessing_results.md +++ b/docs/user_guide/postprocessing_results.md @@ -1,3 +1,16 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.18.1 +kernelspec: + display_name: h2integrate + language: python + name: python3 +--- + # Postprocessing results When running H2Integrate, results from the simulation and individual technologies are generated automatically. @@ -191,3 +204,114 @@ Pass the base name and the function handles the rest: # Aggregates cases.sql_0, cases.sql_1, ... into a single DataFrame summary_df = convert_sql_to_csv_summary("output_dir/cases.sql") ``` + +## Printing model results + +`print_results` prints a hierarchical summary of an OpenMDAO model's inputs and outputs and returns +a dictionary containing input, explicit-output, and implicit-output metadata. The `includes` and +`excludes` arguments accept OpenMDAO variable patterns; `show_units` controls whether units are +included in the report and returned metadata. + +```python +from h2integrate.postprocess.reporting import print_results + +model.run() +result_summary = print_results( + model.prob.model, + includes="*.hydrogen_out", + excludes="*resource_data", + show_units=True, +) +``` + +The returned mapping has `inputs`, `explicit_outputs`, and `implicit_outputs` entries. + +```{eval-rst} +.. autofunction:: h2integrate.postprocess.reporting.print_results + :no-index: +``` + +## ProFAST helpers + +The ProFAST utilities create and populate a `ProFAST` object from a configuration mapping, run its +price solve, or convert an existing object back to configuration data. They are available from +`h2integrate.tools.profast_tools`. + +- `create_and_populate_profast(pf_config)` builds a ProFAST instance from a dictionary of financial + parameters and cost items. +- `run_profast(pf)` runs ProFAST to calculate the LCO and returns the solved price, summary values, + and cost-breakdown DataFrame. +- `make_price_breakdown(price_breakdown, pf_config)` outputs an LCO breakdown that includes the LCO + contribution of each technology cost. Non-component financial costs are distributed across the + primary cost items, and a check against the solved levelized cost is also returned. +- `format_profast_price_breakdown_per_year(price_breakdown)` formats a ProFAST cost breakdown by + year for reporting. +- `convert_pf_to_dict(pf)` converts a ProFAST instance into a dictionary of finance parameters and + cost inputs that is compatible with `create_and_populate_profast()`. + +```python +from h2integrate.tools.profast_tools import create_and_populate_profast, run_profast + +profast_model = create_and_populate_profast(pf_config) +solution, summary, price_breakdown = run_profast(profast_model) +``` + +### Re-running ProFAST with different finance parameters + +Setting `save_profast_config: true` in the finance `model_inputs` of the plant config saves the +ProFAST configuration of each finance subgroup to the driver's `folder_output` after a run. +[Example 2](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/02_texas_ammonia) +does this, so running it writes `outputs/profast_output_ammonia_config.yaml` for the ammonia +subgroup. First, run Example 2: + +```{code-cell} ipython3 +import os +from copy import deepcopy +from pathlib import Path + +import numpy as np +import pandas as pd + +from h2integrate import EXAMPLE_DIR, H2IntegrateModel, load_yaml +from h2integrate.tools.profast_tools import run_profast, create_and_populate_profast + +os.chdir(EXAMPLE_DIR / "02_texas_ammonia") + +model = H2IntegrateModel("02_texas_ammonia.yaml") +model.setup() + +# Set the battery set point to the electrolyzer capacity, as in the Example 2 run script +model.prob.set_val("battery.electricity_set_point", np.ones(8760) * 640.0, units="MW") +model.run() + +lcoa_h2i = model.prob.get_val("finance_subgroup_ammonia.LCOA", units="USD/kg")[0] +print(f"LCOA from the H2I run: {lcoa_h2i:.3f} USD/kg") +``` + +The saved configuration can be loaded and its `params` changed, and ProFAST can then be run again +without re-running H2I. Here, the discount rate and debt interest rate are changed. The `baseline` +case reproduces the LCOA from the H2I run: + +```{code-cell} ipython3 +pf_config = load_yaml(Path("outputs") / "profast_output_ammonia_config.yaml") + +cases = { + "baseline": {}, + "8% discount rate": {"leverage after tax nominal discount rate": 0.08}, + "10% discount rate": {"leverage after tax nominal discount rate": 0.10}, + "5% debt interest rate": {"debt interest rate": 0.05}, +} + +results = [] +for case_name, updated_params in cases.items(): + case_config = deepcopy(pf_config) + case_config["params"].update(updated_params) + + pf = create_and_populate_profast(case_config) + solution, _summary, _price_breakdown = run_profast(pf) + results.append({"case": case_name, "LCOA (USD/kg)": solution["lco"]}) + +print(pd.DataFrame(results).round(3).to_string(index=False)) +``` + +A higher discount rate raises the LCOA, and a lower debt interest rate lowers it. diff --git a/docs/user_guide/specifying_finance_parameters.md b/docs/user_guide/specifying_finance_parameters.md index 2c624154c..57660bceb 100644 --- a/docs/user_guide/specifying_finance_parameters.md +++ b/docs/user_guide/specifying_finance_parameters.md @@ -49,6 +49,18 @@ Within this framework, there are two distinct layers, **finance groups** and **f - `use_commodity_stream_timeseries` (optional): A boolean that defaults to False. If True, then flags to use a timeseries profile for the finance calculation rather than the capacity factor and rated commodity production of the `commodity_stream` technology. - `commodity_stream_output`: The name of timeseries profile output variable from `commodity_stream` to use for the finance calculation(s). This parameter is required if `use_commodity_stream_timeseries` is True. + For this timeseries path, a subgroup may set `annual_extrapolation_method` to override the + plant-level setting. If omitted, the plant-level value is used. See + {ref}`Plant lifetime and simulation ` for the available methods and + when a method is required. + + ```{note} + A subgroup `annual_extrapolation_method` should generally match + `plant.simulation.annual_extrapolation_method`. The finance model divides variable costs by + production, so a different method for the capacity factor than for variable operating costs + distorts the resulting unit costs. + ``` + ```{important} If no subgroups are defined, a **default subgroup** is created that contains *all technologies* and references the default finance model and commodity defined in `finance_groups`. ``` diff --git a/docs/user_guide/technology_config_reference.md b/docs/user_guide/technology_config_reference.md new file mode 100644 index 000000000..1ba6012a0 --- /dev/null +++ b/docs/user_guide/technology_config_reference.md @@ -0,0 +1,41 @@ +# Technology Configuration Reference + +The technology configuration declares each technology, its model classes, and model-specific +inputs. It is referenced from the top-level configuration with `technology_config`. The schema is +maintained in [`tech_schema.yaml`](../../h2integrate/core/inputs/tech_schema.yaml), and +[`populate_tech_yaml`](populate_tech_yaml.md) can generate model input templates. The parameters +that each model accepts are documented in the [API documentation](../api.md), and the +{ref}`technology models overview ` lists the available models. + +## Technology entries + +Each key under `technologies` is a user-chosen technology name. Model class names are specified +under their respective model sections: + +| Field | Purpose | +|---|---| +| `performance_model.model` | Registered performance model class. Required by the technology schema. | +| `cost_model.model` | Registered cost model class when the technology has a cost model. Some registered models are explicitly cost-free. | +| `control_strategy.model` | Optional controller class for technology-level control. | +| `dispatch_rule_set.model` | Rule class used by heuristic Pyomo dispatch. Optimized dispatch does not use this field. | +| `finance_model.model` | Optional technology-level finance model. | +| `model_location` | Optional source path for a custom performance, cost, or finance model. | +| `finance_model.group` | Name of the plant finance group that receives the technology-specific finance model. | + +The performance, cost, control, dispatch-rule, and finance sections contain a `model` field; put +model parameters under `model_inputs`, not alongside `model`. + +## Model inputs + +`model_inputs` may contain: + +- `shared_parameters`: values shared by at least two model roles. +- `performance_parameters`: performance-model configuration. +- `cost_parameters`: cost-model configuration. +- `financial_parameters`: technology finance-model configuration. +- `control_parameters`: technology controller configuration. + +The accepted fields in each parameter section depend on the selected model. The model documentation +and generated templates are the sources for those model-specific values. For dispatch rule wiring, +see [Pyomo Controllers](../control/technology_level_control/pyomo_controllers.md); for technology +finance groups, see [Specifying Finance Parameters](specifying_finance_parameters.md). diff --git a/docs/user_guide/time_marching_simulation.md b/docs/user_guide/time_marching_simulation.md new file mode 100644 index 000000000..6e4da3170 --- /dev/null +++ b/docs/user_guide/time_marching_simulation.md @@ -0,0 +1,50 @@ +# Time-Marching Simulation + +By default, H2Integrate simulates each technology in a linear fashion: each model computes its full time series before the next model in the plant runs. +In a time-marching simulation, the plant advances through the simulation one window of timesteps at a time. +Every model computes only its current window, then the next window is simulated, and so on until the end of the simulation. +Models that carry state between timesteps, such as storage, keep that state from one window to the next. + +Time-marching is opt-in and does not change the results of a simulation that is supported. +Models must be flagged as steppable (see below) before they can be used in a time-marching simulation. + +## Configuring a time-marching simulation + +Set `n_timesteps_window`, the number of timesteps in each window, in the `plant.simulation` section of the plant config: + +```yaml +plant: + simulation: + dt: 3600 + n_timesteps: 8760 + n_timesteps_window: 24 +``` + +- Omit `n_timesteps_window`, or set it equal to `n_timesteps`, to simulate all timesteps at once (the default behavior). +- `n_timesteps_window` must be a positive integer that evenly divides `n_timesteps`. +- Cost and finance models are computed once, after the final window. + +## Current limitations + +- Time-marching is not supported together with system-level control or a technology interconnection graph that contains a cycle. + H2Integrate raises a `NotImplementedError` in these cases. +- Every technology performance model and technology control strategy in the plant must be steppable. + If any is not, H2Integrate raises an error that lists the unsupported models. +- Derivatives are not propagated through a time-marching simulation, so use gradient-free or finite-difference drivers. + +## Steppable models + +The following technology performance models and control strategies are currently steppable: + +- `PYSAMSolarPlantPerformanceModel` +- `GridPerformanceModel` +- `GenericCombinerPerformanceModel` +- `GenericDemandComponent` +- `StoragePerformanceModel` +- `DemandOpenLoopStorageController` +- `PassthroughController` + +## Example + +[Example 24](https://github.com/NatLabRockies/H2Integrate/tree/develop/examples/24_solar_battery_grid) (`examples/24_solar_battery_grid/run_time_marching.py`) simulates a solar, battery, and grid plant both +at once and in 24-timestep windows, and shows that the results match. diff --git a/examples/03_methanol/co2_hydrogenation_doc/plot_co2h_methanol.py b/examples/03_methanol/co2_hydrogenation_doc/plot_co2h_methanol.py index 3f8174687..d67c555f0 100644 --- a/examples/03_methanol/co2_hydrogenation_doc/plot_co2h_methanol.py +++ b/examples/03_methanol/co2_hydrogenation_doc/plot_co2h_methanol.py @@ -19,7 +19,7 @@ def plot_methanol(model): "electricity_in", units="MW" ) elyzer_h2_out = model.plant.electrolyzer.ECOElectrolyzerPerformanceModel.get_val( - "hydrogen_out", units="t/day" + "hydrogen_out", units="t/d" ) plt.plot(times, elyzer_elec_in, label="Electricity Available [MW]", color=[0.5, 0.5, 1]) plt.plot( @@ -69,8 +69,8 @@ def plot_methanol(model): plt.subplot(3, 2, 3) T = plt.title("Hydrogen\nStorage") T.set_position([-0.2, 1.1]) - h2_storage_in = model.plant.electrolyzer_to_h2_storage_pipe.get_val("hydrogen_in", units="kg/h") - h2_storage_out = model.plant.h2_storage_to_methanol_pipe.get_val("hydrogen_out", units="kg/h") + h2_storage_in = model.plant.h2_storage.get_val("hydrogen_in", units="kg/h") + h2_storage_out = model.plant.h2_storage.get_val("hydrogen_out", units="kg/h") plt.plot(times, h2_storage_in, label="Hydrogen In [kg/hr]", color=[1, 0.5, 0]) plt.plot(times, h2_storage_out, label="Hydrogen Out [kg/hr]", color=[0, 0.5, 0]) plt.legend(bbox_to_anchor=(0, 1.02), loc=3) @@ -85,8 +85,8 @@ def plot_methanol(model): plt.subplot(3, 2, 4) T = plt.title("CO$_2$\nStorage") T.set_position([-0.2, 1.1]) - co2_storage_in = model.plant.doc_to_co2_storage_pipe.get_val("co2_in", units="kg/h") - co2_storage_out = model.plant.co2_storage_to_methanol_pipe.get_val("co2_out", units="kg/h") + co2_storage_in = model.plant.co2_storage.get_val("co2_in", units="kg/h") + co2_storage_out = model.plant.co2_storage.get_val("co2_out", units="kg/h") plt.plot(times, co2_storage_in, label="CO$_2$ In [kg/hr]", color=[0.5, 0.25, 0]) plt.plot(times, co2_storage_out, label="CO$_2$ Out [kg/hr]", color=[0, 0.25, 0.5]) plt.legend(bbox_to_anchor=(0, 1.02), loc=3) @@ -125,8 +125,8 @@ def plot_methanol(model): plt.subplot(3, 2, 6) T = plt.title("State of\nCharge\n(SOC)") T.set_position([-0.2, 1.1]) - h2_soc = model.plant.h2_storage.get_val("hydrogen_soc", units="percent") - co2_soc = model.plant.co2_storage.get_val("co2_soc", units="percent") + h2_soc = model.plant.h2_storage.get_val("SOC", units="percent") + co2_soc = model.plant.co2_storage.get_val("SOC", units="percent") plt.plot(times, h2_soc, label="Hydrogen SOC [%]", color=[1, 0.5, 0]) plt.plot(times, co2_soc, label="CO$_2$ SOC [%]", color=[0.5, 0.25, 0]) plt.legend(bbox_to_anchor=(0, 1.02), loc=3) diff --git a/examples/09_co2/ocean_alkalinity_enhancement_financials/offshore_plant_oae.yaml b/examples/09_co2/ocean_alkalinity_enhancement/offshore_plant_oae_financials.yaml similarity index 100% rename from examples/09_co2/ocean_alkalinity_enhancement_financials/offshore_plant_oae.yaml rename to examples/09_co2/ocean_alkalinity_enhancement/offshore_plant_oae_financials.yaml diff --git a/examples/09_co2/ocean_alkalinity_enhancement_financials/plant_config_financials.yaml b/examples/09_co2/ocean_alkalinity_enhancement/plant_config_financials.yaml similarity index 100% rename from examples/09_co2/ocean_alkalinity_enhancement_financials/plant_config_financials.yaml rename to examples/09_co2/ocean_alkalinity_enhancement/plant_config_financials.yaml diff --git a/examples/09_co2/ocean_alkalinity_enhancement/run_wind_wave_oae.py b/examples/09_co2/ocean_alkalinity_enhancement/run_wind_wave_oae.py index e228098fa..f44589b23 100644 --- a/examples/09_co2/ocean_alkalinity_enhancement/run_wind_wave_oae.py +++ b/examples/09_co2/ocean_alkalinity_enhancement/run_wind_wave_oae.py @@ -1,8 +1,18 @@ +"""Ocean alkalinity enhancement (OAE) powered by wind, with two cost modeling approaches. + +- `offshore_plant_oae.yaml`: offshore wind and a battery power the OAE system, which uses + `OAECostModel` and is included in a ProFAST levelized cost of CO2 calculation. +- `offshore_plant_oae_financials.yaml`: a smaller wind plant in Puget Sound powers the OAE system, + which uses `OAECostAndFinancialModel` to estimate the carbon credit value needed for viability. +""" + import numpy as np from h2integrate import H2IntegrateModel +################################## +# OAE with the OAE cost model and ProFAST finance h2i_model = H2IntegrateModel("offshore_plant_oae.yaml") # Set battery demand profile @@ -16,3 +26,11 @@ # Post-process the results h2i_model.post_process() + +################################## +# OAE with the OAE cost and financial model +h2i_financials = H2IntegrateModel("offshore_plant_oae_financials.yaml") + +h2i_financials.run() + +h2i_financials.post_process() diff --git a/examples/09_co2/ocean_alkalinity_enhancement_financials/tech_config_financials.yaml b/examples/09_co2/ocean_alkalinity_enhancement/tech_config_financials.yaml similarity index 100% rename from examples/09_co2/ocean_alkalinity_enhancement_financials/tech_config_financials.yaml rename to examples/09_co2/ocean_alkalinity_enhancement/tech_config_financials.yaml diff --git a/examples/09_co2/ocean_alkalinity_enhancement_financials/driver_config.yaml b/examples/09_co2/ocean_alkalinity_enhancement_financials/driver_config.yaml deleted file mode 100644 index d4a803402..000000000 --- a/examples/09_co2/ocean_alkalinity_enhancement_financials/driver_config.yaml +++ /dev/null @@ -1,4 +0,0 @@ -name: driver_config -description: This analysis runs a wind plant connected to a ocean alkalinity enhancement device -general: - folder_output: wind_oae diff --git a/examples/09_co2/ocean_alkalinity_enhancement_financials/run_wind_wave_oae.py b/examples/09_co2/ocean_alkalinity_enhancement_financials/run_wind_wave_oae.py deleted file mode 100644 index 9f3124512..000000000 --- a/examples/09_co2/ocean_alkalinity_enhancement_financials/run_wind_wave_oae.py +++ /dev/null @@ -1,10 +0,0 @@ -from h2integrate import H2IntegrateModel - - -h2i_model = H2IntegrateModel("offshore_plant_oae.yaml") - -# Run the model -h2i_model.run() - -# Post-process the results -h2i_model.post_process() diff --git a/examples/09_co2/ocean_alkalinity_enhancement_financials/weather_data/48.153889_-122.774111_windtoolkit_2013_60min_80m.srw b/examples/09_co2/ocean_alkalinity_enhancement_financials/weather_data/48.153889_-122.774111_windtoolkit_2013_60min_80m.srw deleted file mode 100644 index 00b0a8060..000000000 --- a/examples/09_co2/ocean_alkalinity_enhancement_financials/weather_data/48.153889_-122.774111_windtoolkit_2013_60min_80m.srw +++ /dev/null @@ -1,8765 +0,0 @@ -313065,city??,state??,country??,2013,48.1530265808,-122.761062622,Not Available,1,8760 -WIND Toolkit data from NREL downloaded on 2025-5-30 -Temperature,Pressure,Speed,Direction -C,atm,m/s,Degrees -80,80,80,80 -1.0,1.01,1.800,333.3 -0.4,1.01,1.190,326.2 -0.6,1.01,1.260,342.2 -0.6,1.01,1.310,327.7 -0.4,1.01,1.530,328.8 -0.3,1.01,1.370,350.1 -0.1,1.01,1.260,33.7 --0.3,1.01,0.900,31.7 --0.3,1.01,1.380,315.6 --0.4,1.01,1.800,321.0 --0.1,1.01,2.000,333.5 --0.5,1.01,2.160,320.9 --0.1,1.01,2.250,306.2 -0.2,1.01,1.820,320.6 -0.6,1.01,1.380,320.2 -1.1,1.00,1.220,333.0 -1.5,1.00,1.270,327.1 -1.6,1.00,1.480,354.7 -1.3,1.00,1.090,14.2 -1.4,1.00,0.760,65.0 -1.6,1.00,1.590,96.6 -1.9,1.00,2.840,92.3 -1.9,1.00,2.530,109.2 -2.0,1.00,3.960,127.5 -3.1,1.00,4.510,137.3 -3.4,1.00,5.330,143.4 -4.1,1.00,6.910,149.6 -4.1,1.00,6.180,150.0 -2.1,1.00,4.440,145.0 -1.9,1.00,3.460,151.8 -2.4,1.00,3.390,140.2 -2.5,1.00,3.630,139.4 -2.2,1.00,3.310,135.4 -2.9,1.00,4.170,126.3 -2.9,1.00,4.170,123.3 -3.6,1.00,4.980,129.4 -3.6,1.00,5.260,128.7 -4.4,1.00,4.150,119.9 -4.4,1.00,4.260,125.2 -4.4,1.00,4.190,118.5 -4.9,1.00,5.220,127.3 -4.6,1.00,5.670,140.8 -3.3,1.00,5.080,156.5 -4.4,1.00,6.010,155.6 -4.7,1.00,5.020,164.5 -2.8,1.00,3.570,138.4 -2.6,1.00,1.440,86.2 -2.6,1.00,1.690,145.0 -2.6,1.00,1.300,120.1 -3.0,1.00,1.200,104.0 -2.0,1.00,0.990,82.7 -2.1,1.00,0.750,89.0 -1.0,1.00,0.860,81.9 -0.3,0.99,0.930,109.1 --0.1,0.99,2.160,126.5 -2.0,0.99,3.660,137.2 -1.9,0.99,3.330,150.8 -2.0,0.99,2.670,132.8 -0.9,0.99,2.940,121.4 -0.1,0.99,4.980,136.4 -1.6,0.99,5.810,145.2 -0.5,0.99,0.920,211.3 -1.2,0.99,1.730,170.7 -3.1,0.99,6.830,138.7 -4.7,0.99,8.230,161.3 -6.3,0.99,10.550,171.9 -6.6,0.99,11.090,158.1 -5.5,0.99,12.290,161.4 -5.3,0.99,10.110,161.7 -5.1,0.99,11.880,157.4 -4.4,0.99,14.010,151.7 -4.3,0.99,15.160,153.0 -4.0,0.99,14.740,147.6 -3.8,0.99,14.170,162.1 -3.4,0.99,11.770,168.5 -3.3,1.00,7.180,174.8 -3.0,1.00,3.760,219.0 -3.4,1.00,2.180,232.0 -3.6,1.00,0.560,153.3 -4.1,1.00,1.430,84.0 -3.7,1.00,0.980,46.3 -3.9,1.00,2.250,83.1 -3.5,1.00,1.740,81.8 -3.1,1.00,0.950,48.4 -3.1,1.00,0.420,84.6 -3.1,1.00,0.850,194.1 -3.7,1.00,0.680,191.2 -3.9,1.00,1.680,187.1 -4.2,1.00,2.800,130.0 -4.5,1.00,2.290,140.2 -4.5,1.00,3.370,136.1 -4.3,1.00,3.410,151.3 -4.0,1.00,3.100,136.6 -4.0,1.00,2.370,143.3 -4.3,1.00,2.050,135.5 -4.0,1.00,2.180,136.5 -4.1,1.00,1.040,158.9 -4.1,1.00,0.390,149.1 -4.0,1.00,1.430,121.6 -4.0,1.00,1.140,168.8 -4.3,1.00,2.720,157.9 -4.8,1.00,4.480,140.2 -5.2,1.00,6.380,142.8 -5.1,1.00,5.680,131.3 -4.9,1.00,7.280,139.6 -5.4,1.00,7.810,128.7 -5.5,1.00,7.460,160.9 -5.7,1.00,8.510,156.4 -4.9,0.99,9.520,157.9 -5.6,0.99,9.680,165.0 -6.0,0.99,11.480,164.6 -6.2,0.99,11.920,161.9 -5.9,0.99,12.640,153.5 -6.0,0.99,13.970,157.8 -6.0,0.99,13.420,156.7 -5.9,0.99,14.050,165.7 -6.1,0.99,15.050,165.6 -6.2,0.99,8.810,202.5 -6.4,0.99,3.200,213.0 -5.8,0.99,3.140,185.4 -5.7,0.99,2.310,181.9 -5.5,0.99,2.480,157.2 -5.0,0.99,3.770,151.6 -4.6,0.99,4.260,140.1 -2.3,0.99,5.050,155.0 -2.2,0.99,5.880,147.3 -2.1,0.99,6.580,143.7 -1.7,0.99,8.310,154.2 -2.9,0.99,10.250,146.8 -3.2,0.99,10.060,147.8 -3.5,0.99,12.750,162.3 -3.4,0.99,11.560,156.0 -3.6,0.99,11.520,153.5 -4.0,0.99,11.340,147.8 -5.1,0.99,11.850,152.4 -5.4,0.99,11.050,155.8 -5.8,0.99,10.900,161.0 -5.7,0.99,8.370,169.9 -5.7,0.99,10.120,169.5 -5.2,0.99,12.770,163.8 -4.7,0.99,15.000,167.2 -4.5,0.99,17.010,164.5 -4.2,0.99,16.630,155.8 -4.2,0.99,16.420,151.4 -4.3,0.99,15.460,146.9 -4.3,0.99,14.320,145.2 -4.6,0.99,14.340,150.0 -4.7,0.98,13.000,149.8 -4.9,0.98,10.910,148.5 -5.6,0.98,9.130,150.7 -5.6,0.98,7.610,142.1 -6.1,0.98,6.990,139.8 -6.4,0.98,4.590,148.0 -6.5,0.99,4.430,139.7 -6.2,0.99,4.570,149.5 -5.9,0.99,4.290,145.8 -7.2,0.99,3.770,155.9 -9.3,0.99,3.490,152.7 -9.5,0.99,2.890,173.3 -8.7,0.99,5.410,261.1 -8.5,0.99,7.220,266.6 -8.6,0.99,11.310,279.8 -8.2,0.99,15.780,288.5 -7.9,0.99,11.780,281.6 -8.2,0.99,4.130,276.3 -7.2,0.99,6.130,280.4 -6.9,0.99,2.500,224.3 -7.0,0.99,4.950,259.8 -7.2,0.99,6.450,286.9 -7.5,0.99,4.250,263.3 -6.6,0.99,3.710,194.2 -5.8,0.99,3.970,143.2 -6.2,0.99,4.690,131.6 -5.9,0.99,4.860,144.9 -5.3,0.99,5.780,132.1 -5.6,0.99,8.430,159.0 -5.3,0.99,9.540,152.7 -5.0,0.99,12.550,152.5 -5.3,0.99,13.360,144.2 -5.0,0.99,13.670,145.5 -5.3,0.98,13.110,147.1 -6.3,0.98,12.640,149.8 -9.3,0.98,10.490,164.7 -8.8,0.98,1.550,225.7 -10.2,0.98,1.180,163.6 -10.0,0.98,1.050,273.1 -10.8,0.98,0.840,277.3 -10.6,0.98,7.010,232.8 -10.4,0.98,2.990,199.9 -10.3,0.98,3.980,212.8 -10.4,0.98,8.810,187.0 -10.4,0.98,8.400,202.3 -10.0,0.98,11.560,189.4 -9.2,0.98,3.330,276.7 -7.6,0.98,5.550,330.9 -7.3,0.98,3.520,315.8 -6.8,0.98,2.660,279.8 -6.3,0.98,12.070,288.6 -6.2,0.98,7.150,264.5 -6.0,0.98,4.750,258.4 -6.4,0.98,8.750,274.2 -6.0,0.98,1.740,206.1 -5.5,0.98,4.000,224.1 -5.6,0.98,3.070,139.8 -5.9,0.98,2.870,145.5 -5.5,0.98,4.130,126.3 -5.8,0.98,3.210,145.2 -5.1,0.98,3.730,24.2 -5.2,0.98,2.380,95.1 -4.2,0.98,1.580,254.0 -4.2,0.98,1.260,108.6 -2.7,0.99,7.150,168.7 -3.1,0.99,4.110,148.9 -3.5,0.99,4.300,184.2 -3.6,0.99,4.880,160.3 -2.9,0.99,5.600,165.6 -3.0,0.99,3.350,180.1 -2.5,0.99,3.160,119.7 -2.0,0.99,2.250,153.5 -3.1,0.99,2.150,278.7 -3.3,0.99,4.330,318.1 -3.4,0.99,6.930,339.4 -2.8,0.99,6.220,353.1 -2.0,0.99,7.390,356.5 -1.7,0.99,6.510,343.6 -1.5,0.99,7.290,314.4 -1.4,0.99,7.330,307.4 -1.7,0.99,8.510,316.4 -1.9,0.99,7.110,312.4 -2.0,0.99,7.290,314.7 -2.0,0.99,6.410,318.7 -3.2,0.99,6.650,319.3 -1.7,0.99,8.370,330.3 -1.6,0.99,9.140,337.4 -1.9,0.99,7.770,328.5 -1.1,0.99,7.080,335.1 -1.2,0.99,9.360,356.3 -1.0,0.99,7.760,349.1 -0.9,1.00,7.810,1.9 --0.3,1.00,6.360,319.7 --0.7,1.00,5.530,326.5 -0.1,1.00,9.000,356.7 -0.1,1.00,9.210,354.6 -0.0,1.00,9.970,325.4 --0.7,1.00,11.260,319.6 --1.0,1.00,11.450,318.4 --1.8,1.00,12.460,300.7 --2.6,1.00,11.320,302.0 --1.7,1.00,11.630,315.1 --0.9,1.00,10.970,319.4 --0.6,1.00,9.560,319.3 --0.7,1.00,6.730,335.4 --1.7,1.00,5.670,305.8 --0.9,1.00,5.750,318.9 --0.7,1.00,4.650,335.2 --0.6,1.00,3.610,329.3 --0.3,1.00,2.630,316.4 --0.1,1.00,3.250,342.7 --0.1,1.00,2.900,343.3 --0.3,1.00,1.990,298.5 --0.1,1.00,0.510,312.4 --0.0,1.00,0.280,225.1 -0.1,1.00,0.520,170.5 --0.1,1.00,0.430,182.8 --0.4,1.00,0.690,149.8 --0.8,1.00,0.900,165.4 --2.3,1.00,1.150,129.5 --2.0,1.00,1.240,108.2 --2.3,1.00,1.170,113.0 --2.7,1.00,0.760,116.6 --3.1,1.00,0.410,73.4 --3.5,1.00,1.260,331.2 --3.5,1.00,2.580,326.7 --4.0,1.00,3.300,322.7 --5.0,1.00,4.220,310.3 --4.8,1.00,4.330,298.7 --2.9,1.00,4.210,298.5 --1.8,1.00,5.170,326.5 --0.9,1.00,5.930,323.0 --0.5,1.00,5.860,328.7 --0.4,1.00,6.970,340.8 --0.3,1.00,7.110,350.3 --0.5,1.00,5.900,352.6 --0.6,1.00,5.290,352.7 --0.6,1.00,3.960,340.3 --0.6,1.00,3.800,337.8 --0.7,1.00,3.530,328.4 --1.6,1.01,3.580,321.0 --2.8,1.01,2.970,317.4 --3.1,1.01,2.170,307.4 --3.0,1.01,2.230,309.4 --2.9,1.01,3.250,308.2 --3.0,1.01,4.110,311.0 --3.4,1.01,4.250,308.7 --3.7,1.01,4.440,304.8 --3.9,1.01,4.690,311.3 --3.9,1.01,5.220,309.5 --3.7,1.01,5.020,312.6 --4.5,1.01,4.200,321.8 --3.7,1.01,3.220,298.0 --2.9,1.01,3.000,295.9 --2.4,1.01,3.080,286.3 --1.6,1.01,2.320,297.7 --1.1,1.01,2.020,296.6 --0.4,1.01,1.390,305.7 --0.0,1.01,1.010,317.6 -0.1,1.01,0.670,355.6 --0.4,1.01,0.390,64.3 --0.8,1.01,1.070,121.0 --0.5,1.01,1.710,117.4 --0.9,1.01,2.030,127.8 --0.9,1.01,2.070,133.9 --1.0,1.01,1.740,145.0 --1.3,1.01,2.200,147.8 --1.3,1.01,2.470,148.6 --1.8,1.01,3.460,146.2 --2.1,1.01,4.080,146.1 --2.7,1.01,4.310,149.1 --2.9,1.01,4.500,145.0 --2.4,1.01,5.390,148.3 --2.1,1.01,6.780,149.0 --1.2,1.01,7.220,150.2 --1.0,1.01,7.270,148.5 --0.9,1.01,7.280,149.2 --1.1,1.01,6.860,148.1 --1.1,1.01,6.680,146.4 --0.6,1.01,6.350,141.0 --0.3,1.01,5.660,135.9 --0.2,1.01,5.480,141.8 -0.2,1.01,4.970,141.3 -0.3,1.01,4.780,141.3 -0.1,1.01,4.250,142.7 -0.2,1.01,3.960,139.4 -0.0,1.01,4.130,136.0 -0.4,1.01,4.560,132.7 -0.1,1.01,5.170,116.7 -0.2,1.01,4.500,124.6 -0.6,1.01,4.480,110.2 -0.2,1.01,3.900,116.1 --0.7,1.01,2.460,126.2 --0.7,1.01,2.170,121.2 --1.2,1.01,2.670,133.1 --1.4,1.01,2.430,125.5 --1.1,1.01,1.850,139.8 --0.6,1.01,1.310,144.5 --0.7,1.01,1.580,129.3 --1.0,1.01,2.310,122.1 --0.1,1.01,2.040,112.0 --0.1,1.01,1.810,135.2 -0.6,1.01,1.680,129.4 -0.9,1.01,0.960,108.9 -1.3,1.01,1.250,111.0 -1.5,1.01,1.740,117.2 -1.5,1.01,2.460,124.3 -0.8,1.01,2.410,145.0 -1.6,1.01,2.110,152.9 -0.8,1.01,2.130,143.3 -0.7,1.01,2.000,128.5 -0.6,1.01,2.180,129.2 -0.5,1.01,2.450,123.5 -0.8,1.01,1.730,120.7 -0.6,1.01,1.320,107.6 -0.3,1.01,0.980,78.5 --0.2,1.01,0.510,55.9 --0.3,1.01,1.080,11.1 --0.8,1.01,1.260,329.6 --1.1,1.01,1.750,314.7 --1.0,1.01,1.880,324.9 --0.8,1.01,1.870,329.2 --0.8,1.01,2.020,330.6 --0.6,1.01,2.230,331.4 --0.7,1.01,2.570,326.8 --0.5,1.01,2.970,333.9 --0.1,1.01,3.160,334.7 -0.6,1.01,3.970,348.6 -0.9,1.01,3.640,345.1 -1.1,1.01,3.250,344.1 -1.2,1.01,3.020,338.7 -1.3,1.01,3.120,335.9 -1.5,1.01,3.490,330.0 -1.6,1.01,3.230,329.2 -1.5,1.01,3.050,330.3 -1.5,1.01,3.440,328.3 -1.0,1.01,4.440,333.8 -0.9,1.01,4.260,330.0 -0.5,1.01,3.580,332.9 -0.1,1.01,3.270,327.2 -0.0,1.01,2.870,326.7 -0.1,1.01,2.510,324.8 -0.2,1.01,1.840,330.8 -0.3,1.01,1.380,346.9 -0.1,1.01,1.230,348.0 --0.3,1.00,1.090,327.9 --0.4,1.00,1.170,320.7 --0.3,1.00,1.330,311.6 --0.4,1.00,2.150,307.5 --0.1,1.00,2.140,306.1 -0.7,1.00,1.750,310.3 -1.2,1.00,1.160,315.1 -1.8,1.00,0.150,12.8 -2.3,1.00,1.060,136.8 -2.1,1.00,1.760,131.1 -2.5,1.00,2.270,130.6 -2.6,1.00,2.410,142.1 -2.4,1.00,2.350,143.9 -2.6,1.00,2.360,144.0 -2.6,1.00,2.050,141.2 -2.5,1.00,2.060,140.3 -2.3,1.00,1.620,146.7 -2.2,1.00,1.430,158.5 -1.6,1.00,0.980,170.8 -1.4,1.00,0.780,182.9 -1.4,1.00,1.070,161.7 -1.4,1.00,1.200,151.3 -1.5,1.00,1.220,146.0 -1.4,1.00,1.330,149.7 -1.2,1.00,0.680,140.7 -0.8,1.00,0.100,20.4 -1.0,1.00,1.090,329.3 -1.4,1.00,1.730,332.0 -0.8,1.00,2.750,320.3 -0.8,1.00,3.090,310.9 -1.1,1.00,3.380,308.5 -1.6,1.00,3.350,316.4 -1.9,1.00,3.170,327.9 -2.1,1.00,3.010,340.2 -1.7,1.00,3.450,332.8 -2.1,1.00,3.500,314.4 -2.1,1.00,4.030,298.4 -1.7,1.00,4.320,298.5 -1.9,1.00,3.960,301.6 -1.9,1.00,3.570,315.0 -1.7,1.00,3.690,328.8 -0.7,1.00,3.590,317.4 -0.6,1.00,3.380,322.2 -0.7,1.00,4.020,314.7 -0.4,1.00,4.060,318.8 -0.0,1.00,4.560,310.8 --0.2,1.00,4.580,309.6 --0.3,1.00,4.570,308.2 --0.3,1.00,4.540,307.7 --0.2,1.00,4.590,309.3 --0.2,1.00,4.280,307.8 -0.0,1.00,4.150,307.2 -0.2,1.00,4.140,308.2 -1.2,1.00,4.340,308.4 -2.0,1.00,4.760,311.8 -2.7,1.00,5.040,303.9 -3.2,1.00,5.430,306.4 -3.2,1.00,5.670,311.6 -2.6,1.00,5.460,318.0 -2.3,1.00,5.550,317.8 -2.1,1.00,5.660,318.6 -2.1,1.00,5.490,317.6 -2.4,1.00,5.330,321.6 -2.3,1.00,5.420,324.0 -0.7,1.00,5.170,315.0 --0.3,1.00,5.420,311.5 --0.3,1.00,5.380,310.7 --0.4,1.00,5.670,311.4 --0.4,1.00,5.900,307.2 --0.3,1.00,5.940,312.5 --0.2,1.00,5.750,310.8 --0.5,1.00,6.290,310.0 --0.2,1.00,5.850,308.2 -0.1,1.00,5.470,307.9 -0.4,1.00,5.080,307.5 -1.1,1.00,4.600,306.7 -1.6,1.00,4.160,309.2 -2.5,1.00,4.050,320.2 -2.8,1.00,4.010,316.8 -3.1,1.00,4.030,314.1 -3.5,1.00,4.290,313.7 -3.6,1.00,4.590,311.0 -3.7,1.00,4.550,305.4 -4.0,1.00,4.500,308.3 -3.5,1.00,4.310,314.4 -3.1,1.00,4.500,312.8 -3.1,1.00,4.460,310.1 -2.9,1.00,4.510,314.4 -2.8,1.00,4.610,310.3 -2.7,1.00,4.310,310.6 -2.4,1.00,4.200,311.4 -1.6,1.00,3.870,307.6 -1.9,1.00,3.420,311.6 -1.8,1.00,3.120,312.9 -1.7,1.00,2.660,315.0 -1.6,1.00,2.360,312.0 -1.4,1.00,2.160,312.3 -1.5,1.00,1.650,323.2 -1.2,1.00,1.300,324.7 -1.3,1.00,1.500,326.5 -1.4,1.00,1.620,330.9 -1.6,1.00,1.750,336.4 -2.3,1.00,1.150,355.5 -2.9,1.00,0.770,35.8 -3.2,1.00,0.910,82.8 -2.9,1.00,1.460,121.1 -2.8,1.00,2.190,122.8 -2.4,1.00,2.880,128.1 -0.4,1.00,2.200,149.2 -0.2,1.00,2.550,145.0 -0.2,0.99,2.890,143.1 --0.4,0.99,2.990,140.2 --0.2,0.99,3.990,155.2 --0.5,0.99,3.950,148.5 --0.6,0.99,3.500,146.5 --0.1,0.99,3.390,146.0 -0.2,0.99,3.100,136.3 --0.5,0.99,1.800,156.6 --0.9,0.99,1.370,137.9 --0.0,0.99,2.090,127.3 --0.4,0.99,1.400,137.1 --0.4,0.99,0.840,127.5 --0.3,0.99,0.480,131.4 -0.4,0.99,0.990,109.2 -2.2,0.99,3.650,127.5 -3.0,0.99,2.610,126.3 -3.1,0.99,2.690,107.2 -3.3,0.99,1.610,104.3 -3.4,0.99,1.620,95.4 -3.9,0.99,2.780,124.2 -4.4,0.99,6.000,143.2 -6.3,0.99,7.180,167.7 -6.9,0.99,8.180,165.1 -7.9,0.99,8.800,157.9 -6.9,0.99,8.200,151.1 -6.7,0.99,6.100,152.0 -5.6,0.99,5.580,157.7 -5.4,0.99,4.220,164.5 -4.4,0.99,1.790,172.7 -3.8,0.99,0.620,144.2 -3.9,0.99,0.770,273.1 -4.0,0.99,1.130,285.0 -4.5,0.99,0.510,54.9 -4.5,0.99,1.550,110.3 -4.8,0.99,2.390,135.3 -4.6,0.99,4.920,128.2 -4.4,0.99,6.000,149.8 -6.5,0.99,10.390,166.5 -5.8,0.99,12.200,158.4 -6.5,0.99,11.510,164.7 -6.2,0.99,9.860,165.9 -5.4,0.99,8.030,176.0 -3.7,0.99,2.190,268.5 -4.3,0.99,2.040,205.6 -5.0,0.99,2.760,215.3 -5.2,0.99,3.650,235.3 -5.2,0.99,1.650,197.2 -5.0,0.99,3.460,169.8 -4.6,0.99,4.850,180.0 -4.8,0.99,5.330,188.8 -5.2,0.99,5.020,185.1 -4.0,0.99,5.070,179.4 -2.7,1.00,4.640,158.9 -2.8,1.00,5.020,155.1 -2.8,1.00,5.150,155.2 -2.4,1.00,5.290,152.5 -2.5,1.00,5.610,148.1 -2.6,1.00,6.040,149.2 -2.2,1.00,7.660,142.9 -2.6,1.00,9.090,143.5 -3.3,1.00,11.780,142.8 -3.8,0.99,13.250,140.0 -4.0,0.99,13.780,140.6 -4.7,0.99,14.110,145.0 -4.8,0.99,14.160,157.4 -5.3,0.99,12.990,154.6 -5.4,0.99,13.610,151.0 -5.0,0.99,13.710,162.0 -5.1,0.99,14.360,159.3 -4.9,0.99,15.140,158.7 -5.1,0.99,15.090,159.3 -5.2,0.99,15.170,165.8 -5.3,0.99,17.130,169.6 -5.7,0.99,15.780,171.2 -5.9,0.99,12.680,168.0 -6.0,0.99,12.400,174.3 -6.1,0.99,10.920,179.7 -6.0,0.99,9.280,180.3 -5.8,0.99,5.510,200.8 -5.7,0.99,4.100,201.8 -4.7,0.99,0.830,219.3 -5.4,0.99,0.190,353.1 -5.1,0.99,0.840,141.1 -6.2,0.99,0.430,137.6 -6.1,0.99,1.400,87.9 -5.5,0.99,2.240,141.2 -5.3,0.99,2.040,126.3 -4.1,0.99,1.880,147.7 -3.8,0.99,2.370,151.9 -3.7,0.99,0.400,139.1 -3.1,0.99,1.950,132.7 -3.4,0.99,2.140,158.2 -3.8,0.99,3.440,156.7 -3.0,0.99,2.170,199.6 -2.7,0.99,1.710,229.4 -2.9,0.99,1.060,175.7 -2.7,0.99,1.840,145.9 -0.8,0.99,3.490,156.9 -3.8,0.99,5.690,159.9 -3.7,0.99,8.550,164.1 -3.8,0.99,9.540,168.8 -3.9,0.99,11.700,166.9 -3.4,0.99,11.410,167.0 -3.4,0.99,12.250,164.0 -3.7,0.99,13.490,166.7 -4.0,0.99,12.400,162.9 -3.9,0.99,11.410,160.7 -3.9,0.99,11.790,163.5 -4.0,0.99,8.010,168.8 -4.5,0.99,4.160,171.4 -3.4,0.99,2.020,257.5 -4.2,0.99,4.560,277.4 -3.6,0.99,5.000,292.9 -3.5,0.99,4.140,303.8 -3.6,0.99,3.540,309.1 -5.1,0.99,3.690,258.4 -4.6,0.99,4.640,254.9 -4.6,0.99,4.660,276.8 -4.0,0.99,5.090,284.1 -4.2,0.99,5.610,288.5 -4.0,0.99,4.670,283.5 -4.0,0.99,3.820,275.6 -4.0,0.99,3.990,287.3 -3.9,0.99,4.680,264.5 -4.2,0.99,3.690,267.6 -4.6,0.99,3.030,243.0 -5.2,0.99,3.420,240.8 -5.7,0.99,4.110,231.8 -4.4,0.99,4.980,194.4 -4.0,0.99,5.770,180.1 -2.8,0.99,6.450,171.9 -2.9,0.99,6.460,170.9 -2.3,0.99,6.830,167.4 -2.4,0.99,5.350,161.1 -2.7,0.99,6.320,151.6 -2.8,0.99,6.140,150.2 -3.3,0.99,5.550,160.6 -4.4,0.99,3.930,202.2 -4.6,0.99,1.690,237.6 -4.4,0.99,1.540,240.5 -4.5,0.99,1.870,166.5 -5.1,0.99,2.790,207.7 -5.3,0.99,3.080,217.1 -5.2,0.99,2.450,253.9 -4.8,0.99,2.520,216.8 -5.1,0.99,4.500,249.9 -4.7,0.99,5.940,267.1 -5.1,0.99,11.420,284.5 -4.8,0.99,10.010,297.2 -4.7,0.99,9.590,296.6 -4.9,0.99,10.050,285.7 -5.5,0.99,13.080,279.4 -4.4,0.99,6.930,302.7 -4.4,0.99,7.630,279.1 -4.3,0.99,10.150,286.0 -4.0,0.99,8.560,274.7 -3.8,0.99,9.330,277.1 -3.6,0.99,8.840,282.1 -4.0,0.99,10.040,283.5 -4.0,0.99,8.430,277.6 -4.3,0.99,5.680,226.0 -3.7,0.99,5.200,219.6 -4.4,0.99,7.400,264.6 -4.2,0.99,11.750,281.7 -4.8,0.99,13.380,280.5 -4.5,0.99,14.110,284.8 -4.6,0.99,13.690,284.1 -4.7,0.99,13.600,284.3 -4.9,0.99,14.430,282.1 -4.9,0.99,14.660,283.3 -4.9,0.99,14.600,281.3 -5.3,0.99,15.370,284.1 -5.2,0.99,14.840,287.6 -5.5,0.99,14.200,288.7 -5.5,1.00,13.440,294.1 -5.6,1.00,13.120,295.8 -5.7,1.00,10.940,299.4 -5.5,1.00,9.140,296.0 -5.3,1.00,8.830,293.1 -5.4,1.00,8.490,296.8 -5.0,1.00,7.370,300.2 -5.1,1.00,5.240,312.9 -4.9,1.00,5.540,323.6 -5.3,1.00,4.470,345.1 -5.3,1.00,1.390,159.8 -6.6,1.00,6.360,216.2 -7.1,1.00,6.880,209.4 -6.9,1.00,6.970,202.2 -6.5,1.00,6.470,189.1 -6.3,1.00,5.300,170.1 -5.5,1.00,3.630,163.3 -5.5,1.00,3.400,165.5 -5.4,1.00,3.620,153.1 -5.5,1.00,4.850,159.7 -6.1,1.00,5.960,165.5 -6.2,1.00,6.970,167.3 -6.3,1.00,6.050,168.9 -6.0,1.00,5.110,162.2 -5.7,1.00,6.160,163.7 -5.9,1.00,5.890,172.6 -6.1,1.00,5.340,174.2 -6.1,1.00,4.620,173.3 -6.8,1.00,4.210,180.7 -7.4,1.00,2.720,199.2 -7.1,1.00,1.730,250.9 -6.3,1.00,1.190,281.9 -6.2,1.00,1.780,301.3 -6.0,1.00,1.330,288.1 -6.1,1.00,0.980,296.3 -6.3,1.00,0.440,230.8 -6.9,1.00,1.200,151.6 -6.8,1.00,2.000,137.1 -7.0,1.00,2.840,164.0 -7.6,1.00,3.360,177.7 -7.5,1.00,2.690,172.2 -7.4,1.00,2.370,170.1 -7.3,1.00,1.790,159.3 -7.2,1.00,1.820,156.1 -7.0,1.00,2.700,156.1 -7.0,1.00,2.730,154.1 -6.9,1.00,2.920,153.9 -6.6,1.00,3.240,159.8 -6.4,1.00,2.680,149.5 -6.1,1.00,4.010,146.3 -5.6,1.00,4.910,151.2 -5.8,1.00,6.050,158.6 -6.1,1.00,5.020,152.9 -6.1,1.00,6.010,154.5 -6.0,1.00,6.760,154.5 -5.7,1.00,8.020,144.5 -6.5,1.00,8.880,145.1 -7.7,1.00,8.540,140.8 -7.9,1.00,6.370,132.8 -8.5,1.00,5.220,138.5 -7.4,1.00,0.790,81.4 -7.1,1.00,1.370,352.3 -6.2,1.00,0.990,314.5 -6.4,1.00,0.950,308.4 -6.8,1.00,1.600,268.8 -6.3,1.00,1.460,246.9 -5.7,1.00,1.960,267.3 -4.7,1.00,3.470,308.4 -3.3,1.00,4.990,309.0 -3.2,1.00,5.020,309.3 -3.5,1.00,4.320,322.4 -3.9,1.00,3.010,331.2 -4.2,1.00,1.850,313.9 -4.5,1.00,1.400,343.1 -5.3,1.00,0.360,3.0 -4.8,1.00,1.550,94.1 -5.2,1.00,0.820,55.1 -2.9,1.00,1.500,20.3 -3.0,1.00,1.150,19.0 -3.3,1.00,1.240,321.6 -3.6,1.00,2.450,336.5 -3.8,1.00,2.050,326.2 -4.0,1.00,3.250,317.7 -5.0,1.00,4.180,334.3 -4.9,1.00,5.080,337.3 -3.9,1.00,4.320,327.0 -4.1,1.00,4.240,319.5 -3.9,1.00,3.830,311.2 -4.0,1.00,4.270,325.7 -4.2,1.00,3.610,318.7 -5.3,1.00,3.960,330.4 -5.0,1.00,5.310,332.4 -3.1,1.00,6.100,340.8 -2.0,1.00,5.730,341.1 -1.8,1.00,4.860,330.3 -1.3,1.00,5.060,320.5 -1.6,1.00,4.810,319.4 -1.7,1.00,4.640,315.8 -1.4,1.00,4.160,318.1 -1.1,1.00,4.340,320.3 -0.8,1.00,4.220,318.7 -0.5,1.00,4.670,305.4 -0.5,1.00,4.020,304.9 -1.0,1.00,3.470,305.1 -1.5,1.00,3.330,308.4 -1.2,1.00,2.480,295.9 -1.2,1.00,1.650,270.2 -1.5,1.00,1.120,278.6 -1.5,1.00,0.400,267.8 -2.2,1.00,1.740,146.0 -3.0,1.00,3.500,150.6 -3.7,1.00,4.310,149.7 -3.4,1.00,5.160,155.1 -3.7,1.00,4.870,159.7 -3.4,1.00,4.660,172.0 -3.1,1.00,4.530,152.1 -2.4,1.00,2.660,159.0 -2.2,1.00,2.740,154.0 -1.7,1.00,3.050,152.5 -1.4,1.00,3.110,148.3 -0.4,1.00,3.680,154.3 -0.2,1.00,3.280,163.2 -0.8,1.00,3.230,148.7 -1.0,1.00,4.150,153.8 -1.2,1.00,2.850,168.8 -1.8,1.00,1.820,146.5 -1.9,1.00,0.800,151.8 -2.5,1.00,1.230,189.5 -2.9,1.00,2.450,197.3 -3.2,1.00,2.190,170.1 -3.8,1.00,3.100,168.7 -4.4,1.00,4.330,165.4 -4.8,1.00,3.540,169.4 -5.0,1.00,3.710,166.7 -5.2,1.00,4.300,164.0 -5.3,1.00,4.250,159.0 -5.5,1.00,5.180,157.8 -5.4,1.00,4.660,143.3 -5.3,1.00,4.400,140.1 -5.1,1.00,3.900,154.4 -5.4,1.00,2.730,162.0 -5.5,1.00,1.790,242.6 -5.0,1.00,1.190,227.1 -4.1,1.00,2.980,168.3 -3.0,1.00,4.710,160.6 -3.7,1.00,5.400,154.4 -4.3,0.99,7.550,155.5 -4.1,1.00,8.010,158.2 -4.2,1.00,7.610,157.2 -4.4,1.00,9.050,156.4 -4.5,1.00,8.600,156.1 -4.7,0.99,8.890,155.0 -5.1,0.99,7.890,152.4 -5.7,0.99,8.900,148.1 -6.5,0.99,9.070,147.6 -7.1,0.99,8.180,141.5 -7.7,0.99,7.990,141.7 -7.7,0.99,7.660,139.4 -7.7,0.99,6.710,132.8 -7.7,0.99,11.170,155.2 -7.9,0.99,13.360,153.8 -7.9,0.99,14.890,161.2 -7.8,0.99,12.550,152.0 -8.4,0.99,12.350,131.5 -7.1,0.99,12.410,147.0 -6.5,0.98,11.030,147.5 -7.1,0.98,10.970,188.0 -6.5,0.98,19.400,177.4 -5.7,0.98,18.200,172.5 -5.7,0.98,18.500,170.3 -5.2,0.98,18.150,169.6 -5.5,0.98,16.040,164.9 -5.4,0.98,16.110,164.3 -5.7,0.98,11.390,167.6 -6.4,0.98,10.630,168.0 -7.7,0.98,10.300,172.3 -8.8,0.98,11.450,173.6 -8.8,0.98,10.660,165.0 -8.4,0.98,11.980,165.1 -8.3,0.98,13.700,170.0 -8.4,0.98,14.800,178.8 -7.5,0.98,10.290,180.1 -7.9,0.98,7.810,188.0 -7.3,0.98,4.840,242.6 -7.5,0.98,3.360,162.5 -7.2,0.98,4.430,188.2 -6.5,0.99,2.470,216.8 -6.7,0.99,5.480,198.9 -6.1,0.99,5.870,165.8 -6.0,0.99,4.640,187.9 -5.9,0.99,2.930,176.3 -5.4,0.99,3.690,158.8 -5.6,0.99,3.900,180.7 -5.9,0.99,6.480,188.1 -5.7,0.99,6.830,182.9 -5.3,0.99,6.740,188.9 -5.5,0.99,7.400,194.3 -5.5,0.99,4.990,169.7 -6.2,0.99,12.500,168.3 -5.9,0.99,14.340,162.7 -6.3,0.99,12.670,153.7 -6.9,0.99,13.810,153.0 -7.1,0.99,13.490,154.1 -6.8,0.99,15.210,158.3 -6.9,0.99,14.220,155.5 -6.5,0.99,13.690,153.0 -6.7,0.99,15.850,147.5 -7.2,0.98,16.810,143.7 -7.5,0.98,17.140,142.4 -7.4,0.98,13.990,137.8 -7.2,0.98,14.860,151.2 -6.9,0.98,14.590,152.3 -6.8,0.98,17.470,159.5 -6.8,0.98,18.370,157.6 -6.7,0.98,17.030,161.2 -6.4,0.98,16.530,167.6 -6.0,0.98,12.210,165.9 -5.7,0.98,13.810,167.1 -5.4,0.98,12.610,175.5 -5.2,0.99,9.810,175.5 -5.1,0.99,11.330,185.6 -5.4,0.99,7.180,174.1 -5.9,0.99,7.380,194.2 -6.9,0.99,6.250,322.3 -6.8,0.99,4.190,352.4 -7.1,0.99,4.370,358.2 -7.2,0.99,4.180,9.8 -6.9,0.99,2.780,339.8 -6.4,0.99,3.640,300.9 -6.5,0.99,4.550,304.0 -6.6,0.99,3.450,303.7 -5.9,0.99,4.000,288.6 -6.2,0.99,5.180,287.9 -6.1,1.00,6.770,290.6 -5.1,1.00,7.370,293.0 -4.9,1.00,7.350,297.5 -4.7,1.00,8.610,293.9 -4.2,1.00,7.660,306.1 -4.3,1.00,7.040,312.5 -3.9,1.00,6.420,316.9 -2.5,1.00,6.110,330.1 -0.0,1.00,5.310,329.6 -0.1,1.00,5.140,323.4 -1.1,1.00,4.550,339.5 -1.4,1.00,3.950,349.0 -1.3,1.00,4.090,357.5 -0.3,1.00,4.350,349.4 -0.5,1.00,3.770,332.8 -1.0,1.00,2.750,327.7 -1.0,1.00,3.530,336.7 -0.7,1.00,2.490,322.6 -0.3,1.00,2.390,331.1 -0.4,1.00,0.860,270.4 -0.6,1.00,1.550,180.1 -1.1,1.00,2.670,160.2 -1.1,1.00,3.330,163.3 -0.6,1.00,3.430,164.3 -0.8,1.00,2.630,172.7 -1.5,1.00,1.630,142.1 -1.9,1.00,1.020,134.2 -2.0,1.00,1.920,128.0 -1.8,1.00,3.280,131.1 -1.9,1.00,3.830,149.6 -1.8,1.00,3.900,137.9 -1.6,1.00,3.570,146.0 -2.0,1.00,3.430,165.3 -2.3,1.00,1.470,212.2 -1.8,1.00,1.690,255.6 -1.5,1.00,0.660,259.9 -1.5,1.00,0.690,333.6 -1.5,1.00,1.530,82.1 -1.9,1.00,2.180,78.0 -2.4,1.00,2.300,99.1 -3.0,1.00,1.670,111.3 -3.3,1.00,1.860,99.7 -3.4,1.00,2.050,93.2 -3.6,1.00,2.680,100.8 -3.8,1.00,2.740,120.5 -3.6,1.00,1.840,122.6 -3.3,1.00,2.800,118.6 -3.0,1.00,2.970,99.7 -2.7,1.00,1.770,62.5 -2.4,1.00,2.310,45.8 -1.3,1.00,2.740,26.2 -1.5,1.00,3.050,12.6 -1.9,1.00,2.460,325.4 -1.8,1.00,3.830,336.4 -1.1,1.00,5.000,328.3 -0.0,1.00,3.420,335.6 -0.0,1.00,4.260,329.7 --0.1,1.00,4.170,318.0 -0.0,1.00,4.030,315.2 --0.1,1.00,3.740,317.6 --0.2,1.00,3.660,301.9 --0.0,1.00,4.110,308.6 --0.1,1.00,3.860,305.6 --0.0,1.00,3.650,304.0 -0.5,1.00,3.180,299.3 -0.1,1.00,2.350,280.2 -0.4,1.00,1.260,287.4 -0.3,1.00,0.960,148.9 -0.8,1.00,1.510,185.9 -1.1,1.00,1.910,178.8 -0.9,1.00,2.660,172.3 -0.4,1.00,1.660,200.6 -0.6,1.00,1.170,238.1 -0.8,1.00,0.900,260.0 -1.1,1.00,0.970,239.8 -1.3,1.00,0.480,226.6 -1.4,1.00,1.920,125.2 -1.6,1.00,2.380,150.0 -1.6,1.00,2.610,153.5 -1.7,1.00,2.420,166.7 -2.0,1.00,2.210,144.9 -2.0,1.00,4.010,136.0 -1.7,1.00,6.150,139.9 -1.5,1.00,6.890,158.1 -1.3,1.00,6.350,167.4 -1.4,1.00,7.270,169.6 -1.9,1.00,8.030,165.3 -1.9,1.00,8.400,162.5 -2.3,1.00,8.700,159.3 -2.7,1.00,8.810,157.4 -2.7,1.00,8.780,155.1 -2.9,1.00,8.410,155.5 -3.0,1.00,7.750,155.5 -3.2,1.00,7.990,156.7 -3.3,1.00,8.060,155.4 -2.9,1.00,7.550,153.9 -3.0,1.00,7.540,154.6 -3.1,1.00,7.150,158.0 -2.9,1.00,7.270,152.5 -2.9,1.00,6.970,152.4 -2.9,1.00,6.550,161.4 -2.9,1.00,6.100,162.9 -3.3,1.00,6.020,163.4 -3.6,1.00,6.480,160.5 -3.7,1.00,6.290,159.5 -3.5,1.00,6.070,156.9 -3.6,1.00,6.790,150.5 -3.6,1.00,7.070,151.3 -3.6,1.00,6.400,147.9 -3.8,1.00,5.620,146.9 -4.2,1.00,6.440,139.9 -5.2,1.00,6.000,143.8 -6.7,1.00,6.630,148.9 -7.4,1.00,7.880,145.5 -8.0,1.00,8.240,154.5 -8.5,1.00,4.520,164.9 -8.7,1.00,3.710,162.8 -7.9,1.00,0.260,310.2 -8.1,1.00,3.260,76.9 -8.2,1.00,4.470,82.1 -6.9,1.00,5.890,114.7 -7.7,1.00,6.480,106.4 -9.2,1.00,1.720,45.8 -8.1,1.00,5.090,322.5 -7.9,1.00,3.050,263.6 -7.3,1.00,3.120,299.3 -6.9,1.00,9.780,277.8 -6.6,1.00,4.020,257.4 -6.7,1.00,5.890,248.6 -6.5,1.00,6.440,269.2 -6.6,1.00,6.150,277.4 -6.4,1.00,5.080,281.8 -6.5,1.00,6.300,269.1 -6.5,1.00,5.180,267.6 -6.3,1.00,6.400,286.3 -6.4,1.00,5.770,266.1 -6.4,1.00,5.010,284.2 -6.4,1.00,4.990,279.2 -7.2,1.00,5.160,247.3 -7.0,1.00,4.630,204.0 -7.4,1.00,5.470,213.9 -7.5,1.01,4.940,227.5 -7.6,1.01,4.140,232.0 -7.4,1.01,4.660,199.0 -7.5,1.01,3.690,195.3 -7.5,1.01,2.440,239.9 -7.1,1.01,1.440,196.2 -6.0,1.01,2.290,149.7 -5.1,1.01,3.710,126.7 -4.2,1.01,2.890,153.0 -3.6,1.01,3.750,148.2 -3.6,1.01,4.690,150.9 -3.5,1.01,4.870,151.3 -3.2,1.01,5.020,156.8 -3.4,1.01,4.740,153.1 -3.5,1.01,5.240,156.1 -3.9,1.01,6.400,151.2 -4.2,1.01,7.350,148.3 -4.3,1.01,7.590,147.0 -4.6,1.01,7.820,146.8 -5.2,1.01,8.370,147.9 -5.7,1.01,8.150,148.4 -6.1,1.01,7.490,148.5 -6.5,1.01,7.160,149.0 -6.5,1.01,6.490,152.4 -6.4,1.01,5.450,158.5 -6.2,1.01,4.400,156.0 -6.2,1.01,4.040,147.9 -6.6,1.01,4.380,148.9 -6.8,1.01,4.250,146.8 -6.6,1.01,4.110,135.0 -5.6,1.01,3.440,126.0 -5.2,1.01,1.740,103.3 -5.3,1.01,1.510,90.3 -5.1,1.01,0.500,103.5 -4.2,1.01,1.040,347.5 -5.0,1.01,1.380,51.0 -5.0,1.01,1.890,346.2 -4.8,1.01,2.270,333.8 -5.1,1.01,2.550,318.2 -4.1,1.01,3.800,325.9 -2.4,1.01,3.610,338.3 -3.8,1.00,3.730,341.0 -4.5,1.00,4.000,338.4 -4.5,1.00,4.260,334.1 -5.1,1.00,4.650,331.0 -5.5,1.00,3.600,324.2 -5.9,1.00,3.890,312.4 -6.0,1.00,3.910,308.6 -6.6,1.00,3.970,298.4 -7.4,1.00,3.090,307.7 -7.5,1.00,3.120,307.8 -7.6,1.00,2.300,311.3 -7.7,1.00,1.790,306.9 -7.5,1.00,0.700,286.7 -6.7,1.00,0.950,132.2 -3.1,1.00,3.740,171.1 -2.9,1.00,5.560,156.0 -3.4,1.00,6.340,164.9 -3.7,1.00,4.530,172.7 -3.6,1.00,4.340,157.5 -3.9,1.00,4.610,163.4 -4.0,1.00,4.350,163.8 -4.0,1.00,5.750,168.2 -4.2,1.00,4.010,153.3 -4.1,1.00,4.830,141.5 -4.8,1.00,3.320,140.0 -5.2,1.00,3.880,161.8 -6.6,1.00,2.000,239.5 -6.4,1.00,2.360,242.6 -6.3,1.00,3.320,295.1 -6.7,0.99,14.380,293.7 -6.7,0.99,13.760,286.7 -6.2,0.99,15.040,283.0 -6.0,0.99,17.090,278.9 -5.7,1.00,17.720,280.5 -5.7,1.00,17.320,282.1 -5.7,1.00,15.030,284.7 -5.2,1.00,14.000,285.4 -5.2,1.00,13.040,283.7 -5.3,1.00,13.230,284.9 -5.1,1.00,10.770,287.4 -5.0,1.00,9.510,291.4 -4.7,1.00,4.950,237.4 -4.6,1.00,3.660,200.6 -3.9,1.00,4.780,196.7 -2.8,1.00,5.210,179.3 -0.8,1.00,6.100,169.8 -1.9,1.00,6.630,173.5 -2.4,1.00,5.650,166.1 -1.9,1.00,4.530,152.4 -2.9,1.00,5.080,159.9 -3.7,1.00,5.030,158.0 -4.0,1.00,4.670,154.5 -4.2,1.00,3.940,148.2 -4.3,1.00,3.830,158.4 -3.9,1.00,2.860,155.0 -3.6,1.00,3.430,140.3 -3.2,1.00,3.460,144.1 -3.3,1.00,3.570,139.5 -3.3,0.99,3.790,137.2 -3.5,0.99,3.330,146.6 -3.3,0.99,4.460,156.4 -2.3,0.99,5.420,154.4 -0.3,0.99,5.530,152.8 -0.7,0.99,5.470,159.3 -0.8,0.99,4.840,154.8 -1.0,0.99,5.150,154.5 -1.1,0.99,4.940,159.8 -1.5,0.99,4.280,169.3 -1.9,0.99,3.620,172.6 -1.8,0.99,3.630,164.0 -2.1,0.99,3.990,149.7 -2.2,0.99,4.230,137.6 -2.6,0.99,4.540,134.3 -3.9,0.99,7.220,143.6 -4.2,0.99,7.690,145.9 -5.2,0.99,7.960,155.3 -5.3,0.99,9.640,150.9 -5.3,0.98,10.580,147.9 -5.5,0.98,9.990,150.0 -5.5,0.98,10.360,158.2 -4.8,0.98,9.840,150.3 -4.8,0.98,11.330,155.7 -4.4,0.98,11.400,158.3 -4.5,0.98,12.190,157.6 -3.9,0.98,11.210,154.2 -3.7,0.98,10.830,153.0 -3.9,0.98,10.240,146.0 -3.9,0.98,9.430,143.9 -3.8,0.98,8.280,145.8 -3.7,0.98,6.150,142.9 -3.7,0.98,3.550,135.8 -4.4,0.98,2.320,138.1 -4.4,0.98,0.130,207.1 -1.9,0.98,5.980,287.5 -0.5,0.98,5.500,287.1 -2.6,0.99,6.800,296.7 -1.9,0.99,6.780,293.4 -2.7,0.99,6.870,294.8 -4.9,0.99,4.180,332.1 -5.3,0.99,7.360,331.6 -5.8,0.99,8.030,325.3 -5.3,0.99,7.030,310.1 -5.5,0.99,5.500,309.9 -5.7,0.99,5.200,311.1 -5.6,0.99,4.840,315.4 -6.2,0.99,5.090,306.1 -5.8,0.99,6.340,296.5 -3.1,0.99,6.980,289.5 -0.6,0.99,7.730,287.0 -1.1,0.99,6.850,289.0 -0.3,0.99,4.480,291.5 -0.4,0.99,1.670,278.5 --0.0,0.99,2.990,159.2 --0.1,0.99,4.690,160.9 --0.2,0.99,6.160,164.6 --0.5,0.99,6.240,153.7 --0.4,0.99,7.460,152.8 --0.2,0.99,7.530,150.5 --0.1,0.99,8.170,150.4 -0.1,0.99,7.590,145.5 -1.1,0.99,5.650,151.5 -3.4,0.99,7.610,151.8 -4.1,0.99,7.580,151.2 -5.0,0.99,5.960,145.6 -5.9,0.99,4.480,134.6 -5.9,0.99,4.970,155.6 -3.9,0.99,4.550,257.8 -5.1,0.99,2.860,209.0 -4.5,0.99,4.810,170.2 -4.2,0.99,9.700,164.0 -3.8,0.99,9.080,158.1 -3.3,0.99,7.610,155.0 -2.8,0.99,7.200,167.3 -2.6,0.99,6.460,176.8 -2.3,0.99,6.910,170.8 -1.8,0.99,5.700,177.1 -1.4,0.99,5.450,173.8 -1.1,0.99,5.300,166.7 -1.3,0.99,6.260,161.6 -1.8,0.99,5.060,154.0 -2.2,0.99,4.340,162.5 -2.6,0.99,3.780,150.5 -3.0,0.99,5.520,159.2 -4.5,0.99,7.010,165.2 -5.1,0.99,9.840,169.5 -5.6,0.99,9.890,161.8 -6.2,0.99,10.150,162.9 -6.3,0.99,10.530,162.0 -6.2,0.99,8.640,172.2 -6.2,0.99,4.200,150.6 -5.7,0.99,3.610,143.3 -5.7,0.99,1.370,163.0 -5.0,0.99,2.380,138.1 -4.6,0.99,2.860,151.7 -5.7,0.99,1.840,138.0 -5.2,0.99,3.270,121.7 -4.9,0.99,4.570,125.0 -5.0,0.99,6.240,144.4 -5.1,0.99,8.910,153.9 -5.2,0.99,10.010,151.8 -5.8,0.99,11.690,160.4 -5.7,0.99,11.340,167.1 -5.4,0.99,12.750,164.3 -5.3,0.98,14.640,162.6 -5.8,0.98,12.550,169.9 -7.1,0.98,9.190,176.3 -6.7,0.98,12.390,183.0 -6.4,0.98,14.990,168.7 -6.2,0.98,13.260,167.7 -6.6,0.98,10.000,167.4 -7.5,0.98,5.990,302.5 -7.5,0.98,1.730,283.7 -7.0,0.98,1.350,97.9 -6.2,0.98,12.290,286.8 -6.4,0.98,11.220,283.8 -6.1,0.98,10.550,261.0 -5.0,0.98,8.920,278.6 -5.0,0.98,14.460,294.9 -4.7,0.98,14.460,279.8 -4.7,0.98,12.680,278.1 -4.9,0.99,11.570,272.5 -5.0,0.99,11.060,257.7 -5.1,0.99,6.260,209.7 -4.8,0.99,7.710,250.2 -4.6,0.99,12.600,265.0 -4.5,0.99,10.410,263.1 -4.8,0.99,14.470,273.6 -4.7,0.99,16.640,276.7 -4.4,0.99,16.660,278.8 -4.2,0.99,14.970,277.7 -4.4,0.99,14.790,277.8 -4.5,1.00,15.150,275.8 -4.7,1.00,15.560,282.3 -4.8,1.00,15.920,284.2 -5.0,1.00,15.390,283.9 -5.1,1.00,13.480,287.4 -4.7,1.00,7.580,310.6 -5.2,1.00,11.830,299.0 -5.5,1.00,14.310,285.7 -5.7,1.00,13.550,281.0 -5.6,1.00,13.780,274.6 -5.5,1.00,11.340,276.6 -5.4,1.00,6.540,260.9 -5.8,1.00,8.150,277.5 -5.6,1.00,7.270,223.6 -5.1,1.00,5.670,194.1 -4.7,1.00,5.860,171.2 -3.2,1.00,7.900,170.5 -2.5,1.00,6.130,152.9 -2.4,1.00,8.010,138.4 -1.7,1.00,7.290,150.3 -2.7,1.00,9.890,143.5 -2.7,1.00,11.840,143.1 -2.7,1.00,11.000,148.5 -3.5,1.00,13.710,161.6 -3.9,1.00,13.190,157.8 -5.2,1.00,11.910,158.9 -6.2,1.00,12.580,165.5 -6.7,1.00,14.310,164.9 -6.6,1.00,15.940,160.6 -6.5,0.99,15.640,161.1 -6.4,0.99,18.990,158.9 -6.3,0.99,18.290,167.5 -6.2,0.99,20.240,163.9 -6.2,0.99,20.560,169.6 -5.9,0.99,18.850,171.1 -5.3,0.99,17.680,166.1 -4.5,0.99,16.420,167.2 -4.4,0.99,17.790,173.2 -4.2,0.99,18.590,171.9 -4.2,0.99,16.390,165.7 -4.2,0.99,16.130,173.5 -4.6,0.99,10.860,178.8 -4.6,0.99,6.600,189.0 -5.5,0.99,4.080,157.4 -5.9,0.99,5.160,157.2 -4.5,0.99,4.700,250.1 -6.2,0.99,4.620,273.9 -5.5,0.99,14.280,261.8 -5.4,0.99,14.300,265.8 -5.1,0.99,18.350,278.7 -5.5,0.99,18.330,280.8 -5.5,0.99,18.360,286.1 -5.7,0.99,18.860,284.2 -5.9,0.99,18.380,285.7 -6.0,0.99,17.240,284.7 -6.3,0.99,17.790,282.7 -6.1,1.00,17.020,281.3 -5.8,1.00,15.580,277.8 -5.7,1.00,14.590,279.7 -5.6,1.00,12.610,276.8 -5.5,1.00,10.990,273.7 -5.5,1.00,9.700,276.2 -5.7,1.00,8.030,261.8 -4.9,1.00,5.980,186.4 -4.5,1.00,5.800,174.9 -3.5,1.00,5.900,172.0 -0.3,1.00,6.860,163.5 -1.1,1.00,6.940,142.5 -0.6,1.00,9.170,142.1 -0.1,1.00,7.600,143.0 --0.2,1.00,8.010,146.2 -1.2,1.00,9.780,138.5 -1.1,1.00,10.910,142.2 -3.4,1.00,11.290,153.2 -4.5,1.00,11.640,148.1 -5.6,1.00,11.580,156.3 -6.3,1.00,12.460,162.2 -6.7,1.00,14.560,162.5 -6.3,1.00,12.360,159.5 -6.7,1.00,12.830,160.0 -6.3,1.00,13.160,163.3 -5.7,1.00,14.920,175.0 -5.2,1.00,14.320,168.6 -5.5,1.00,15.030,176.9 -5.2,1.00,14.880,166.0 -4.2,1.00,14.860,166.1 -4.0,1.00,15.430,174.5 -3.5,1.00,9.560,163.5 -3.6,1.00,5.050,187.8 -3.4,1.00,5.220,167.2 -3.0,1.00,3.950,178.1 -4.2,1.00,1.480,171.6 -4.4,1.00,2.100,152.4 -4.5,1.00,2.390,149.4 -4.6,1.00,2.590,139.9 -3.3,1.00,3.970,136.9 -3.9,1.00,4.130,139.5 -4.5,1.00,3.980,120.8 -5.3,1.00,4.460,125.9 -6.0,1.00,5.710,139.6 -6.7,1.00,5.910,144.3 -7.3,1.00,6.210,142.1 -7.7,1.00,7.300,153.1 -7.4,1.00,6.250,158.4 -7.4,1.00,6.830,167.5 -7.4,1.00,5.450,163.1 -7.1,1.00,5.100,177.8 -6.4,1.00,3.820,184.7 -6.7,1.00,6.170,157.0 -6.8,1.00,6.000,161.2 -6.2,1.00,4.070,151.0 -6.1,1.00,3.970,139.0 -6.0,1.00,4.580,129.4 -6.0,1.00,6.410,126.1 -6.1,1.00,7.320,131.5 -5.5,1.00,6.540,130.0 -5.7,1.00,6.610,144.2 -5.9,1.00,8.700,146.7 -5.8,1.00,10.230,145.8 -5.9,1.00,12.010,149.4 -6.1,1.00,12.310,152.6 -6.9,1.00,12.020,151.2 -7.4,1.00,11.480,154.2 -7.7,1.00,11.240,155.3 -8.2,1.00,10.400,157.6 -9.0,1.00,10.370,158.7 -9.8,1.00,10.790,162.0 -10.0,1.00,9.700,163.7 -9.8,1.00,7.960,163.4 -8.4,1.00,3.130,210.8 -8.3,1.00,1.630,218.3 -10.0,1.00,4.220,170.7 -9.7,1.00,4.210,189.9 -10.1,1.00,5.520,182.6 -10.0,1.00,6.110,173.5 -10.0,1.00,3.650,166.0 -10.5,1.00,10.170,179.8 -10.7,1.00,13.680,174.3 -10.6,1.00,11.200,172.8 -10.3,1.00,7.840,175.8 -10.2,1.00,10.380,189.7 -10.1,1.00,13.150,190.5 -10.2,1.00,12.300,192.5 -10.8,1.00,14.060,186.2 -11.4,1.00,14.140,187.8 -11.4,1.00,13.850,185.6 -11.8,1.00,13.390,180.1 -12.4,1.00,15.420,181.3 -11.4,1.00,8.350,166.0 -12.0,1.00,9.060,189.5 -12.6,1.00,13.220,187.8 -12.1,1.00,10.240,194.9 -11.8,1.00,6.180,201.4 -11.1,1.00,5.440,191.7 -10.8,1.00,6.130,180.6 -10.2,1.00,7.280,167.6 -9.2,1.00,6.800,182.8 -8.6,1.00,3.970,225.0 -8.6,1.00,8.070,166.4 -8.3,1.00,10.420,168.1 -8.4,1.00,11.420,164.0 -8.6,1.00,12.520,163.5 -8.6,1.00,12.400,161.8 -8.7,1.00,10.330,162.7 -8.8,1.00,9.550,160.9 -8.7,1.00,8.110,162.4 -8.5,0.99,8.030,162.9 -9.4,0.99,7.050,177.1 -9.6,0.99,7.950,169.0 -10.3,0.99,5.830,173.9 -6.9,0.99,11.110,282.9 -6.0,0.99,10.620,278.1 -6.0,1.00,11.000,285.3 -5.8,0.99,11.830,291.4 -6.5,0.99,13.080,285.8 -5.6,0.99,14.560,282.2 -6.2,0.99,16.910,279.4 -5.8,0.99,16.600,285.1 -5.6,1.00,17.620,282.5 -5.4,1.00,17.440,280.5 -5.3,1.00,17.490,279.7 -4.7,1.00,17.230,282.2 -4.4,1.00,15.600,281.2 -4.4,1.00,14.840,281.9 -4.1,1.00,13.400,284.4 -4.0,1.00,13.170,284.3 -4.2,1.00,13.070,282.0 -4.1,1.00,12.250,281.3 -4.0,1.00,13.240,279.2 -4.0,1.00,11.290,277.6 -3.8,1.00,13.430,279.7 -3.9,1.00,11.650,280.5 -3.6,1.00,11.370,287.2 -4.2,1.00,10.390,288.5 -4.4,1.00,8.450,301.7 -4.7,1.00,6.480,319.0 -4.6,1.00,3.950,317.6 -4.7,1.00,3.400,309.4 -5.0,1.00,2.160,308.0 -5.4,1.00,3.810,267.6 -5.4,1.00,4.280,275.4 -5.5,1.00,4.390,271.8 -5.6,1.00,3.480,282.0 -6.0,1.00,3.160,317.8 -5.4,1.00,1.010,125.9 -5.3,1.00,1.220,239.1 -4.8,1.00,2.320,243.6 -4.2,1.00,2.210,240.9 -3.0,1.00,1.680,190.0 -3.6,1.00,1.580,163.3 -3.0,1.00,1.980,166.8 -2.6,1.00,0.880,236.9 -2.0,1.00,1.160,325.7 -1.4,1.00,1.630,329.5 -2.6,1.00,1.530,325.8 -2.8,1.00,1.850,334.5 -2.1,1.00,2.480,330.5 -1.9,1.00,4.370,326.8 -2.5,1.00,3.590,320.4 -3.6,1.00,3.790,329.6 -4.8,0.99,3.990,329.7 -5.5,0.99,4.350,328.9 -6.6,0.99,3.190,332.8 -7.8,0.99,1.590,352.3 -7.0,0.99,1.500,347.6 -7.4,0.99,1.520,80.7 -7.5,0.99,4.720,112.9 -7.4,0.99,5.950,118.1 -7.4,0.99,6.260,114.5 -7.0,0.99,7.650,122.0 -6.8,0.99,7.360,121.8 -7.5,0.99,5.940,125.4 -6.3,0.99,6.790,121.0 -6.0,0.99,5.920,119.9 -6.9,0.99,5.500,118.5 -6.3,0.99,3.080,125.2 -6.2,0.99,2.100,155.7 -7.2,0.99,2.830,161.1 -7.3,0.99,2.610,137.5 -7.5,0.99,2.280,135.4 -8.0,0.99,1.090,108.0 -7.9,0.98,0.340,112.5 -8.6,0.98,3.230,142.5 -9.8,0.98,2.830,172.0 -10.0,0.98,3.320,181.6 -9.6,0.98,3.890,210.3 -8.4,0.98,4.960,215.1 -7.4,0.98,4.760,194.9 -7.1,0.98,4.850,166.3 -6.8,0.98,3.010,162.1 -6.9,0.98,3.140,167.2 -6.1,0.98,1.710,256.7 -5.4,0.98,2.510,325.7 -5.7,0.98,2.090,338.1 -5.8,0.98,2.570,343.4 -5.6,0.98,3.470,343.0 -5.6,0.98,3.300,318.4 -5.0,0.98,4.880,298.3 -4.8,0.98,5.760,288.9 -4.8,0.98,5.600,295.9 -5.0,0.98,5.430,344.6 -3.7,0.98,6.320,311.8 -3.3,0.98,9.030,309.6 -3.4,0.98,8.600,306.5 -3.6,0.98,7.710,308.6 -3.5,0.98,7.460,293.9 -4.1,0.98,7.040,302.7 -4.2,0.98,5.900,301.4 -4.4,0.98,5.540,298.3 -4.5,0.98,5.500,294.0 -4.5,0.98,4.690,291.3 -4.7,0.98,3.660,286.6 -4.8,0.98,3.310,269.8 -4.8,0.98,2.590,277.2 -4.7,0.98,2.800,269.4 -4.6,0.98,2.300,266.1 -4.7,0.98,1.360,209.8 -4.5,0.99,1.840,163.6 -4.4,0.99,2.220,153.1 -4.4,0.99,2.740,167.7 -4.3,0.99,2.990,179.3 -4.4,0.99,2.920,198.8 -4.8,0.99,2.940,194.5 -4.7,0.99,3.800,180.0 -4.5,0.99,3.740,165.4 -4.5,0.99,3.540,154.4 -4.5,0.99,3.000,128.3 -4.6,0.99,3.730,109.5 -5.2,0.99,3.620,100.7 -5.6,0.99,3.270,115.3 -6.0,0.99,3.440,114.4 -6.1,0.99,3.200,121.0 -6.7,0.99,2.770,105.2 -7.4,0.99,2.070,104.7 -7.7,0.99,1.380,108.4 -7.7,0.99,0.240,67.8 -7.5,0.99,0.640,19.6 -7.6,0.99,1.030,11.1 -7.5,0.99,1.220,347.9 -7.3,0.99,1.770,23.5 -7.2,0.99,2.160,17.7 -6.6,0.99,1.570,2.1 -5.4,0.99,1.170,30.5 -5.5,0.99,1.320,356.7 -5.6,0.99,1.710,338.8 -5.5,0.99,2.600,326.0 -4.1,0.99,3.930,318.4 -4.4,0.99,4.130,316.3 -4.1,0.99,3.940,304.2 -3.5,0.99,4.070,328.7 -3.1,0.99,4.410,323.1 -2.8,1.00,4.590,325.7 -2.5,1.00,4.250,329.8 -3.1,1.00,3.780,338.1 -3.5,1.00,4.700,330.4 -3.8,1.00,4.410,320.7 -4.0,1.00,3.850,320.8 -5.0,1.00,4.300,320.8 -4.9,1.00,4.010,331.3 -5.3,1.00,3.550,320.6 -5.7,1.00,3.400,318.2 -5.6,1.00,3.630,311.5 -4.2,1.00,3.110,325.2 -4.4,1.00,2.420,328.6 -4.7,1.00,2.220,324.8 -2.7,1.00,2.400,331.7 -3.6,1.00,3.500,310.4 -3.4,1.00,3.260,311.6 -3.0,1.00,2.600,313.7 -1.9,1.00,2.050,325.3 -1.9,1.00,1.170,353.2 -1.7,1.00,0.510,27.5 -1.5,1.00,0.650,112.4 -1.4,1.00,0.850,138.4 -1.3,1.00,1.020,136.6 -1.2,1.00,0.640,138.2 -1.2,1.00,2.080,122.3 -1.9,1.00,2.720,141.9 -2.5,1.01,2.900,143.1 -2.8,1.01,3.140,142.7 -3.1,1.01,2.900,122.5 -4.7,1.01,2.680,132.7 -5.4,1.01,0.220,19.7 -4.3,1.01,3.620,297.2 -4.3,1.01,3.350,291.8 -5.8,1.01,3.900,296.0 -6.4,1.01,5.470,296.8 -6.2,1.01,4.790,285.6 -6.3,1.01,4.440,282.6 -5.3,1.01,4.700,282.9 -4.9,1.01,4.380,287.7 -5.0,1.01,3.580,286.6 -5.1,1.01,2.110,279.2 -5.1,1.01,1.160,284.5 -4.3,1.01,1.480,125.1 -3.9,1.01,3.710,135.8 -3.5,1.01,3.900,151.0 -3.1,1.01,4.040,161.3 -3.0,1.01,3.880,163.6 -3.1,1.01,3.610,157.5 -3.2,1.01,3.060,144.7 -3.9,1.01,1.310,133.7 -4.2,1.01,0.090,287.3 -4.8,1.01,1.480,326.6 -4.8,1.01,1.450,328.2 -5.0,1.01,0.910,329.8 -5.5,1.01,0.300,57.3 -5.9,1.01,1.030,145.0 -6.1,1.00,1.880,169.6 -6.0,1.00,3.050,170.9 -5.8,1.00,3.730,173.4 -5.6,1.00,3.680,178.7 -5.9,1.00,3.490,185.0 -6.1,1.00,2.880,179.8 -5.1,1.00,3.000,173.0 -5.1,1.00,2.280,176.1 -4.5,1.00,3.010,161.0 -4.4,1.00,2.900,155.6 -4.2,1.00,2.630,149.7 -4.3,1.00,2.220,155.2 -4.4,1.00,2.950,152.6 -4.8,1.00,4.050,159.5 -4.9,1.00,3.590,162.0 -4.9,1.00,3.560,154.6 -5.4,1.00,3.720,133.3 -6.1,1.00,3.890,111.8 -6.5,1.00,3.780,108.4 -7.1,1.00,3.490,110.3 -7.5,1.00,2.810,118.2 -8.1,1.00,2.790,125.7 -8.5,1.00,4.790,143.4 -8.4,1.00,6.100,146.9 -7.6,1.00,6.740,148.8 -7.1,1.00,7.710,146.7 -7.0,1.00,8.760,148.2 -7.3,1.00,10.340,150.8 -7.7,0.99,12.140,158.3 -7.5,0.99,11.580,157.2 -7.9,0.99,13.430,163.3 -8.3,0.99,11.100,163.7 -8.7,0.99,8.550,163.6 -8.5,0.99,3.930,187.8 -9.2,0.99,4.240,168.1 -9.6,0.99,5.670,195.8 -10.4,0.99,7.260,184.2 -10.7,0.99,6.790,189.7 -10.6,0.99,7.700,180.2 -11.8,0.99,8.870,187.8 -11.4,0.99,6.970,187.4 -12.0,0.99,7.440,179.9 -12.3,0.99,8.120,179.9 -12.7,0.99,5.830,202.3 -10.5,0.99,4.160,246.8 -9.4,0.99,3.220,290.4 -8.4,0.99,2.720,319.0 -8.2,0.99,2.850,331.2 -8.5,0.99,2.300,327.6 -8.5,0.99,1.950,324.7 -8.6,0.99,0.500,37.0 -9.6,0.99,1.730,117.7 -9.7,0.99,2.040,196.1 -9.4,0.99,2.990,163.3 -10.6,0.99,3.710,160.3 -10.6,0.99,2.890,182.7 -9.7,0.99,4.480,176.4 -10.3,0.99,5.420,175.9 -10.0,0.99,3.330,186.6 -10.0,0.99,6.080,172.1 -8.9,0.99,4.390,170.0 -9.0,0.99,5.760,166.6 -9.0,0.99,8.390,152.1 -9.5,0.99,7.900,163.8 -9.6,0.99,8.290,169.7 -9.7,0.99,8.270,170.3 -10.4,0.99,7.620,174.5 -9.9,0.99,8.700,164.2 -9.5,0.99,8.560,157.7 -9.7,0.99,8.390,163.8 -9.7,0.99,6.750,167.9 -9.7,0.99,7.930,154.6 -9.6,0.99,8.660,174.3 -9.9,0.99,9.840,165.1 -10.1,0.99,11.180,164.5 -10.1,0.99,14.200,165.9 -10.1,0.99,15.300,165.5 -9.9,0.99,13.750,160.4 -9.9,0.99,12.720,163.3 -10.0,0.99,11.320,172.8 -9.7,0.99,10.330,180.2 -8.8,0.99,3.650,198.4 -8.7,0.99,2.180,315.2 -9.2,0.99,0.320,250.3 -9.2,0.99,2.430,127.5 -10.0,0.99,5.770,165.2 -9.4,0.99,6.430,159.5 -10.5,0.99,13.960,165.0 -10.5,0.99,16.530,163.4 -9.1,0.99,11.990,165.7 -9.2,0.99,7.560,180.7 -9.7,0.99,5.090,188.4 -9.4,0.99,5.720,166.2 -9.6,0.99,5.180,157.7 -10.2,0.99,6.410,157.4 -10.3,0.99,9.440,162.3 -10.0,0.99,12.010,169.8 -9.7,0.99,11.390,170.5 -8.8,0.99,3.870,187.7 -9.3,0.99,4.760,159.8 -7.8,0.99,2.880,251.4 -7.7,0.99,2.830,295.6 -7.7,0.99,1.870,284.2 -7.6,0.99,0.230,291.1 -7.2,0.99,1.450,129.8 -6.6,0.99,1.430,111.1 -5.3,1.00,2.310,111.4 -6.2,1.00,2.800,120.9 -7.1,1.00,1.640,103.9 -7.6,1.00,0.740,68.7 -7.6,1.00,1.190,359.2 -7.3,1.00,1.400,16.1 -8.0,1.00,1.600,119.1 -8.3,1.00,1.980,140.2 -8.6,1.00,1.580,115.6 -9.5,1.00,0.430,324.4 -9.8,1.00,1.790,339.9 -9.5,1.00,2.290,319.0 -9.8,1.00,2.160,343.4 -10.7,1.00,2.570,7.5 -10.6,1.00,2.150,359.5 -9.7,1.00,4.100,314.3 -9.1,1.00,7.240,288.2 -8.9,1.00,7.640,291.6 -9.1,0.99,6.260,292.4 -9.0,0.99,5.180,308.1 -8.2,0.99,2.130,105.0 -8.6,0.99,3.120,130.0 -8.4,0.99,5.360,140.0 -8.5,0.99,5.000,153.7 -7.9,0.99,2.310,160.9 -7.2,0.99,3.120,149.9 -6.7,0.99,4.020,164.3 -6.1,0.99,1.740,250.8 -7.2,0.99,5.600,338.1 -6.8,0.99,0.820,166.2 -6.9,0.99,0.220,26.7 -6.2,0.99,3.930,297.7 -6.3,0.99,7.140,285.2 -6.4,0.99,11.650,287.7 -5.8,0.99,11.180,299.8 -5.7,0.99,13.740,300.4 -5.4,0.99,17.000,287.3 -4.9,0.99,15.470,290.3 -4.8,0.99,19.170,283.4 -4.7,0.99,16.940,287.5 -4.2,0.99,15.560,279.2 -4.5,0.99,13.330,280.6 -4.7,0.99,14.590,281.4 -4.1,0.99,14.810,278.1 -4.2,0.99,14.590,278.6 -4.4,0.99,15.040,281.6 -4.4,0.99,17.700,276.6 -4.5,0.99,16.020,279.5 -4.3,0.99,12.580,280.1 -4.2,0.99,13.720,273.4 -4.2,0.99,13.820,275.3 -4.2,0.99,15.000,271.2 -4.1,0.99,10.810,264.3 -4.5,1.00,12.050,279.9 -3.5,1.00,11.850,280.3 -4.7,1.00,11.630,276.4 -4.8,1.00,13.830,274.6 -4.7,1.00,12.280,275.4 -4.9,1.00,12.590,280.3 -4.9,1.00,13.950,284.8 -4.7,1.00,11.810,283.7 -4.9,1.00,11.050,289.1 -4.9,0.99,11.900,295.4 -5.4,1.00,12.170,294.2 -4.9,1.00,4.780,278.0 -5.3,1.00,4.040,217.8 -4.8,1.00,5.160,194.4 -3.8,1.00,6.670,171.1 -3.0,0.99,5.730,166.0 -2.9,0.99,7.870,159.4 -2.9,0.99,8.470,161.2 -2.3,0.99,3.430,160.9 -3.4,0.99,3.190,169.6 -3.5,0.99,3.490,182.9 -4.2,0.99,3.620,257.3 -4.5,0.99,7.530,260.4 -4.8,0.99,9.210,283.3 -4.9,1.00,10.350,288.0 -5.0,1.00,13.600,281.8 -5.0,1.00,12.930,290.4 -5.3,1.00,11.090,290.8 -5.4,1.00,11.990,293.4 -5.6,1.00,11.150,293.6 -5.4,1.00,9.870,287.8 -5.3,1.00,8.210,281.2 -6.0,1.00,8.590,278.4 -5.9,1.00,7.370,254.8 -5.8,1.00,7.220,260.3 -5.8,1.00,7.400,283.9 -5.7,1.00,6.770,290.5 -5.4,1.00,5.210,281.6 -4.9,1.00,2.630,269.0 -4.5,1.00,6.130,124.0 -4.5,1.00,7.570,127.2 -3.1,1.00,5.690,122.5 -2.9,1.00,4.970,118.2 -4.0,0.99,6.370,102.4 -3.4,0.99,4.390,90.5 -3.9,0.99,3.450,94.5 -4.0,0.99,3.640,82.7 -3.9,0.99,2.870,120.7 -4.9,0.99,3.440,122.8 -5.5,0.99,3.830,129.9 -6.9,0.99,4.690,140.4 -8.1,0.99,6.250,150.0 -9.0,0.99,7.290,147.1 -10.8,0.99,11.420,171.2 -9.3,0.98,8.510,190.9 -8.5,0.98,10.820,178.1 -7.5,0.98,11.240,155.5 -7.3,0.98,14.400,142.6 -7.5,0.98,15.360,145.7 -7.6,0.98,15.040,148.3 -7.6,0.98,13.780,146.7 -7.7,0.98,14.110,150.8 -7.7,0.97,15.570,147.3 -8.0,0.97,14.620,144.1 -8.3,0.97,14.870,150.1 -8.5,0.97,15.400,149.1 -8.8,0.97,16.230,146.1 -9.0,0.97,16.820,158.4 -9.6,0.97,16.660,169.1 -9.7,0.97,15.270,169.8 -10.1,0.97,17.660,173.7 -10.6,0.97,12.180,185.6 -10.0,0.97,6.960,335.3 -9.8,0.97,2.920,8.9 -10.0,0.97,2.590,328.0 -9.5,0.97,8.560,296.9 -8.0,0.97,15.640,292.5 -6.1,0.98,15.060,290.0 -5.9,0.98,17.770,281.5 -6.6,0.98,17.240,276.0 -5.5,0.98,14.220,290.8 -5.4,0.98,18.810,281.9 -4.6,0.98,21.210,281.2 -4.7,0.99,17.200,282.4 -4.6,0.99,12.810,277.4 -4.5,0.99,9.460,271.2 -3.6,0.99,7.020,292.3 -3.3,0.99,4.320,255.6 -3.4,0.99,8.370,275.8 -3.5,0.99,9.490,264.7 -3.8,0.99,10.280,265.5 -3.1,0.99,10.060,255.3 -3.2,0.99,11.250,264.8 -3.1,0.99,11.170,259.2 -3.1,0.99,12.980,267.9 -3.1,0.99,10.690,270.4 -3.2,1.00,10.920,271.7 -3.4,1.00,11.050,275.0 -3.5,1.00,9.290,277.7 -3.8,1.00,9.900,283.8 -3.9,1.00,10.320,295.0 -3.9,1.00,7.080,285.6 -4.0,1.00,11.910,289.0 -4.4,1.00,10.380,300.4 -4.3,1.00,10.080,301.2 -4.0,1.00,8.660,313.6 -3.6,1.00,15.890,288.5 -3.4,1.00,8.750,286.8 -3.8,1.00,12.690,280.4 -3.8,1.00,11.880,282.3 -3.1,1.00,10.360,281.4 -3.0,1.00,8.820,280.9 -2.8,1.00,7.930,284.1 -2.9,1.00,8.960,282.5 -2.7,1.00,7.870,288.9 -2.3,1.00,6.020,288.4 -2.2,1.00,6.160,295.4 -2.2,1.00,6.770,291.5 -1.7,1.00,1.570,242.6 -1.9,1.00,1.070,255.9 -2.1,1.00,1.850,287.3 -2.0,1.00,0.820,328.9 -3.5,1.00,2.080,355.1 -4.1,1.00,2.160,353.3 -4.2,1.00,2.640,347.3 -4.4,1.00,2.390,0.1 -4.5,1.00,1.330,324.8 -4.9,1.00,2.510,341.4 -5.3,1.00,2.080,359.4 -5.2,1.00,1.320,347.9 -5.3,1.00,0.660,333.8 -5.2,1.00,0.520,86.4 -5.0,1.00,1.150,140.7 -5.4,1.00,2.020,243.3 -4.8,1.00,2.810,280.3 -3.5,1.00,1.120,100.4 -2.3,1.00,5.090,140.7 -2.1,1.00,4.510,136.3 -1.6,1.00,4.000,143.4 -1.2,1.00,4.250,143.9 -0.7,1.00,5.180,147.8 -0.8,1.00,5.550,145.4 -1.0,1.00,5.480,143.8 -1.4,1.00,5.380,148.2 -2.2,1.00,5.370,146.8 -3.3,1.00,5.130,150.4 -4.2,1.00,4.730,147.9 -5.0,1.00,3.880,142.3 -5.6,1.00,2.520,125.5 -6.0,1.00,2.570,128.1 -6.4,1.00,2.320,129.0 -6.9,1.00,1.910,128.3 -7.1,1.00,1.590,95.3 -7.2,1.00,1.330,62.8 -7.1,1.00,1.200,40.4 -6.9,1.00,2.910,69.5 -6.9,1.00,3.490,75.2 -5.8,1.00,2.690,105.1 -5.2,1.00,3.870,123.8 -4.2,1.00,4.070,122.0 -3.8,1.00,5.870,127.4 -3.1,1.00,5.070,133.7 -2.5,1.00,3.850,131.5 -2.6,1.00,4.610,132.0 -2.4,1.00,4.620,134.3 -2.1,1.00,4.460,132.1 -2.3,1.00,4.130,131.6 -3.0,1.00,4.980,137.7 -3.9,1.00,4.300,121.3 -5.4,1.00,3.940,127.3 -5.7,1.00,3.310,135.0 -6.7,1.00,3.600,120.2 -7.7,1.00,2.720,128.8 -8.4,1.00,2.780,128.5 -9.2,1.00,4.120,137.5 -9.9,1.00,4.120,140.1 -10.3,1.00,3.960,142.0 -10.3,1.00,3.810,132.2 -10.0,0.99,5.770,115.4 -9.2,0.99,9.920,140.4 -7.9,0.99,7.680,134.9 -7.7,0.99,7.210,132.5 -6.7,0.99,8.470,128.6 -6.8,0.99,8.290,134.9 -6.5,0.99,7.070,142.0 -6.1,0.99,6.380,140.2 -6.0,0.99,6.580,138.4 -5.4,0.99,6.040,137.6 -5.3,0.99,5.500,147.3 -5.1,0.99,5.490,132.1 -4.8,0.99,5.090,137.1 -5.8,0.99,5.070,131.0 -6.7,0.99,4.480,130.2 -7.1,0.99,2.620,122.3 -8.0,0.99,2.880,117.8 -9.4,0.99,3.260,115.5 -10.3,0.99,2.750,108.0 -11.4,0.99,2.780,82.1 -11.0,0.99,1.010,20.3 -11.9,0.99,1.910,357.8 -12.5,0.99,3.490,26.2 -12.3,0.99,4.540,36.2 -12.1,0.99,4.490,47.3 -10.7,0.99,1.410,46.6 -10.5,0.99,0.490,322.1 -10.3,0.99,2.470,145.6 -9.8,0.99,3.220,126.2 -8.6,0.99,2.850,135.8 -8.9,0.99,1.200,144.2 -8.2,0.99,1.510,142.8 -8.3,0.99,1.480,131.3 -7.9,0.99,1.190,129.2 -7.9,0.99,2.050,143.0 -7.6,0.99,0.840,117.5 -7.2,0.99,0.530,92.5 -7.4,0.99,1.210,314.6 -7.6,0.99,1.710,298.5 -8.0,0.99,1.300,311.0 -8.6,0.99,0.920,331.7 -9.3,0.99,0.280,357.5 -9.4,0.99,1.210,335.4 -9.8,0.99,1.940,324.5 -9.9,0.99,2.580,307.9 -9.7,0.99,3.030,308.7 -10.0,0.99,3.450,301.9 -10.0,0.99,5.170,294.3 -9.9,0.99,6.980,291.8 -10.0,0.99,7.580,290.6 -9.8,0.99,8.110,290.4 -9.7,0.99,8.220,291.3 -9.0,0.99,9.380,290.0 -8.1,0.99,9.750,287.8 -7.6,0.99,10.110,285.8 -7.4,0.99,9.810,285.2 -7.3,0.99,9.610,284.3 -7.0,0.99,8.690,287.1 -7.2,0.99,7.130,293.9 -7.2,0.99,5.840,304.8 -7.2,0.99,5.070,307.7 -7.8,0.99,4.700,307.0 -8.3,0.99,4.090,312.3 -8.1,0.99,3.430,319.4 -7.6,0.99,2.700,323.4 -8.2,0.99,2.730,311.8 -9.4,0.99,1.340,336.9 -10.0,0.99,1.680,346.4 -10.7,0.99,1.750,332.4 -10.9,0.99,2.750,316.0 -10.9,0.99,2.890,322.1 -11.5,0.99,2.850,344.2 -11.8,0.99,3.480,8.2 -11.4,0.99,1.830,324.3 -11.3,0.99,2.670,293.4 -10.9,0.99,3.520,292.0 -10.7,0.99,3.760,299.9 -10.3,0.99,4.230,300.2 -10.1,0.99,3.660,300.0 -8.9,0.99,3.450,306.1 -8.7,0.99,3.600,295.7 -8.8,0.99,4.190,285.8 -8.5,0.99,4.390,287.1 -8.5,0.99,3.620,293.9 -7.9,0.99,3.900,310.2 -7.9,0.99,3.300,302.8 -7.9,0.99,2.940,281.6 -8.5,0.99,3.320,286.3 -9.0,0.99,2.770,302.9 -9.6,0.99,2.020,314.7 -10.2,0.99,1.330,311.9 -10.8,0.99,1.790,314.2 -10.9,0.99,2.360,311.1 -11.7,0.99,2.380,314.3 -12.2,0.99,1.940,314.6 -11.2,0.99,3.640,306.7 -9.9,0.99,5.400,283.7 -8.8,0.99,5.880,280.5 -7.8,0.99,5.070,291.2 -8.0,0.99,5.120,289.5 -7.6,1.00,5.630,296.5 -6.7,1.00,5.560,294.1 -6.3,1.00,4.260,289.0 -6.1,1.00,3.230,298.0 -5.5,1.00,2.860,297.7 -5.0,1.00,1.320,294.5 -4.9,1.00,1.710,324.4 -4.7,1.00,1.710,290.1 -4.5,1.00,2.020,302.7 -4.3,1.00,2.080,300.0 -4.3,1.00,3.450,296.7 -5.0,1.00,4.230,281.8 -5.7,1.00,4.170,286.6 -6.2,1.00,3.660,297.6 -6.4,1.00,3.890,297.6 -7.0,1.00,3.560,310.6 -7.5,1.00,3.470,309.2 -7.4,1.00,3.490,313.1 -8.4,1.00,3.300,312.6 -9.5,1.00,3.070,313.9 -10.3,1.00,3.700,311.1 -10.7,1.00,4.710,307.7 -10.9,1.00,6.090,304.0 -10.6,1.00,6.840,300.3 -9.1,1.00,7.230,297.4 -7.4,1.00,5.990,307.0 -6.2,1.00,5.660,299.2 -5.6,1.00,5.090,310.5 -5.7,1.00,5.410,314.9 -5.3,1.00,5.230,316.4 -4.0,1.00,4.850,319.7 -3.6,1.00,5.010,316.9 -3.4,1.00,5.920,307.6 -3.2,1.00,5.800,301.6 -3.6,1.00,4.800,317.0 -3.9,1.00,5.350,311.7 -5.4,1.00,4.480,312.8 -6.3,1.00,4.200,327.0 -7.2,1.00,4.280,317.3 -8.2,1.00,4.170,303.7 -9.2,1.00,3.940,308.5 -10.5,1.00,4.520,321.1 -11.9,1.00,5.160,312.4 -12.7,1.00,5.840,313.0 -13.6,0.99,5.380,323.4 -13.2,0.99,5.790,323.8 -13.2,0.99,6.050,320.2 -13.0,0.99,6.200,315.7 -12.1,0.99,6.750,316.9 -10.6,0.99,6.310,325.1 -10.1,0.99,6.440,319.8 -9.7,0.99,5.860,313.5 -9.1,0.99,5.610,319.0 -8.5,0.99,5.420,316.4 -8.7,0.99,4.500,328.5 -8.1,0.99,5.890,320.4 -8.3,0.99,4.500,324.6 -8.0,0.99,4.380,324.0 -8.4,0.99,5.440,307.2 -8.2,0.99,4.720,312.9 -9.3,0.99,3.980,299.2 -10.3,0.99,3.670,308.6 -11.0,0.99,3.650,306.6 -11.7,0.99,3.800,312.1 -12.3,0.99,3.690,317.2 -13.4,0.99,3.920,304.1 -14.7,0.99,4.060,294.6 -15.7,0.99,4.160,295.6 -16.1,0.99,4.790,297.0 -16.3,0.99,4.950,301.2 -16.1,0.99,3.890,318.5 -16.3,0.99,3.010,320.5 -15.6,0.99,2.980,307.5 -13.9,0.99,3.250,299.4 -14.0,0.99,4.850,274.4 -13.2,0.99,4.560,286.8 -11.4,0.99,1.910,211.2 -9.8,0.99,4.550,175.3 -6.2,0.99,7.330,164.8 -6.9,0.99,6.320,152.7 -7.3,0.99,4.790,153.7 -7.6,0.99,1.720,189.6 -6.6,0.99,0.840,291.1 -5.4,0.99,1.550,311.8 -6.4,0.99,1.800,316.9 -7.4,0.99,2.730,352.8 -8.0,0.99,3.440,330.7 -8.7,0.99,3.620,325.0 -9.4,0.99,4.000,318.1 -8.9,0.99,4.050,303.4 -10.4,0.99,4.060,294.9 -10.9,0.99,4.910,291.1 -10.9,0.99,5.600,285.5 -10.9,0.99,6.680,285.5 -10.7,0.99,6.420,282.9 -10.6,0.99,6.770,288.2 -9.4,1.00,8.000,291.9 -8.1,1.00,8.400,295.4 -7.8,1.00,4.530,279.4 -7.7,1.00,3.540,284.5 -7.4,1.00,1.840,281.5 -7.5,1.00,0.570,308.2 -7.6,1.00,0.330,312.8 -7.6,1.00,1.050,44.8 -7.7,1.00,0.970,68.5 -7.8,1.00,0.720,328.0 -7.8,1.00,0.700,78.7 -8.0,1.00,1.150,272.3 -7.6,1.00,2.570,317.4 -7.7,1.00,2.380,299.1 -8.0,1.00,2.690,315.7 -8.2,1.00,2.480,322.4 -8.4,1.00,1.260,336.6 -9.0,1.00,1.060,107.0 -9.2,1.00,0.250,131.1 -9.4,1.00,0.980,335.8 -9.3,1.00,5.010,289.5 -9.1,1.00,6.190,297.5 -9.1,1.00,6.480,295.1 -9.0,1.00,6.080,301.2 -8.7,1.00,6.180,295.7 -8.4,1.00,5.420,293.1 -8.0,1.00,4.950,289.5 -7.8,1.00,5.010,282.6 -7.7,1.00,5.120,281.5 -7.5,1.00,4.330,282.2 -7.5,1.00,3.750,273.5 -7.5,1.00,3.010,279.9 -7.9,1.00,2.460,285.7 -8.0,1.00,1.640,262.1 -8.4,1.00,0.690,229.5 -8.2,1.00,0.790,127.6 -9.4,1.00,1.500,131.7 -9.7,1.00,2.390,124.0 -10.3,0.99,2.090,113.9 -11.1,0.99,2.830,102.8 -11.2,0.99,1.370,98.3 -11.3,0.99,0.770,84.9 -11.8,0.99,0.940,350.1 -12.5,0.99,1.580,6.2 -12.4,0.99,1.790,37.9 -12.5,0.99,1.670,49.0 -12.2,0.99,2.560,64.0 -12.2,0.99,2.630,58.2 -11.7,0.99,1.970,62.8 -11.6,0.99,2.030,20.7 -10.6,0.99,2.460,335.9 -9.8,0.99,3.030,327.3 -9.4,0.99,3.100,321.6 -9.4,0.99,3.820,292.3 -9.3,0.99,4.110,284.3 -9.8,0.98,1.910,280.5 -9.0,0.98,1.600,104.4 -8.8,0.98,3.180,123.9 -9.3,0.98,3.790,136.2 -10.0,0.98,3.740,125.8 -10.6,0.98,3.440,132.9 -10.9,0.98,4.100,140.9 -11.1,0.98,5.920,136.2 -11.5,0.98,8.230,144.0 -11.4,0.98,10.700,149.0 -12.2,0.98,13.690,157.4 -12.3,0.98,14.690,147.1 -12.4,0.98,13.780,154.9 -12.4,0.98,15.680,166.8 -12.3,0.98,12.980,181.4 -11.3,0.98,17.200,171.1 -10.8,0.98,18.140,171.1 -10.4,0.98,17.600,165.3 -10.2,0.98,18.710,167.3 -10.4,0.98,14.960,171.6 -10.5,0.98,10.440,190.7 -9.4,0.98,2.050,287.9 -10.5,0.98,3.550,148.8 -10.5,0.98,2.920,111.6 -10.4,0.98,3.490,92.5 -9.8,0.98,2.820,115.1 -10.3,0.98,2.210,144.9 -10.6,0.98,3.510,131.2 -11.9,0.98,1.300,121.0 -11.8,0.98,1.340,282.2 -11.3,0.98,2.270,311.1 -11.9,0.98,0.880,72.7 -12.4,0.98,1.870,103.0 -14.1,0.98,7.440,163.7 -14.0,0.98,6.880,199.5 -12.3,0.98,7.500,181.3 -11.1,0.98,10.840,175.5 -10.8,0.98,13.720,171.8 -10.1,0.98,11.820,160.1 -9.8,0.98,14.650,158.4 -9.5,0.98,11.670,146.3 -9.5,0.98,16.560,153.4 -9.5,0.98,13.840,152.0 -9.3,0.98,14.490,156.6 -9.9,0.98,16.240,165.7 -9.7,0.98,15.630,169.5 -9.2,0.98,12.400,172.8 -9.0,0.98,8.040,185.6 -8.7,0.98,6.790,195.5 -8.6,0.98,1.450,244.5 -8.1,0.98,4.170,309.2 -7.4,0.99,5.000,301.3 -7.2,0.99,0.760,334.0 -7.7,0.99,1.590,0.4 -7.8,0.99,4.360,332.5 -8.3,0.99,4.480,324.6 -7.9,0.99,4.110,343.3 -8.3,0.99,1.690,11.0 -8.9,0.99,2.560,13.3 -9.3,0.99,3.410,17.6 -9.0,0.99,3.620,24.6 -8.3,0.99,4.600,17.5 -8.3,0.98,4.050,16.4 -8.2,0.98,4.010,36.7 -7.8,0.98,3.720,135.4 -7.5,0.98,6.900,176.2 -6.9,0.98,8.490,161.1 -6.4,0.98,7.260,150.2 -5.9,0.98,6.500,155.1 -5.7,0.98,5.280,156.1 -5.5,0.98,2.280,122.7 -5.3,0.98,3.140,29.8 -5.0,0.98,5.090,350.0 -5.4,0.98,6.860,319.1 -5.5,0.98,8.860,313.4 -5.0,0.98,9.550,297.7 -4.9,0.98,9.060,287.1 -5.0,0.98,6.690,303.2 -5.3,0.98,4.800,308.8 -5.4,0.98,2.530,316.3 -5.9,0.98,1.790,293.4 -6.4,0.98,3.860,304.7 -7.0,0.98,13.080,303.7 -7.2,0.98,13.010,304.1 -7.5,0.98,10.540,296.2 -7.4,0.98,12.000,285.1 -7.8,0.98,12.420,272.9 -7.8,0.98,10.210,279.4 -7.1,0.99,4.550,222.5 -7.2,0.99,4.840,194.5 -6.9,0.99,5.220,193.4 -7.1,0.99,4.510,197.8 -7.1,0.99,2.670,203.1 -6.7,0.99,2.910,192.3 -6.5,0.99,2.460,211.0 -6.7,0.99,1.920,252.2 -6.3,0.99,1.460,258.4 -5.8,0.99,1.400,220.7 -5.5,0.99,1.760,213.7 -4.2,0.99,3.510,195.4 -4.6,0.99,1.980,208.9 -5.8,0.99,1.830,202.1 -7.0,0.99,0.560,232.8 -8.0,1.00,1.590,153.6 -8.7,1.00,2.200,127.3 -9.1,1.00,2.960,131.8 -10.0,1.00,3.430,158.2 -10.5,1.00,2.810,153.6 -10.9,1.00,1.920,148.3 -10.9,1.00,2.010,179.0 -9.5,1.00,6.330,304.4 -9.1,1.00,7.340,288.4 -8.9,1.00,6.700,290.9 -8.0,1.00,6.020,283.3 -7.4,1.00,5.730,285.9 -7.1,1.00,4.370,286.3 -6.9,1.00,1.900,167.9 -5.5,1.00,3.830,141.2 -6.0,1.00,1.120,179.3 -5.7,1.00,5.600,175.4 -5.4,1.00,5.750,157.8 -5.4,1.00,7.210,161.1 -5.8,1.00,7.040,163.8 -6.5,1.00,7.660,161.3 -7.1,1.00,8.390,154.1 -7.9,1.00,9.110,153.8 -8.2,1.00,9.660,152.1 -8.4,1.00,10.010,143.9 -8.9,1.00,10.760,143.2 -9.8,1.00,11.200,141.2 -10.6,1.00,11.640,146.5 -11.2,1.00,10.960,170.6 -10.7,1.00,12.310,163.5 -10.2,1.00,10.430,163.9 -9.8,1.00,10.560,162.2 -9.8,1.00,12.440,168.4 -10.0,0.99,10.730,163.3 -9.3,0.99,12.170,158.0 -9.1,0.99,12.950,158.9 -9.3,0.99,14.360,163.6 -9.6,0.99,14.100,181.5 -9.1,0.99,6.390,182.7 -9.6,0.99,7.150,188.7 -10.3,0.99,14.700,185.2 -10.2,0.99,12.240,184.7 -8.7,0.99,4.900,235.3 -8.9,0.99,2.890,318.0 -8.7,0.99,1.880,314.5 -8.9,0.99,12.600,287.5 -8.3,0.99,16.530,292.8 -8.4,0.99,16.950,289.4 -8.6,0.99,18.880,290.2 -8.8,0.99,18.900,287.4 -8.8,0.99,18.040,288.1 -8.7,0.99,18.020,287.6 -9.6,0.99,19.350,288.5 -9.5,0.99,19.200,288.9 -9.2,1.00,19.750,287.8 -8.9,1.00,18.990,284.0 -9.1,1.00,17.810,283.1 -8.9,1.00,16.830,279.1 -7.6,1.00,14.440,282.0 -7.2,1.00,9.570,269.7 -6.8,1.00,5.950,268.5 -6.5,1.00,6.610,276.7 -6.3,1.00,4.890,252.8 -6.3,1.00,1.940,255.3 -6.2,1.00,4.190,259.3 -5.8,1.00,2.630,259.4 -5.8,1.00,5.630,278.7 -5.8,1.00,8.710,292.1 -5.7,1.00,8.780,293.9 -5.7,1.00,8.190,300.1 -6.1,1.00,7.690,310.4 -6.4,1.00,6.060,305.9 -6.5,1.00,5.940,291.2 -6.8,1.00,5.150,299.1 -6.9,1.00,4.780,299.9 -7.0,1.00,5.490,282.8 -7.7,1.00,7.860,278.9 -7.5,1.00,7.400,276.8 -7.6,1.00,7.730,275.6 -7.5,1.00,8.150,275.4 -7.3,1.00,7.850,279.1 -6.9,1.00,7.110,274.5 -6.1,1.00,6.840,278.9 -5.8,1.00,6.830,280.1 -5.5,1.00,5.530,279.7 -5.6,0.99,5.050,278.0 -5.2,0.99,3.740,229.2 -4.6,0.99,2.450,217.6 -4.1,0.99,2.580,191.4 -3.2,0.99,4.400,144.1 -2.7,0.99,4.220,149.6 -3.0,0.99,3.920,159.5 -3.7,0.99,3.450,153.9 -4.6,0.99,4.160,154.2 -5.3,0.99,3.890,162.8 -5.5,0.99,2.840,133.9 -6.6,0.99,6.650,141.3 -7.5,0.99,8.270,174.0 -7.5,0.98,8.420,171.8 -7.8,0.98,8.090,172.6 -7.1,0.98,10.980,158.6 -6.7,0.98,10.000,156.6 -5.9,0.98,13.320,166.4 -4.8,0.98,11.260,268.4 -5.0,0.98,16.010,287.9 -4.8,0.98,11.990,282.2 -4.3,0.98,14.150,281.1 -4.6,0.98,13.800,270.9 -4.5,0.98,11.700,270.3 -4.5,0.98,13.040,279.7 -4.5,0.98,11.210,276.1 -4.6,0.98,7.470,259.5 -4.9,0.98,7.610,254.3 -4.5,0.99,4.780,229.7 -4.2,0.99,6.390,195.3 -3.9,0.99,7.010,161.6 -3.0,0.99,7.000,150.0 -4.0,0.99,6.230,169.5 -5.1,0.99,5.280,174.2 -5.2,0.99,5.340,172.5 -5.7,0.99,2.620,178.9 -5.5,0.99,3.430,149.4 -6.3,0.99,3.110,251.7 -6.9,0.99,7.180,275.9 -7.2,0.99,11.390,290.4 -6.4,0.99,13.690,279.0 -6.2,0.99,8.530,311.6 -6.0,0.99,7.180,302.2 -6.2,0.99,9.490,302.5 -6.1,0.99,6.600,292.1 -5.7,0.99,6.640,281.3 -5.6,0.99,7.640,285.6 -5.7,0.99,6.800,290.8 -5.8,0.99,4.930,299.3 -5.4,0.99,0.490,227.9 -5.2,0.99,4.340,199.1 -5.0,0.99,3.430,189.3 -4.5,0.99,4.250,179.8 -3.8,0.99,3.840,162.3 -2.3,0.99,5.270,148.6 -3.1,0.99,5.160,149.7 -4.6,0.99,3.990,152.3 -5.4,0.99,2.860,131.2 -6.4,0.99,2.630,136.8 -7.1,0.99,1.430,321.6 -6.7,0.99,2.740,310.6 -6.9,0.99,3.370,314.6 -7.3,0.99,3.360,316.1 -7.1,0.99,3.760,312.1 -6.9,0.99,3.590,298.6 -7.8,0.99,3.770,295.9 -6.9,0.99,5.890,282.4 -6.5,0.99,6.470,275.3 -6.8,0.99,6.180,277.9 -6.2,0.99,6.100,284.9 -6.2,0.99,7.060,289.1 -6.3,0.99,6.070,288.1 -6.1,0.99,4.760,295.2 -5.5,0.99,3.130,183.2 -5.3,0.99,4.220,197.9 -5.2,0.99,4.590,194.7 -5.1,0.99,2.890,280.7 -4.0,0.99,3.570,275.4 -4.5,0.99,4.330,300.7 -4.4,0.99,4.350,301.4 -4.3,0.99,5.930,306.3 -5.6,0.99,6.210,313.8 -6.1,0.99,5.810,305.6 -6.3,0.99,6.000,299.5 -6.6,0.99,5.450,300.1 -7.2,0.99,5.500,312.5 -7.9,0.99,5.870,325.0 -8.3,0.99,7.070,300.1 -8.4,0.99,7.270,296.1 -8.6,0.99,9.030,292.4 -8.9,0.99,9.600,285.2 -8.8,0.99,8.510,290.2 -8.2,1.00,6.280,289.8 -8.0,1.00,6.750,307.1 -7.5,1.00,6.990,310.1 -7.0,1.00,6.200,301.2 -6.3,1.00,5.370,301.7 -5.7,1.00,4.600,292.9 -4.8,1.00,2.900,314.7 -1.1,1.00,2.440,337.3 -0.9,1.00,3.460,317.4 -2.8,1.00,4.580,325.3 -3.2,1.00,4.240,322.8 -3.7,1.00,4.110,326.3 -5.5,1.00,4.770,323.9 -6.6,1.00,4.240,306.6 -7.4,1.00,3.880,300.9 -8.3,1.00,4.770,291.5 -9.2,1.00,5.960,284.2 -9.4,1.00,5.610,285.6 -10.3,1.00,5.190,283.3 -10.8,1.00,4.510,294.8 -11.4,1.00,3.930,298.1 -11.6,1.00,3.860,301.4 -11.7,1.00,2.850,302.1 -11.7,1.00,3.280,308.0 -11.1,1.00,2.960,288.0 -10.4,1.00,2.930,275.5 -9.7,1.00,0.560,210.5 -7.9,1.00,3.260,154.6 -8.6,1.00,4.160,168.8 -6.7,1.00,4.110,155.0 -5.9,1.00,5.090,152.5 -5.3,1.00,5.500,155.4 -5.2,1.00,6.040,157.2 -4.6,1.00,6.330,155.6 -5.3,1.00,6.230,154.6 -6.2,1.00,5.210,160.1 -7.5,1.00,4.640,162.4 -7.6,1.00,1.460,274.2 -7.9,1.00,3.560,305.8 -8.5,1.00,4.840,309.1 -9.0,1.00,6.160,304.1 -8.7,1.00,7.860,286.4 -8.8,1.00,10.330,282.2 -9.1,1.00,11.380,284.5 -9.0,1.00,11.810,288.3 -8.5,1.00,11.480,292.4 -8.1,1.00,10.600,295.4 -7.9,1.00,9.580,300.2 -7.9,1.00,8.160,302.2 -6.9,1.00,6.760,308.5 -6.2,1.00,4.510,307.0 -6.0,1.00,3.630,306.3 -6.1,1.00,2.060,304.1 -6.0,1.00,0.720,284.7 -6.1,1.00,0.890,325.1 -6.1,1.00,1.270,70.0 -6.1,1.00,2.600,115.6 -5.9,1.00,3.790,124.8 -5.7,1.00,4.440,135.5 -5.6,1.00,5.610,125.6 -6.2,1.00,5.100,147.3 -7.1,1.00,7.050,145.3 -7.5,1.00,7.980,138.8 -8.1,1.00,9.020,140.5 -8.5,1.00,9.730,134.5 -9.0,1.00,9.450,144.1 -8.8,1.00,10.930,145.6 -8.4,1.00,11.430,144.4 -8.1,1.00,10.550,149.3 -8.0,1.00,9.010,162.8 -8.0,1.00,7.830,160.8 -7.8,1.00,7.620,151.5 -7.7,1.00,7.330,164.0 -8.2,1.00,7.060,151.3 -8.4,1.00,7.510,156.9 -7.8,0.99,3.020,215.1 -7.3,0.99,3.180,286.7 -7.2,0.99,3.650,222.4 -7.3,0.99,1.800,209.4 -7.5,0.99,1.920,208.2 -7.9,0.99,2.340,168.1 -7.7,0.99,1.030,162.7 -7.9,0.99,0.910,215.7 -8.0,0.99,0.790,250.8 -7.9,0.99,0.660,332.1 -8.2,0.99,0.940,9.7 -8.3,0.99,0.880,20.4 -8.7,0.99,0.600,358.6 -8.8,0.99,2.780,280.7 -8.7,0.99,9.920,274.9 -8.6,0.99,8.610,268.8 -9.1,0.99,12.190,281.3 -9.4,0.99,16.720,288.7 -9.4,0.99,16.060,283.4 -9.5,0.99,14.880,285.9 -9.3,0.99,15.530,284.5 -9.2,1.00,14.280,281.2 -8.7,1.00,14.730,285.9 -8.8,1.00,13.080,282.0 -8.6,1.00,8.660,285.4 -8.7,1.00,4.290,273.3 -8.5,1.00,5.490,273.4 -7.9,1.00,5.590,182.0 -7.9,1.00,2.650,281.1 -8.2,1.00,6.040,259.5 -7.5,1.00,3.150,262.9 -7.1,1.00,7.050,271.7 -7.0,1.00,8.380,281.6 -7.1,1.00,10.560,284.7 -7.5,1.00,9.500,283.6 -7.9,1.00,8.310,282.1 -8.2,1.00,8.610,277.8 -8.3,1.00,9.140,272.4 -8.6,1.00,11.220,273.3 -8.7,1.00,10.780,270.7 -8.8,1.00,10.260,276.6 -8.8,1.00,11.340,284.5 -8.5,1.00,11.870,282.6 -8.5,1.00,12.300,282.9 -8.5,1.00,12.530,285.9 -8.4,1.00,13.180,286.2 -7.7,1.00,12.360,288.3 -7.2,1.00,11.470,289.0 -7.1,1.00,10.400,278.0 -6.9,1.00,6.350,266.7 -6.7,1.00,6.900,274.4 -6.7,1.00,6.690,271.7 -6.3,1.00,6.830,269.9 -6.2,1.00,6.610,270.4 -5.9,1.00,6.610,278.0 -5.8,1.00,6.500,284.2 -5.7,1.00,5.490,296.0 -5.8,1.00,5.940,289.1 -6.0,1.00,5.010,295.8 -6.8,1.00,6.820,334.4 -7.2,1.00,5.060,355.2 -6.9,1.00,5.950,339.7 -7.8,1.00,2.760,356.8 -7.2,1.00,6.440,293.6 -7.3,1.00,8.470,284.4 -8.1,1.00,7.910,294.1 -8.4,1.00,8.090,294.2 -8.7,1.00,8.340,288.7 -8.3,1.00,8.150,289.1 -8.1,1.00,9.150,290.7 -8.0,1.00,9.350,289.3 -7.9,1.00,8.310,308.0 -7.1,1.00,7.350,307.7 -6.5,1.00,7.250,307.4 -6.1,1.01,7.130,302.7 -5.8,1.01,7.050,306.7 -5.6,1.01,6.630,306.2 -4.6,1.01,6.460,310.0 -5.0,1.01,6.180,308.8 -4.0,1.01,7.190,320.7 -4.6,1.01,6.820,326.9 -5.9,1.01,6.360,324.5 -7.5,1.01,6.800,311.5 -8.5,1.01,6.460,308.1 -9.1,1.01,6.320,303.9 -9.4,1.01,6.080,297.1 -9.6,1.01,5.630,291.1 -9.5,1.01,5.590,288.5 -10.9,1.01,5.210,296.3 -11.7,1.01,5.020,302.2 -12.1,1.01,4.720,296.3 -12.4,1.01,5.270,295.0 -12.0,1.01,4.880,294.3 -11.8,1.00,6.420,290.8 -10.2,1.00,9.200,295.2 -8.5,1.00,8.990,297.2 -7.3,1.00,7.790,291.9 -6.4,1.00,5.990,288.9 -6.1,1.00,6.850,304.8 -6.0,1.00,5.850,306.5 -6.7,1.00,3.170,321.4 -7.7,1.00,1.570,351.4 -6.3,1.00,1.180,95.0 -6.8,1.00,2.860,109.0 -8.0,1.00,2.300,100.6 -8.7,1.00,2.000,127.6 -9.2,1.00,0.620,341.0 -9.6,1.00,3.730,343.6 -10.7,1.00,3.540,343.1 -11.0,1.00,2.670,328.2 -11.1,1.00,3.310,292.3 -11.6,1.00,4.480,307.0 -11.8,1.00,5.000,306.5 -12.1,1.00,5.070,301.3 -12.5,1.00,5.670,308.6 -12.7,1.00,5.650,307.5 -13.1,1.00,4.770,299.4 -12.8,1.00,5.020,307.6 -12.4,1.00,5.330,324.3 -10.9,1.00,5.640,317.1 -11.3,1.00,6.500,310.1 -11.1,1.00,6.190,299.9 -11.9,1.00,5.610,309.9 -11.0,1.00,6.060,317.1 -10.0,1.00,5.710,312.6 -9.4,1.00,5.300,309.4 -8.7,1.00,5.310,310.1 -8.6,1.00,5.470,308.4 -7.7,1.00,4.620,311.6 -8.2,1.00,3.930,305.6 -9.7,1.00,3.040,308.4 -11.0,1.00,2.700,321.4 -11.7,1.00,2.730,317.1 -12.5,1.00,2.970,306.7 -13.1,1.00,2.620,302.4 -13.5,1.00,2.570,313.0 -14.4,1.00,1.970,297.4 -14.7,0.99,3.640,288.0 -14.1,0.99,7.080,285.5 -14.1,0.99,9.300,277.9 -13.9,0.99,9.130,276.3 -14.0,0.99,9.880,283.4 -11.8,0.99,9.080,286.8 -9.6,0.99,10.750,292.3 -8.9,0.99,9.710,290.6 -8.9,0.99,9.100,278.9 -10.0,0.99,5.940,288.4 -10.0,0.99,3.980,288.2 -10.1,0.99,2.250,333.3 -8.2,0.99,2.720,74.8 -8.1,0.99,2.520,100.9 -8.7,0.99,3.140,127.1 -9.7,0.99,3.810,119.5 -10.8,0.99,3.550,120.0 -11.7,0.99,2.190,111.5 -12.1,0.99,1.070,108.1 -13.8,0.99,0.870,87.7 -13.8,0.99,0.810,353.6 -12.3,0.99,2.770,302.9 -12.0,0.99,4.510,298.8 -13.1,0.99,6.470,290.4 -13.3,0.99,8.560,287.3 -13.3,0.99,9.800,283.6 -13.1,0.99,10.330,280.8 -12.7,0.99,10.780,280.7 -12.3,0.99,10.660,279.4 -10.1,1.00,9.830,279.1 -8.4,1.00,9.870,278.8 -7.7,1.00,9.660,274.9 -7.4,1.00,9.510,273.6 -7.7,1.00,8.620,269.3 -7.7,1.00,6.390,259.8 -8.0,1.00,3.320,238.4 -7.7,1.00,2.610,239.1 -7.4,1.00,1.970,231.1 -7.3,1.00,0.900,180.8 -6.7,1.00,2.410,161.4 -7.1,1.00,1.930,158.2 -7.8,1.00,2.620,300.9 -9.4,1.00,2.760,298.3 -9.9,1.00,3.780,299.0 -10.2,1.00,3.280,298.8 -10.6,1.00,3.640,303.0 -10.8,1.00,5.420,291.1 -12.2,1.00,6.450,288.2 -12.5,1.00,8.550,288.9 -12.5,1.00,9.950,285.4 -12.1,1.00,10.660,285.3 -12.1,1.00,6.070,274.6 -12.0,1.00,5.660,287.8 -10.4,1.00,10.820,284.3 -9.5,1.00,8.490,281.0 -9.2,1.00,7.740,276.9 -9.0,1.00,6.650,279.6 -8.8,1.00,6.080,281.7 -8.7,1.00,5.120,278.2 -8.6,1.00,4.610,280.4 -8.5,1.00,1.840,177.6 -9.5,1.00,2.240,103.4 -9.1,1.00,2.470,94.0 -9.0,1.00,4.180,124.5 -8.9,1.00,8.560,156.1 -9.9,1.00,7.640,156.0 -10.7,1.00,6.850,151.3 -11.7,1.00,6.680,127.6 -12.8,1.00,3.060,152.4 -12.1,1.00,4.120,356.6 -11.9,0.99,6.720,333.9 -10.4,1.00,9.600,292.3 -9.7,1.00,8.290,289.6 -9.5,0.99,8.660,286.6 -9.2,1.00,10.500,282.1 -8.8,1.00,7.320,301.8 -8.6,1.00,9.150,280.2 -8.4,1.00,7.570,298.1 -8.3,1.00,8.070,282.9 -8.3,1.00,7.390,293.2 -8.2,1.00,6.350,282.1 -8.4,1.00,2.590,258.9 -8.1,1.00,4.700,274.1 -7.5,1.00,5.990,255.6 -7.5,1.00,6.560,267.2 -7.3,1.00,5.460,284.8 -7.3,1.00,4.310,290.8 -7.3,1.00,3.030,297.9 -7.2,1.00,3.670,296.5 -7.4,1.00,3.470,301.2 -7.6,1.00,0.580,260.5 -7.9,1.00,0.400,167.7 -8.8,1.00,2.210,339.5 -9.4,0.99,2.770,332.3 -9.3,0.99,2.190,335.7 -9.5,0.99,1.880,318.0 -10.1,0.99,0.860,224.5 -9.7,0.99,1.660,169.9 -9.1,0.99,3.850,116.1 -9.2,0.99,8.280,183.6 -9.2,0.99,6.960,204.5 -8.3,0.99,3.710,274.1 -7.4,0.99,13.240,292.4 -7.6,0.99,12.820,278.4 -7.1,0.99,13.420,284.7 -6.9,0.99,14.590,283.6 -6.7,0.99,16.380,285.3 -6.1,0.99,16.020,285.6 -5.8,0.99,16.170,286.4 -5.7,0.99,15.370,284.1 -5.6,0.99,15.220,286.0 -5.8,0.99,15.840,284.6 -5.9,0.99,15.380,288.9 -6.7,0.99,14.650,287.9 -7.0,0.99,14.510,290.4 -7.2,0.99,12.970,310.9 -7.3,0.99,14.370,309.8 -7.3,0.99,13.640,312.9 -7.4,0.99,13.000,315.6 -8.1,0.99,17.610,298.3 -7.9,0.99,19.860,286.2 -7.7,0.99,19.910,285.7 -7.5,0.99,18.030,286.3 -7.1,0.99,17.760,284.8 -6.7,1.00,14.800,280.1 -6.0,1.00,13.340,272.3 -5.8,1.00,10.480,282.1 -5.0,1.00,7.360,312.8 -5.3,1.00,4.860,264.9 -5.3,1.00,10.110,280.3 -5.8,1.00,8.660,268.0 -5.2,1.00,8.270,274.9 -4.7,1.00,5.330,287.4 -5.2,1.00,3.910,296.8 -5.0,1.00,2.020,0.8 -4.7,1.00,3.750,355.2 -6.5,1.00,2.530,12.6 -6.6,1.00,3.000,328.8 -6.7,1.00,3.570,332.8 -7.0,1.00,2.850,333.1 -7.3,1.00,2.530,321.7 -7.8,1.00,2.300,317.4 -7.9,1.00,1.980,317.2 -8.5,1.00,1.490,291.2 -9.0,1.00,2.040,296.3 -8.6,1.00,6.120,284.7 -8.5,1.00,8.530,278.4 -8.4,1.00,8.780,274.9 -7.9,1.00,8.990,275.0 -7.5,1.00,10.430,281.6 -6.7,1.01,10.240,286.1 -6.4,1.01,8.740,283.2 -6.4,1.01,8.030,285.5 -6.0,1.01,6.940,296.8 -4.8,1.01,4.890,310.1 -5.0,1.01,3.150,289.5 -5.4,1.01,4.540,289.5 -5.7,1.01,4.260,281.2 -5.1,1.01,2.940,295.3 -5.6,1.01,2.970,289.4 -6.2,1.01,2.510,292.5 -7.4,1.01,2.890,291.1 -8.2,1.01,3.200,308.6 -8.7,1.01,3.620,310.3 -9.5,1.01,3.340,317.0 -10.0,1.01,3.540,320.8 -10.9,1.01,3.790,335.3 -12.0,1.01,3.200,330.1 -12.5,1.01,2.810,332.5 -12.7,1.01,2.590,329.1 -12.8,1.01,3.210,319.8 -12.8,1.01,3.190,319.0 -12.7,1.01,3.610,326.8 -12.3,1.01,4.450,320.2 -10.1,1.01,4.820,300.5 -9.6,1.01,5.200,302.7 -9.1,1.01,5.150,305.2 -9.0,1.01,5.220,302.6 -8.7,1.01,5.110,305.8 -8.3,1.01,4.490,308.3 -8.1,1.01,4.550,304.4 -8.4,1.01,5.090,301.8 -8.3,1.01,4.730,305.7 -7.8,1.01,4.740,311.3 -9.1,1.01,4.700,307.9 -10.1,1.01,4.720,302.2 -11.4,1.01,5.130,303.2 -12.4,1.01,5.180,305.4 -13.0,1.01,5.350,308.2 -13.2,1.00,6.070,305.1 -13.4,1.00,6.760,301.0 -13.8,1.00,8.350,303.2 -14.0,1.00,8.870,305.2 -13.9,1.00,9.190,301.0 -13.7,1.00,9.480,295.2 -13.8,1.00,9.320,293.1 -13.7,1.00,8.970,296.3 -12.7,1.00,9.240,297.9 -12.9,1.00,7.580,312.7 -12.8,1.00,8.600,308.8 -11.8,1.00,12.190,316.5 -10.6,1.00,10.360,312.6 -9.9,1.00,10.650,307.0 -8.9,1.00,10.070,312.6 -8.3,1.00,9.650,321.0 -7.5,1.00,9.290,325.7 -8.0,1.00,9.730,329.2 -9.3,1.00,11.650,329.2 -10.0,1.00,11.590,326.9 -10.7,1.00,7.000,310.9 -11.8,1.00,6.000,307.4 -12.7,1.00,5.940,310.3 -13.7,1.00,6.680,317.0 -13.3,1.00,6.640,308.3 -15.7,1.00,6.680,318.3 -16.0,1.00,6.140,314.0 -16.4,1.00,6.200,314.2 -16.7,1.00,7.140,323.8 -16.6,1.00,8.110,302.5 -16.5,1.00,8.650,304.0 -16.4,1.00,8.240,307.0 -16.1,1.00,9.670,315.2 -15.6,1.00,10.380,319.1 -13.8,1.00,11.030,302.4 -13.5,1.00,11.640,278.7 -12.2,1.00,9.630,289.1 -12.0,1.00,10.200,288.3 -12.3,1.00,7.330,310.4 -12.2,1.00,7.100,310.1 -12.3,1.00,6.660,304.1 -11.9,1.00,7.330,299.1 -12.4,1.00,6.710,294.1 -13.4,1.00,5.740,298.9 -14.3,1.00,5.510,310.9 -15.1,1.00,5.070,308.4 -16.0,1.00,4.690,309.7 -16.5,1.00,4.730,305.7 -17.3,1.00,4.910,308.1 -18.2,0.99,5.100,304.2 -19.0,0.99,5.330,302.1 -19.4,0.99,5.320,309.8 -20.0,0.99,5.690,319.6 -20.0,0.99,5.630,313.4 -19.7,0.99,5.220,317.5 -19.8,0.99,3.930,311.5 -19.1,0.99,6.250,308.6 -18.7,0.99,8.340,319.7 -18.5,0.99,7.110,316.8 -17.1,0.99,6.010,308.3 -16.6,0.99,5.110,297.1 -14.8,0.99,4.160,304.4 -15.2,0.99,4.020,298.2 -14.6,0.99,4.550,288.2 -15.1,0.99,4.870,290.1 -15.6,0.99,4.270,290.7 -16.0,0.99,4.390,302.7 -16.1,0.99,4.530,309.6 -16.5,0.99,3.080,307.5 -17.5,0.99,3.980,306.4 -18.4,0.99,3.490,314.1 -19.4,0.99,3.450,324.4 -20.9,0.99,4.080,327.3 -19.9,0.99,3.830,311.8 -21.0,0.98,3.960,300.9 -21.8,0.98,3.130,313.9 -21.9,0.98,2.340,336.9 -22.1,0.98,1.760,350.1 -22.1,0.98,0.940,348.2 -22.2,0.98,0.440,349.5 -21.6,0.98,1.020,128.5 -19.9,0.98,2.400,135.7 -20.1,0.98,2.220,150.2 -19.5,0.99,1.900,142.3 -18.9,0.99,1.670,137.0 -18.4,0.99,1.170,98.3 -19.2,0.99,0.990,87.5 -17.7,0.99,1.090,87.5 -18.0,0.99,0.570,149.1 -16.8,0.99,1.250,304.5 -18.1,0.99,2.400,304.1 -18.6,0.99,2.900,310.7 -18.7,0.99,4.090,322.4 -18.9,0.99,3.060,321.6 -20.3,0.98,3.050,330.1 -21.6,0.98,2.890,319.4 -20.7,0.98,3.570,305.2 -20.7,0.98,4.660,296.9 -21.0,0.98,8.180,287.2 -21.2,0.98,9.990,286.2 -20.9,0.98,10.590,282.2 -20.8,0.98,11.450,279.4 -20.7,0.98,12.330,280.6 -20.5,0.98,12.390,282.3 -17.0,0.98,11.510,279.8 -15.1,0.98,12.210,270.1 -15.8,0.99,11.170,268.1 -15.2,0.99,9.170,268.4 -14.9,0.99,7.730,272.7 -13.1,0.99,3.930,257.7 -11.5,0.99,3.520,148.7 -10.1,0.99,4.940,152.8 -9.3,0.99,5.220,169.7 -10.1,0.99,3.360,169.6 -9.6,0.99,5.250,148.7 -10.4,0.99,4.030,149.6 -11.8,0.99,3.860,141.2 -13.0,0.99,3.550,133.7 -14.1,0.99,1.840,136.5 -13.0,0.99,3.680,295.6 -14.0,0.99,3.510,303.3 -14.5,0.99,4.530,288.0 -16.2,0.99,6.730,281.4 -16.5,0.99,7.080,276.8 -16.5,0.99,7.770,278.3 -16.1,0.99,8.800,276.1 -15.6,0.99,8.330,273.2 -15.5,0.99,7.940,277.1 -13.8,0.99,9.140,280.4 -11.1,0.99,10.330,278.3 -10.3,0.99,8.060,273.1 -10.1,0.99,6.210,274.2 -9.5,0.99,5.240,269.6 -9.3,0.99,4.300,274.3 -9.7,0.99,2.240,292.1 -9.1,0.99,2.330,287.1 -8.8,0.99,2.400,291.3 -8.8,0.99,2.410,290.7 -8.8,0.99,3.290,289.0 -8.8,0.99,2.830,295.9 -9.8,0.99,1.720,309.7 -10.6,0.99,1.740,341.3 -11.1,1.00,1.620,321.4 -11.8,0.99,2.460,313.5 -12.0,0.99,2.990,300.9 -12.4,0.99,3.020,300.0 -14.2,0.99,3.910,293.7 -14.9,0.99,4.950,283.7 -15.2,0.99,6.410,278.8 -15.2,0.99,7.600,275.6 -14.7,0.99,8.320,270.1 -14.3,1.00,7.890,264.0 -12.9,1.00,9.450,275.9 -10.6,1.00,11.980,273.5 -9.7,1.00,11.200,276.2 -8.6,1.00,9.960,272.9 -7.7,1.00,9.230,273.7 -7.1,1.00,9.030,272.3 -7.2,1.00,9.780,275.1 -6.0,1.00,9.710,277.0 -6.0,1.00,8.810,273.0 -6.3,1.00,7.150,274.9 -6.9,1.00,5.640,283.5 -7.2,1.00,5.490,288.6 -10.0,1.00,5.410,292.5 -11.6,1.00,5.440,296.0 -12.6,1.00,5.170,294.5 -13.3,1.00,5.610,293.3 -13.6,1.00,7.220,291.7 -13.7,1.00,7.830,289.4 -15.4,1.00,8.120,290.9 -16.0,1.00,9.510,286.5 -16.2,0.99,9.150,289.1 -16.4,1.00,9.630,284.8 -15.7,1.00,9.440,276.9 -15.5,1.00,8.780,272.8 -14.3,1.00,10.230,278.0 -12.0,1.00,11.410,273.8 -11.1,1.00,11.300,273.9 -9.9,1.00,10.840,274.3 -8.8,1.00,8.830,274.8 -7.6,1.00,7.840,280.2 -6.8,1.00,6.890,284.9 -5.6,1.00,7.670,284.1 -5.7,1.00,8.000,284.0 -5.8,1.00,6.900,287.3 -5.9,1.00,5.960,291.3 -6.8,1.00,5.720,293.4 -10.3,1.00,3.960,298.5 -12.5,1.00,3.140,300.8 -14.0,1.00,3.550,303.8 -15.1,1.00,3.970,302.5 -15.9,1.00,3.970,301.1 -16.5,1.00,4.250,298.3 -17.7,0.99,5.370,295.0 -18.3,0.99,6.490,290.1 -18.5,0.99,6.900,287.9 -18.4,0.99,7.390,286.8 -18.4,0.99,7.240,284.2 -18.2,0.99,7.070,280.2 -16.8,1.00,8.420,281.7 -15.0,1.00,9.140,280.4 -14.1,1.00,8.670,279.9 -13.6,1.00,6.540,280.1 -14.2,1.00,4.530,281.5 -13.0,0.99,1.970,181.4 -12.7,1.00,4.940,138.5 -11.7,0.99,2.170,150.4 -11.2,1.00,1.340,346.9 -12.6,1.00,3.190,300.2 -12.4,0.99,2.480,299.8 -13.0,0.99,1.680,337.2 -13.8,0.99,2.710,318.7 -15.3,0.99,2.660,321.4 -15.8,0.99,2.480,332.2 -15.9,0.99,3.970,319.2 -16.1,0.99,7.790,291.7 -15.7,0.99,7.630,291.8 -17.2,0.99,6.420,290.1 -17.7,0.99,8.250,288.3 -17.4,0.99,8.960,283.2 -17.4,0.99,7.940,285.2 -17.1,0.99,7.200,292.8 -16.9,0.99,6.450,301.2 -16.4,0.99,3.740,311.0 -15.1,0.99,6.930,294.4 -14.2,0.99,5.520,296.4 -14.2,0.99,2.920,299.5 -14.3,0.99,1.390,112.0 -14.4,0.99,1.920,111.5 -16.2,0.99,5.850,166.0 -15.8,0.99,8.740,160.4 -15.3,0.99,6.020,162.0 -14.9,0.99,3.960,196.7 -13.3,0.99,3.020,294.6 -13.5,0.99,1.610,349.5 -14.0,0.99,1.220,36.5 -14.2,0.99,0.780,5.7 -14.7,0.99,0.420,155.1 -13.8,0.99,4.080,309.1 -14.7,0.99,4.550,317.3 -14.3,0.99,3.080,331.6 -15.1,0.99,4.310,333.0 -15.3,0.99,3.580,341.3 -14.8,0.99,3.890,336.4 -13.9,0.99,5.150,297.1 -13.3,0.99,3.410,315.1 -13.1,0.99,2.050,299.0 -13.1,0.99,1.390,328.7 -13.0,0.99,0.640,352.1 -13.0,0.99,1.340,63.8 -13.0,0.99,2.840,49.3 -12.9,0.99,3.920,22.7 -12.8,0.99,2.180,2.5 -12.4,0.99,2.740,312.4 -12.0,0.99,1.890,245.2 -12.2,0.99,1.480,134.6 -13.2,0.99,1.690,105.3 -14.0,0.99,6.070,164.0 -16.0,0.99,9.240,179.1 -15.4,0.99,9.980,188.7 -11.5,0.99,9.810,291.1 -11.4,0.99,5.770,293.5 -11.5,0.99,5.860,327.2 -12.6,0.99,2.550,331.1 -11.5,0.99,5.030,314.2 -11.8,0.99,4.730,295.4 -11.4,0.99,9.070,279.2 -11.5,0.99,12.310,296.4 -10.8,0.99,11.250,312.2 -10.7,0.99,14.630,296.8 -10.6,0.99,12.460,302.2 -9.8,1.00,11.510,303.1 -9.1,1.00,0.780,330.9 -9.4,1.00,2.690,92.9 -9.6,1.00,3.940,62.4 -9.3,1.00,2.990,134.0 -8.2,1.00,4.360,140.7 -8.1,1.00,4.820,127.7 -8.4,1.00,4.910,134.3 -8.1,1.00,3.380,133.9 -8.2,1.00,3.290,125.4 -8.6,1.00,2.170,122.2 -8.5,1.00,1.410,118.9 -9.5,1.00,1.860,144.1 -9.9,1.00,1.040,303.0 -9.6,1.00,2.060,319.6 -10.3,1.00,2.660,307.1 -11.1,1.00,3.180,326.9 -12.6,1.00,2.960,348.5 -13.1,0.99,2.910,327.1 -13.2,0.99,3.690,331.4 -12.6,0.99,4.220,314.9 -12.3,0.99,4.380,306.6 -12.5,0.99,4.300,297.3 -12.5,0.99,3.990,297.9 -11.9,0.99,4.550,290.1 -11.4,0.99,4.940,290.7 -11.0,0.99,5.270,283.5 -10.7,0.99,4.700,280.9 -10.6,0.99,4.530,285.5 -10.3,0.99,4.850,281.9 -9.7,0.99,1.680,209.8 -9.7,0.99,1.330,235.6 -10.1,0.99,1.060,219.1 -10.3,0.99,1.490,164.1 -10.5,0.99,1.270,194.2 -10.4,0.99,1.720,127.3 -10.7,0.99,2.780,105.3 -11.8,0.99,4.210,128.3 -12.0,0.99,3.330,124.0 -12.6,0.99,3.810,132.6 -13.3,0.99,4.270,146.8 -13.6,0.99,7.660,151.2 -11.8,0.99,4.920,242.5 -11.1,0.99,3.160,252.9 -10.7,0.99,1.690,277.2 -10.7,0.99,1.190,332.2 -10.6,0.99,1.840,315.5 -10.5,0.99,1.330,321.0 -10.6,0.99,0.860,13.0 -10.6,0.99,0.360,19.0 -10.6,0.99,1.150,84.5 -10.2,0.99,1.660,129.1 -10.2,0.99,1.780,175.3 -10.2,0.99,2.940,165.0 -10.6,0.99,3.940,171.7 -10.5,0.99,3.540,160.9 -10.6,0.99,2.850,150.9 -10.8,0.99,2.820,139.8 -11.3,0.99,3.400,123.6 -11.9,0.99,2.780,118.0 -12.7,0.99,2.010,118.6 -13.6,0.99,0.370,355.1 -13.4,0.99,3.060,324.1 -13.8,0.99,3.340,326.3 -14.2,0.99,4.400,316.4 -14.5,0.99,3.900,320.9 -14.6,0.99,3.510,304.3 -14.8,0.99,2.820,307.2 -14.8,0.99,3.150,320.7 -14.5,0.99,6.020,290.6 -14.3,0.99,7.050,285.6 -14.2,0.99,6.970,280.5 -13.3,0.99,7.330,281.0 -12.9,0.99,7.360,280.2 -12.3,0.99,7.420,279.1 -12.2,0.99,6.690,268.4 -11.3,0.99,5.920,267.9 -11.1,0.99,3.100,231.2 -10.2,0.99,2.420,145.6 -10.3,0.99,3.730,80.2 -9.9,0.99,3.280,59.0 -9.7,0.99,2.450,74.3 -9.5,0.99,1.470,110.5 -9.9,0.99,2.600,142.7 -10.5,0.99,3.680,164.6 -11.9,0.99,2.190,146.0 -12.2,0.99,0.270,16.3 -11.7,0.99,4.450,302.1 -11.5,0.99,4.430,310.2 -11.5,0.99,4.710,304.3 -12.0,0.99,5.470,301.6 -12.0,0.99,5.290,294.9 -12.5,0.99,4.670,303.9 -12.3,0.99,3.880,295.2 -12.2,0.99,4.340,315.3 -12.0,0.99,4.190,320.3 -11.8,0.99,2.840,334.6 -11.6,0.99,1.200,67.2 -10.6,0.99,4.620,93.3 -9.6,0.99,4.330,99.0 -9.4,0.99,4.830,106.0 -9.4,0.99,3.090,131.8 -9.3,0.99,2.900,164.3 -9.2,0.99,6.130,139.8 -10.1,0.99,6.200,167.5 -10.5,0.99,5.130,175.8 -9.9,0.99,2.170,220.2 -9.6,0.99,3.640,312.3 -10.1,0.99,3.230,335.6 -10.1,1.00,2.220,298.4 -10.8,1.00,0.680,344.7 -11.7,1.00,0.650,158.2 -11.8,1.00,0.320,80.4 -12.8,1.00,1.300,336.4 -13.1,1.00,2.090,350.5 -12.6,1.00,2.530,310.2 -11.7,1.00,6.400,282.8 -11.8,1.00,7.000,275.8 -11.6,1.00,9.560,277.9 -11.1,1.00,9.650,278.1 -10.7,1.00,9.660,276.7 -10.2,1.00,9.730,277.5 -8.8,1.00,9.850,297.7 -9.1,1.00,8.010,287.0 -9.1,1.00,6.830,280.8 -9.0,1.00,5.910,292.7 -8.6,1.00,6.300,304.2 -8.4,1.00,6.300,306.8 -8.8,1.00,6.720,311.3 -9.6,1.00,6.730,313.4 -9.7,1.00,4.710,308.2 -10.0,1.00,2.710,304.7 -10.2,1.00,2.670,292.4 -10.7,1.00,2.950,298.1 -11.2,1.00,3.000,301.4 -11.4,1.00,3.010,303.1 -11.8,1.00,2.880,306.9 -12.0,1.00,4.230,304.8 -12.7,1.00,6.740,295.0 -12.7,1.00,8.720,285.0 -12.5,1.00,8.380,286.3 -12.6,1.00,9.080,285.2 -12.5,1.00,9.170,281.1 -12.0,1.00,8.580,277.2 -11.0,1.00,9.550,281.4 -9.9,1.00,10.310,282.6 -9.6,1.00,9.680,281.9 -9.2,1.00,9.260,283.7 -8.8,1.00,8.780,280.9 -9.0,1.00,8.610,272.0 -9.2,1.00,7.630,274.0 -9.2,1.00,6.490,277.6 -9.3,1.00,5.070,280.9 -9.3,1.00,3.780,274.3 -8.5,1.00,2.990,293.4 -8.7,1.00,2.460,314.7 -9.6,1.00,2.190,322.7 -10.3,1.00,3.080,315.6 -11.2,1.00,3.500,307.4 -11.6,0.99,4.180,303.8 -11.7,0.99,6.690,295.7 -11.1,0.99,10.510,286.6 -12.1,0.99,11.460,285.1 -12.1,0.99,12.700,282.5 -11.6,0.99,14.440,283.6 -11.2,0.99,13.980,291.2 -10.7,0.99,11.990,293.5 -10.5,0.99,10.150,299.9 -9.1,0.99,8.850,296.7 -8.2,0.99,7.860,295.4 -7.8,0.99,7.990,290.2 -7.5,0.99,6.750,287.4 -7.3,0.99,4.990,277.1 -7.3,0.99,3.020,259.0 -7.4,0.99,1.420,246.6 -7.4,0.99,4.790,188.3 -6.7,0.98,4.700,150.5 -6.6,0.98,5.380,137.9 -6.9,0.98,5.920,149.2 -7.2,0.98,6.230,149.3 -7.9,0.98,4.570,148.2 -9.0,0.98,3.210,143.2 -9.9,0.98,1.440,108.6 -10.2,0.98,1.470,315.0 -9.7,0.98,2.970,302.4 -9.4,0.98,6.310,285.0 -9.0,0.98,8.230,284.2 -9.1,0.98,7.990,286.3 -9.2,0.98,7.360,287.5 -9.1,0.98,8.520,294.8 -9.2,0.98,7.260,296.7 -9.2,0.98,8.190,291.4 -8.9,0.98,5.480,281.6 -8.6,0.98,5.650,281.7 -9.0,0.98,7.200,216.6 -8.1,0.99,6.300,186.1 -8.2,0.99,5.550,179.8 -8.4,0.99,5.710,176.4 -8.2,0.99,5.080,180.4 -8.5,0.99,4.770,183.2 -8.3,0.99,3.480,163.6 -8.1,0.99,1.790,180.7 -8.0,0.99,2.520,168.2 -8.3,0.99,1.670,170.6 -9.2,0.99,0.530,246.4 -9.3,0.99,3.500,296.4 -9.9,0.99,3.360,291.4 -10.6,0.99,3.020,299.5 -11.3,0.99,1.720,308.4 -11.5,0.99,2.050,314.6 -12.1,0.99,1.390,278.8 -12.6,0.99,0.590,311.7 -12.3,0.99,2.160,129.3 -10.8,0.99,4.000,68.6 -8.8,0.99,2.940,133.7 -11.1,0.99,1.130,167.9 -9.8,0.99,6.280,147.5 -9.2,0.99,6.460,190.1 -9.8,0.99,6.040,191.5 -7.8,0.99,6.780,155.1 -8.2,0.99,8.200,169.8 -7.6,0.99,6.510,172.6 -7.2,0.99,6.270,168.5 -6.3,0.99,5.340,157.2 -4.8,0.99,4.900,150.5 -5.1,0.99,4.700,147.7 -8.2,0.99,7.730,148.4 -8.7,0.99,7.880,141.7 -9.2,0.99,8.180,137.6 -10.1,0.99,8.120,134.5 -10.6,0.99,9.310,132.2 -11.5,0.99,8.980,135.3 -12.4,0.99,7.590,136.7 -13.0,0.99,8.250,136.3 -13.2,0.99,7.450,117.3 -12.9,0.99,7.060,171.9 -12.4,0.99,7.070,162.2 -12.0,0.99,6.710,169.4 -11.2,0.99,5.480,173.4 -11.4,0.99,5.270,163.2 -11.1,0.99,6.410,185.1 -10.5,0.99,5.890,188.7 -10.3,0.99,5.050,164.3 -10.0,0.99,4.770,161.6 -9.8,0.99,3.660,149.7 -9.7,0.99,3.550,152.4 -9.9,0.99,3.660,164.5 -9.6,0.99,3.060,158.9 -9.0,0.99,2.490,122.8 -9.6,0.99,3.070,131.5 -10.5,0.99,4.130,137.5 -11.4,0.99,4.950,147.0 -11.6,0.99,4.460,134.6 -12.6,0.99,4.640,136.4 -13.1,0.99,3.540,128.3 -13.5,0.99,2.410,115.0 -13.8,0.99,1.050,22.4 -12.2,0.99,5.110,292.8 -12.6,0.99,4.570,284.2 -13.0,0.99,5.430,294.3 -12.0,0.99,7.750,289.8 -12.0,0.99,8.110,283.5 -11.8,0.99,7.220,279.8 -11.8,0.99,6.610,277.9 -11.0,0.99,6.010,270.5 -10.5,0.99,5.670,276.3 -9.9,0.99,5.350,284.4 -9.6,0.99,4.040,266.4 -9.3,0.99,2.810,245.0 -9.3,0.99,1.840,246.6 -9.0,0.99,2.440,136.3 -8.1,0.99,2.240,145.4 -8.3,0.99,1.750,157.5 -9.4,0.99,1.710,151.9 -9.8,0.99,4.060,141.2 -10.0,0.99,3.350,143.0 -10.8,0.99,5.760,138.2 -11.7,0.99,5.570,138.0 -12.4,0.99,4.230,132.6 -13.3,0.99,2.730,143.8 -13.5,0.99,1.290,173.6 -11.5,0.99,4.980,322.8 -12.3,0.99,5.100,301.9 -12.1,0.99,5.420,305.4 -12.2,0.99,6.070,295.8 -12.0,0.99,7.010,289.1 -12.1,0.99,7.080,289.3 -11.8,0.99,6.930,290.9 -10.8,0.99,7.510,289.5 -10.4,0.99,7.560,292.7 -10.3,0.99,7.190,296.0 -10.1,0.99,7.060,285.1 -10.1,0.99,7.030,285.6 -10.1,0.99,6.700,283.1 -9.7,0.99,6.010,290.1 -9.7,0.99,5.120,290.6 -9.5,0.99,4.140,295.3 -9.3,0.99,2.820,297.5 -9.4,0.99,2.820,288.7 -10.7,0.99,0.800,87.0 -11.5,0.99,2.510,110.7 -12.6,0.99,2.780,90.1 -13.0,0.99,1.600,72.0 -13.5,0.99,1.020,62.4 -11.6,0.99,5.720,295.6 -12.7,0.99,2.380,328.5 -11.9,0.99,2.430,34.3 -11.6,0.99,2.430,294.9 -12.0,0.99,2.650,304.7 -12.3,0.99,3.230,303.3 -12.4,0.99,1.500,305.0 -12.6,0.99,0.840,254.0 -12.4,0.99,2.900,134.8 -11.3,0.99,6.540,169.3 -11.2,0.99,11.530,166.7 -11.0,0.99,12.560,160.3 -11.2,0.99,12.280,155.4 -10.9,0.99,11.480,150.1 -10.6,0.98,11.140,150.9 -10.8,0.98,10.490,153.5 -10.5,0.98,10.390,145.8 -10.7,0.98,10.900,142.1 -10.7,0.98,10.060,147.1 -11.3,0.98,9.380,142.3 -12.8,0.98,10.620,143.7 -13.5,0.98,11.360,146.5 -13.6,0.98,14.190,154.2 -13.8,0.98,13.830,157.7 -13.7,0.98,13.660,160.1 -14.4,0.98,12.780,149.3 -14.4,0.98,12.200,152.4 -14.2,0.98,12.020,153.9 -13.3,0.98,5.400,253.1 -12.9,0.98,4.510,279.9 -13.6,0.98,4.310,177.8 -13.1,0.98,5.530,152.7 -11.7,0.98,6.930,198.3 -11.8,0.98,8.210,178.0 -11.6,0.98,9.420,168.9 -11.7,0.98,7.500,171.3 -11.2,0.98,6.440,181.2 -10.8,0.98,12.280,173.5 -10.4,0.98,12.940,167.6 -10.4,0.98,11.820,170.5 -10.9,0.98,12.400,168.5 -12.0,0.98,11.250,172.9 -11.8,0.98,12.420,167.7 -11.5,0.98,13.160,160.0 -12.6,0.98,12.450,159.5 -12.5,0.98,12.540,157.2 -13.2,0.98,11.760,156.2 -13.8,0.98,9.820,160.3 -14.9,0.98,7.390,163.8 -14.5,0.98,6.060,187.5 -13.4,0.98,6.520,326.6 -11.6,0.98,10.910,287.9 -11.2,0.98,7.230,287.0 -11.2,0.98,5.790,291.0 -11.3,0.98,4.140,309.0 -11.1,0.98,2.680,311.4 -10.8,0.98,1.530,297.0 -10.3,0.98,0.400,312.1 -10.0,0.98,1.580,56.0 -9.4,0.98,1.890,112.5 -8.4,0.98,1.080,163.2 -9.1,0.98,4.150,245.7 -9.2,0.98,2.360,267.6 -9.0,0.98,0.620,330.9 -8.6,0.98,4.020,274.5 -8.5,0.98,3.570,280.9 -8.5,0.98,3.690,286.4 -8.7,0.98,3.440,290.3 -9.2,0.98,3.190,300.5 -9.8,0.98,4.300,294.3 -10.1,0.98,5.840,288.2 -11.3,0.98,6.200,288.5 -10.0,0.98,7.800,282.0 -10.5,0.98,8.680,283.5 -10.8,0.98,9.410,279.2 -11.0,0.99,8.640,272.9 -10.8,0.99,9.570,279.5 -10.5,0.99,9.640,283.1 -10.7,0.99,8.350,276.6 -10.7,0.99,3.930,254.3 -10.3,0.99,2.690,272.0 -9.9,0.99,0.640,311.8 -9.7,0.99,3.190,99.1 -9.7,0.99,5.950,126.2 -9.6,0.99,4.900,110.6 -8.8,0.99,3.780,129.8 -8.1,0.99,4.100,125.1 -7.9,0.99,4.890,122.7 -8.5,0.99,3.300,132.1 -9.6,0.99,3.560,154.1 -11.0,0.99,6.690,155.3 -11.8,0.99,5.200,138.7 -12.9,0.99,8.210,146.2 -13.3,0.99,7.910,175.7 -13.4,0.99,9.370,179.3 -12.9,0.99,8.830,181.4 -12.7,0.99,4.200,181.3 -12.5,0.99,3.460,247.5 -11.0,0.99,6.440,293.0 -11.0,0.99,6.770,300.1 -11.3,1.00,3.580,292.9 -11.4,1.00,4.590,291.5 -11.4,1.00,3.930,289.2 -11.1,1.00,4.660,293.2 -11.0,1.00,4.850,300.9 -9.7,1.00,1.040,94.2 -10.5,1.00,1.110,353.2 -10.6,1.00,2.010,32.8 -10.3,1.00,2.640,44.3 -9.9,1.00,2.640,32.9 -9.3,1.00,1.840,59.8 -9.9,1.00,1.890,32.2 -9.3,1.00,1.580,97.3 -9.8,1.00,1.700,108.0 -10.5,1.00,0.880,126.6 -11.0,1.00,1.140,316.9 -11.1,1.00,1.430,329.3 -11.5,1.00,2.530,310.4 -11.7,1.00,3.000,316.7 -11.8,1.00,3.610,307.2 -12.3,1.00,3.530,320.4 -12.3,1.00,4.010,313.3 -12.5,1.00,6.190,294.2 -12.7,1.00,6.920,289.1 -12.8,1.00,6.570,290.2 -12.8,1.00,7.310,287.9 -12.8,1.00,7.780,285.1 -12.5,1.00,7.780,281.1 -11.7,1.00,8.430,281.2 -11.3,1.00,8.570,281.1 -11.2,1.00,7.720,285.6 -11.1,1.00,6.460,288.4 -11.2,1.00,5.650,286.9 -11.3,1.00,5.730,288.0 -10.9,1.00,5.650,282.0 -11.0,1.00,4.620,278.8 -11.2,1.00,3.910,275.4 -10.9,1.00,3.500,294.5 -10.9,1.00,2.800,310.0 -11.3,1.00,2.830,314.8 -11.5,1.00,3.140,314.3 -12.3,1.00,3.760,296.3 -12.8,1.00,4.170,296.0 -13.1,1.00,3.960,299.6 -13.4,1.00,4.330,305.7 -13.5,0.99,4.760,305.5 -14.4,0.99,5.480,303.5 -14.8,0.99,6.220,303.0 -14.7,0.99,7.440,297.7 -14.5,0.99,9.660,296.6 -14.3,0.99,11.720,286.5 -14.1,0.99,12.990,285.3 -12.0,0.99,13.460,283.4 -10.9,0.99,12.380,292.3 -10.8,0.99,10.170,289.3 -10.7,0.99,9.390,291.8 -10.0,0.99,7.370,304.8 -9.8,0.99,5.450,303.5 -9.4,0.99,3.050,319.8 -9.7,0.99,3.140,308.7 -10.4,0.99,4.180,301.3 -10.5,0.99,4.030,309.2 -11.2,0.99,2.820,292.8 -10.4,0.99,3.980,307.5 -10.8,0.99,3.660,310.3 -12.3,0.99,3.970,323.7 -12.6,0.99,3.040,305.3 -12.9,0.99,3.240,299.5 -13.1,0.99,3.910,291.0 -13.2,0.99,5.420,287.5 -14.3,0.99,8.290,282.3 -14.9,0.99,9.160,280.1 -15.4,0.99,10.860,280.3 -15.6,0.99,11.490,276.5 -15.3,0.99,10.580,272.0 -15.0,0.99,9.600,270.3 -13.5,0.99,8.290,265.6 -11.8,0.99,8.910,277.4 -11.1,0.99,8.440,282.5 -10.9,0.99,7.220,284.8 -10.6,0.99,5.650,283.0 -10.8,0.99,3.550,295.9 -10.9,0.99,1.630,269.4 -10.6,0.99,1.510,189.6 -10.6,0.99,3.200,175.2 -11.4,0.99,2.770,151.8 -12.5,0.99,2.460,133.3 -12.8,0.99,1.570,114.6 -12.8,0.99,0.840,85.4 -14.0,0.99,1.320,57.3 -14.5,0.99,0.820,23.6 -13.3,0.99,4.210,311.0 -13.6,0.99,3.510,310.8 -14.3,0.99,2.910,313.8 -15.3,0.99,3.710,298.1 -15.4,0.99,6.370,296.4 -15.5,0.99,7.960,290.0 -15.4,0.99,9.330,285.7 -15.4,0.99,9.900,284.3 -15.3,0.99,9.630,282.1 -13.7,0.99,10.110,285.6 -11.9,0.99,9.710,284.1 -11.3,0.99,10.070,283.7 -11.4,0.99,9.920,282.0 -11.2,0.99,9.570,282.1 -10.8,0.99,9.270,284.0 -11.8,0.99,8.000,296.8 -10.2,0.99,7.180,299.8 -8.9,1.00,5.190,319.9 -9.6,1.00,3.720,315.5 -11.5,1.00,5.660,331.5 -12.9,0.99,4.270,322.9 -12.9,0.99,3.630,314.3 -14.4,0.99,3.830,311.9 -15.3,0.99,3.760,303.3 -15.9,0.99,4.290,301.9 -16.6,0.99,4.510,302.4 -16.9,0.99,4.660,297.6 -18.4,0.99,6.250,293.8 -18.7,0.99,8.430,287.9 -18.8,0.99,9.310,283.9 -18.6,0.99,9.780,286.1 -18.3,0.99,10.160,283.1 -17.9,0.99,10.620,280.4 -15.4,0.99,10.630,281.0 -13.5,0.99,12.490,278.1 -12.7,0.99,12.240,279.9 -12.1,0.99,11.470,283.6 -11.6,0.99,11.500,279.0 -11.4,0.99,11.720,274.0 -11.4,0.99,11.220,281.6 -11.5,0.99,11.470,284.9 -11.1,0.99,10.530,286.5 -9.9,0.99,9.180,287.5 -10.1,0.99,7.590,288.0 -11.6,0.99,10.420,291.2 -13.4,0.99,8.430,290.3 -13.9,0.99,7.610,293.2 -14.6,0.99,7.240,295.6 -14.8,0.99,6.450,296.3 -15.1,0.99,6.760,293.1 -15.5,0.99,8.440,293.6 -16.9,0.99,8.280,295.6 -17.1,0.99,8.580,290.9 -17.3,0.99,8.950,288.3 -17.4,0.99,9.220,288.5 -17.5,0.99,8.980,289.8 -17.5,0.99,8.290,288.7 -15.8,0.99,7.720,288.9 -14.8,0.99,6.950,295.2 -13.5,0.99,4.360,318.6 -13.3,0.99,3.130,318.1 -13.9,0.99,3.100,315.3 -13.7,0.99,2.100,333.2 -13.5,0.99,2.670,319.7 -13.0,0.99,1.940,322.5 -13.2,0.99,1.910,312.4 -14.0,0.99,2.920,338.4 -13.9,0.99,2.790,327.8 -14.3,0.99,2.400,311.1 -14.8,0.99,2.590,318.4 -15.6,0.99,2.560,315.4 -16.4,0.99,2.990,308.8 -16.9,0.99,3.750,305.4 -17.5,0.99,5.340,308.7 -16.7,0.99,7.770,291.7 -17.9,0.99,10.900,286.4 -17.8,0.99,11.840,280.7 -17.5,0.99,13.330,277.2 -17.3,0.99,13.120,278.7 -17.0,0.99,13.580,278.4 -16.8,0.99,13.230,278.4 -14.3,0.99,11.270,281.3 -12.7,0.99,11.590,273.1 -12.7,0.99,11.100,266.7 -13.0,0.99,10.190,270.4 -12.6,0.99,8.140,269.9 -12.1,0.99,6.370,261.0 -11.8,0.99,5.520,274.0 -11.5,0.99,6.140,276.3 -11.5,0.99,6.570,282.3 -11.3,0.99,6.070,281.5 -10.7,0.99,4.630,286.7 -10.7,0.99,3.830,290.9 -11.3,0.99,3.680,292.7 -12.2,0.99,3.610,296.9 -13.0,0.99,4.430,299.1 -14.1,0.99,5.070,300.3 -15.1,0.99,6.210,282.8 -14.2,0.99,5.940,302.0 -13.5,0.99,11.690,282.3 -14.0,0.99,11.370,283.3 -13.7,0.99,11.780,282.3 -13.5,0.99,12.460,281.1 -13.3,0.99,13.360,280.0 -12.8,1.00,12.130,277.7 -11.8,1.00,11.160,282.6 -11.2,1.00,11.210,279.9 -10.9,1.00,11.250,279.4 -10.9,1.00,10.310,280.6 -10.7,1.00,9.820,276.5 -10.5,1.00,8.610,271.8 -10.4,1.00,10.130,274.3 -10.2,1.00,10.230,278.2 -10.5,1.00,9.510,278.4 -10.4,1.00,7.900,272.2 -11.0,1.00,7.990,273.9 -10.6,1.00,8.340,278.7 -10.8,1.00,8.430,284.8 -11.1,1.00,7.400,295.4 -11.2,1.00,8.000,292.1 -11.3,1.00,6.860,294.7 -11.5,1.00,7.750,289.4 -11.8,1.00,8.930,282.8 -12.7,1.00,11.660,275.4 -13.0,0.99,12.080,274.2 -13.1,0.99,12.870,272.8 -13.1,0.99,12.090,272.6 -13.1,0.99,11.560,274.8 -12.7,0.99,12.910,275.0 -10.5,0.99,8.960,273.9 -10.0,0.99,10.580,277.2 -9.9,0.99,10.410,279.3 -10.0,0.99,10.610,279.6 -10.0,0.99,10.320,282.4 -10.0,0.99,10.340,284.7 -9.8,0.99,10.090,286.9 -9.9,0.99,10.120,292.5 -9.5,0.99,9.730,289.9 -9.6,0.99,10.510,292.8 -9.6,0.99,10.140,295.5 -9.6,0.99,8.660,292.7 -10.8,0.99,7.600,296.1 -11.2,0.99,5.830,294.6 -11.2,0.99,6.710,287.1 -11.2,0.99,7.650,281.5 -11.3,0.99,9.100,279.4 -11.4,0.99,9.730,276.4 -12.2,0.99,11.490,272.0 -12.6,0.99,13.180,276.7 -12.6,0.99,14.310,279.8 -12.4,0.99,13.990,282.8 -12.2,0.99,14.270,280.9 -12.0,0.99,13.470,280.8 -10.2,0.99,11.510,282.3 -10.0,0.99,11.290,281.4 -9.8,0.99,9.640,278.9 -9.7,0.99,9.830,285.0 -9.7,0.99,10.620,289.5 -9.6,0.99,8.700,287.7 -9.7,0.99,8.760,297.8 -9.1,0.99,6.820,302.8 -9.1,0.99,6.360,304.8 -8.6,0.99,4.630,308.6 -10.3,0.99,4.780,310.9 -10.7,0.99,4.790,307.4 -11.3,0.99,4.580,304.2 -12.4,0.99,3.900,304.5 -12.8,0.99,4.100,293.9 -13.1,0.99,4.110,297.4 -12.7,0.99,7.000,290.1 -12.6,0.99,9.460,287.2 -13.3,0.99,10.900,284.9 -13.3,0.99,11.620,283.1 -13.3,0.99,12.050,278.2 -13.1,0.99,13.070,281.4 -12.8,0.99,13.610,282.4 -12.5,0.99,13.530,281.3 -11.1,0.99,11.990,278.6 -10.6,0.99,10.190,277.5 -10.6,0.99,8.000,276.6 -10.8,0.99,6.940,257.7 -10.6,0.99,6.080,204.0 -10.1,0.99,6.750,163.6 -9.6,0.99,7.270,157.3 -8.8,0.99,7.820,147.5 -8.5,0.99,7.660,141.6 -9.1,0.99,8.030,127.4 -9.3,0.99,7.030,119.8 -9.5,0.99,6.040,124.4 -10.1,0.99,4.880,161.4 -12.3,0.99,3.300,146.2 -12.7,0.99,1.980,111.0 -13.8,0.99,1.230,85.9 -12.7,0.99,1.490,348.6 -12.9,0.99,3.160,316.1 -12.5,0.99,5.540,312.4 -12.2,0.99,5.830,298.9 -12.2,0.99,6.140,296.0 -12.5,0.99,5.810,302.0 -12.7,0.99,6.280,304.9 -12.3,0.99,4.200,315.7 -11.9,0.99,2.890,4.5 -11.4,0.99,4.040,103.6 -10.4,0.99,4.320,119.4 -9.7,0.99,3.860,125.6 -9.4,0.99,3.460,116.8 -9.3,0.99,3.410,152.2 -8.9,0.99,6.200,162.6 -8.6,0.99,5.910,160.1 -9.1,0.99,6.150,158.8 -9.9,0.99,5.890,157.8 -11.2,0.99,5.360,145.4 -12.4,0.99,5.910,142.0 -13.3,0.99,7.460,138.4 -13.0,0.99,5.380,197.5 -12.8,0.99,6.610,181.9 -13.4,0.99,3.390,162.7 -12.9,0.99,4.440,283.2 -12.0,0.99,6.090,290.0 -12.3,0.99,6.250,277.3 -12.0,0.99,3.100,313.6 -12.3,0.99,2.710,316.9 -12.3,0.99,3.970,298.4 -12.3,0.99,6.600,281.6 -11.8,0.99,7.770,269.3 -11.3,0.99,8.160,264.0 -11.2,0.99,7.790,271.2 -11.1,0.99,8.150,284.6 -10.6,1.00,6.830,278.6 -10.6,1.00,5.350,266.6 -10.6,1.00,5.960,260.3 -10.5,1.00,5.830,261.8 -10.6,1.00,5.010,243.8 -10.7,1.00,4.020,247.2 -10.9,1.00,5.510,269.7 -11.2,1.00,6.450,274.3 -11.2,1.00,5.170,273.1 -11.5,1.00,2.890,311.6 -11.7,1.00,2.750,302.6 -12.2,1.00,4.020,295.7 -12.8,1.00,5.520,289.8 -13.3,1.00,6.590,292.5 -11.8,1.00,8.500,280.6 -13.1,1.00,11.020,276.3 -14.0,1.00,13.260,277.4 -13.9,1.00,13.260,272.2 -13.6,1.00,13.400,273.3 -13.2,1.00,12.980,276.9 -12.9,1.00,12.640,278.6 -11.4,1.00,9.760,276.2 -11.0,1.00,9.350,274.8 -10.9,1.00,7.770,278.0 -10.9,1.00,7.630,261.5 -11.1,1.00,6.250,207.9 -10.4,1.00,5.180,173.7 -9.7,1.00,4.500,167.9 -10.2,1.00,3.640,148.3 -10.2,1.00,2.830,146.7 -10.1,1.00,2.420,145.3 -10.2,1.00,2.050,145.0 -10.5,1.00,1.700,158.5 -10.7,1.00,1.590,162.5 -11.0,1.00,0.590,88.6 -11.9,1.00,0.330,262.8 -12.1,1.00,0.960,87.9 -12.2,1.00,1.650,344.3 -12.5,1.00,1.720,320.5 -13.2,1.00,2.590,309.5 -13.5,0.99,2.300,311.7 -13.7,0.99,2.340,315.5 -13.8,0.99,2.380,317.2 -13.9,0.99,2.430,318.4 -13.9,0.99,2.940,312.5 -13.6,0.99,3.720,311.7 -12.5,0.99,4.640,307.1 -11.7,0.99,4.920,303.1 -10.4,0.99,4.340,317.3 -10.1,0.99,4.260,311.5 -9.7,0.99,4.430,304.3 -9.7,0.99,4.650,304.4 -9.2,0.99,5.150,308.3 -9.8,0.99,5.450,300.1 -10.1,0.99,5.680,301.9 -11.9,0.99,5.540,300.3 -12.3,0.99,5.000,308.4 -13.1,0.99,4.210,304.3 -14.8,0.99,4.360,307.7 -15.6,0.99,4.260,309.3 -16.1,0.99,4.870,298.4 -16.6,0.99,4.760,297.9 -17.0,0.99,4.840,294.1 -18.8,0.99,4.290,291.3 -19.7,0.99,4.220,290.9 -20.5,0.99,4.840,286.4 -20.7,0.99,4.490,288.3 -20.1,0.99,8.370,288.9 -19.0,0.99,9.250,285.5 -17.1,0.99,10.160,283.4 -15.1,0.99,11.560,282.9 -14.3,0.99,11.190,278.0 -13.7,0.99,10.100,276.5 -13.9,0.99,10.060,270.3 -13.6,0.99,8.990,264.9 -14.1,0.99,6.370,244.7 -13.6,0.99,4.940,255.5 -13.5,0.99,3.810,259.4 -13.3,0.99,3.320,275.6 -12.8,0.99,4.110,290.6 -11.8,0.99,5.440,284.6 -13.2,0.99,5.750,289.5 -13.9,0.99,7.510,285.9 -14.2,0.99,7.060,286.3 -14.6,0.99,7.840,288.6 -14.7,0.99,7.780,287.9 -15.0,0.99,9.300,281.4 -15.3,0.99,10.220,277.2 -15.3,0.99,11.000,279.7 -15.5,0.99,11.730,276.7 -15.2,0.99,11.490,276.5 -14.8,0.99,10.860,276.9 -14.7,0.99,10.080,283.3 -13.3,0.99,9.390,278.6 -12.6,0.99,8.250,269.5 -12.1,0.99,8.280,273.4 -12.1,0.99,7.100,275.1 -12.0,0.99,5.900,278.1 -12.1,0.99,5.540,269.0 -12.3,0.99,5.470,273.3 -12.7,0.99,3.050,237.8 -13.1,0.99,1.480,142.1 -13.1,0.99,2.420,126.6 -13.4,0.99,1.270,123.4 -13.6,0.99,1.410,107.0 -13.9,0.99,1.770,153.0 -14.7,0.99,1.430,345.2 -15.0,0.99,2.650,318.1 -15.5,0.99,3.620,308.3 -15.9,0.99,4.230,305.1 -15.9,0.99,4.440,314.4 -16.3,0.99,5.640,301.4 -16.2,0.99,7.950,291.5 -15.7,0.99,9.130,279.3 -15.4,0.99,9.320,284.9 -15.4,0.99,8.270,287.8 -15.6,0.99,6.980,281.6 -14.9,0.99,4.560,278.3 -14.6,0.99,3.690,273.3 -14.0,0.99,2.160,287.5 -13.6,0.99,2.400,113.0 -12.3,0.99,4.780,172.7 -11.8,0.99,3.110,156.4 -11.5,0.99,3.950,174.2 -11.7,0.99,4.080,165.5 -11.7,0.99,2.770,170.6 -12.2,0.99,2.460,157.3 -12.8,0.99,3.250,156.5 -13.4,0.99,3.250,145.0 -13.7,0.99,3.220,114.1 -15.3,0.99,5.920,134.0 -16.1,0.99,6.320,126.6 -16.9,0.99,4.940,123.3 -17.5,0.99,4.330,122.7 -16.8,0.99,1.490,74.0 -14.9,0.99,9.070,278.5 -14.3,0.99,8.000,284.6 -14.8,0.99,6.460,285.8 -15.2,0.99,6.050,283.5 -14.8,0.99,7.310,275.1 -14.3,0.99,7.560,276.8 -13.8,0.99,7.580,272.1 -13.3,0.99,8.400,277.7 -12.8,0.99,7.310,279.2 -12.7,0.99,5.800,262.3 -12.5,0.99,6.520,258.4 -12.6,0.99,6.420,255.6 -12.4,0.99,5.460,244.4 -12.0,0.99,4.950,242.0 -11.0,0.99,6.830,258.1 -10.6,0.99,8.010,275.8 -10.9,0.99,8.520,279.1 -11.4,0.99,7.640,279.1 -12.1,0.99,6.240,273.3 -12.9,0.99,5.530,274.6 -13.8,0.99,3.940,258.3 -14.3,0.99,5.340,278.6 -14.0,0.99,6.520,266.5 -14.6,0.99,9.390,272.3 -14.7,0.99,10.310,275.3 -14.7,0.99,10.540,271.0 -14.6,0.99,11.010,273.3 -14.4,0.99,11.920,278.9 -14.2,0.99,13.710,282.5 -14.0,0.99,13.390,280.8 -12.9,0.99,11.950,283.2 -12.6,0.99,11.750,278.7 -12.7,0.99,10.780,282.1 -12.6,0.99,12.740,289.8 -12.2,0.99,2.660,233.1 -11.9,0.99,4.110,230.1 -10.7,0.99,5.400,180.8 -11.1,0.99,4.550,187.4 -11.6,0.99,4.360,186.4 -11.9,0.99,3.120,279.8 -11.7,0.99,3.510,239.2 -11.8,0.99,6.430,253.4 -11.7,0.99,6.680,267.1 -12.1,0.99,6.600,269.0 -12.3,0.99,7.600,278.8 -12.6,0.99,9.190,291.1 -12.4,0.99,10.370,293.3 -12.8,0.99,10.820,291.3 -12.3,0.99,12.000,293.7 -12.6,0.99,13.750,291.7 -12.4,0.99,8.210,274.2 -12.3,0.99,5.410,257.6 -12.4,0.99,5.600,279.5 -12.6,0.99,8.780,280.9 -12.3,0.99,9.080,284.7 -11.8,0.99,5.970,219.0 -11.4,0.99,8.250,165.7 -11.2,0.99,4.170,192.9 -11.4,0.99,2.800,220.6 -11.6,0.99,0.610,243.8 -11.3,0.99,0.970,166.9 -11.5,0.99,0.420,38.2 -11.5,1.00,1.570,70.4 -11.5,1.00,2.430,119.5 -11.2,1.00,3.150,146.0 -11.3,1.00,3.180,152.1 -11.8,1.00,2.630,145.2 -12.7,1.00,2.040,135.3 -13.9,1.00,0.110,158.2 -14.1,1.00,1.310,344.4 -14.2,1.00,1.880,324.1 -14.1,0.99,3.430,307.3 -14.3,0.99,3.350,304.5 -14.4,0.99,5.730,293.7 -14.5,0.99,7.280,282.7 -14.7,0.99,7.510,284.4 -14.6,0.99,8.150,281.7 -14.1,0.99,8.120,277.1 -13.7,0.99,7.790,282.8 -13.0,0.99,8.800,286.4 -12.5,0.99,7.960,282.8 -12.5,0.99,6.920,282.3 -12.8,0.99,5.210,282.3 -12.1,0.99,3.360,260.2 -11.3,0.99,2.500,193.0 -9.8,0.99,2.950,158.6 -9.5,0.99,2.970,144.0 -11.4,0.99,3.180,146.2 -13.4,0.99,3.280,151.8 -14.2,0.99,3.030,136.9 -15.5,0.99,2.900,143.3 -16.5,0.99,2.540,119.9 -17.4,0.99,2.540,110.3 -18.4,0.99,2.580,99.8 -19.1,0.99,2.990,98.6 -18.3,0.99,0.900,359.4 -19.3,0.99,2.570,353.7 -19.1,0.99,4.000,355.8 -19.1,0.99,3.650,343.7 -18.2,0.99,4.630,305.8 -18.1,0.99,4.510,306.0 -18.1,0.99,4.220,312.5 -17.5,0.99,4.670,300.3 -17.0,0.99,4.390,303.0 -16.2,0.98,4.500,304.5 -15.8,0.98,4.400,309.8 -15.5,0.98,4.490,304.3 -15.3,0.98,4.760,302.4 -14.9,0.98,3.980,297.5 -15.0,0.98,3.310,298.0 -15.5,0.98,1.980,298.4 -15.9,0.98,0.410,98.2 -16.2,0.98,2.910,132.9 -16.5,0.98,5.510,150.6 -16.4,0.98,6.210,151.5 -17.2,0.98,5.850,155.0 -18.1,0.98,5.520,151.9 -18.9,0.98,5.090,152.2 -19.5,0.98,3.710,141.2 -15.9,0.98,7.270,289.0 -16.1,0.98,6.050,294.7 -16.2,0.98,5.590,289.8 -16.4,0.98,3.750,298.7 -16.1,0.98,1.330,303.5 -15.9,0.98,0.680,93.2 -15.9,0.98,2.950,103.8 -16.5,0.98,5.660,127.9 -16.4,0.98,8.870,141.0 -15.8,0.98,8.490,143.8 -15.6,0.98,9.910,163.5 -15.2,0.98,6.280,186.3 -14.8,0.98,2.780,262.0 -14.2,0.98,1.580,293.1 -14.5,0.98,1.480,20.5 -14.5,0.98,1.670,44.3 -14.4,0.98,1.950,67.0 -15.2,0.98,2.650,161.2 -15.9,0.98,5.150,160.7 -16.1,0.98,3.480,165.6 -16.4,0.98,9.120,150.4 -16.8,0.98,7.330,152.1 -15.2,0.98,3.910,285.8 -13.8,0.98,4.590,277.1 -13.9,0.98,3.950,284.2 -14.2,0.98,3.210,296.6 -14.4,0.98,1.690,286.1 -14.9,0.98,1.360,295.1 -15.1,0.98,1.110,143.8 -15.3,0.98,3.190,139.1 -14.5,0.98,1.750,195.9 -13.8,0.98,3.840,178.8 -13.9,0.98,6.140,165.4 -15.1,0.98,8.760,155.1 -14.7,0.98,8.230,141.2 -13.5,0.98,8.590,143.6 -13.3,0.98,10.620,142.5 -13.7,0.98,12.410,143.8 -13.8,0.98,11.120,147.5 -14.0,0.98,12.320,152.9 -15.2,0.98,11.990,151.7 -15.5,0.98,13.000,153.5 -16.4,0.98,12.450,154.2 -17.4,0.98,9.540,161.5 -17.5,0.98,10.740,172.3 -17.9,0.98,9.560,171.1 -17.5,0.98,12.290,178.5 -18.2,0.98,7.590,177.2 -17.4,0.98,8.630,184.6 -17.7,0.98,7.440,172.6 -15.6,0.98,5.570,269.4 -15.7,0.98,2.010,219.8 -17.4,0.98,8.670,164.6 -16.6,0.98,9.760,177.6 -16.2,0.98,11.650,166.3 -14.8,0.98,8.420,163.9 -14.9,0.98,11.400,154.9 -14.6,0.98,12.050,158.7 -14.4,0.98,12.550,149.7 -14.3,0.98,11.350,154.6 -14.1,0.99,11.420,156.8 -13.8,0.99,11.460,140.9 -13.7,0.99,10.150,143.1 -14.2,0.99,8.900,152.3 -14.9,0.99,9.660,153.0 -16.4,0.99,12.240,163.0 -16.6,0.99,13.230,160.2 -16.9,0.99,11.810,161.7 -17.4,0.99,10.900,162.2 -17.3,0.99,5.410,173.2 -14.5,0.99,2.630,320.9 -15.5,0.99,2.290,255.7 -17.8,0.99,2.970,62.3 -18.1,0.99,3.070,38.0 -16.1,0.99,6.060,312.6 -15.8,0.99,6.560,277.4 -16.5,0.99,4.030,280.9 -16.7,0.99,3.500,282.7 -16.8,0.99,2.270,254.7 -16.3,0.99,1.360,161.1 -15.8,0.99,1.840,178.1 -15.9,0.99,2.460,180.9 -15.7,0.99,4.040,165.6 -15.7,0.99,4.720,150.8 -16.3,0.99,4.550,161.2 -15.3,0.99,4.590,145.2 -14.9,0.99,5.480,142.7 -14.8,0.99,6.290,139.7 -14.9,0.99,7.430,140.9 -15.7,0.99,9.250,144.6 -16.9,0.99,9.600,148.2 -17.8,0.99,11.350,148.4 -18.3,0.99,11.310,146.3 -19.0,0.99,10.040,142.8 -16.9,0.99,3.050,199.8 -15.7,0.99,5.220,280.4 -15.9,0.99,5.710,287.4 -15.9,1.00,6.620,291.5 -16.4,1.00,6.580,291.5 -16.7,1.00,7.540,289.8 -16.6,1.00,7.240,286.5 -16.6,1.00,6.740,288.5 -16.4,1.00,6.630,282.4 -16.3,1.00,6.510,287.7 -16.3,1.00,6.700,287.7 -16.1,1.00,6.130,287.9 -16.0,1.00,5.550,287.2 -15.3,1.00,5.620,294.9 -13.4,1.00,4.630,303.6 -13.3,1.00,4.370,303.7 -12.9,1.00,4.530,300.8 -13.5,1.00,4.840,299.6 -13.7,1.00,3.700,300.5 -15.1,1.00,3.860,298.9 -15.9,0.99,2.920,304.1 -16.6,0.99,2.080,318.6 -17.3,0.99,2.770,303.8 -17.8,0.99,3.300,320.4 -18.7,0.99,4.290,317.8 -19.3,0.99,4.310,311.5 -20.6,0.99,4.390,310.5 -21.5,0.99,4.770,301.4 -21.6,0.99,6.880,291.8 -21.7,0.99,8.420,290.2 -21.2,0.99,9.380,287.6 -21.0,0.99,8.940,290.7 -20.7,0.99,9.080,289.2 -19.3,0.99,9.140,282.5 -18.7,0.99,9.380,282.6 -18.3,0.99,7.940,280.7 -17.9,0.99,6.480,286.0 -17.8,0.99,5.320,286.9 -17.2,0.99,5.740,288.9 -17.6,0.99,5.490,292.3 -17.4,0.99,6.390,281.8 -17.5,0.99,6.210,281.1 -16.9,0.99,7.250,283.9 -17.8,0.99,6.970,285.4 -18.1,0.99,6.170,284.9 -18.3,0.99,5.000,289.9 -18.6,0.99,5.530,291.3 -19.0,0.99,6.780,289.2 -19.1,0.99,6.330,295.8 -19.1,0.99,7.260,292.2 -19.1,0.99,6.700,296.4 -20.2,0.99,7.440,285.6 -20.6,0.99,7.870,288.3 -20.6,0.99,8.800,282.6 -20.7,0.99,7.940,285.6 -20.7,0.99,7.500,289.4 -20.3,0.99,8.260,282.9 -18.9,0.99,7.900,286.0 -17.4,0.99,8.120,284.9 -16.3,0.99,7.610,286.1 -15.7,0.99,6.150,289.1 -15.0,0.99,5.600,291.2 -14.1,0.99,4.520,301.6 -12.9,0.99,5.610,300.4 -12.7,0.99,6.360,298.9 -13.3,0.99,5.490,296.1 -13.8,0.99,5.280,303.7 -14.2,0.99,5.160,295.9 -14.7,0.99,5.330,291.1 -17.3,0.99,4.050,304.1 -19.2,0.99,4.620,301.2 -20.3,0.99,4.370,306.0 -21.0,0.99,4.870,302.8 -21.6,0.99,5.720,298.4 -23.0,0.99,6.710,294.5 -24.4,0.99,8.400,291.4 -24.5,0.99,10.150,287.1 -24.5,0.99,11.440,284.3 -24.4,0.99,12.570,282.1 -24.0,0.99,12.150,281.9 -23.8,0.99,11.520,281.2 -20.9,0.99,11.420,281.9 -18.2,0.99,12.280,278.8 -17.5,0.99,11.960,277.7 -16.7,0.99,11.430,278.1 -16.3,0.99,11.140,274.3 -15.9,0.99,10.980,274.8 -15.2,0.99,11.150,279.4 -14.7,0.99,11.780,277.6 -14.7,0.99,9.920,270.5 -14.4,0.99,9.910,277.8 -15.2,0.99,9.770,281.5 -15.0,0.99,9.540,282.3 -17.8,0.99,9.160,280.7 -18.7,0.99,9.250,281.5 -19.4,0.99,8.990,282.3 -19.9,0.99,9.340,282.2 -19.9,0.99,10.050,282.9 -20.0,0.99,10.170,281.3 -21.6,0.99,11.010,283.8 -21.8,0.99,12.430,280.9 -21.5,0.99,13.670,278.8 -21.1,0.99,13.950,278.9 -20.9,0.99,13.190,277.8 -20.4,0.99,13.400,277.7 -17.5,0.99,13.620,280.0 -15.6,0.99,12.500,275.1 -15.1,0.99,12.140,273.4 -15.0,0.99,10.960,273.4 -14.9,0.99,9.170,271.1 -15.0,0.99,7.470,275.9 -13.9,0.99,8.250,275.0 -13.4,0.99,7.670,278.2 -13.3,0.99,7.220,277.9 -13.3,0.99,7.130,276.9 -13.4,0.99,6.840,281.1 -13.3,0.99,6.780,281.7 -14.7,0.99,6.380,282.9 -15.7,0.99,5.720,286.0 -16.0,0.99,5.600,295.3 -16.3,0.99,7.000,284.6 -16.4,0.99,8.300,278.0 -16.4,0.99,11.170,274.5 -18.2,0.99,12.980,278.2 -19.2,0.99,13.490,278.5 -19.4,0.99,13.700,277.6 -19.5,0.99,13.380,278.8 -19.2,0.99,13.800,280.2 -18.8,0.99,14.060,281.1 -16.0,0.99,13.810,289.7 -14.8,0.99,12.300,291.1 -14.3,0.99,11.180,284.0 -14.1,0.99,10.620,281.5 -13.8,0.99,8.950,289.9 -13.6,1.00,8.650,285.9 -12.9,1.00,7.020,297.3 -12.0,1.00,6.950,293.0 -11.6,1.00,6.290,282.2 -11.9,1.00,5.830,290.1 -11.7,1.00,6.230,286.2 -12.2,1.00,8.680,292.5 -14.2,1.00,9.100,290.8 -15.0,1.00,9.330,290.8 -15.6,0.99,9.030,286.1 -15.8,0.99,9.340,284.5 -16.0,0.99,9.060,285.7 -16.1,0.99,10.290,284.1 -17.1,0.99,11.580,282.3 -17.3,0.99,12.550,282.1 -17.3,0.99,13.120,277.7 -17.2,0.99,13.800,276.7 -17.0,0.99,14.260,277.7 -16.6,0.99,13.980,279.7 -14.1,0.99,12.040,277.2 -13.4,0.99,11.480,277.6 -13.3,0.99,10.550,274.1 -13.1,0.99,10.130,267.4 -13.3,0.99,8.640,263.9 -13.5,0.99,7.110,272.9 -13.8,0.99,7.230,272.2 -13.9,0.99,6.500,275.5 -13.4,0.99,6.960,285.0 -13.2,0.99,6.060,278.9 -13.1,0.99,5.730,273.5 -13.0,0.99,6.630,281.7 -14.1,0.99,5.360,289.5 -14.9,0.99,5.270,293.3 -15.5,0.99,5.240,271.2 -14.8,0.99,7.950,284.0 -14.8,0.99,7.690,292.0 -14.5,0.99,9.810,285.2 -15.0,0.99,10.490,279.8 -15.8,0.99,11.390,280.4 -16.2,0.99,12.030,281.2 -16.3,0.99,13.080,282.1 -16.0,0.99,12.740,281.6 -15.8,0.99,12.030,285.2 -14.1,0.99,10.990,284.3 -13.7,0.99,10.240,278.1 -13.5,0.99,8.190,280.6 -13.5,0.99,3.490,265.7 -13.6,0.99,6.420,266.0 -13.8,0.99,4.580,265.3 -14.0,0.99,3.180,234.2 -14.3,0.99,2.780,214.2 -14.6,0.99,0.850,231.0 -14.6,0.99,2.200,188.3 -14.8,0.99,1.050,144.3 -14.9,0.99,0.180,15.3 -14.5,0.99,0.980,286.4 -15.6,0.99,0.410,62.4 -15.9,0.99,0.530,112.8 -16.2,0.99,0.900,123.2 -16.5,0.99,0.730,71.9 -16.5,0.99,1.800,284.0 -17.1,0.99,4.410,284.1 -17.4,0.99,6.390,283.6 -17.4,0.99,8.990,283.5 -17.4,0.99,9.850,279.1 -17.3,0.99,8.790,275.7 -17.1,0.99,9.330,276.0 -16.0,0.99,8.620,280.0 -14.4,0.99,9.520,280.6 -13.7,0.99,9.530,277.3 -13.2,0.99,8.220,279.3 -13.3,0.99,7.110,285.1 -13.4,0.99,5.000,292.8 -13.7,0.99,4.150,287.4 -13.6,0.99,3.610,298.6 -14.0,0.99,3.130,292.8 -13.4,0.99,4.400,296.7 -13.1,0.99,4.250,302.0 -14.0,0.99,5.080,305.3 -15.3,0.99,4.840,313.8 -16.7,0.99,5.090,304.1 -17.1,0.99,5.860,298.3 -17.2,0.99,6.190,296.4 -17.3,0.99,7.460,291.3 -17.3,0.99,8.600,288.3 -19.0,0.99,9.380,284.0 -19.4,0.99,10.290,283.4 -19.8,0.99,10.910,282.3 -19.9,0.99,11.210,282.0 -20.0,0.99,11.000,278.8 -19.8,0.99,9.780,274.8 -17.7,0.99,8.590,284.4 -16.1,0.99,5.390,269.4 -15.5,0.99,4.380,261.6 -14.5,0.99,6.620,273.4 -14.3,0.99,6.710,287.0 -14.2,0.99,6.430,286.1 -13.7,0.99,5.570,288.5 -14.0,0.99,5.080,285.1 -13.2,0.99,6.360,285.5 -13.1,0.99,5.350,286.9 -13.6,0.99,4.240,291.1 -14.1,0.99,2.920,309.9 -14.8,0.99,2.600,322.9 -16.0,0.99,2.350,319.4 -16.7,0.99,2.020,317.4 -18.0,0.99,2.680,316.9 -17.9,0.99,3.140,294.3 -18.0,0.99,6.480,289.2 -19.5,0.99,9.540,281.3 -19.2,0.99,10.420,282.0 -19.4,0.99,10.480,277.1 -19.4,0.99,11.210,275.0 -19.0,0.99,11.730,276.1 -18.6,0.99,11.060,283.5 -16.4,0.99,11.460,275.4 -14.7,0.99,11.870,273.2 -13.9,0.99,9.340,273.9 -13.7,0.99,7.840,284.0 -13.4,0.99,5.690,270.4 -14.2,0.99,4.160,243.5 -13.8,0.99,2.990,140.3 -13.9,0.99,2.830,112.5 -13.5,0.99,3.510,159.5 -13.8,0.99,2.910,165.6 -13.0,0.99,1.700,203.8 -13.6,0.99,0.360,108.4 -14.1,0.99,1.530,338.4 -15.6,0.99,1.350,317.7 -16.0,0.99,2.890,315.0 -16.9,0.99,2.630,311.5 -17.4,0.99,3.180,303.0 -17.6,0.99,4.150,303.2 -18.9,0.99,6.080,298.2 -19.6,0.99,7.440,285.5 -19.7,0.99,8.160,282.9 -19.7,0.99,9.000,283.9 -19.4,0.99,9.560,280.0 -18.9,0.99,9.530,279.5 -17.2,0.99,10.510,283.2 -14.8,0.99,11.060,276.9 -13.8,0.99,10.390,275.9 -12.9,0.99,10.590,280.2 -13.0,0.99,10.820,283.2 -13.0,0.99,10.300,279.4 -12.5,0.99,11.010,281.1 -12.5,0.99,9.570,286.2 -12.3,0.99,7.550,296.7 -12.7,0.99,4.540,307.6 -12.4,0.99,4.100,307.7 -12.1,0.99,4.060,304.1 -15.5,0.99,4.210,303.8 -16.9,0.99,6.290,293.3 -17.6,0.99,7.250,292.0 -17.9,0.99,7.950,291.7 -18.1,0.99,9.220,290.0 -18.1,0.99,10.730,288.3 -19.6,0.99,11.430,287.2 -20.2,0.99,12.420,282.2 -20.4,0.99,13.390,278.7 -20.4,0.99,13.530,277.2 -20.3,0.99,14.160,278.0 -19.8,0.99,15.310,278.8 -16.3,0.99,14.840,278.4 -14.9,0.99,13.520,276.8 -14.4,0.99,12.610,277.5 -14.2,0.99,11.420,278.4 -14.1,0.99,11.200,277.7 -13.9,0.99,10.720,279.2 -13.1,0.99,9.940,280.9 -12.7,0.99,9.410,274.8 -12.5,0.99,9.510,272.3 -12.0,0.99,9.620,277.4 -11.8,0.99,10.070,278.4 -12.0,0.99,10.370,279.3 -13.5,0.99,9.340,278.8 -14.0,0.99,8.790,280.0 -14.3,0.99,8.540,284.2 -14.4,0.99,9.420,285.4 -14.3,0.99,11.320,281.8 -14.4,0.99,12.270,278.1 -15.1,0.99,13.640,275.0 -15.2,0.99,14.480,274.2 -15.1,0.99,14.790,274.9 -14.9,0.99,14.760,277.2 -14.5,0.99,14.880,278.6 -14.4,0.99,15.570,280.7 -12.9,0.99,13.580,282.4 -12.5,0.99,15.310,280.1 -12.6,0.99,13.760,282.4 -12.8,0.99,10.540,279.5 -12.7,0.99,11.950,281.8 -12.8,0.99,11.020,285.7 -12.1,0.99,10.820,296.5 -11.7,0.99,9.830,290.2 -11.3,0.99,8.540,282.9 -11.4,0.99,8.260,280.4 -11.8,0.99,9.410,285.1 -12.5,0.99,10.130,288.4 -12.6,0.99,6.800,293.2 -13.3,0.99,6.540,290.2 -13.4,0.99,5.890,284.6 -13.7,0.99,6.430,286.2 -13.7,0.99,7.170,288.7 -13.7,0.99,8.470,283.3 -14.7,0.99,9.820,282.5 -15.1,0.99,11.080,281.8 -15.1,0.99,11.930,280.7 -15.0,0.99,13.410,280.1 -15.0,0.99,14.080,281.1 -14.6,0.99,13.010,280.8 -12.6,0.99,11.010,280.0 -12.0,0.99,12.050,281.6 -11.9,0.99,11.240,287.2 -11.7,0.99,10.890,287.7 -11.5,0.99,9.380,281.5 -11.4,0.99,8.310,283.9 -11.5,0.99,6.980,288.1 -11.4,0.99,6.360,295.0 -12.2,0.99,5.380,304.7 -11.5,0.99,3.790,308.0 -11.9,0.99,2.970,313.4 -12.6,0.99,2.560,309.0 -12.9,0.99,2.830,292.3 -13.6,0.99,3.170,290.1 -14.1,0.99,3.000,296.3 -15.1,0.99,2.780,311.6 -15.8,0.99,2.690,310.1 -16.1,0.99,4.600,298.4 -14.9,0.99,7.600,285.2 -15.3,0.99,10.730,285.9 -15.1,0.99,11.710,280.4 -15.3,0.99,11.380,275.4 -15.2,0.99,11.600,276.6 -15.0,0.99,11.630,278.2 -13.3,0.99,10.520,278.8 -12.5,0.99,10.730,278.1 -12.0,0.99,9.940,276.8 -11.6,1.00,8.740,277.9 -11.3,1.00,7.620,276.3 -11.7,1.00,7.300,256.7 -11.9,1.00,6.580,256.9 -11.8,1.00,5.740,263.8 -12.3,1.00,5.010,276.8 -12.1,1.00,4.820,282.2 -12.5,1.00,4.650,295.7 -11.8,1.00,3.550,308.4 -13.5,1.00,3.600,320.0 -14.6,1.00,3.740,317.8 -15.2,1.00,4.160,311.2 -15.7,1.00,4.320,310.7 -16.3,1.00,4.700,304.1 -16.5,1.00,5.840,296.8 -17.7,1.00,7.390,295.7 -17.9,1.00,9.090,291.1 -18.0,1.00,10.010,287.2 -18.1,1.00,10.850,286.3 -18.1,1.00,11.480,286.6 -18.1,1.00,11.360,285.3 -15.4,0.99,10.660,288.3 -13.7,0.99,9.640,288.2 -13.1,1.00,9.840,285.3 -12.6,0.99,9.120,280.3 -12.3,0.99,8.380,280.6 -12.6,1.00,6.830,283.2 -13.7,1.00,4.340,301.3 -13.6,1.00,3.380,316.7 -11.2,1.00,3.000,309.6 -9.3,1.00,3.240,299.6 -9.5,1.00,3.000,307.2 -9.9,0.99,2.620,315.5 -12.4,0.99,2.910,311.2 -14.2,0.99,3.770,316.7 -15.6,0.99,3.950,308.8 -16.6,0.99,4.660,303.7 -17.0,0.99,6.200,298.8 -17.0,0.99,8.390,294.6 -17.9,0.99,11.850,286.8 -17.9,0.99,13.150,281.6 -18.3,0.99,13.050,283.6 -18.5,0.99,13.290,284.8 -18.3,0.99,12.440,290.9 -18.1,0.99,11.020,287.7 -15.8,0.99,10.620,288.5 -14.7,0.99,9.750,294.4 -14.5,0.99,7.980,296.7 -14.9,0.99,6.680,303.2 -14.0,0.99,7.320,299.1 -14.9,0.99,8.920,320.2 -14.9,0.99,8.990,318.4 -12.6,0.99,8.750,313.4 -12.7,0.99,7.960,304.1 -13.8,0.99,7.580,303.6 -14.4,0.99,9.040,300.7 -15.1,0.99,9.030,302.5 -16.2,0.99,8.560,301.2 -17.7,0.99,8.560,304.0 -18.3,0.99,8.540,300.9 -18.9,0.99,8.210,294.9 -19.8,0.99,8.250,298.6 -20.8,0.99,8.020,303.6 -21.6,0.99,7.680,305.4 -22.1,0.99,8.060,314.0 -22.5,0.99,8.550,315.5 -22.7,0.99,8.980,311.2 -22.2,0.99,9.140,309.6 -22.0,0.99,8.810,311.9 -20.5,0.99,10.210,300.8 -20.3,0.99,9.590,303.4 -19.1,0.99,9.730,299.9 -17.9,0.99,7.200,307.8 -17.4,0.99,5.920,309.6 -16.8,0.99,7.310,305.8 -16.2,0.99,7.220,309.9 -16.7,0.99,7.770,287.1 -16.4,0.99,7.400,303.3 -16.6,0.99,6.570,298.7 -17.3,0.99,7.240,294.9 -18.1,0.99,7.720,289.6 -19.3,0.99,6.740,293.8 -20.6,0.99,6.190,293.3 -21.5,0.99,6.030,291.6 -22.3,0.99,5.660,298.6 -22.7,0.99,5.920,292.0 -23.4,0.98,6.060,291.7 -24.8,0.98,5.300,295.5 -25.7,0.98,4.960,288.9 -26.0,0.98,4.370,293.8 -25.8,0.98,4.080,292.6 -26.1,0.98,3.200,331.2 -26.2,0.98,4.400,322.4 -25.6,0.99,2.660,296.6 -24.0,0.99,4.450,289.2 -23.9,0.99,6.830,282.7 -22.8,0.99,4.660,293.5 -22.3,0.99,4.230,97.5 -18.8,0.99,10.510,155.3 -17.4,0.99,6.990,156.3 -16.9,0.99,4.560,180.8 -16.0,0.99,7.770,286.8 -15.2,0.99,5.880,259.8 -16.6,0.99,2.560,131.7 -17.4,0.99,5.260,157.0 -17.9,0.99,9.250,140.7 -16.0,0.99,4.420,293.9 -15.7,0.99,6.590,283.6 -15.9,0.99,7.760,277.8 -15.9,1.00,8.350,274.9 -15.8,1.00,8.170,273.8 -16.4,1.00,8.810,276.2 -16.7,1.00,8.930,277.7 -16.6,1.00,10.220,277.6 -16.5,1.00,9.980,278.9 -16.4,1.00,9.780,273.2 -16.3,1.00,8.940,277.3 -14.8,1.00,9.550,282.9 -13.7,1.00,8.700,278.3 -13.3,1.00,7.960,278.0 -13.3,1.00,7.800,280.4 -13.2,1.00,7.770,285.0 -13.1,1.00,7.370,281.5 -12.8,1.00,8.500,284.3 -12.6,1.00,8.940,282.6 -12.7,1.00,9.110,282.0 -12.9,1.00,9.480,276.2 -12.8,1.00,9.020,279.8 -13.1,1.00,8.780,290.0 -14.3,1.00,7.170,286.4 -15.0,1.00,6.940,289.2 -15.4,1.00,7.010,292.3 -15.6,1.00,7.780,287.3 -15.7,1.00,8.170,286.7 -15.8,1.00,8.910,281.5 -17.4,1.00,9.570,282.0 -17.7,1.00,9.870,281.3 -17.9,1.00,10.610,281.2 -17.8,0.99,11.250,281.1 -17.8,1.00,11.740,280.9 -17.5,1.00,11.850,279.7 -15.0,0.99,11.090,279.4 -14.0,1.00,11.530,277.3 -13.7,1.00,12.340,277.8 -13.2,1.00,11.790,279.3 -12.9,1.00,11.090,276.8 -12.8,1.00,10.860,277.7 -12.5,1.00,11.000,281.1 -12.5,1.00,10.810,280.3 -12.5,1.00,9.390,284.7 -12.7,1.00,9.260,287.3 -12.4,1.00,7.990,291.1 -11.9,1.00,6.070,296.4 -14.5,1.00,6.250,293.5 -15.8,1.00,5.990,290.6 -16.3,1.00,6.570,289.9 -16.5,0.99,7.800,288.2 -16.6,0.99,9.090,284.9 -16.7,0.99,10.140,283.8 -18.3,0.99,11.490,281.8 -18.7,0.99,12.630,281.5 -18.8,0.99,12.520,278.6 -18.8,0.99,12.120,275.6 -18.7,0.99,12.470,275.7 -18.6,0.99,13.480,279.2 -15.5,0.99,9.770,276.3 -14.1,0.99,11.220,273.0 -13.4,0.99,10.040,271.7 -13.1,0.99,9.560,275.0 -13.0,0.99,9.320,275.8 -12.6,0.99,8.640,279.9 -12.7,0.99,7.620,273.7 -12.7,0.99,8.440,280.9 -12.6,0.99,9.420,284.6 -13.4,0.99,8.330,284.2 -13.4,0.99,7.150,290.4 -12.8,0.99,6.510,298.5 -14.5,0.99,4.700,283.9 -15.1,0.99,4.400,295.8 -15.4,0.99,4.180,296.3 -15.8,0.99,4.220,293.2 -16.0,0.99,5.810,289.3 -16.3,0.99,7.270,281.1 -17.8,0.99,8.180,281.0 -18.0,0.99,10.020,279.4 -18.0,0.99,10.830,279.2 -17.7,0.99,11.270,278.0 -17.1,0.99,11.170,275.0 -16.7,0.99,10.890,274.4 -14.6,0.99,10.440,277.9 -13.3,0.99,10.180,276.4 -13.0,0.99,9.920,273.8 -12.9,0.99,9.430,275.8 -12.9,0.99,8.600,277.2 -12.8,0.99,7.770,273.6 -12.1,0.99,8.890,281.5 -12.2,0.99,7.810,276.7 -12.1,0.99,7.190,280.3 -12.3,0.99,5.830,283.1 -12.9,0.99,5.360,286.9 -13.2,0.99,4.520,303.9 -14.1,0.99,3.070,309.8 -14.8,0.99,3.690,305.0 -15.4,0.99,4.190,306.1 -15.6,0.99,3.870,302.1 -16.0,0.99,3.280,307.6 -16.2,0.99,3.480,306.3 -18.0,0.99,5.920,290.8 -18.0,0.99,8.680,283.0 -17.9,0.99,9.710,281.7 -17.6,0.99,10.520,277.2 -17.4,0.99,10.790,277.1 -16.9,0.99,10.630,276.2 -14.8,0.99,10.090,282.6 -13.7,0.99,10.440,276.5 -13.2,0.99,10.230,272.8 -13.0,0.99,9.340,273.3 -13.0,0.99,8.290,279.9 -13.0,0.99,8.200,269.4 -12.9,0.99,8.110,276.7 -13.2,0.99,6.830,275.6 -13.0,0.99,5.810,280.2 -12.8,0.99,5.110,285.1 -13.5,0.99,3.380,292.6 -13.1,0.99,2.920,320.2 -14.0,0.99,2.240,316.9 -14.7,0.99,3.320,301.3 -15.4,0.99,3.840,307.8 -15.6,0.99,4.450,302.2 -15.8,0.99,4.260,300.7 -16.4,0.99,4.260,303.7 -17.8,0.99,4.230,305.4 -18.5,0.99,6.060,290.6 -18.7,0.99,8.230,286.7 -18.7,0.99,9.060,279.8 -18.5,0.99,9.300,279.4 -18.4,0.99,9.050,278.4 -16.8,0.99,8.890,280.4 -15.0,0.99,10.250,282.8 -13.9,0.99,10.100,278.1 -13.3,0.99,10.580,279.5 -12.8,0.99,9.720,282.2 -12.0,0.99,8.220,288.0 -10.7,0.99,8.830,288.7 -10.5,0.99,8.400,287.9 -11.6,0.99,5.990,290.8 -11.7,0.99,4.270,303.1 -11.8,0.99,4.160,306.3 -11.7,0.99,4.330,303.2 -14.7,0.99,4.220,304.7 -16.9,0.99,4.080,308.0 -17.8,0.99,5.460,300.3 -18.4,0.99,6.280,296.2 -18.7,0.99,7.620,293.7 -18.8,0.99,9.060,290.1 -20.4,0.99,10.690,286.8 -20.9,0.99,12.110,283.9 -21.3,0.99,12.790,281.3 -21.5,0.99,12.970,281.4 -21.7,0.99,13.290,280.6 -21.4,0.99,13.180,281.6 -17.2,0.99,12.280,287.0 -15.5,0.99,11.180,279.6 -14.6,0.99,10.250,276.3 -13.7,0.99,9.340,280.3 -13.4,0.99,8.440,275.6 -13.1,0.99,7.370,279.9 -12.2,0.99,7.120,287.3 -11.5,0.99,7.150,291.5 -10.6,0.99,6.030,293.8 -10.9,0.99,5.400,304.1 -10.9,0.99,6.750,293.2 -11.7,0.99,5.090,300.2 -14.0,0.99,4.360,312.7 -16.1,0.99,4.150,308.2 -17.1,0.99,4.250,299.6 -17.8,0.99,4.570,290.7 -18.4,0.99,5.720,296.1 -18.4,0.99,8.140,292.8 -20.1,0.99,9.840,284.2 -20.6,0.99,10.700,283.5 -20.8,0.99,11.740,280.2 -20.8,0.99,11.670,279.7 -20.8,0.99,12.590,281.2 -20.6,0.99,12.950,281.1 -16.8,0.99,12.240,284.8 -14.9,0.99,12.010,283.9 -14.3,0.99,11.780,278.1 -13.3,0.99,10.340,278.0 -13.2,0.99,10.530,276.5 -13.0,0.99,8.220,280.4 -12.8,0.99,7.370,290.3 -12.8,0.99,5.680,296.2 -12.5,0.99,4.850,295.0 -11.8,0.99,4.930,292.9 -12.1,0.99,4.440,292.4 -11.5,0.99,4.870,305.8 -13.8,0.99,4.130,302.9 -16.1,0.99,3.770,306.9 -17.1,0.99,4.120,295.5 -17.7,0.99,5.360,301.3 -17.7,0.99,7.150,283.6 -17.9,0.99,9.380,281.1 -19.4,0.99,10.730,280.3 -19.8,0.99,11.460,280.1 -20.0,0.99,12.050,282.2 -20.0,0.99,12.630,282.2 -19.9,0.99,12.400,283.2 -19.9,0.99,12.080,283.9 -17.0,0.99,10.600,285.4 -15.1,0.99,9.660,281.3 -14.2,0.99,8.180,280.7 -14.3,0.99,6.650,286.4 -15.0,0.99,5.290,288.5 -14.9,0.99,3.310,302.4 -14.7,1.00,1.570,304.6 -14.3,1.00,1.720,321.4 -13.8,1.00,2.360,310.7 -13.1,1.00,3.260,305.3 -11.6,1.00,3.790,311.5 -11.6,1.00,4.110,300.8 -14.1,1.00,3.450,319.1 -15.7,1.00,3.980,321.3 -16.9,1.00,3.210,317.1 -17.7,1.00,4.430,312.2 -17.4,0.99,6.530,295.6 -17.7,0.99,10.550,283.8 -18.6,0.99,12.330,280.7 -19.3,0.99,12.710,284.7 -19.4,0.99,13.440,285.4 -19.1,0.99,12.440,285.5 -18.8,0.99,12.620,287.1 -18.4,0.99,11.700,283.8 -15.7,0.99,9.500,298.7 -14.3,1.00,8.440,287.6 -13.8,1.00,6.710,282.2 -14.2,1.00,4.860,281.9 -14.5,1.00,3.750,279.7 -14.8,1.00,2.630,283.2 -14.8,1.00,2.060,272.6 -14.3,1.00,1.720,273.2 -13.7,1.00,1.800,293.8 -13.9,1.00,0.420,268.6 -14.4,1.00,1.070,106.6 -14.6,1.00,0.990,69.1 -14.8,0.99,1.160,13.8 -14.6,0.99,1.620,332.5 -15.4,0.99,2.160,311.4 -15.8,0.99,3.080,318.6 -16.1,0.99,3.130,301.3 -16.0,0.99,4.250,297.0 -17.7,0.99,6.420,287.7 -18.1,0.99,7.950,283.1 -18.1,0.99,8.200,285.0 -18.1,0.99,8.690,286.5 -17.7,0.99,8.850,279.8 -17.5,0.99,8.010,277.7 -16.8,0.99,7.110,275.9 -14.6,0.99,9.510,281.4 -13.9,0.99,9.170,277.1 -13.6,0.99,7.220,273.5 -14.7,0.99,7.000,267.6 -14.5,0.99,4.010,255.9 -12.8,0.99,4.730,173.4 -13.4,0.99,5.930,155.8 -13.0,0.99,5.960,151.3 -13.4,0.99,6.610,164.6 -13.8,0.99,6.480,165.8 -14.5,0.99,7.320,159.0 -15.4,0.99,6.610,155.4 -16.4,0.99,6.450,148.7 -17.0,0.99,6.550,146.9 -17.5,0.99,5.940,155.1 -17.0,0.99,2.090,270.5 -16.4,0.99,3.140,291.0 -16.5,0.99,4.550,282.8 -17.2,0.99,6.440,282.9 -17.4,0.99,6.550,289.7 -17.2,0.99,6.980,287.0 -16.9,0.99,8.620,273.8 -16.7,0.99,8.590,272.7 -15.9,0.99,8.710,280.1 -14.2,0.99,9.330,277.1 -13.1,0.99,8.070,270.3 -13.1,0.99,5.960,265.9 -13.1,0.99,2.580,262.0 -12.6,0.99,2.230,265.2 -12.7,0.99,1.810,234.3 -12.3,0.99,1.590,227.7 -12.0,0.99,1.280,228.4 -10.9,0.99,0.330,206.3 -11.5,0.99,0.900,197.7 -11.6,0.99,2.590,142.1 -14.3,0.99,3.140,145.1 -15.2,0.99,0.960,141.3 -15.0,0.99,1.100,307.8 -15.3,0.99,2.920,298.0 -15.4,0.99,3.920,290.9 -15.8,0.99,4.520,294.7 -17.3,0.99,5.440,285.7 -17.9,0.99,6.650,278.6 -18.1,0.99,6.900,277.6 -18.1,0.99,8.390,277.0 -17.6,0.99,8.230,270.8 -17.3,0.99,7.540,273.2 -16.5,0.99,7.800,272.6 -14.4,0.99,10.050,278.3 -13.8,0.99,9.680,276.9 -13.3,0.99,8.900,277.0 -13.2,0.99,8.410,268.5 -13.4,0.99,6.040,265.1 -13.5,0.99,4.110,255.8 -12.9,0.99,2.030,220.8 -12.8,0.99,0.640,81.0 -12.7,0.99,1.640,263.3 -12.4,0.99,2.600,288.3 -12.7,0.99,2.650,300.8 -13.9,0.99,2.340,310.3 -14.6,0.99,2.630,313.6 -15.2,0.99,3.330,307.8 -15.9,0.99,3.890,299.8 -16.3,0.99,4.650,294.5 -16.5,0.99,5.060,293.1 -18.1,0.99,5.720,293.7 -18.6,0.99,6.630,293.3 -18.8,0.99,8.110,278.7 -18.6,0.99,8.420,278.1 -18.3,0.99,7.980,283.2 -18.2,0.99,8.450,282.3 -16.1,0.99,8.800,278.7 -14.1,0.99,9.820,279.3 -13.3,0.99,8.730,275.4 -12.8,0.99,7.770,282.0 -12.5,0.99,6.940,288.1 -12.5,0.99,7.050,274.7 -11.8,0.99,7.320,279.9 -11.6,0.99,6.260,276.5 -11.7,0.99,4.730,277.4 -11.9,0.99,4.360,285.1 -11.8,0.99,5.120,280.6 -11.8,0.99,5.140,285.8 -13.5,0.99,4.390,290.8 -14.4,0.99,3.970,291.1 -15.2,0.99,4.390,291.9 -15.5,0.99,5.850,291.9 -15.8,0.99,3.140,301.8 -16.2,0.99,7.670,269.2 -18.3,0.99,5.020,298.3 -18.6,0.99,7.440,294.1 -18.2,0.99,9.710,284.5 -17.8,0.99,10.220,277.6 -17.2,0.99,10.190,274.7 -17.5,0.99,10.370,270.7 -15.2,0.99,10.360,275.9 -13.7,0.99,11.530,280.3 -13.2,0.99,10.830,276.3 -13.1,0.99,11.000,267.6 -12.9,0.99,8.810,270.6 -12.9,0.99,6.750,269.8 -12.6,0.99,6.850,265.8 -13.0,0.99,4.970,263.1 -13.6,0.99,1.030,220.7 -13.7,0.99,3.150,143.0 -12.1,0.99,3.180,283.9 -12.4,0.99,2.630,268.6 -13.2,0.99,2.620,308.2 -13.4,0.99,5.360,291.9 -14.1,0.99,5.310,290.8 -14.2,0.99,3.470,269.2 -14.8,0.99,2.260,141.8 -14.5,0.99,2.610,316.1 -15.2,0.99,0.300,242.6 -16.1,0.99,5.080,174.5 -16.1,0.99,3.890,202.6 -15.8,0.99,3.640,252.2 -16.1,0.99,2.770,264.6 -16.0,0.99,5.170,263.2 -14.6,0.99,6.340,257.0 -14.1,0.99,4.410,272.5 -13.8,0.99,1.770,302.7 -13.5,0.99,2.480,265.5 -13.5,0.99,0.500,204.9 -13.3,0.99,1.750,274.5 -13.1,0.99,3.150,249.2 -13.1,0.99,1.480,237.2 -13.1,0.99,1.620,241.7 -13.0,0.99,2.550,273.4 -12.7,0.99,0.750,332.3 -12.7,0.99,0.450,28.0 -12.9,0.99,0.480,17.7 -13.1,0.99,0.780,89.3 -13.3,0.99,1.000,130.4 -13.6,0.99,1.280,97.9 -13.9,0.99,1.470,125.6 -14.5,0.99,2.020,149.6 -14.9,0.99,1.950,161.4 -15.3,0.99,1.610,157.3 -15.7,0.99,0.040,11.3 -15.8,0.99,1.970,317.2 -15.8,0.99,2.320,307.6 -15.7,0.99,4.860,289.1 -15.0,0.99,6.180,281.5 -14.1,0.99,6.040,286.7 -13.3,0.99,6.400,288.5 -13.0,0.99,6.530,283.6 -13.0,0.99,6.820,280.5 -12.9,0.99,6.220,282.1 -12.9,0.99,6.320,285.7 -12.8,0.99,6.610,286.8 -12.8,1.00,7.530,281.3 -12.7,1.00,7.350,281.7 -13.0,1.00,7.670,283.9 -13.0,1.00,8.540,280.1 -13.7,0.99,7.730,279.6 -14.3,0.99,7.730,286.3 -14.8,0.99,7.700,291.4 -15.3,0.99,7.540,293.5 -15.6,0.99,7.390,292.3 -15.9,0.99,7.380,291.8 -17.4,0.99,7.770,291.0 -17.9,0.99,7.660,289.1 -18.1,0.99,8.360,289.0 -18.0,0.99,8.960,285.2 -17.9,0.99,9.190,287.8 -17.8,0.99,9.520,287.8 -15.5,0.99,9.920,283.7 -14.0,0.99,9.780,282.5 -13.1,0.99,10.400,278.3 -12.8,0.99,9.970,280.3 -12.7,0.99,9.940,281.0 -12.7,0.99,10.080,285.0 -12.8,0.99,10.140,279.0 -13.9,0.99,8.640,291.7 -12.5,0.99,6.860,307.3 -12.1,0.99,4.410,323.4 -11.9,0.99,3.790,317.9 -12.0,0.99,3.550,309.8 -13.9,0.99,3.730,313.3 -15.9,0.99,3.490,311.4 -17.2,0.99,4.070,304.4 -17.8,0.99,3.910,308.2 -18.2,0.99,3.920,302.5 -18.6,0.99,4.860,294.2 -20.6,0.99,5.320,301.2 -20.8,0.99,7.390,294.9 -20.7,0.99,9.100,288.7 -20.7,0.99,9.960,288.8 -20.7,0.99,10.540,286.2 -20.6,0.99,10.480,285.8 -17.9,0.99,10.380,286.0 -16.1,0.99,10.780,282.3 -15.1,0.99,10.070,277.3 -14.8,0.99,9.650,277.5 -14.6,0.99,9.250,280.6 -14.1,0.99,7.630,283.0 -14.2,0.99,7.390,285.5 -11.5,0.99,6.850,284.6 -11.2,0.99,6.380,291.9 -11.6,0.99,5.990,298.0 -11.7,0.99,6.130,297.5 -12.1,0.99,5.400,300.4 -14.2,0.99,4.360,307.6 -16.3,0.99,4.350,308.9 -17.3,0.99,4.580,302.7 -18.0,0.99,5.320,296.9 -18.2,0.99,6.750,295.2 -18.3,0.99,7.380,291.6 -19.7,0.99,8.590,286.1 -20.0,0.99,10.260,281.5 -20.1,0.99,11.590,281.5 -19.7,0.99,11.870,276.9 -19.2,0.99,10.950,273.8 -19.2,0.99,11.120,274.1 -16.5,0.99,10.870,273.2 -14.0,0.99,11.530,272.4 -13.0,0.99,10.370,276.5 -12.0,0.99,9.880,272.7 -11.7,0.99,9.250,275.9 -11.7,0.99,8.110,275.4 -11.5,0.99,7.710,281.2 -11.3,0.99,7.080,274.8 -11.4,0.99,7.680,269.8 -12.5,0.99,7.060,271.9 -12.9,0.99,7.140,280.5 -13.1,0.99,6.870,284.9 -15.5,0.99,4.820,293.0 -16.8,0.99,4.250,298.4 -17.6,0.99,4.230,302.1 -18.2,0.99,4.590,295.3 -18.5,0.99,5.480,293.1 -18.9,0.99,6.010,296.5 -20.6,0.99,6.670,290.4 -20.9,0.99,7.570,287.2 -20.8,0.99,8.580,285.9 -20.8,0.99,8.970,285.3 -20.2,0.99,9.440,277.7 -20.1,0.99,9.650,277.0 -18.6,0.99,11.560,279.5 -16.0,0.99,11.420,275.3 -14.8,0.99,10.480,274.1 -14.5,0.99,10.760,267.8 -14.3,0.99,10.200,272.4 -13.3,0.99,7.850,279.0 -12.8,0.99,8.910,280.9 -12.8,0.99,8.100,280.0 -13.0,0.99,7.380,282.0 -13.2,0.99,5.500,284.3 -13.5,0.99,4.530,290.7 -13.5,0.99,4.740,290.6 -15.2,0.99,3.910,304.3 -16.1,0.99,3.450,311.8 -17.1,0.99,3.680,305.1 -17.7,0.99,4.390,295.3 -18.0,0.99,5.910,289.1 -18.2,0.99,5.900,294.5 -19.9,0.99,7.480,291.0 -20.6,0.99,10.020,285.7 -20.9,0.99,9.640,277.7 -21.1,0.99,10.730,271.4 -20.7,0.99,8.360,262.2 -20.6,0.99,8.870,266.4 -19.0,0.99,7.380,259.8 -16.4,0.99,8.920,270.6 -15.6,0.99,9.710,264.0 -15.5,0.99,10.800,272.6 -14.8,0.99,10.930,276.0 -14.9,0.99,9.900,271.1 -14.4,0.99,9.590,272.8 -14.5,0.99,8.880,279.9 -14.4,0.99,8.720,277.7 -14.3,0.99,7.350,276.1 -14.2,0.99,6.880,285.2 -13.7,0.99,7.230,279.0 -15.3,0.99,6.750,286.4 -15.9,0.99,7.220,284.4 -16.2,0.99,7.640,286.4 -16.6,0.99,9.070,288.3 -16.6,0.99,9.450,281.9 -16.8,0.99,9.920,282.6 -18.6,0.99,10.800,283.2 -19.2,0.99,11.040,285.3 -19.5,0.99,11.440,282.0 -19.6,0.99,10.900,276.3 -20.0,0.99,11.640,279.1 -20.2,0.99,12.240,281.0 -16.8,0.99,11.230,282.0 -15.1,0.99,9.560,282.2 -13.9,0.99,10.040,276.0 -13.4,0.99,8.650,284.6 -12.9,0.99,8.590,287.6 -13.1,0.99,7.800,287.4 -13.0,0.99,6.440,285.6 -12.5,0.99,7.580,285.3 -12.5,0.99,5.730,286.3 -12.7,0.99,4.040,289.6 -13.1,0.99,4.800,291.2 -13.3,0.99,3.390,286.0 -14.4,0.99,3.220,294.7 -15.6,0.99,3.810,297.5 -18.3,0.99,0.970,255.5 -20.8,0.99,0.810,337.5 -19.0,0.99,6.750,304.4 -19.1,0.99,4.470,307.2 -20.4,0.99,8.620,290.4 -21.6,0.99,8.540,288.9 -21.8,0.98,10.500,289.4 -21.4,0.99,10.640,278.5 -21.0,0.99,3.460,142.7 -21.2,0.99,1.380,352.7 -21.3,0.99,4.910,330.1 -17.1,0.99,7.470,264.4 -17.2,0.99,6.820,267.6 -17.4,0.99,4.920,306.1 -15.5,0.99,3.690,298.3 -14.8,0.99,7.310,278.7 -15.3,0.99,5.690,253.1 -15.7,0.99,3.180,232.1 -15.0,0.99,1.330,46.5 -14.7,0.99,1.830,304.9 -14.4,0.99,3.600,293.6 -14.4,0.99,4.250,285.4 -15.4,0.99,4.010,289.1 -15.8,0.99,3.120,307.6 -16.2,0.99,3.370,311.1 -16.5,0.99,3.740,297.4 -16.1,0.99,7.010,294.4 -16.3,0.99,8.000,282.5 -18.2,0.99,7.930,289.6 -18.8,0.99,8.200,284.5 -19.0,0.99,8.230,285.8 -19.0,0.99,9.440,278.0 -18.4,0.99,10.570,279.5 -18.3,0.99,11.490,273.0 -16.1,0.99,9.240,277.4 -14.8,0.99,9.680,278.8 -14.3,0.99,8.810,270.6 -14.2,0.99,7.340,269.8 -14.2,0.99,6.700,280.4 -14.1,0.99,5.380,266.7 -13.7,0.99,6.410,266.3 -13.5,0.99,6.770,278.8 -13.7,0.99,5.240,281.3 -13.0,0.99,5.050,279.1 -13.0,0.99,5.200,283.4 -13.1,0.99,4.740,280.9 -13.8,0.99,3.700,285.9 -14.2,0.99,3.870,281.6 -14.7,0.99,2.250,268.8 -15.1,0.99,1.770,259.7 -16.0,0.99,2.510,274.7 -15.5,0.99,4.230,283.9 -16.9,0.99,6.360,282.2 -16.9,0.99,6.570,289.7 -16.8,0.99,7.510,282.9 -16.7,0.99,7.800,281.1 -16.6,0.99,7.760,280.7 -16.5,0.99,8.150,281.2 -15.0,0.99,8.260,277.5 -14.0,0.99,8.690,278.7 -13.5,0.99,7.040,282.2 -13.4,0.99,7.390,275.4 -13.3,0.99,7.010,281.2 -13.3,0.99,7.580,275.8 -13.2,0.99,7.450,274.7 -13.4,0.99,7.520,278.1 -13.3,0.99,7.460,276.7 -13.0,0.99,6.930,274.6 -13.5,0.99,6.020,274.9 -14.1,0.99,5.580,269.9 -14.5,0.99,4.000,276.5 -15.1,0.99,3.890,291.3 -15.6,0.99,4.190,294.6 -16.1,0.99,4.810,296.9 -15.9,0.99,6.560,294.5 -15.8,0.99,7.910,288.5 -16.8,0.99,9.080,286.0 -16.9,0.99,10.460,283.6 -16.9,0.99,10.910,282.4 -16.6,0.99,11.230,277.4 -16.4,0.99,10.450,279.8 -16.5,0.99,9.560,280.6 -14.7,0.99,9.060,276.9 -13.8,0.99,10.070,274.3 -13.2,0.99,7.990,278.4 -13.2,0.99,7.540,272.3 -13.5,0.99,7.620,278.0 -13.4,0.99,6.460,275.6 -13.7,0.99,6.220,270.9 -13.8,0.99,5.250,267.5 -13.8,0.99,2.980,253.1 -13.3,0.99,1.370,269.0 -13.7,0.99,1.780,291.7 -14.3,0.99,2.110,321.7 -14.6,0.99,2.830,325.9 -15.8,0.99,2.830,324.9 -16.1,0.99,3.280,323.8 -16.7,0.99,3.500,320.2 -17.1,0.99,3.570,308.6 -17.4,0.99,3.430,313.8 -18.7,0.99,2.940,301.8 -19.1,0.99,3.440,298.2 -19.1,0.99,4.370,297.7 -19.2,0.99,5.110,294.3 -19.2,0.99,5.370,285.6 -18.9,0.99,5.160,296.8 -17.9,0.99,5.340,291.7 -16.8,0.99,4.910,297.4 -16.5,0.99,3.660,294.7 -15.8,0.99,2.870,295.6 -14.6,0.99,1.560,321.9 -14.7,0.99,1.240,279.8 -14.8,0.99,0.440,285.2 -14.9,0.99,3.270,103.0 -14.1,0.99,3.390,139.0 -14.7,0.99,3.930,146.0 -15.1,0.99,4.260,158.8 -15.7,0.99,1.310,210.4 -17.0,0.99,2.160,293.6 -18.1,0.99,0.480,9.4 -19.3,0.99,1.180,179.5 -19.3,0.99,2.720,305.3 -18.9,0.99,4.220,302.9 -18.7,0.99,2.360,324.4 -19.6,0.99,2.130,278.2 -20.5,0.99,7.880,171.2 -20.5,0.99,10.380,176.6 -19.5,0.99,6.400,184.0 -18.8,0.99,4.450,207.5 -19.8,0.99,6.110,162.5 -18.6,0.99,8.940,156.1 -17.5,0.99,8.190,164.8 -17.5,0.99,6.360,159.6 -16.9,0.99,4.850,180.7 -16.6,0.99,3.390,293.3 -16.2,0.99,5.520,280.1 -16.3,0.99,5.450,283.7 -16.0,0.99,5.190,282.9 -16.1,0.99,4.190,285.2 -16.0,0.99,4.070,285.1 -15.8,0.99,4.900,291.0 -15.7,0.99,5.010,284.5 -15.9,0.99,4.180,283.0 -16.0,0.99,3.160,276.8 -16.2,0.99,2.670,293.1 -16.5,0.99,3.230,301.4 -16.9,0.99,4.390,299.4 -17.0,0.99,3.680,306.0 -17.2,0.99,3.540,297.2 -16.9,0.99,4.550,289.9 -16.6,0.99,6.300,295.9 -16.5,0.99,6.640,285.8 -16.3,0.99,6.760,280.9 -16.0,0.99,6.020,281.7 -15.5,0.99,6.370,284.9 -15.6,0.99,6.320,286.6 -15.9,0.99,5.700,289.1 -16.0,0.99,4.250,290.9 -15.0,0.99,2.080,288.0 -15.0,0.99,0.920,275.4 -13.9,0.99,1.190,119.0 -12.2,0.99,3.190,126.7 -12.6,0.99,3.000,125.6 -13.3,0.99,2.560,144.7 -13.4,0.99,3.130,141.7 -13.3,0.99,2.530,145.8 -16.0,0.99,2.000,139.3 -17.1,0.99,1.710,306.3 -17.8,0.99,2.350,304.7 -19.2,0.99,1.650,309.5 -19.9,0.99,1.170,341.9 -20.3,0.99,1.420,314.3 -20.2,0.99,4.030,295.6 -20.0,0.99,5.800,297.2 -19.9,0.99,7.300,289.3 -20.0,0.99,7.880,288.3 -20.0,0.99,7.340,294.0 -19.9,0.99,6.500,292.2 -18.6,0.99,5.520,284.8 -18.2,0.99,5.010,297.5 -17.9,0.99,3.380,297.0 -17.8,0.99,2.670,295.2 -17.3,0.99,0.830,137.4 -16.1,0.99,2.450,135.2 -16.2,0.99,1.530,127.8 -16.0,0.99,1.320,306.3 -16.0,0.99,5.440,286.2 -15.9,0.99,2.140,252.0 -15.7,0.99,2.260,260.8 -16.0,0.99,1.440,264.9 -16.2,0.99,1.650,301.2 -16.3,0.99,2.080,292.1 -16.7,0.99,3.570,289.6 -16.5,0.99,4.270,283.5 -16.3,0.99,6.890,281.5 -16.2,0.99,8.520,281.9 -16.8,0.99,8.390,281.6 -17.2,0.99,9.020,281.0 -17.1,0.99,9.160,283.2 -17.0,0.99,9.190,284.5 -16.8,0.99,8.890,287.6 -16.6,0.99,8.390,286.7 -15.8,0.99,8.720,285.7 -15.0,0.99,7.630,279.9 -14.5,0.99,7.730,275.0 -14.3,0.99,6.470,276.6 -14.3,0.99,5.810,276.8 -14.3,0.99,5.170,283.7 -14.6,0.99,3.030,284.4 -14.4,0.99,2.440,286.9 -14.2,0.99,2.200,306.9 -13.8,0.99,0.690,212.9 -14.3,0.99,2.120,151.6 -14.5,0.99,3.140,128.0 -15.3,0.99,3.580,145.7 -16.2,0.99,4.050,138.3 -17.4,0.99,3.660,147.1 -18.3,0.99,2.450,129.6 -18.9,0.99,1.300,8.3 -16.5,0.99,5.350,297.2 -17.0,0.99,6.850,304.0 -16.4,0.99,9.200,289.6 -16.2,0.99,9.690,284.1 -16.3,0.99,9.700,282.6 -16.1,0.99,9.070,283.3 -15.9,0.99,8.900,284.6 -14.8,0.99,7.640,289.0 -14.3,0.99,7.200,285.4 -14.4,0.99,7.120,280.4 -14.8,0.99,6.000,278.3 -14.5,0.99,6.110,279.5 -14.4,0.99,4.680,277.2 -14.6,0.99,3.280,281.2 -14.3,0.99,3.900,272.2 -15.1,0.99,2.910,259.5 -14.6,0.99,1.610,162.8 -15.2,0.99,3.110,154.0 -14.0,0.99,0.430,112.7 -14.4,0.99,1.660,318.7 -15.1,0.99,3.420,307.6 -15.7,0.99,4.140,310.3 -16.9,0.99,6.160,293.4 -16.4,0.99,7.890,283.5 -16.7,0.99,11.850,275.0 -17.3,0.99,11.600,277.9 -17.1,0.99,12.690,278.6 -17.4,0.99,14.050,282.4 -17.4,0.99,14.230,282.3 -17.0,0.99,14.230,284.5 -16.7,0.99,13.290,282.7 -14.8,0.99,12.160,286.4 -13.8,1.00,10.860,290.6 -13.2,1.00,10.300,284.6 -12.6,1.00,9.970,294.0 -12.7,1.00,9.430,290.1 -12.7,1.00,7.720,286.6 -12.7,1.00,7.450,292.2 -12.7,1.00,6.630,289.1 -12.3,1.00,5.660,297.5 -11.8,1.00,4.720,305.1 -11.8,1.00,3.850,316.2 -13.3,1.00,4.150,314.4 -13.5,1.00,3.750,316.9 -14.3,1.00,3.680,319.4 -15.5,1.00,3.690,311.3 -16.1,1.00,4.310,310.6 -16.4,1.00,4.760,307.5 -16.9,0.99,4.330,311.3 -18.1,0.99,5.340,305.7 -17.9,0.99,6.910,297.8 -18.1,0.99,8.290,289.8 -18.0,0.99,8.750,291.6 -18.1,0.99,9.250,293.7 -18.1,0.99,8.810,297.3 -17.2,0.99,9.820,296.9 -16.4,0.99,8.740,298.1 -16.6,0.99,7.990,304.2 -16.2,0.99,7.110,319.1 -16.3,0.99,7.600,320.2 -16.5,0.99,8.960,314.7 -15.4,0.99,10.380,321.7 -14.1,0.99,8.840,319.2 -13.7,0.99,7.310,322.4 -13.5,0.99,7.850,329.1 -14.0,0.99,7.220,317.0 -14.5,0.99,6.780,319.5 -16.0,0.99,5.690,310.8 -17.2,0.99,5.500,308.7 -18.0,0.99,6.190,297.8 -18.8,0.99,6.230,297.0 -19.9,0.99,5.900,300.8 -20.9,0.99,6.290,307.2 -21.6,0.99,6.520,304.9 -21.8,0.99,6.770,304.4 -21.9,0.99,7.380,308.8 -21.8,0.99,8.000,300.4 -21.6,0.99,8.680,296.3 -21.6,0.99,8.340,296.6 -20.2,0.99,8.450,298.0 -19.5,0.99,8.230,295.4 -19.6,0.99,7.830,296.9 -18.8,0.99,7.450,302.6 -16.4,0.99,5.920,303.0 -16.7,0.99,5.580,322.2 -16.8,0.99,6.110,331.0 -16.9,0.99,6.530,332.3 -16.5,0.99,6.940,330.8 -16.4,0.99,6.630,330.6 -15.9,0.99,6.610,328.8 -16.0,0.99,6.090,325.5 -16.8,0.99,5.360,315.9 -18.6,0.99,5.820,313.4 -19.4,0.99,5.090,305.1 -20.1,0.99,4.910,301.9 -20.4,0.98,4.560,300.2 -20.5,0.98,4.980,296.1 -21.8,0.98,6.250,286.9 -22.1,0.98,7.020,277.5 -22.1,0.98,8.020,282.9 -21.8,0.98,8.770,280.8 -21.0,0.98,9.040,278.6 -20.8,0.98,8.800,275.6 -19.4,0.98,10.670,280.6 -16.8,0.98,11.050,278.1 -15.2,0.98,10.080,273.6 -14.8,0.98,8.220,260.4 -15.1,0.98,1.940,305.2 -14.3,0.99,6.360,287.5 -14.0,0.99,5.450,279.9 -14.1,0.99,5.850,265.3 -14.0,0.99,4.760,262.3 -14.0,0.99,5.650,272.4 -14.8,0.99,3.410,260.4 -15.0,0.99,1.720,260.9 -14.9,0.99,2.330,302.9 -15.5,0.99,3.090,294.4 -16.1,0.99,3.210,299.0 -16.5,0.99,3.230,316.9 -16.6,0.99,3.830,315.1 -17.7,0.99,3.990,331.0 -18.1,0.99,3.260,331.8 -17.9,0.99,3.000,338.4 -17.4,0.98,2.460,337.2 -17.3,0.99,2.010,300.0 -17.6,0.99,3.500,294.6 -17.6,0.99,4.960,302.9 -16.7,0.99,5.810,284.5 -16.1,0.99,5.880,293.9 -15.5,0.99,3.690,314.4 -15.2,0.99,0.770,298.8 -15.4,0.99,0.860,41.6 -15.3,0.99,1.910,136.4 -15.2,0.99,2.360,127.9 -15.1,0.99,2.650,122.7 -14.9,0.99,2.340,113.7 -14.8,0.99,2.000,107.3 -15.1,0.99,2.630,117.0 -15.5,0.99,2.320,124.3 -15.8,0.99,0.050,165.9 -15.9,0.99,1.180,319.9 -16.2,0.99,1.360,328.8 -16.8,0.99,2.000,293.1 -17.8,0.99,4.140,287.9 -17.9,0.99,6.260,291.4 -16.7,0.99,6.140,296.2 -16.1,0.99,6.710,299.1 -16.4,0.99,10.190,289.4 -15.9,0.99,9.360,280.5 -15.5,0.99,9.920,278.0 -15.4,0.99,9.140,276.5 -14.5,0.99,9.210,280.8 -14.0,0.99,8.880,282.6 -13.3,0.99,9.300,284.1 -12.0,0.99,7.320,284.4 -11.6,0.99,7.860,288.6 -12.1,0.99,7.860,283.6 -11.9,0.99,6.310,285.0 -11.7,0.99,3.740,292.6 -10.2,0.99,3.250,302.6 -10.1,0.99,1.560,307.5 -10.2,0.99,0.910,325.8 -10.4,0.99,0.830,347.0 -12.0,0.99,1.830,320.4 -14.2,0.99,2.190,320.7 -15.3,0.99,2.980,320.8 -15.8,0.99,3.020,314.9 -16.2,0.99,3.080,309.3 -16.6,0.99,3.240,301.0 -17.9,0.99,2.160,307.2 -18.4,0.99,2.890,294.4 -17.7,0.99,5.740,284.8 -17.7,0.99,6.250,286.9 -17.5,0.99,6.170,283.5 -17.1,0.99,5.150,284.5 -16.3,0.99,5.180,278.8 -16.1,0.99,4.720,283.4 -15.8,0.99,5.370,280.7 -16.0,0.99,3.640,281.2 -15.1,0.99,3.030,245.8 -13.7,0.99,3.410,134.6 -13.0,0.99,4.090,144.9 -12.5,0.99,5.800,151.0 -14.2,0.99,11.190,163.8 -14.7,0.99,10.410,156.5 -16.0,0.99,11.200,153.0 -16.9,0.99,13.090,153.1 -17.8,0.99,11.210,153.9 -19.1,0.99,11.780,165.7 -19.6,0.99,11.280,158.5 -20.1,0.99,10.060,174.8 -20.8,0.98,8.370,174.2 -18.9,0.99,11.240,177.9 -19.1,0.99,10.680,189.2 -17.8,0.99,14.280,185.2 -17.2,0.99,14.060,174.4 -17.0,0.99,15.430,170.1 -17.1,0.99,14.670,169.0 -17.2,0.99,13.940,164.1 -16.6,0.99,12.970,161.7 -16.4,0.99,8.370,174.5 -15.8,0.99,3.920,149.8 -15.8,0.99,5.330,160.5 -15.8,0.99,6.550,162.2 -15.6,0.99,8.210,157.3 -15.3,0.99,7.980,155.1 -15.2,0.99,7.180,152.7 -15.1,0.99,7.570,149.9 -15.5,0.99,7.370,158.8 -16.0,0.99,7.860,152.2 -16.8,0.99,7.310,156.6 -17.8,0.99,7.010,157.2 -16.9,0.99,2.660,187.4 -16.3,0.99,2.570,306.5 -16.2,0.99,3.210,296.8 -16.4,0.99,3.570,292.5 -15.6,0.98,3.200,300.6 -16.3,0.98,4.410,284.9 -16.5,0.98,5.370,291.1 -16.3,0.98,6.520,293.1 -16.1,0.98,6.460,286.0 -16.0,0.98,4.690,288.9 -15.9,0.98,4.670,282.2 -15.7,0.98,2.310,266.4 -15.4,0.98,2.830,290.2 -15.3,0.98,0.110,0.8 -15.3,0.98,0.360,180.1 -15.2,0.98,0.990,115.1 -15.3,0.98,1.430,161.3 -15.6,0.99,2.710,180.2 -15.4,0.99,1.680,191.2 -15.4,0.99,0.420,230.9 -14.8,0.99,1.440,273.4 -16.0,0.99,0.670,265.1 -16.4,0.99,1.680,313.0 -16.2,0.99,2.940,316.5 -16.6,0.99,3.080,311.1 -17.6,0.99,2.640,304.8 -17.2,0.99,4.270,289.0 -16.9,0.99,4.860,279.7 -17.1,0.99,3.510,299.9 -17.7,0.99,3.740,299.3 -18.4,0.99,4.560,290.4 -18.8,0.99,4.290,295.4 -18.9,0.99,3.810,305.0 -19.1,0.99,3.620,306.5 -18.9,0.99,2.960,319.9 -18.5,0.99,2.990,313.7 -17.5,0.99,3.850,285.3 -16.6,0.99,2.990,297.1 -16.3,0.99,2.040,309.0 -16.0,0.99,1.990,293.2 -16.6,0.99,1.610,128.9 -17.6,0.99,8.010,152.4 -17.7,0.99,9.480,154.4 -18.2,0.99,10.260,165.9 -18.3,0.99,8.920,164.2 -18.4,0.99,8.920,157.3 -19.3,0.99,7.620,165.5 -19.4,0.99,8.410,172.3 -19.1,0.99,10.040,152.8 -18.9,0.99,13.990,153.5 -19.7,0.99,12.020,154.2 -20.9,0.99,13.970,155.6 -20.8,0.99,15.430,158.2 -20.9,0.99,13.460,169.5 -20.4,0.99,15.360,166.2 -19.6,0.99,14.940,166.0 -19.1,0.99,15.210,172.2 -18.5,0.99,10.840,174.5 -18.0,0.99,7.840,161.1 -17.6,0.99,11.030,176.6 -16.4,0.99,5.010,248.7 -16.2,0.99,1.990,285.7 -17.0,0.99,1.710,357.2 -16.3,0.99,2.120,61.7 -14.9,0.99,2.160,104.7 -14.9,0.99,2.250,101.5 -13.4,0.99,1.970,198.7 -13.7,0.99,3.690,164.9 -15.3,0.99,5.750,148.4 -16.2,0.99,7.190,148.8 -16.9,0.99,6.600,151.2 -14.8,1.00,1.520,241.9 -14.7,0.99,1.320,290.2 -15.7,0.99,2.160,294.3 -16.5,0.99,1.980,294.2 -18.1,0.99,1.800,288.9 -16.1,0.99,4.120,290.4 -16.4,0.99,4.060,297.2 -16.8,0.99,4.150,301.9 -17.4,0.99,5.990,298.5 -17.1,0.99,6.590,289.9 -16.9,0.99,6.810,286.6 -16.6,0.99,6.400,286.7 -16.1,0.99,6.870,289.8 -15.9,0.99,6.810,290.8 -15.1,0.99,6.630,290.7 -13.3,0.99,6.380,290.4 -12.0,0.99,5.610,291.7 -11.8,0.99,5.070,294.0 -11.1,0.99,4.300,300.3 -11.3,0.99,3.600,298.5 -11.0,0.99,3.980,293.6 -11.3,0.99,3.900,308.0 -11.2,0.99,3.160,314.9 -12.6,0.99,3.240,317.3 -13.6,0.99,2.920,312.7 -15.5,0.99,3.310,312.6 -16.0,0.99,4.230,298.3 -16.9,0.99,4.490,300.4 -17.5,0.99,4.640,300.6 -18.6,0.99,4.420,301.3 -20.2,0.99,4.660,296.2 -20.9,0.99,5.090,295.6 -21.4,0.99,5.700,294.0 -21.6,0.99,5.820,294.0 -21.6,0.99,5.570,314.6 -21.5,0.99,5.150,304.1 -20.8,0.99,5.270,297.9 -20.1,0.99,5.660,297.9 -19.0,0.99,6.540,282.0 -16.5,0.99,6.240,280.4 -15.5,0.99,6.280,285.0 -14.1,0.99,5.370,289.4 -14.5,0.99,4.480,287.7 -12.7,0.99,2.520,291.0 -11.7,0.99,2.330,283.4 -12.0,0.99,2.000,276.9 -11.5,0.99,0.720,234.7 -13.2,0.99,1.080,263.1 -14.2,0.99,2.930,277.5 -15.4,0.99,3.100,297.9 -16.3,0.99,3.090,300.8 -17.0,0.99,3.290,315.3 -17.2,0.98,4.520,294.6 -17.0,0.98,5.410,293.3 -18.2,0.98,6.930,291.4 -18.5,0.98,7.680,287.9 -18.5,0.98,8.230,285.3 -18.2,0.98,7.980,281.5 -18.0,0.99,6.770,279.9 -18.0,0.99,6.300,279.3 -17.1,0.99,6.630,284.1 -16.9,0.99,6.660,287.4 -16.9,0.99,5.860,288.2 -16.8,0.99,5.080,291.1 -16.7,0.99,4.270,286.3 -16.7,0.99,2.960,294.8 -16.2,0.99,1.910,0.9 -16.3,0.99,0.890,222.9 -15.2,0.99,0.570,198.4 -15.6,0.99,1.070,100.8 -16.2,0.99,1.970,106.6 -16.6,0.99,2.990,108.1 -17.2,0.99,2.420,128.7 -17.8,0.99,2.110,117.8 -18.4,0.99,1.600,121.0 -18.7,0.99,0.340,206.8 -17.6,0.99,3.740,298.3 -17.2,0.99,5.460,294.4 -17.5,0.99,6.180,296.2 -18.1,0.99,4.480,303.6 -17.9,0.99,4.040,297.1 -17.9,0.99,4.930,298.3 -18.1,0.99,5.230,290.3 -18.7,0.99,5.540,284.4 -18.2,0.99,6.950,279.7 -18.0,0.99,7.120,283.6 -17.8,0.99,7.380,284.3 -17.6,0.99,6.950,278.4 -17.4,0.99,6.270,287.8 -15.8,0.99,3.090,294.9 -15.8,0.99,4.010,290.6 -15.5,0.99,4.090,289.0 -15.3,0.99,3.370,275.6 -15.2,0.99,3.180,326.8 -15.5,0.99,4.390,271.1 -14.7,0.99,0.670,314.1 -15.5,0.99,0.470,121.8 -16.9,0.99,1.010,113.7 -17.6,0.99,1.810,118.4 -18.4,0.99,1.920,111.3 -18.6,0.99,0.540,93.3 -19.0,0.99,0.440,42.1 -18.9,0.99,2.410,335.3 -19.3,0.99,2.070,344.9 -18.9,0.99,4.170,301.1 -18.3,0.99,5.370,291.1 -18.6,0.99,5.710,290.2 -18.3,0.99,5.380,289.4 -17.9,0.99,6.250,287.3 -17.5,0.99,7.330,288.5 -17.0,0.99,8.370,294.4 -16.1,0.99,9.070,294.1 -15.8,0.99,7.640,297.3 -15.7,0.99,6.180,299.0 -15.4,0.99,5.770,307.1 -13.8,0.99,4.490,305.2 -13.2,0.99,4.210,296.2 -13.4,0.99,3.360,342.7 -14.4,0.99,4.320,355.1 -14.1,0.99,4.550,299.8 -14.7,0.99,2.440,341.4 -15.2,0.99,5.140,286.4 -15.9,0.99,3.050,316.8 -16.4,0.99,4.560,290.9 -17.8,0.99,3.540,320.7 -17.8,0.99,2.820,291.3 -18.4,0.99,4.490,288.1 -18.3,0.99,3.760,336.6 -18.5,0.99,4.080,328.1 -18.6,0.99,5.630,293.7 -18.2,0.99,8.420,285.5 -18.1,0.99,8.980,287.3 -16.7,0.99,9.560,286.0 -15.5,0.99,11.030,287.9 -15.2,0.99,11.090,287.2 -14.8,0.99,10.840,286.2 -14.5,0.99,9.950,280.2 -14.7,0.99,9.490,286.5 -16.9,0.99,8.270,315.7 -15.8,0.99,4.950,280.9 -16.5,0.99,0.650,320.1 -14.1,0.99,7.010,320.1 -15.4,0.99,7.880,322.0 -13.5,0.99,4.480,331.9 -14.7,0.99,3.810,333.1 -15.3,0.99,3.750,351.1 -16.1,0.99,6.070,326.6 -16.8,0.99,4.450,310.6 -18.3,0.99,5.320,319.9 -18.1,0.99,7.670,321.8 -19.0,0.99,8.420,294.8 -19.6,0.99,9.560,294.9 -19.5,0.99,9.680,295.6 -19.7,0.99,9.400,300.3 -19.8,0.99,7.630,295.9 -18.3,0.99,6.850,234.4 -17.9,0.99,7.000,283.6 -18.1,0.99,3.260,265.4 -16.2,0.99,1.480,266.9 -16.1,0.99,2.570,235.0 -15.6,0.99,2.620,171.5 -16.1,0.99,3.400,165.9 -16.0,0.99,4.400,142.5 -15.6,0.99,4.380,187.5 -15.4,0.99,3.510,181.3 -15.1,0.99,3.980,160.6 -15.0,0.99,6.160,148.3 -15.1,0.99,5.270,151.6 -15.8,0.99,6.170,147.2 -16.6,0.99,6.250,145.0 -17.2,0.99,6.110,143.8 -17.7,0.99,6.830,153.0 -18.4,0.99,6.280,147.4 -18.8,0.99,6.980,151.9 -19.1,0.99,6.390,153.3 -19.1,0.99,5.720,162.6 -19.1,0.99,6.620,177.1 -18.5,0.99,5.950,167.7 -17.9,0.99,5.710,166.3 -17.4,0.99,4.030,182.3 -17.2,0.99,2.780,185.2 -16.9,0.99,1.860,299.7 -16.1,0.99,1.980,309.3 -16.0,0.99,2.670,283.9 -15.8,0.99,0.650,291.0 -15.7,0.99,0.170,282.8 -15.7,0.99,0.590,112.9 -15.7,0.99,0.950,133.6 -15.8,0.99,1.310,80.8 -15.5,0.99,1.940,62.7 -15.8,0.99,2.180,23.1 -15.6,0.99,1.700,3.3 -15.7,0.99,1.880,308.9 -16.3,0.99,2.300,298.3 -16.5,0.99,4.030,291.8 -16.9,0.99,4.010,297.1 -17.0,0.99,3.600,315.7 -17.1,0.99,3.450,308.1 -17.8,0.99,3.160,311.2 -18.3,0.99,3.680,305.9 -18.5,0.99,4.560,305.1 -18.5,0.99,5.330,299.6 -18.5,0.99,5.990,296.6 -18.4,0.99,6.640,292.3 -17.5,0.99,6.970,293.9 -16.9,0.99,7.170,292.5 -15.9,0.99,7.170,291.3 -15.3,0.99,6.930,293.7 -14.2,0.99,5.560,298.7 -13.9,0.99,4.380,296.0 -13.7,0.99,4.040,290.6 -12.6,0.99,4.520,306.5 -12.0,0.99,6.580,295.2 -12.2,0.99,6.900,292.8 -13.0,0.99,5.200,290.6 -13.2,0.99,4.700,301.2 -14.4,0.99,4.010,299.4 -15.3,0.99,4.200,296.2 -15.7,0.99,4.850,293.6 -16.0,0.99,5.530,293.4 -16.2,0.99,3.700,298.9 -16.7,0.99,4.520,297.2 -17.6,0.99,5.980,293.3 -18.2,0.99,7.820,288.9 -18.5,0.99,8.950,277.6 -18.1,0.99,9.170,273.8 -17.9,0.99,7.230,274.4 -18.1,0.99,8.020,277.1 -16.9,0.99,7.870,277.4 -15.2,0.99,11.420,281.4 -14.6,0.99,10.330,280.1 -14.9,0.99,9.510,282.8 -15.1,0.99,8.590,287.1 -15.5,0.99,6.720,299.6 -14.6,0.99,5.350,314.8 -14.4,0.99,3.970,7.0 -14.1,0.99,2.610,355.4 -13.9,0.99,2.300,2.4 -14.1,0.99,2.720,27.7 -14.4,0.99,2.260,7.2 -14.6,0.99,4.150,2.9 -14.7,0.99,4.430,351.8 -15.4,0.99,3.620,330.5 -15.9,0.99,3.180,323.8 -16.4,0.99,2.950,312.2 -16.7,0.99,3.260,310.6 -18.0,0.99,3.060,303.3 -18.6,0.99,3.720,299.2 -18.8,0.99,5.060,306.0 -18.7,0.99,6.140,300.3 -18.4,0.99,6.600,298.1 -18.2,0.99,6.890,295.8 -16.6,0.99,7.620,289.9 -15.6,0.99,7.690,291.2 -13.9,0.99,6.680,290.8 -13.1,0.99,6.060,294.2 -12.2,0.99,5.870,304.2 -12.2,0.99,5.440,316.7 -11.6,0.99,5.770,324.1 -10.2,0.99,5.640,315.1 -10.5,0.99,6.180,319.9 -11.2,0.99,6.050,321.8 -11.4,0.99,4.640,318.7 -11.4,0.99,4.500,313.6 -13.0,0.99,4.490,308.9 -14.1,0.99,4.570,307.9 -15.8,0.99,4.400,308.7 -17.3,0.99,4.760,303.9 -18.8,0.99,5.290,305.2 -20.4,0.99,5.690,306.5 -21.4,0.99,5.880,305.4 -21.8,0.99,6.330,307.9 -22.0,0.99,6.780,310.2 -22.2,0.99,7.000,310.9 -22.4,0.99,6.440,313.7 -22.5,0.99,5.990,309.8 -21.9,0.99,5.810,305.2 -20.7,0.99,6.060,301.9 -20.7,0.99,5.520,304.4 -20.0,0.99,4.930,304.9 -19.4,0.99,4.210,301.4 -20.3,0.99,4.450,296.6 -19.5,0.99,4.240,299.8 -18.9,0.99,4.850,298.2 -17.9,0.99,4.900,297.7 -18.3,0.99,4.960,304.2 -18.5,0.99,4.880,296.2 -18.7,0.99,4.540,297.6 -19.1,0.99,3.640,303.9 -20.1,0.99,3.970,300.1 -20.7,0.99,4.080,301.8 -21.3,0.99,3.930,298.9 -22.6,0.99,3.840,307.1 -24.0,0.99,3.990,297.7 -25.3,0.99,4.550,291.9 -26.2,0.99,4.850,291.3 -26.5,0.99,5.290,290.3 -26.9,0.99,4.940,293.0 -26.5,0.99,4.910,304.0 -26.0,0.99,4.370,314.0 -25.7,0.99,5.250,314.6 -24.2,0.99,5.460,311.7 -21.9,0.99,8.000,290.1 -19.8,0.99,9.080,286.8 -19.0,0.99,9.910,282.9 -16.7,0.99,9.880,279.0 -15.3,0.99,10.150,279.6 -13.8,0.99,8.520,279.5 -12.8,0.99,5.770,276.1 -12.6,0.99,5.010,277.7 -13.4,0.99,2.890,258.6 -14.1,0.99,1.400,275.0 -14.8,0.99,2.700,294.1 -15.7,0.99,1.690,291.4 -16.6,0.99,3.970,285.4 -17.0,0.99,5.790,285.5 -17.2,0.99,6.850,284.5 -17.3,0.99,7.450,281.0 -19.2,0.99,8.480,279.3 -19.6,0.99,9.070,276.4 -19.7,0.99,8.310,274.1 -19.6,0.99,7.900,275.2 -19.6,0.99,8.150,276.3 -19.4,0.99,8.100,273.7 -17.6,0.99,9.980,278.8 -15.3,0.99,10.800,274.5 -14.2,0.99,9.390,277.8 -14.0,0.99,8.660,277.2 -13.9,0.99,8.260,277.7 -13.9,0.99,7.340,274.9 -13.5,0.99,7.330,274.4 -13.3,0.99,7.000,272.9 -13.3,0.99,6.640,273.6 -13.6,0.99,5.930,276.0 -13.5,0.99,6.380,278.4 -13.5,0.99,6.860,282.4 -14.5,0.99,6.050,283.1 -14.8,0.99,5.950,282.4 -15.2,0.99,5.540,285.3 -15.2,0.99,5.130,289.3 -15.7,0.99,4.700,286.5 -16.0,0.99,5.200,281.5 -17.2,0.99,5.050,274.5 -17.5,0.99,4.740,271.6 -17.5,0.99,4.590,267.4 -17.3,0.99,4.890,273.5 -17.1,0.99,5.090,278.7 -16.6,0.99,4.630,292.1 -15.7,0.99,3.770,291.4 -14.7,0.99,4.910,285.4 -14.4,0.99,6.290,279.9 -13.8,0.99,7.590,283.6 -13.5,0.99,7.730,275.3 -13.2,0.99,7.660,277.1 -12.8,0.99,8.190,279.6 -12.6,0.99,7.840,278.9 -12.4,0.99,6.630,282.8 -12.3,0.99,5.900,286.1 -12.1,0.99,5.850,288.9 -11.9,0.99,4.990,293.7 -13.3,0.99,5.020,293.3 -14.7,0.99,3.740,309.2 -15.4,0.98,3.840,314.1 -15.8,0.98,3.970,309.2 -16.2,0.98,3.570,311.4 -17.0,0.98,3.200,307.6 -19.2,0.98,2.930,307.7 -20.2,0.98,3.240,306.1 -20.9,0.98,3.520,308.6 -21.0,0.98,3.750,318.2 -21.3,0.98,4.370,314.3 -21.3,0.98,4.800,309.7 -20.4,0.98,6.810,298.8 -19.0,0.98,8.060,292.4 -17.9,0.98,9.120,286.3 -16.4,0.98,9.840,283.3 -15.4,0.98,9.800,283.3 -13.1,0.98,8.910,282.9 -12.5,0.98,9.320,280.4 -12.2,0.98,8.610,280.9 -12.6,0.98,8.270,281.4 -12.6,0.98,5.870,275.5 -12.9,0.98,6.360,262.4 -13.2,0.98,8.990,270.7 -14.1,0.98,6.150,261.3 -15.5,0.98,4.570,275.9 -14.9,0.98,2.790,26.0 -16.2,0.98,6.520,130.0 -16.8,0.98,5.510,122.4 -17.9,0.98,5.290,187.9 -18.8,0.98,2.680,220.3 -15.8,0.98,6.610,278.5 -15.3,0.98,7.660,281.1 -15.0,0.98,7.830,277.1 -14.9,0.98,4.330,269.0 -14.8,0.98,1.820,116.9 -15.1,0.98,2.750,107.9 -14.8,0.98,3.280,104.4 -14.7,0.98,1.200,147.4 -14.8,0.98,1.320,311.6 -14.8,0.98,0.270,177.7 -14.8,0.99,2.860,142.5 -14.6,0.99,7.260,157.2 -14.6,0.99,7.660,150.4 -14.5,0.99,7.380,149.9 -14.7,0.99,4.980,197.0 -14.4,0.99,3.420,268.1 -14.2,0.99,4.310,304.2 -14.1,0.99,3.550,280.4 -14.5,0.99,4.110,277.8 -15.2,0.99,5.580,271.6 -15.3,0.99,5.030,263.6 -15.5,0.99,5.890,265.6 -15.2,0.99,7.110,266.3 -15.1,0.99,8.840,273.0 -15.3,0.99,11.880,283.2 -15.2,0.99,12.720,284.5 -15.0,0.99,12.960,292.5 -14.6,0.99,10.770,287.8 -14.4,0.99,10.390,289.2 -14.1,0.99,8.620,284.3 -14.0,0.99,9.670,281.9 -14.1,0.99,9.330,278.9 -14.0,0.99,8.240,283.3 -13.4,0.99,5.840,286.7 -13.3,0.99,6.260,285.7 -13.3,0.99,8.720,288.8 -13.2,0.99,9.400,295.3 -13.1,0.99,9.070,296.3 -13.1,0.99,8.880,295.3 -13.6,0.99,8.970,295.1 -13.5,0.99,8.090,300.4 -12.8,0.99,5.290,311.6 -13.2,0.99,3.560,315.2 -13.5,0.99,2.240,315.9 -13.9,0.99,2.770,302.2 -14.2,0.99,3.840,289.7 -14.0,0.99,7.430,277.5 -14.4,0.99,9.670,278.7 -14.7,0.99,7.520,278.1 -15.1,0.99,11.700,284.2 -15.0,0.99,10.900,277.2 -14.6,0.99,13.070,281.7 -14.3,0.99,13.110,282.7 -13.7,0.99,12.410,282.3 -12.8,0.99,7.580,272.9 -13.1,0.99,10.580,280.7 -13.3,0.99,4.470,269.7 -13.0,0.99,6.330,286.7 -13.1,0.99,5.130,284.7 -12.6,0.99,4.750,240.5 -11.7,0.99,4.910,193.5 -11.1,0.99,5.670,182.7 -12.3,0.99,4.430,171.6 -10.6,0.99,4.430,169.0 -12.5,0.99,6.040,169.0 -12.9,0.99,5.910,166.3 -14.0,0.99,5.520,163.3 -14.6,0.99,5.630,158.0 -15.1,0.99,5.400,160.5 -15.5,0.99,5.460,162.9 -16.0,0.99,5.630,162.6 -16.5,0.99,5.300,168.2 -16.7,0.99,4.970,170.7 -16.1,0.99,4.520,175.7 -15.2,0.99,3.520,157.1 -14.9,0.99,4.040,151.1 -15.0,0.99,4.890,126.8 -15.1,0.99,7.340,117.8 -13.5,0.99,7.130,126.3 -13.0,0.99,7.330,128.4 -13.3,0.99,6.280,134.1 -12.8,0.99,5.500,135.9 -12.4,0.99,5.200,134.9 -12.5,0.99,5.340,138.1 -12.9,0.99,5.450,130.0 -12.9,0.99,5.710,135.0 -12.4,0.99,5.450,141.4 -13.2,0.99,6.090,136.4 -15.0,0.99,7.680,139.9 -16.3,0.99,7.660,140.5 -17.9,0.99,8.730,152.0 -19.4,0.99,8.900,152.2 -20.3,0.99,7.780,152.6 -22.2,0.98,8.670,165.6 -21.8,0.98,6.310,158.7 -22.4,0.98,8.090,173.9 -22.2,0.98,7.430,160.7 -22.3,0.98,9.230,166.6 -21.6,0.98,8.030,157.1 -20.6,0.98,7.490,156.1 -20.8,0.98,8.260,164.6 -21.4,0.98,9.210,169.3 -20.3,0.98,9.420,170.4 -20.1,0.98,8.560,168.2 -18.0,0.98,8.200,160.8 -16.7,0.98,7.450,157.6 -15.1,0.98,5.370,141.9 -13.4,0.98,5.110,144.3 -12.7,0.98,4.590,145.9 -12.9,0.98,5.560,146.0 -13.0,0.98,7.420,146.2 -14.6,0.98,10.710,153.3 -16.0,0.98,11.820,156.2 -17.0,0.98,9.750,160.8 -18.0,0.98,9.790,160.7 -18.4,0.98,11.400,159.7 -18.7,0.98,11.320,157.2 -15.7,0.98,7.050,287.5 -15.6,0.98,5.970,286.4 -15.0,0.98,3.680,299.6 -14.9,0.98,5.750,283.8 -14.7,0.98,2.360,328.7 -14.8,0.98,3.580,75.5 -15.4,0.98,7.430,170.5 -15.1,0.98,8.680,183.1 -14.3,0.98,2.550,250.3 -13.8,0.98,0.790,247.0 -13.6,0.98,2.180,313.9 -13.6,0.98,0.320,250.7 -13.9,0.98,0.760,242.1 -13.3,0.98,1.250,298.8 -12.7,0.98,3.750,291.1 -13.1,0.98,3.570,291.5 -12.9,0.98,4.100,280.7 -13.1,0.98,3.870,270.9 -12.6,0.98,3.580,254.6 -12.9,0.98,2.720,260.6 -13.1,0.98,0.380,137.6 -13.5,0.98,1.950,142.6 -14.3,0.98,1.160,149.2 -14.5,0.98,1.080,129.2 -14.2,0.98,1.120,300.7 -14.8,0.98,2.510,286.9 -15.6,0.98,1.840,244.7 -15.2,0.98,2.030,151.8 -14.6,0.98,2.210,148.4 -14.0,0.98,0.980,356.8 -14.1,0.98,1.710,329.3 -14.1,0.98,1.640,348.3 -13.9,0.98,1.240,309.5 -14.1,0.98,0.470,79.4 -12.8,0.98,5.850,150.0 -12.6,0.98,9.760,144.4 -12.8,0.98,12.310,163.9 -13.1,0.98,10.800,178.1 -12.8,0.98,13.180,167.4 -12.6,0.98,14.130,160.3 -12.5,0.98,16.020,164.1 -12.5,0.98,16.110,164.3 -12.5,0.98,16.570,157.3 -12.6,0.97,15.540,154.8 -12.7,0.97,15.430,155.7 -13.1,0.97,12.340,159.9 -13.2,0.97,14.090,164.3 -14.8,0.97,8.680,171.4 -15.9,0.97,5.090,178.8 -16.6,0.98,2.520,162.6 -14.6,0.98,4.240,334.8 -15.4,0.98,5.820,342.7 -13.8,0.98,2.710,340.2 -13.6,0.98,1.260,4.7 -13.5,0.98,3.020,102.0 -13.4,0.98,2.570,118.5 -12.4,0.98,4.030,158.8 -11.7,0.98,2.410,32.9 -12.7,0.98,3.300,178.8 -12.0,0.98,4.710,179.1 -12.2,0.98,4.620,165.0 -12.4,0.98,8.660,193.9 -12.0,0.98,5.390,183.4 -11.9,0.98,5.520,183.2 -12.1,0.98,6.100,162.8 -12.1,0.98,4.640,213.9 -12.5,0.99,2.560,190.2 -12.5,0.99,2.380,306.1 -12.8,0.99,1.470,50.6 -13.6,0.99,0.540,194.4 -13.2,0.99,2.870,320.4 -12.0,0.99,4.370,297.8 -12.2,0.99,4.260,280.6 -13.6,0.99,6.170,266.1 -13.7,0.99,3.290,268.7 -13.3,0.99,3.220,326.3 -13.0,0.99,4.860,334.3 -12.7,0.99,4.320,305.6 -12.2,0.99,0.690,141.3 -12.3,0.99,1.210,347.1 -11.6,0.99,1.900,131.4 -12.1,0.99,2.390,123.2 -12.4,0.99,3.080,103.5 -10.8,0.99,3.880,110.5 -10.4,0.99,3.610,141.0 -10.0,0.99,3.030,145.4 -9.2,0.99,3.790,130.8 -9.1,0.99,3.610,132.3 -7.7,0.99,5.020,150.1 -8.5,0.99,5.270,158.6 -9.0,0.99,4.790,147.2 -9.6,0.99,3.370,153.7 -10.6,0.99,2.050,156.1 -11.6,0.99,1.800,146.4 -12.4,0.99,2.130,141.0 -12.8,0.99,3.320,179.8 -12.3,0.99,3.490,291.1 -12.0,0.99,2.060,261.6 -11.5,0.99,2.920,262.0 -11.7,0.99,5.250,295.4 -10.8,0.99,6.920,291.6 -10.7,0.99,5.770,288.6 -10.5,0.99,5.890,286.9 -10.3,0.99,5.570,291.3 -10.1,0.99,6.120,297.9 -9.7,0.99,6.150,289.6 -9.5,0.99,6.560,296.0 -9.8,0.99,7.290,324.2 -9.6,0.99,7.490,328.2 -9.1,0.99,6.440,311.7 -8.6,0.99,7.490,295.1 -9.1,0.99,8.450,303.6 -9.1,0.99,9.720,302.8 -9.1,0.99,10.600,315.9 -9.9,0.99,7.430,300.3 -10.3,0.99,8.430,305.7 -9.7,0.99,7.420,311.7 -10.3,0.99,7.320,304.6 -10.8,0.99,8.240,293.0 -12.0,0.99,8.510,293.2 -12.8,0.99,9.460,290.7 -13.1,0.99,9.860,289.9 -11.3,0.99,8.890,289.9 -12.7,0.99,8.930,290.0 -13.3,0.99,10.640,292.2 -13.3,0.99,9.920,287.3 -13.5,0.99,8.530,282.2 -13.1,0.99,6.770,279.5 -11.9,0.99,5.620,277.1 -12.0,0.99,6.550,297.1 -11.0,0.99,3.480,302.8 -9.8,0.99,1.580,279.1 -9.6,0.99,3.060,278.6 -9.6,0.99,2.700,282.8 -9.4,0.99,3.390,284.0 -8.7,0.99,4.640,311.8 -5.1,0.99,5.660,304.4 -5.3,0.99,4.460,303.4 -4.7,0.99,3.850,304.6 -4.9,0.99,3.260,301.6 -7.0,0.99,2.550,302.7 -8.0,0.99,2.430,307.7 -8.7,0.99,2.330,314.7 -10.4,0.99,2.540,322.6 -11.9,0.99,2.120,326.4 -13.4,0.99,1.380,330.9 -13.9,0.99,0.480,215.9 -13.6,0.99,2.160,171.2 -14.0,0.99,3.350,166.8 -12.7,0.99,4.980,165.5 -13.8,0.99,4.950,215.8 -13.2,0.99,2.690,281.2 -12.9,0.99,0.550,359.4 -11.3,0.99,3.580,123.7 -9.1,0.99,4.000,155.4 -8.6,0.99,4.000,151.0 -8.9,0.99,4.440,160.5 -8.9,0.99,4.400,163.4 -9.2,0.99,6.320,161.7 -9.2,0.99,7.370,152.7 -9.6,0.99,8.650,138.3 -9.5,0.99,11.020,143.4 -9.6,0.99,13.410,155.3 -9.8,0.99,14.410,158.8 -10.7,0.99,13.530,161.3 -11.4,0.99,12.570,160.6 -11.8,0.99,11.930,163.3 -11.8,0.99,9.770,155.6 -12.1,0.99,9.590,155.4 -12.2,0.99,8.810,153.8 -12.3,0.99,6.140,155.8 -12.6,0.99,5.150,150.1 -12.4,0.99,4.590,144.4 -11.6,0.98,2.950,144.8 -11.8,0.98,1.150,110.3 -11.8,0.98,1.280,111.9 -11.9,0.98,2.970,117.6 -11.9,0.98,6.380,144.0 -11.6,0.98,9.650,152.2 -11.8,0.98,11.720,158.6 -11.5,0.98,11.310,154.9 -11.7,0.98,13.190,156.5 -11.7,0.98,13.070,152.3 -11.8,0.98,13.950,147.9 -12.2,0.97,13.080,159.8 -14.0,0.97,16.660,169.4 -14.4,0.97,13.820,163.4 -15.4,0.97,11.280,173.1 -14.8,0.97,16.510,172.3 -14.6,0.97,11.990,176.1 -14.3,0.97,14.340,170.0 -14.0,0.97,13.930,157.6 -13.7,0.97,15.700,165.3 -13.8,0.97,15.500,163.2 -14.0,0.97,11.580,164.8 -14.1,0.97,10.720,179.4 -12.3,0.97,6.500,357.1 -12.4,0.97,2.390,93.8 -12.0,0.97,4.910,158.6 -11.8,0.97,9.220,181.1 -11.3,0.97,13.470,184.3 -10.9,0.97,13.010,173.1 -11.1,0.97,14.660,185.9 -11.2,0.97,15.310,183.9 -11.1,0.97,12.750,173.8 -10.7,0.97,14.270,178.8 -11.0,0.97,14.810,175.7 -10.7,0.97,11.790,171.2 -10.6,0.97,13.720,165.1 -10.8,0.97,13.570,166.2 -11.7,0.97,10.820,170.1 -11.2,0.97,15.100,162.0 -11.5,0.97,12.610,164.3 -12.1,0.97,13.240,172.5 -11.7,0.97,15.530,160.5 -12.0,0.97,15.210,160.8 -12.3,0.97,15.040,164.4 -11.6,0.97,16.530,148.5 -11.9,0.96,16.180,151.5 -12.4,0.96,17.980,159.3 -12.1,0.96,14.120,166.5 -13.5,0.96,11.640,187.8 -13.7,0.96,4.540,138.1 -14.6,0.97,5.740,157.3 -13.1,0.97,6.440,223.7 -12.0,0.97,6.280,184.1 -12.0,0.97,9.860,186.6 -11.0,0.97,5.970,189.9 -10.8,0.97,8.980,175.3 -10.3,0.97,8.160,183.5 -10.4,0.97,8.580,173.2 -10.5,0.97,6.100,179.3 -10.2,0.97,7.090,194.7 -10.4,0.97,3.860,161.6 -11.4,0.98,4.440,180.1 -12.0,0.98,7.740,170.7 -13.2,0.98,3.170,207.4 -13.1,0.98,2.210,148.5 -13.1,0.98,3.760,166.0 -13.0,0.98,1.440,168.6 -12.8,0.98,2.130,142.8 -12.8,0.98,2.400,337.9 -12.5,0.98,2.440,108.1 -12.3,0.98,5.240,97.6 -11.7,0.98,3.680,152.4 -10.6,0.98,3.030,211.3 -10.9,0.98,6.710,222.5 -10.2,0.98,6.640,189.6 -9.5,0.98,3.860,184.0 -9.3,0.98,4.010,166.2 -9.6,0.99,5.450,184.6 -9.0,0.99,7.130,175.5 -8.6,0.99,6.380,168.9 -8.5,0.99,5.930,163.8 -8.8,0.99,5.040,174.1 -8.8,0.99,9.300,168.2 -8.7,0.99,6.540,152.1 -8.8,0.99,4.640,186.0 -9.4,0.99,6.080,179.2 -11.0,0.99,5.370,198.5 -11.1,0.99,5.410,166.6 -12.0,0.99,5.170,173.7 -12.9,0.99,6.320,168.6 -13.0,0.99,6.470,172.6 -12.7,0.99,9.100,165.1 -11.4,0.99,1.330,111.3 -11.5,0.99,1.890,102.3 -11.4,0.99,5.310,132.9 -10.9,0.99,4.760,157.5 -10.3,0.99,2.720,157.4 -9.8,0.99,1.690,325.7 -10.0,0.99,1.040,97.5 -10.3,0.99,3.460,143.4 -9.9,0.99,4.000,128.8 -9.7,0.99,8.110,134.4 -9.2,0.99,6.570,143.3 -9.4,0.99,7.080,177.6 -9.5,0.99,6.210,175.5 -9.4,0.99,4.850,201.8 -9.6,0.99,4.250,179.2 -9.1,0.99,6.170,165.2 -9.0,0.99,9.390,171.2 -8.9,0.99,9.180,157.8 -9.0,0.99,9.680,151.7 -9.2,0.99,9.570,150.7 -9.5,0.99,9.660,149.5 -9.9,0.99,8.310,149.6 -9.7,0.99,6.380,153.3 -9.2,0.99,2.140,281.9 -8.9,0.99,4.480,296.8 -8.7,0.99,6.570,295.7 -9.0,0.99,4.630,293.0 -9.2,0.99,5.210,298.1 -9.0,0.99,4.080,296.3 -8.7,1.00,4.640,288.4 -8.4,1.00,6.050,287.4 -7.5,1.00,5.470,308.8 -7.2,1.00,4.030,317.0 -7.0,1.00,4.000,308.7 -6.8,1.00,3.430,313.2 -6.5,1.00,2.640,329.3 -6.5,1.00,3.080,310.1 -6.2,1.00,2.620,319.1 -6.0,1.00,2.570,334.7 -5.6,1.00,4.660,335.6 -5.5,1.00,3.490,332.9 -5.7,1.00,3.970,322.7 -5.6,1.00,3.660,334.7 -6.0,1.00,2.520,348.9 -6.1,1.00,2.450,355.4 -6.4,1.00,0.910,4.8 -6.8,1.00,0.990,42.2 -7.3,1.00,1.100,105.4 -8.0,1.00,2.530,129.6 -8.6,1.00,3.150,136.4 -9.0,1.00,3.130,132.6 -9.5,1.00,2.630,130.0 -9.6,1.00,3.020,156.5 -9.7,1.00,2.940,155.5 -9.6,1.00,2.850,153.7 -8.0,1.01,1.740,250.0 -7.1,1.01,1.510,267.2 -7.2,1.01,1.110,244.5 -6.9,1.01,0.780,217.0 -6.8,1.01,0.580,162.9 -6.6,1.01,0.700,109.0 -6.1,1.01,0.950,123.7 -5.4,1.01,1.700,113.1 -5.0,1.01,1.840,126.2 -4.8,1.01,1.510,117.8 -4.3,1.01,2.200,128.7 -4.4,1.01,2.340,138.9 -5.7,1.01,2.640,127.3 -6.5,1.01,1.740,121.5 -7.3,1.00,2.060,117.9 -7.9,1.00,1.630,124.5 -9.7,1.00,2.010,115.1 -9.9,1.00,0.250,94.8 -9.5,1.00,1.640,303.8 -9.4,1.00,1.080,317.0 -9.5,1.00,0.760,306.9 -10.1,1.00,0.540,306.8 -10.9,1.00,0.450,28.9 -11.1,1.00,0.290,41.5 -10.0,1.00,0.470,140.0 -8.3,1.00,0.840,149.3 -8.4,1.00,0.710,153.1 -8.0,1.00,0.770,176.1 -7.7,1.00,1.200,128.2 -7.8,1.00,1.710,138.5 -7.9,1.00,2.220,149.1 -7.3,1.00,2.440,153.1 -7.5,1.00,2.550,150.3 -7.2,1.00,2.090,152.5 -7.5,1.00,0.570,199.9 -7.5,1.00,0.700,291.3 -8.3,1.00,1.360,309.8 -10.2,1.00,1.880,321.9 -11.5,1.00,1.940,320.0 -11.1,1.00,2.960,313.6 -11.4,1.00,3.710,317.2 -12.2,1.00,3.280,316.2 -12.9,1.00,2.940,313.1 -13.5,1.00,3.020,306.4 -13.8,1.00,2.990,300.1 -14.0,1.00,2.950,294.8 -14.0,1.00,2.520,300.2 -14.1,1.00,2.220,299.3 -14.0,0.99,1.830,316.8 -13.5,0.99,1.060,329.4 -12.8,0.99,0.310,1.9 -11.8,0.99,0.720,113.4 -10.8,0.99,1.950,116.2 -10.4,0.99,2.360,134.6 -10.3,0.99,4.300,151.8 -10.8,0.99,6.430,143.1 -11.0,0.99,7.640,150.7 -10.3,0.99,8.120,149.6 -14.8,0.99,13.830,161.4 -16.0,0.99,15.640,160.2 -15.6,0.99,15.560,149.4 -15.8,0.99,14.000,148.2 -16.8,0.99,12.900,150.5 -18.5,0.99,14.310,163.9 -18.9,0.99,16.210,175.1 -19.2,0.99,18.140,180.7 -19.1,0.98,18.090,187.0 -18.4,0.98,15.270,190.5 -17.5,0.98,17.380,186.5 -16.8,0.98,17.410,185.5 -16.0,0.98,15.740,178.2 -15.1,0.98,14.780,194.9 -12.8,0.98,12.480,173.6 -11.9,0.98,9.590,176.3 -12.2,0.98,9.060,176.2 -11.6,0.99,7.460,171.0 -11.5,0.98,6.750,161.2 -11.5,0.98,6.990,180.3 -11.4,0.99,10.000,212.7 -11.9,0.99,6.970,341.2 -11.0,0.99,1.770,5.0 -11.3,0.99,1.780,109.9 -11.2,0.99,2.690,127.7 -11.9,0.99,3.550,122.7 -12.7,0.99,4.390,97.8 -13.0,0.99,4.300,105.1 -13.3,0.99,2.420,83.1 -13.6,0.99,0.800,282.7 -12.3,0.99,2.910,302.8 -12.8,0.99,3.690,282.9 -13.6,0.99,4.040,242.8 -12.8,0.99,5.220,239.0 -12.4,0.99,9.890,257.5 -12.2,0.99,7.310,238.6 -11.4,0.99,7.280,274.0 -11.2,0.99,15.340,282.9 -10.9,0.99,15.460,288.4 -11.1,0.99,13.300,280.0 -10.8,0.99,8.330,265.3 -10.8,0.99,8.230,256.5 -10.9,0.99,7.420,253.3 -10.9,0.99,9.850,276.3 -10.3,0.99,9.660,281.4 -10.1,0.99,12.700,280.4 -10.2,0.99,14.460,284.3 -10.0,0.99,12.290,288.3 -9.8,0.99,11.200,284.3 -9.5,0.99,10.370,292.4 -9.4,0.99,8.980,293.3 -10.1,0.99,5.370,304.5 -10.6,0.99,4.580,309.7 -10.9,0.99,6.580,326.8 -11.1,0.99,8.900,343.3 -11.1,0.99,8.220,343.5 -10.8,0.99,5.750,335.8 -10.4,0.99,5.610,318.6 -10.6,0.99,6.010,338.3 -11.2,0.99,6.390,316.4 -11.4,0.99,6.100,320.5 -10.8,0.99,6.430,312.6 -10.7,0.99,7.780,314.7 -10.2,0.99,8.030,319.9 -9.4,0.99,8.450,310.9 -9.2,0.99,7.320,307.9 -8.6,0.99,6.630,332.4 -8.6,0.99,6.760,335.6 -8.1,0.99,6.020,350.4 -7.8,0.99,4.200,329.6 -8.0,0.99,4.160,326.9 -7.2,0.99,2.550,315.0 -7.2,0.99,2.330,299.4 -7.9,0.99,1.880,344.8 -8.8,0.99,1.200,10.0 -9.3,0.99,0.650,328.7 -9.9,0.99,1.170,319.8 -10.1,0.99,1.340,318.6 -10.4,0.99,1.970,317.5 -10.6,0.99,1.560,333.5 -11.0,0.99,0.800,322.3 -11.3,0.99,0.320,208.4 -11.5,0.99,1.680,180.0 -11.8,0.99,1.540,190.3 -11.9,0.99,1.780,180.9 -11.3,0.99,1.050,246.6 -11.1,0.99,2.130,316.7 -9.9,0.99,3.700,287.2 -10.0,0.99,3.490,287.7 -9.6,0.99,2.150,297.7 -9.7,0.99,1.440,288.1 -8.9,0.99,1.200,156.0 -8.2,0.99,3.230,136.4 -7.8,0.99,3.540,140.5 -7.4,0.99,3.900,149.4 -7.1,0.99,4.790,147.4 -6.9,0.99,5.040,155.0 -7.3,0.99,5.560,150.6 -7.6,0.99,4.460,152.5 -7.9,0.99,2.990,150.2 -8.1,0.99,1.980,184.4 -8.3,0.99,1.680,201.9 -8.6,0.99,1.260,250.5 -9.5,0.99,3.130,264.2 -9.4,0.99,1.100,310.5 -9.2,0.99,0.560,142.9 -9.6,0.99,0.880,156.6 -9.2,0.99,0.940,120.1 -9.5,0.99,0.360,116.0 -9.6,0.99,0.780,171.8 -10.1,0.99,0.400,228.5 -9.8,0.99,0.570,338.3 -9.8,0.99,0.810,15.2 -9.6,0.99,1.910,9.0 -8.3,0.99,0.490,18.3 -8.4,0.99,0.410,47.6 -8.8,0.99,0.570,3.4 -8.4,0.99,0.950,331.7 -7.0,1.00,0.330,353.3 -7.8,1.00,0.710,73.9 -7.9,1.00,0.680,108.2 -8.2,1.00,0.630,131.0 -9.1,1.00,0.300,30.9 -9.8,1.00,0.400,39.3 -10.3,1.00,0.310,45.7 -10.6,0.99,0.830,15.5 -11.0,0.99,0.600,35.3 -11.2,0.99,0.290,47.2 -11.6,0.99,0.290,13.5 -11.6,0.99,0.370,133.9 -11.4,0.99,1.050,129.7 -11.3,0.99,0.830,125.2 -10.6,0.99,0.480,6.8 -10.4,0.99,1.800,271.6 -9.5,0.99,5.120,293.1 -8.5,0.99,5.530,301.7 -8.5,0.99,3.610,307.2 -7.8,0.99,2.760,287.9 -7.9,0.99,3.110,303.6 -8.1,0.99,3.400,322.2 -8.1,0.99,3.610,297.3 -8.3,0.99,4.260,293.8 -8.3,0.99,4.870,297.9 -7.7,0.99,5.050,319.4 -7.5,1.00,6.030,329.4 -7.7,1.00,6.020,330.6 -8.2,1.00,5.490,318.7 -8.6,1.00,5.560,308.8 -9.1,1.00,6.070,308.0 -9.4,1.00,5.880,311.2 -9.7,1.00,4.910,306.9 -9.8,1.00,3.980,315.5 -10.4,1.00,4.430,315.9 -10.9,1.00,4.600,312.3 -10.9,1.00,4.520,313.1 -10.6,1.00,3.890,319.1 -11.1,1.00,5.240,327.0 -10.8,1.00,4.980,313.5 -9.6,1.00,5.020,306.8 -5.9,1.00,6.130,305.1 -4.6,1.00,6.090,294.4 -3.8,1.00,5.120,299.1 -4.1,1.00,6.320,302.1 -4.5,1.00,7.080,301.0 -3.9,1.00,6.120,314.6 -3.1,1.00,6.080,312.7 -3.9,1.00,6.100,315.3 -2.7,1.00,5.000,315.4 -2.4,1.00,4.390,314.5 -3.5,1.00,4.260,314.7 -5.3,1.00,4.110,313.0 -6.0,1.00,3.580,308.1 -7.2,1.00,3.850,310.9 -8.3,1.00,4.450,315.3 -9.1,1.00,4.550,318.6 -9.7,1.00,4.580,316.4 -10.2,1.00,4.920,311.3 -10.0,1.00,5.360,319.6 -9.7,1.00,4.990,333.8 -7.9,1.00,2.760,319.9 -7.8,1.00,2.420,317.5 -7.7,1.00,2.560,322.3 -5.7,1.00,3.390,315.2 -5.6,1.00,3.420,311.3 -4.6,1.00,3.840,332.4 -4.6,1.00,4.040,324.9 -3.2,1.00,3.950,301.0 -2.6,1.00,3.570,293.8 -2.1,1.00,3.880,313.7 -2.6,1.00,4.030,323.0 -1.5,1.00,3.460,313.5 -1.7,1.00,3.020,324.1 -2.0,1.00,2.430,328.3 -1.8,1.00,1.800,323.9 -3.5,1.00,2.020,294.8 -4.9,1.00,2.800,297.1 -5.4,1.00,3.180,309.2 -5.9,1.00,3.210,311.8 -8.3,1.00,4.150,321.6 -9.1,1.00,3.460,320.2 -9.0,1.00,2.820,320.7 -9.5,1.00,2.940,321.8 -9.2,1.00,2.950,323.0 -8.0,1.00,2.930,326.0 -7.9,1.00,2.860,347.5 -8.2,1.00,1.750,347.1 -7.3,1.00,2.060,327.9 -6.3,1.00,2.420,332.0 -5.4,1.00,3.100,335.8 -4.6,1.00,2.850,319.7 -3.8,1.00,2.690,317.2 -3.6,1.00,2.490,310.3 -3.7,1.00,2.490,313.2 -2.4,1.00,2.440,315.9 -2.0,1.00,2.670,309.4 -2.6,1.00,2.460,311.5 -2.5,1.00,2.490,311.0 -2.4,1.00,2.240,313.7 -4.4,1.00,2.360,301.4 -6.0,1.00,2.720,325.6 -6.6,1.00,2.580,309.2 -7.2,1.00,2.840,317.2 -7.3,1.00,2.850,322.4 -7.0,1.00,2.900,316.2 -8.0,1.00,3.320,322.2 -8.4,1.00,3.540,318.9 -9.1,1.00,3.830,319.3 -10.0,1.00,3.020,307.4 -11.0,1.00,3.940,324.1 -11.4,1.00,4.180,331.1 -10.7,1.00,4.510,325.8 -9.7,1.00,4.910,323.9 -9.7,1.00,5.780,328.7 -9.3,1.00,5.840,321.3 -9.7,1.00,6.200,322.5 -9.5,1.00,7.240,310.7 -9.2,1.00,8.310,307.9 -5.9,1.00,6.340,326.7 -5.5,1.00,5.960,322.6 -5.2,1.00,6.430,323.0 -5.3,1.00,6.150,318.7 -5.2,1.00,5.790,322.7 -6.1,1.00,4.150,309.5 -6.7,1.00,3.540,305.4 -7.1,1.00,3.640,307.3 -7.0,1.00,2.990,304.2 -7.7,1.00,3.660,311.2 -9.3,1.00,4.480,324.3 -11.0,1.00,3.850,314.9 -11.8,1.00,4.090,317.7 -12.3,1.00,4.290,326.3 -10.3,1.00,2.570,332.9 -10.3,1.00,1.510,329.0 -9.1,1.00,1.500,317.3 -7.5,1.00,2.460,296.8 -6.9,1.00,3.220,300.7 -7.6,1.00,3.560,315.4 -6.4,1.00,4.800,318.0 -3.8,1.00,3.940,322.5 -3.8,1.00,4.040,329.1 -3.6,1.00,3.490,321.3 -3.4,1.00,3.470,332.1 -3.3,1.00,3.650,332.8 -3.0,1.00,3.250,328.9 -2.5,1.00,2.850,327.9 -3.8,1.00,3.430,330.5 -4.9,1.00,2.980,335.8 -5.8,1.00,3.100,329.9 -6.5,1.00,2.830,330.7 -6.8,1.00,2.570,326.1 -6.8,1.00,2.660,319.8 -6.6,1.00,2.570,320.8 -7.7,1.00,2.510,327.8 -8.3,1.00,2.450,332.9 -8.4,1.00,2.460,340.2 -9.0,1.00,2.810,347.2 -8.8,1.00,2.670,336.7 -8.7,1.00,2.680,326.8 -7.6,1.00,3.440,317.7 -6.7,1.00,3.510,320.6 -5.3,1.00,3.840,316.2 -5.2,1.00,3.630,315.7 -3.4,1.00,3.480,316.5 -2.5,1.00,3.320,315.0 -3.4,1.00,3.070,329.8 -3.0,1.00,3.290,324.7 -3.0,1.00,3.190,321.2 -2.4,1.00,3.500,319.3 -2.6,1.00,3.310,318.7 -2.2,1.00,3.260,320.0 -4.4,1.00,3.630,315.7 -5.4,1.00,3.820,316.3 -6.3,1.00,4.220,316.6 -6.7,1.00,4.060,312.8 -7.1,1.00,3.710,313.6 -7.9,1.00,3.410,318.4 -9.3,1.00,3.390,322.8 -9.5,1.00,3.600,335.5 -9.1,1.00,3.650,328.3 -9.5,1.00,3.420,315.5 -9.8,1.00,3.850,309.9 -9.2,1.00,4.460,305.9 -8.3,1.00,5.270,300.8 -7.1,1.00,5.100,298.6 -5.9,1.00,5.080,298.0 -5.0,1.00,4.770,299.4 -3.2,1.00,4.600,302.8 -3.0,1.00,4.030,308.0 -3.0,1.00,3.770,304.1 -3.3,1.00,3.490,315.6 -3.6,1.00,3.370,323.1 -3.3,1.00,3.350,308.3 -3.0,1.00,4.200,303.0 -3.4,1.00,4.270,318.3 -5.1,1.00,4.030,327.3 -5.9,1.00,3.160,320.6 -6.2,1.00,3.570,313.8 -6.5,1.00,3.220,316.8 -6.5,1.00,2.590,325.3 -7.1,1.00,1.960,335.6 -7.7,1.00,1.880,328.6 -8.0,1.00,1.710,346.4 -7.6,1.00,1.440,332.0 -8.0,1.00,0.990,347.4 -8.7,1.00,1.790,320.8 -9.1,1.00,2.420,317.7 -8.8,1.00,2.640,321.9 -5.7,1.00,3.280,343.6 -4.6,1.00,2.620,310.7 -3.1,1.00,3.050,324.7 -3.7,1.00,3.740,321.8 -2.9,1.00,3.100,324.1 -3.4,1.00,2.650,331.4 -3.8,1.00,2.460,324.2 -3.8,1.00,2.800,328.0 -4.2,1.00,2.820,322.3 -4.3,1.00,3.110,328.8 -4.2,1.00,3.140,330.1 -4.8,1.00,2.850,322.1 -5.4,1.00,3.170,322.7 -5.4,1.00,2.830,318.4 -5.7,1.00,2.660,322.3 -6.0,1.00,2.340,323.5 -6.2,1.00,1.830,319.1 -7.5,1.00,1.480,342.9 -7.7,1.00,1.340,62.1 -8.0,1.00,1.080,67.2 -8.5,1.00,1.000,357.0 -8.5,1.00,1.940,330.7 -8.6,1.00,2.750,339.7 -7.7,1.00,3.460,328.2 -7.0,1.00,3.920,326.3 -6.3,1.00,4.020,316.7 -5.4,1.00,3.980,318.1 -4.2,1.00,4.550,318.9 -4.2,1.00,3.940,311.6 -4.0,1.00,4.220,324.0 -5.0,1.00,3.990,322.5 -5.1,1.00,3.610,320.2 -5.0,1.00,3.510,320.0 -5.2,1.00,3.120,324.9 -4.9,1.00,3.240,313.4 -5.8,1.00,3.460,315.1 -6.8,1.00,3.670,316.7 -7.4,1.00,3.380,316.3 -7.8,1.00,3.240,317.9 -8.4,0.99,3.090,314.4 -9.3,0.99,2.920,311.0 -9.6,0.99,3.280,311.4 -11.0,0.99,3.720,307.9 -10.7,0.99,4.630,302.3 -11.8,0.99,4.530,309.7 -12.2,0.99,4.820,315.5 -11.7,0.99,5.400,302.2 -10.6,0.99,6.250,312.4 -9.4,0.99,6.640,309.6 -6.3,0.99,6.480,311.2 -5.2,0.99,5.910,306.9 -4.7,0.99,5.970,305.4 -5.1,0.99,5.920,311.7 -5.1,0.99,5.250,321.0 -5.0,0.99,4.860,316.7 -4.3,0.99,4.530,316.5 -3.9,0.99,4.410,318.3 -4.0,0.99,4.190,322.4 -3.8,0.99,3.920,315.4 -6.4,0.99,4.010,308.4 -7.5,0.99,4.250,315.8 -8.0,0.99,4.090,316.9 -8.7,0.99,4.250,315.3 -8.8,0.99,3.650,315.9 -8.7,0.99,3.350,305.5 -9.2,0.99,3.150,311.0 -9.9,0.99,3.240,312.8 -10.2,0.99,3.000,310.8 -10.2,0.99,2.700,313.9 -10.3,0.99,2.480,317.4 -10.5,0.99,3.300,306.6 -8.7,0.99,3.430,312.8 -7.8,0.99,2.850,320.3 -7.2,0.99,2.810,312.3 -6.5,0.99,2.210,310.7 -5.8,0.99,2.250,311.3 -5.0,0.99,1.740,329.3 -4.5,0.99,2.200,339.9 -3.9,0.99,1.370,328.8 -3.3,0.99,1.550,329.5 -2.9,0.99,1.210,327.9 -3.0,0.99,0.970,341.8 -3.0,0.99,0.320,343.4 -4.9,0.99,0.700,316.0 -6.4,0.99,1.040,337.5 -6.8,0.99,1.420,316.9 -7.1,0.99,1.330,312.6 -7.5,0.99,1.730,309.1 -7.7,0.99,2.130,303.4 -8.1,0.99,1.800,300.0 -8.6,0.99,1.390,307.7 -8.8,0.99,0.720,348.2 -9.0,0.99,0.680,66.3 -9.0,0.99,1.080,61.4 -9.1,0.99,0.950,29.7 -8.1,0.99,0.530,29.7 -7.5,0.99,0.860,320.1 -7.5,0.99,1.050,315.4 -7.2,0.99,0.610,327.3 -6.6,0.99,1.120,311.9 -6.0,0.99,0.750,307.2 -5.7,0.99,1.510,309.5 -5.5,0.99,0.910,317.5 -5.2,0.99,1.160,302.9 -4.8,0.99,1.000,306.3 -4.4,0.99,1.190,290.0 -4.3,0.99,1.740,299.9 -5.1,0.99,2.350,308.3 -5.6,0.99,2.530,313.4 -6.3,0.99,2.420,309.9 -6.5,0.99,2.330,314.5 -6.2,0.99,2.100,295.9 -6.7,0.99,2.200,298.2 -8.2,0.99,2.310,288.6 -7.6,0.99,2.160,290.5 -8.6,0.99,2.310,302.2 -8.8,0.99,2.850,293.9 -9.2,0.99,2.760,300.8 -9.2,0.99,0.720,274.2 -9.0,0.99,2.940,279.9 -8.7,0.99,1.880,276.2 -8.0,1.00,1.160,266.0 -7.9,1.00,1.480,209.8 -7.5,1.00,1.560,266.0 -7.5,1.00,1.150,241.8 -7.5,1.00,0.840,195.7 -7.2,1.00,0.980,207.9 -6.8,1.00,1.640,172.8 -6.6,1.00,1.470,173.3 -6.5,1.00,3.040,148.6 -6.6,1.00,1.250,195.1 -6.3,1.00,2.200,150.1 -6.3,1.00,1.900,110.4 -6.7,1.00,1.170,104.0 -6.9,1.00,0.580,126.7 -7.2,1.00,1.450,339.1 -7.5,1.00,1.590,340.4 -7.6,1.00,1.430,288.8 -7.8,1.00,0.860,343.8 -7.8,1.00,0.720,275.9 -7.7,1.00,2.790,337.6 -7.9,1.00,1.860,328.3 -7.9,1.00,2.830,328.4 -7.4,1.00,5.150,355.0 -7.5,1.00,3.760,354.6 -7.1,1.00,5.260,332.3 -6.6,1.00,5.160,344.0 -7.0,1.00,3.950,321.4 -6.1,1.00,4.500,316.4 -6.7,1.00,4.350,312.5 -6.1,1.00,4.470,318.4 -5.9,1.00,4.540,346.9 -6.5,1.00,6.320,350.8 -6.3,1.00,6.680,342.8 -6.2,1.00,5.250,342.4 -5.9,1.00,5.370,331.8 -5.6,1.00,5.330,323.8 -5.7,1.00,4.880,324.4 -6.0,1.00,4.820,318.4 -6.1,1.00,5.040,321.7 -6.1,1.00,3.660,304.9 -6.9,1.00,3.720,316.7 -7.6,1.00,3.680,337.5 -8.2,1.00,3.840,356.8 -8.1,1.00,2.990,326.6 -7.8,1.00,3.770,313.8 -6.9,1.00,4.390,294.9 -6.9,1.00,3.960,286.3 -6.0,1.00,3.150,271.6 -5.4,1.00,2.430,227.7 -5.7,1.00,3.480,209.4 -5.6,0.99,6.040,197.6 -5.7,0.99,5.820,196.7 -6.0,0.99,5.060,197.7 -6.3,0.99,4.110,182.9 -6.8,0.99,2.070,259.4 -7.2,0.99,5.480,318.0 -5.8,0.99,8.190,318.2 -6.1,0.99,6.430,329.2 -6.9,0.99,8.560,306.0 -7.8,0.99,9.670,306.6 -8.3,0.99,10.570,297.5 -9.1,0.99,10.660,304.9 -10.0,0.99,11.110,297.6 -10.9,0.99,12.650,300.4 -11.6,0.99,11.090,305.8 -13.1,0.99,13.210,357.7 -12.8,0.99,13.740,9.1 -12.7,0.99,12.290,28.1 -12.8,0.99,13.010,24.3 -12.4,0.99,12.090,20.1 -11.5,0.99,12.480,16.3 -11.4,0.99,12.970,15.8 -11.2,0.99,11.590,15.7 -11.3,0.99,9.740,11.0 -10.5,0.99,10.030,11.5 -9.7,0.99,9.450,16.6 -8.7,0.99,5.120,348.9 -7.9,0.99,7.090,338.1 -8.1,0.99,10.760,23.8 -9.7,0.99,11.510,13.3 -7.6,0.99,6.820,336.4 -7.7,0.99,7.500,302.1 -10.0,0.99,11.710,1.9 -10.3,0.99,13.600,1.9 -10.7,0.99,11.810,359.2 -11.1,0.99,11.050,348.8 -10.6,0.99,10.640,288.4 -10.2,0.99,11.960,285.9 -10.2,0.99,11.030,291.4 -10.6,0.99,11.410,298.5 -11.4,0.99,10.940,285.2 -11.0,0.99,9.190,294.1 -10.0,0.99,8.090,295.1 -9.9,0.99,8.140,293.1 -9.6,0.99,8.800,288.9 -8.7,0.99,7.130,326.5 -8.6,0.99,6.700,292.9 -8.7,0.99,8.590,296.4 -9.4,0.99,7.340,289.1 -7.8,0.99,6.260,310.1 -8.2,0.99,7.620,305.4 -6.9,0.99,4.290,320.4 -5.1,0.99,1.300,280.8 -6.0,0.99,1.870,274.8 -5.4,0.99,2.490,275.4 -5.8,0.99,2.000,280.2 -5.6,0.99,1.440,161.0 -7.2,0.99,2.570,145.0 -8.3,0.99,3.350,150.5 -8.8,0.99,3.800,161.1 -9.1,0.99,4.080,162.1 -9.8,0.99,4.400,175.8 -10.3,0.99,4.110,185.3 -9.8,0.99,3.810,185.1 -10.1,0.99,4.180,184.2 -9.8,0.99,3.560,184.6 -9.4,0.99,2.990,190.6 -9.4,0.99,1.180,271.1 -9.1,0.99,0.480,191.7 -8.6,0.99,1.540,148.2 -7.8,0.99,3.190,167.4 -7.1,0.99,5.330,171.4 -5.7,0.99,6.130,163.9 -5.0,0.99,6.540,159.2 -4.2,0.99,7.750,154.7 -3.9,0.99,7.260,156.4 -4.2,0.99,7.440,155.3 -3.2,0.99,7.600,153.8 -4.1,0.99,7.260,147.9 -4.2,0.99,7.690,146.8 -5.6,0.99,7.620,143.8 -7.0,0.99,8.190,149.5 -8.0,0.99,7.750,146.9 -9.1,0.99,8.340,145.6 -9.5,0.99,9.380,143.5 -9.7,0.99,9.220,146.4 -10.1,0.99,9.670,145.2 -10.1,0.99,8.680,139.8 -9.5,0.99,9.170,158.3 -8.5,0.99,6.400,170.1 -9.0,0.99,5.900,173.1 -9.2,0.99,5.600,163.1 -8.5,0.99,8.350,155.3 -8.7,0.99,6.340,184.2 -8.5,0.99,7.940,152.8 -8.1,0.99,7.930,169.6 -8.1,0.99,8.760,161.4 -8.0,0.99,8.080,164.0 -7.5,0.99,6.330,167.6 -7.5,0.99,5.210,164.4 -7.8,0.99,5.040,157.0 -7.8,0.99,3.520,149.4 -8.6,1.00,2.490,188.0 -8.5,1.00,3.670,173.9 -9.3,1.00,3.920,175.2 -9.1,1.00,3.820,161.2 -9.5,1.00,3.660,178.3 -10.0,1.00,3.470,184.3 -10.5,1.00,4.440,188.9 -10.6,1.00,4.800,172.0 -10.8,1.00,4.440,193.3 -11.6,1.00,3.940,213.7 -11.5,1.00,2.710,264.1 -10.9,1.00,4.540,271.6 -10.2,1.00,5.230,279.4 -9.8,1.00,4.470,280.2 -9.8,1.00,4.430,272.7 -9.5,1.00,4.600,280.7 -9.8,1.00,3.140,280.6 -9.5,1.00,0.710,266.2 -9.5,1.00,1.110,127.4 -10.3,1.00,0.980,69.1 -8.5,1.00,3.790,145.3 -7.4,1.00,4.200,138.3 -7.7,1.00,3.610,143.2 -8.2,1.00,4.550,158.3 -8.3,1.00,4.850,161.1 -8.7,1.00,5.440,160.4 -8.9,1.00,5.950,152.4 -9.6,1.00,7.110,148.0 -10.3,1.00,7.330,154.9 -11.2,0.99,6.900,147.0 -11.9,0.99,7.420,144.7 -12.3,0.99,7.180,143.0 -12.4,0.99,9.040,144.9 -12.4,0.99,10.420,158.1 -12.1,0.99,7.110,171.9 -12.6,0.99,12.820,157.4 -11.6,0.99,12.940,161.4 -11.9,0.99,13.510,158.6 -12.1,0.98,15.800,169.3 -10.6,0.98,17.280,158.0 -10.0,0.98,19.880,166.1 -9.7,0.98,19.320,161.4 -9.8,0.98,17.560,159.7 -9.6,0.98,17.600,163.5 -9.6,0.98,18.740,166.8 -9.5,0.97,17.700,161.6 -9.7,0.97,15.290,162.5 -10.2,0.97,14.970,174.5 -10.3,0.97,7.730,190.2 -9.8,0.97,10.370,302.1 -7.4,0.98,19.740,283.1 -7.4,0.98,15.330,287.3 -8.0,0.98,17.790,282.1 -8.6,0.98,19.190,281.0 -8.8,0.98,18.520,279.6 -9.0,0.98,20.960,278.1 -8.8,0.98,18.840,275.6 -8.6,0.98,18.980,278.7 -8.4,0.98,17.560,279.8 -8.3,0.98,17.410,277.4 -8.0,0.98,14.990,278.6 -7.8,0.98,14.620,278.8 -7.6,0.99,14.140,280.2 -7.4,0.99,12.700,285.3 -7.2,0.99,12.240,287.3 -7.2,0.99,11.630,293.3 -6.5,0.99,9.090,300.8 -6.6,0.99,11.660,293.0 -7.0,0.99,12.060,298.9 -5.6,0.99,9.100,301.7 -5.6,0.99,7.250,298.6 -6.9,0.99,5.120,338.6 -5.8,0.99,2.480,302.8 -5.5,0.99,2.190,151.4 -5.8,0.99,1.130,86.5 -6.0,0.99,2.360,31.5 -6.3,0.99,2.840,359.3 -6.1,0.99,2.890,356.6 -6.5,0.99,2.490,359.2 -7.0,0.99,2.160,331.3 -7.4,0.99,2.900,357.8 -7.3,0.99,2.200,3.4 -7.2,0.99,2.130,7.7 -6.9,0.99,2.460,13.0 -6.4,0.99,2.070,1.4 -6.1,0.99,0.870,19.5 -5.5,0.99,1.040,53.7 -4.9,0.99,1.080,102.9 -4.7,0.99,1.150,99.9 -4.5,0.99,0.900,103.8 -3.5,0.99,0.460,105.6 -2.4,1.00,0.970,343.8 -1.1,1.00,1.480,348.2 --0.1,1.00,1.900,326.0 --0.2,1.00,1.180,324.7 --0.0,1.00,0.360,14.1 --0.0,1.00,1.570,126.7 -1.7,1.00,3.900,135.2 -3.0,1.00,5.140,140.2 -4.4,1.00,5.350,141.3 -5.4,1.00,6.810,143.4 -6.1,1.00,7.750,142.5 -6.8,0.99,8.630,138.0 -6.8,0.99,8.330,137.2 -6.6,0.99,8.200,135.6 -6.7,0.99,7.960,134.1 -6.8,0.99,8.770,132.7 -6.5,0.99,8.870,137.9 -6.7,0.99,10.910,138.8 -6.4,0.99,11.910,142.2 -5.8,0.99,12.050,141.8 -5.9,0.99,11.310,141.1 -5.8,0.99,10.590,137.1 -6.1,0.99,9.420,134.8 -6.2,0.99,8.890,132.2 -6.4,0.99,7.840,132.7 -6.3,0.99,4.370,158.9 -6.3,0.99,2.330,191.3 -6.0,0.99,2.180,279.6 -5.8,0.99,4.830,254.2 -5.5,0.99,5.140,266.8 -5.5,0.99,5.580,263.7 -5.8,0.99,5.280,274.9 -6.1,0.99,4.270,287.9 -6.3,0.99,2.790,287.9 -6.5,0.99,2.580,312.8 -6.7,0.99,2.130,312.4 -7.0,0.99,2.100,310.5 -6.8,0.99,2.350,313.3 -7.4,1.00,3.170,289.5 -7.4,1.00,3.010,291.4 -7.2,1.00,2.570,298.9 -6.8,1.00,1.180,332.6 -6.9,1.00,0.960,318.9 -7.0,1.00,0.780,346.3 -6.0,1.00,2.340,141.0 -6.5,1.00,2.520,146.6 -7.0,1.00,2.540,144.5 -6.7,1.00,2.460,143.4 -6.5,1.00,2.950,137.5 -6.0,1.00,2.610,135.7 -6.2,1.00,3.050,136.0 -6.4,1.00,3.610,136.0 -6.6,1.00,3.790,143.4 -6.6,1.00,3.730,139.9 -6.8,1.00,4.410,135.8 -6.8,1.00,4.610,134.6 -7.1,1.00,4.490,137.1 -7.6,1.00,6.020,141.4 -7.9,1.00,6.310,139.0 -8.3,1.00,6.170,131.2 -8.4,1.00,5.030,128.0 -8.8,1.00,5.750,130.1 -8.8,1.00,4.840,130.3 -8.5,0.99,5.680,127.7 -8.8,0.99,9.320,151.8 -8.9,0.99,12.120,155.4 -8.6,0.99,12.280,153.1 -8.3,0.99,12.800,145.9 -8.6,0.99,14.560,150.8 -8.6,0.99,13.860,140.8 -9.0,0.99,14.540,145.6 -9.6,0.99,15.030,152.5 -10.1,0.98,14.790,159.2 -10.4,0.98,15.890,155.3 -10.9,0.98,17.310,161.4 -11.8,0.98,17.480,167.5 -11.8,0.98,20.630,170.8 -12.3,0.98,1.590,306.3 -10.9,0.98,0.950,206.4 -11.0,0.98,1.080,125.4 -11.2,0.98,11.560,168.8 -11.2,0.98,13.220,174.3 -10.9,0.98,13.510,181.6 -10.7,0.98,11.950,182.4 -10.5,0.98,9.980,174.5 -10.2,0.98,6.970,183.4 -9.9,0.98,5.280,210.8 -9.9,0.98,7.540,289.7 -8.6,0.98,11.090,282.1 -8.5,0.99,11.500,288.6 -8.7,0.99,14.630,289.9 -8.6,0.99,16.010,284.9 -8.9,0.99,15.400,281.7 -9.1,0.99,16.620,286.7 -9.3,0.99,10.040,275.2 -9.4,0.99,10.950,260.7 -9.1,0.99,6.940,268.5 -9.7,0.99,6.540,219.9 -9.4,0.99,6.380,238.8 -9.2,0.99,5.850,245.7 -9.2,0.99,7.470,269.1 -8.8,0.99,11.450,291.6 -8.9,0.99,11.280,287.4 -8.9,0.99,10.840,295.1 -8.9,1.00,7.200,281.6 -9.1,1.00,5.980,284.2 -9.1,1.00,4.660,273.7 -9.2,1.00,3.050,214.7 -8.9,1.00,3.470,183.6 -9.3,1.00,3.320,181.0 -9.5,1.00,2.830,129.3 -9.1,1.00,3.650,122.0 -8.6,0.99,5.720,114.1 -8.2,1.00,7.010,125.3 -7.5,0.99,6.450,132.3 -6.7,0.99,5.700,149.5 -6.4,1.00,6.250,148.3 -6.4,1.00,7.220,144.2 -6.3,0.99,7.990,147.8 -6.2,0.99,6.590,150.1 -6.1,0.99,6.340,158.6 -5.4,0.99,5.450,158.5 -5.2,0.99,4.660,156.9 -5.2,0.99,3.720,142.1 -5.6,0.99,2.750,147.8 -5.4,0.99,1.910,139.3 -6.5,0.99,1.220,130.9 -6.6,0.99,0.180,295.5 -6.8,0.99,1.860,291.6 -6.4,0.99,2.730,301.1 -7.0,0.99,2.610,321.5 -6.9,0.99,3.070,341.4 -7.0,0.99,3.690,343.2 -6.8,0.99,3.550,318.6 -7.1,0.99,3.330,326.9 -7.0,0.99,3.320,314.4 -7.1,0.99,3.580,309.7 -7.3,0.99,4.070,309.7 -7.1,0.99,4.510,300.6 -7.1,0.99,4.740,300.8 -7.0,0.99,4.690,301.3 -7.0,1.00,3.810,319.3 -6.9,1.00,3.250,308.4 -6.2,1.00,3.470,299.9 -6.0,1.00,4.190,295.7 -6.0,1.00,4.110,302.4 -5.9,1.00,3.650,329.1 -6.4,1.00,4.030,326.4 -6.0,1.00,4.880,327.9 -5.2,1.00,6.270,307.7 -5.1,1.00,6.540,305.6 -5.5,1.00,6.380,303.4 -5.8,1.00,6.360,307.7 -6.3,1.00,5.910,309.8 -6.6,1.00,5.580,312.6 -6.6,1.00,5.210,312.3 -6.9,1.00,5.140,312.8 -7.1,1.00,5.730,306.7 -6.9,1.00,6.500,303.1 -7.0,1.00,6.430,294.5 -7.2,1.00,6.370,289.4 -7.4,1.00,6.410,286.1 -7.4,1.00,6.910,284.7 -7.4,1.00,5.770,285.0 -7.4,1.00,5.900,283.0 -7.2,1.00,5.680,281.7 -7.0,1.00,5.560,280.6 -7.1,1.00,5.240,284.8 -7.0,1.00,4.350,284.0 -7.0,1.00,3.850,290.8 -7.2,1.00,2.180,296.7 -7.5,1.00,1.530,309.9 -7.8,1.00,0.630,42.9 -8.2,1.00,1.110,105.3 -8.8,1.00,2.610,116.7 -10.1,1.00,3.440,130.8 -11.4,0.99,4.690,141.8 -11.4,0.99,3.510,136.4 -11.9,0.99,4.110,139.2 -12.7,0.99,4.670,137.9 -13.1,0.99,4.790,150.1 -13.2,0.99,5.510,159.9 -14.5,0.99,5.820,161.7 -14.6,0.99,6.540,163.6 -14.8,0.99,7.540,167.7 -14.8,0.99,7.950,162.5 -14.0,0.99,7.690,166.7 -14.6,0.99,9.410,170.3 -15.1,0.99,10.860,169.1 -14.5,0.99,10.180,166.1 -14.5,0.99,11.110,161.8 -15.2,0.99,9.740,162.9 -14.3,0.99,12.510,160.0 -12.6,0.99,13.310,170.8 -12.1,0.99,13.450,173.1 -11.2,0.99,13.700,169.4 -10.4,0.99,16.020,161.2 -10.5,0.99,15.010,160.7 -10.6,1.00,13.620,160.2 -11.0,1.00,11.230,160.0 -11.3,1.00,8.780,157.6 -11.8,1.00,7.480,155.0 -12.7,1.00,7.050,161.6 -13.3,1.00,6.030,186.6 -11.8,1.00,1.250,254.3 -12.0,1.00,0.410,247.7 -11.4,1.00,0.980,256.0 -11.9,1.00,4.370,264.7 -11.8,1.00,1.930,278.3 -11.5,1.00,3.300,183.6 -10.6,1.00,3.280,203.8 -10.3,1.00,3.570,191.6 -10.7,1.00,5.720,207.1 -10.9,1.00,2.900,212.4 -10.8,1.00,4.630,202.1 -10.3,1.00,1.000,251.1 -11.5,1.00,1.410,298.7 -11.5,1.00,2.020,14.8 -9.8,1.00,4.080,90.7 -9.1,1.00,3.650,111.0 -9.3,1.00,3.460,128.5 -9.5,1.00,3.360,143.5 -8.9,1.00,4.730,161.7 -9.4,1.00,4.590,153.0 -9.6,1.00,4.820,146.4 -9.5,1.00,5.070,154.0 -9.5,1.00,4.630,158.8 -9.6,1.00,3.630,153.6 -9.9,1.00,4.140,153.7 -9.9,1.00,3.590,146.2 -10.1,1.00,3.580,151.6 -10.2,1.00,2.700,156.6 -10.3,1.00,2.760,156.1 -9.8,1.00,2.870,278.8 -9.8,1.00,3.510,292.2 -10.0,1.00,2.720,284.3 -9.5,1.00,0.810,310.6 -9.9,1.00,1.210,338.7 -10.0,1.00,2.220,279.6 -8.7,1.00,6.030,278.7 -7.9,1.00,5.710,283.4 -7.6,1.00,7.860,287.0 -7.6,1.00,7.950,300.6 -7.2,1.00,6.200,312.0 -6.9,1.00,5.030,327.6 -7.1,1.00,4.130,318.4 -7.0,1.00,4.320,293.6 -7.0,1.00,4.940,292.6 -6.6,1.00,5.500,291.8 -6.8,1.00,5.680,279.9 -6.3,1.00,5.270,291.5 -6.5,0.99,3.850,296.1 -5.6,0.99,4.580,303.8 -5.3,0.99,3.390,278.7 -5.4,0.99,1.520,278.4 -5.7,0.99,0.720,231.1 -5.9,0.99,0.190,250.0 -5.8,0.99,0.920,332.6 -5.9,0.99,0.540,311.0 -6.2,0.99,0.800,259.7 -6.2,0.99,1.770,261.6 -6.0,0.99,1.790,260.4 -5.9,0.99,2.380,272.1 -5.9,0.99,2.000,286.2 -5.4,0.99,1.720,267.3 -6.5,0.99,2.130,183.3 -5.4,0.99,5.950,171.0 -5.3,0.99,7.010,183.7 -5.0,0.99,6.670,170.2 -4.8,0.99,7.260,176.7 -4.7,0.99,8.070,178.7 -4.9,0.98,7.270,172.6 -5.0,0.98,6.320,160.0 -4.7,0.98,6.770,150.5 -5.5,0.98,6.840,152.4 -6.2,0.98,7.890,156.3 -7.3,0.98,8.680,164.7 -7.1,0.97,8.990,168.2 -7.1,0.97,7.920,176.2 -7.9,0.97,6.390,188.1 -8.5,0.97,6.830,250.8 -7.9,0.97,16.170,279.4 -7.9,0.97,21.810,283.9 -7.8,0.97,20.600,282.6 -7.5,0.97,18.730,285.2 -7.2,0.97,18.590,282.5 -7.0,0.97,18.170,282.2 -6.8,0.97,17.480,282.5 -6.6,0.97,16.820,281.2 -6.0,0.97,16.720,282.9 -5.9,0.97,16.730,285.5 -6.1,0.97,16.990,287.6 -6.1,0.97,14.520,281.0 -5.6,0.97,4.400,260.8 -6.2,0.98,5.120,199.0 -6.6,0.98,1.480,207.2 -7.5,0.98,2.790,284.4 -6.4,0.98,5.000,192.9 -6.8,0.98,3.860,192.2 -7.3,0.98,4.480,163.4 -6.8,0.98,4.690,274.7 -6.8,0.98,7.260,256.4 -6.8,0.98,15.060,267.6 -6.8,0.98,11.140,262.3 -6.7,0.98,6.610,217.8 -7.0,0.98,7.490,260.5 -6.5,0.98,5.650,209.7 -5.9,0.98,6.950,180.9 -6.5,0.98,5.730,184.9 -5.7,0.98,4.300,177.5 -5.5,0.98,5.680,165.5 -4.0,0.98,6.640,156.3 -4.2,0.98,7.520,151.5 -4.3,0.98,9.250,152.3 -4.6,0.98,9.310,160.3 -5.2,0.98,7.110,163.3 -5.4,0.98,7.330,180.4 -5.6,0.98,6.820,169.3 -5.9,0.98,7.330,144.4 -7.2,0.98,8.420,148.7 -8.7,0.98,6.780,176.6 -9.8,0.98,1.890,216.6 -10.3,0.98,1.370,254.0 -10.4,0.98,0.920,37.8 -10.0,0.98,2.870,25.0 -9.3,0.98,3.060,25.9 -8.9,0.98,2.340,51.7 -9.0,0.98,3.740,74.3 -9.0,0.98,4.140,55.8 -8.9,0.98,5.560,66.9 -8.9,0.99,5.660,62.3 -8.4,0.99,5.750,58.6 -8.3,0.99,5.900,65.7 -7.8,0.99,5.100,63.8 -7.6,0.99,3.250,65.0 -6.9,0.99,1.320,82.5 -7.0,0.98,1.870,82.8 -6.9,0.99,0.970,133.6 -6.2,0.98,1.580,127.0 -6.2,0.98,2.010,134.4 -5.6,0.98,2.010,111.7 -5.8,0.98,1.770,150.5 -6.4,0.98,1.570,170.9 -6.8,0.98,2.190,166.1 -7.3,0.98,2.090,124.6 -8.1,0.98,6.020,137.5 -8.3,0.98,7.530,137.6 -8.9,0.98,8.950,156.2 -9.6,0.98,7.480,175.2 -9.6,0.98,8.320,168.5 -9.6,0.98,12.790,169.3 -8.0,0.98,4.830,215.5 -7.4,0.98,6.280,259.0 -7.4,0.98,5.750,282.6 -6.8,0.98,6.630,317.4 -6.4,0.98,7.730,331.5 -6.1,0.98,7.670,331.3 -6.1,0.98,5.630,331.7 -6.1,0.98,7.200,331.2 -6.2,0.98,6.640,330.5 -6.7,0.98,14.680,296.6 -6.3,0.98,15.890,300.3 -6.0,0.98,15.170,299.9 -6.1,0.98,14.880,294.0 -5.9,0.98,11.680,301.9 -5.7,0.98,9.050,307.8 -5.7,0.98,7.180,335.6 -5.1,0.98,6.460,345.2 -4.8,0.99,6.980,339.4 -4.8,0.99,10.090,339.2 -4.6,0.99,8.110,350.6 -4.5,0.99,8.830,346.5 -4.1,0.99,7.100,353.2 -3.8,0.99,8.990,356.1 -3.2,0.99,10.380,356.0 -2.6,0.99,13.400,346.9 -2.4,0.99,13.630,338.5 -2.3,0.99,13.400,345.9 -2.0,0.99,13.210,341.9 -1.4,0.99,11.700,331.6 -1.0,0.99,11.280,329.2 -1.4,0.99,11.750,340.2 -0.7,1.00,10.310,346.4 -0.6,1.00,11.090,342.7 -0.6,1.00,9.560,336.9 --0.3,1.00,8.350,344.8 --0.8,1.00,6.480,304.1 --0.9,1.00,6.950,291.5 --0.7,1.00,8.330,290.1 --0.6,1.00,8.960,291.8 --0.8,1.00,7.680,304.2 -0.7,1.00,6.860,339.9 -1.3,1.00,7.470,350.7 -1.7,1.00,7.150,353.4 -1.8,1.00,5.920,351.1 -1.8,1.00,5.080,349.3 -1.9,1.00,4.940,338.6 -1.9,1.00,4.900,326.9 -1.9,1.00,5.060,332.0 -2.0,1.01,5.390,320.4 -2.0,1.01,4.610,322.7 -1.7,1.01,4.280,343.8 -1.8,1.01,5.230,348.2 -1.8,1.01,6.230,350.8 -1.5,1.01,7.240,356.6 -1.5,1.01,6.720,352.0 -1.7,1.01,7.000,349.6 -1.4,1.01,7.510,342.3 -1.6,1.01,6.830,326.4 -0.2,1.01,7.770,350.0 --0.2,1.01,6.550,350.8 --0.3,1.01,7.510,352.2 --0.1,1.01,7.790,3.3 --0.2,1.01,5.600,353.9 -0.6,1.01,5.420,341.5 -0.3,1.01,4.250,336.7 -0.6,1.01,4.130,332.4 -0.8,1.01,3.730,328.3 -1.2,1.01,3.800,325.6 -1.5,1.01,4.170,325.4 -1.8,1.01,3.630,327.8 -1.8,1.01,3.970,338.1 -1.9,1.01,3.700,339.4 -2.0,1.01,3.860,334.2 -1.9,1.01,4.150,342.8 -1.7,1.01,4.400,341.8 -1.7,1.01,4.110,337.9 -1.4,1.01,4.550,340.2 -0.8,1.01,4.860,341.9 -0.9,1.01,4.870,350.1 -0.8,1.01,5.370,348.5 -0.4,1.01,5.420,329.2 -0.6,1.01,6.090,326.6 -1.0,1.01,5.000,315.4 -0.7,1.01,5.700,307.0 -0.6,1.01,5.060,336.5 -1.0,1.01,5.090,314.3 -0.9,1.01,4.440,311.1 -1.0,1.01,3.380,312.3 -1.1,1.01,4.070,303.4 -1.6,1.01,3.680,307.3 -1.8,1.01,3.320,310.5 -2.0,1.01,2.950,316.2 -2.1,1.01,3.030,304.8 -2.8,1.01,2.260,330.8 -3.5,1.01,1.440,0.2 -3.6,1.01,1.800,332.4 -3.5,1.01,3.130,304.5 -3.4,1.01,2.330,329.4 -3.5,1.01,1.670,339.0 -3.4,1.01,2.060,329.9 -3.2,1.01,2.400,322.3 -2.8,1.01,2.530,311.2 -2.8,1.01,1.670,323.4 -2.7,1.00,1.230,347.9 -2.7,1.00,0.570,37.9 -2.3,1.00,0.630,8.9 -1.6,1.00,1.200,343.7 -1.8,1.00,1.340,339.0 -1.4,1.00,1.270,345.2 -1.4,1.00,1.180,315.6 -1.8,1.00,1.040,356.7 -1.7,1.00,0.600,28.5 -2.5,1.00,0.860,115.8 -3.6,1.00,1.160,64.7 -3.7,1.00,1.380,321.4 -3.8,1.00,1.560,311.8 -5.2,1.00,1.010,348.8 -5.5,1.00,0.890,7.8 -5.3,1.00,1.780,333.9 -5.0,1.00,2.020,315.7 -5.0,1.00,2.830,319.4 -5.1,1.00,2.570,309.9 -5.4,1.00,2.390,310.8 -5.2,1.00,2.770,309.4 -5.1,1.00,3.130,308.3 -5.0,1.00,2.980,311.5 -4.9,1.00,2.500,316.4 -4.8,1.00,2.080,315.9 -4.2,1.00,3.100,304.6 -4.0,1.00,4.040,301.5 -3.2,1.00,3.330,314.2 -3.7,1.00,2.620,315.2 -3.5,1.00,2.810,311.8 -3.5,1.00,3.280,302.8 -3.6,1.00,3.180,309.9 -3.6,1.00,2.920,307.5 -3.5,1.00,3.430,298.8 -3.9,1.00,3.010,302.3 -4.2,1.00,3.010,303.7 -4.7,1.00,2.200,315.9 -5.2,1.00,1.560,318.9 -5.5,1.00,0.750,319.8 -5.8,1.00,0.430,341.5 -5.7,1.00,0.790,114.5 -5.0,1.00,1.910,125.7 -4.9,1.00,2.960,138.4 -4.5,1.00,3.130,147.2 -4.3,1.00,3.140,142.9 -4.2,1.00,4.000,141.6 -4.2,1.00,5.290,142.7 -3.6,1.00,6.630,145.4 -3.4,1.00,7.190,146.2 -3.3,1.00,7.120,153.3 -3.1,1.00,6.690,159.9 -3.4,1.00,6.150,160.1 -3.6,1.00,5.560,163.3 -4.0,1.00,5.170,162.3 -4.4,1.00,6.430,153.1 -4.0,1.00,5.920,155.3 -3.5,1.00,4.910,156.2 -3.6,1.00,2.330,173.0 -4.7,1.00,2.010,159.6 -5.4,1.00,1.160,149.5 -5.8,1.00,0.630,207.0 -6.0,1.00,0.520,207.3 -6.0,1.00,0.500,170.4 -6.1,1.00,0.640,152.6 -5.6,1.00,1.020,149.6 -5.0,1.00,1.050,139.2 -5.1,1.00,0.850,140.9 -5.1,1.00,0.420,161.5 -5.4,1.00,0.560,161.5 -5.3,1.00,0.650,125.5 -4.6,1.00,0.110,70.0 -4.5,1.00,0.090,94.1 -4.1,1.00,0.790,112.9 -4.0,1.00,1.930,128.8 -4.1,1.00,3.300,145.5 -3.2,1.00,4.360,144.5 -3.3,1.00,6.200,149.0 -2.1,1.00,6.190,151.2 -2.1,1.00,5.830,151.2 -2.4,1.00,7.510,145.3 -2.2,1.00,7.680,152.5 -4.8,1.00,7.920,167.7 -4.9,1.00,4.190,151.1 -6.2,1.00,5.580,166.9 -5.3,1.00,4.220,173.9 -5.8,1.00,3.140,156.0 -7.1,1.00,6.870,157.3 -7.4,0.99,6.120,157.2 -6.8,0.99,5.140,164.4 -6.2,0.99,2.610,181.8 -5.9,0.99,1.980,185.5 -5.2,1.00,1.160,253.7 -5.5,0.99,1.110,278.1 -5.6,0.99,0.180,347.5 -5.5,0.99,0.840,310.2 -5.7,0.99,0.380,63.8 -4.4,0.99,0.950,25.3 -6.0,0.99,0.850,77.1 -5.7,0.99,0.330,53.3 -5.1,0.99,0.330,36.3 -5.9,0.99,0.580,41.9 -6.2,0.99,0.890,4.0 -6.5,0.99,1.030,323.3 -6.9,0.99,1.150,309.7 -7.1,0.99,0.960,302.3 -6.8,0.99,1.130,324.9 -6.8,0.99,0.680,338.6 -6.6,0.99,1.640,307.6 -7.0,0.99,1.220,298.5 -8.0,0.99,1.200,271.9 -8.5,0.99,0.630,258.9 -8.1,0.99,0.580,306.8 -8.6,0.99,0.620,66.0 -8.5,0.99,1.070,113.1 -7.9,0.99,0.690,105.0 -7.9,0.99,0.120,204.2 -7.8,0.99,0.080,80.5 -8.5,0.99,1.090,147.1 -7.9,0.99,2.050,158.7 -7.6,0.99,2.680,159.7 -7.0,0.99,2.390,151.2 -7.0,0.99,3.100,150.5 -6.5,0.99,3.480,148.4 -5.9,0.99,3.670,152.5 -5.7,0.99,4.330,148.8 -5.0,0.99,5.560,146.6 -5.5,0.99,5.610,160.9 -5.7,0.99,5.600,162.9 -5.7,0.99,4.680,164.0 -6.2,0.99,4.660,166.1 -6.0,0.99,3.970,157.8 -6.4,0.99,3.710,158.2 -6.8,0.99,3.900,157.7 -7.5,0.99,4.640,160.3 -7.9,0.99,5.010,154.8 -7.7,0.99,4.480,158.0 -7.7,0.99,4.260,165.2 -8.0,1.00,4.670,172.2 -7.7,1.00,3.120,177.6 -8.0,1.00,2.740,168.9 -7.9,1.00,3.750,165.2 -8.1,1.00,4.070,167.4 -7.6,1.00,5.520,168.6 -6.1,1.00,6.450,161.6 -5.3,1.00,6.550,157.8 -5.6,1.00,6.820,168.7 -5.4,1.00,6.610,162.6 -4.9,1.00,6.160,159.8 -4.6,1.00,6.210,157.3 -3.9,1.00,6.030,153.8 -3.7,1.00,4.160,150.0 -3.8,1.00,4.270,140.5 -4.0,1.00,4.190,136.6 -4.5,1.00,4.680,140.0 -5.1,1.00,4.370,140.6 -5.6,1.00,4.130,147.3 -5.8,1.00,4.740,154.1 -5.9,1.00,5.710,161.3 -6.8,1.00,6.100,175.3 -7.5,1.00,5.650,181.1 -7.6,1.00,4.820,192.7 -7.7,1.00,3.630,191.6 -7.4,1.00,2.590,173.8 -6.5,1.00,2.690,130.8 -6.3,1.00,3.740,137.8 -6.0,1.00,4.010,146.4 -5.7,1.00,5.920,145.1 -5.6,1.00,6.740,149.0 -5.6,1.00,7.330,153.6 -5.7,1.00,7.700,151.6 -5.4,1.00,7.860,158.4 -5.6,1.00,8.560,151.4 -5.5,1.00,9.860,152.5 -5.5,1.00,10.300,152.3 -5.5,1.00,10.350,150.0 -5.8,1.00,11.670,149.1 -6.1,1.00,11.690,146.8 -6.4,1.00,11.290,145.2 -6.8,1.00,11.260,142.6 -7.3,1.00,10.240,142.5 -7.5,1.00,9.700,142.0 -7.6,1.00,9.390,139.7 -7.7,1.00,7.740,151.7 -8.3,1.00,4.760,161.6 -7.7,1.00,6.430,148.6 -7.5,1.00,5.590,163.4 -7.9,1.00,3.710,158.9 -8.5,0.99,8.040,161.7 -8.7,0.99,8.730,150.6 -8.8,0.99,6.290,172.0 -9.1,0.99,4.750,182.7 -9.2,0.99,6.950,155.8 -9.2,0.99,8.420,158.5 -9.2,0.99,7.970,157.3 -9.4,0.99,8.320,172.3 -9.1,0.99,5.460,198.5 -9.5,0.99,2.980,251.5 -10.9,0.99,8.140,262.3 -10.2,0.99,7.600,294.4 -10.1,0.99,3.020,246.5 -10.1,0.99,4.130,252.3 -9.6,0.99,15.950,289.3 -8.8,0.99,15.050,289.0 -8.8,0.99,13.100,274.5 -9.5,0.99,8.030,279.9 -9.7,0.99,10.870,297.4 -8.1,0.99,13.260,294.1 -8.1,0.98,9.230,291.5 -8.8,0.98,11.270,286.9 -8.4,0.98,12.700,288.1 -7.9,0.98,13.430,289.8 -7.3,0.98,12.930,284.0 -6.4,0.98,15.730,268.8 -6.2,0.98,12.820,280.6 -5.2,0.98,14.180,276.4 -4.4,0.98,11.950,264.0 -4.4,0.98,8.900,258.6 -3.9,0.98,12.970,264.4 -4.3,0.98,11.540,266.4 -3.7,0.98,10.260,259.9 -3.2,0.98,7.970,277.3 -3.1,0.98,9.720,275.2 -3.5,0.98,10.830,274.6 -3.1,0.99,9.920,284.9 -3.5,0.99,7.730,284.9 -3.7,0.99,8.800,295.8 -4.2,0.99,8.030,307.4 -3.9,0.99,5.390,306.9 -3.6,0.99,5.220,319.8 -3.6,0.99,9.460,355.0 -3.4,0.99,8.570,342.9 -2.9,0.99,8.850,324.7 -2.6,0.99,7.720,335.2 -2.4,0.99,9.910,352.6 -2.0,0.99,11.030,357.4 -1.7,0.99,11.950,8.8 -1.5,0.99,12.810,4.1 -1.6,0.99,12.480,17.1 -1.0,0.99,9.170,9.2 -1.0,0.99,7.580,9.3 -0.6,0.99,2.560,328.5 -0.4,1.00,8.780,270.3 -0.6,1.00,4.240,281.8 --0.0,1.00,5.200,314.3 --0.9,1.00,8.070,290.8 --0.7,1.00,5.220,303.9 --0.3,1.00,9.030,282.9 --0.8,1.00,8.670,294.1 --0.5,1.00,11.860,283.6 --0.2,1.00,9.320,316.0 -0.3,1.00,9.680,308.1 -0.6,1.00,9.280,301.3 -0.7,1.00,10.830,297.7 -0.8,1.00,10.890,292.3 -1.1,1.00,10.180,290.3 -1.3,1.00,10.800,280.5 -1.4,1.00,11.350,278.6 -1.2,1.00,10.640,281.7 -0.9,1.00,9.100,286.4 -0.2,1.00,7.580,285.8 -0.2,1.00,7.520,281.5 -0.3,1.00,9.520,273.3 -0.1,1.00,7.490,292.7 --0.7,1.00,5.430,295.1 --0.8,1.00,3.900,310.4 --0.9,1.00,3.090,303.8 --1.2,1.00,3.180,285.6 --1.7,1.00,2.870,281.1 --1.6,1.00,3.220,277.3 --1.8,1.00,3.410,286.4 --2.0,1.00,3.470,291.9 --2.1,1.00,3.720,287.3 --2.1,1.00,3.970,283.3 --1.9,1.00,3.550,294.9 --1.5,1.00,2.900,306.0 --1.4,1.00,2.350,322.0 --1.1,1.00,2.890,320.9 --0.8,1.00,2.800,333.5 --0.7,1.00,2.850,351.2 --1.4,1.00,2.250,327.6 --1.1,1.00,3.970,306.1 --0.5,1.00,5.140,290.8 --0.7,1.00,5.680,288.6 --1.6,1.00,6.040,284.3 --2.1,1.00,5.330,279.5 --1.8,1.00,5.230,287.5 --2.8,1.00,5.460,277.1 --3.9,1.00,6.140,278.8 --4.4,1.00,5.950,284.9 --4.4,1.00,4.870,289.7 --4.2,1.00,3.820,285.1 --4.5,1.00,2.800,267.9 --4.0,1.00,2.010,254.5 --3.9,1.00,1.810,238.5 --3.4,1.00,2.080,191.2 --3.1,1.00,3.560,176.3 --3.3,1.00,4.420,170.0 --3.1,1.00,4.760,159.8 --2.4,1.00,4.320,146.7 --2.0,1.00,3.610,144.6 --1.8,1.00,2.920,142.2 --1.6,1.00,2.490,131.3 --1.5,1.00,1.950,121.3 --1.2,1.00,2.060,107.5 --1.0,1.00,3.120,93.2 --1.2,1.00,4.080,99.1 --1.3,1.00,3.690,109.9 --1.5,1.00,4.280,111.5 --1.2,1.00,5.770,126.9 --1.7,1.00,7.690,129.4 --2.1,1.00,7.220,124.7 --2.1,1.00,5.940,122.4 --3.0,1.00,3.120,96.3 --2.9,1.00,4.100,100.9 --3.0,1.00,4.880,100.8 --2.1,1.00,6.440,108.3 --2.3,1.00,3.540,85.0 --2.8,1.00,4.880,100.1 --3.5,1.00,4.310,78.9 --4.2,1.00,4.010,74.8 --4.1,1.00,4.290,63.8 --3.6,1.00,2.530,50.2 --3.3,1.00,3.950,351.4 --3.1,1.00,6.900,7.0 --3.0,1.00,5.600,14.6 --2.9,1.00,5.570,21.4 --3.1,1.00,3.440,39.9 --2.6,1.00,1.620,155.3 --3.6,1.00,4.970,6.6 --5.8,1.00,4.910,59.7 --6.3,1.00,3.430,35.1 --6.6,1.00,3.280,28.6 --6.5,1.00,3.930,26.1 --6.0,1.00,6.310,22.7 --7.8,1.00,6.820,358.9 --9.3,1.00,8.120,281.5 --7.3,1.00,3.880,297.9 --8.9,1.00,6.770,351.7 --9.3,1.00,5.370,316.9 --8.6,1.00,8.560,285.6 --7.8,1.00,6.940,287.4 --8.6,1.00,3.540,288.6 --8.5,1.00,8.700,281.8 --9.1,1.00,8.540,290.3 --9.2,1.00,5.420,320.1 --9.3,1.00,7.300,283.6 --6.7,1.00,6.110,294.3 --5.9,1.00,6.860,303.7 --5.4,1.00,6.810,354.2 --4.4,1.00,10.340,353.4 --4.1,1.00,7.290,345.4 --4.3,1.00,5.320,294.6 --5.3,1.00,6.100,297.9 --5.9,1.00,5.240,325.0 --4.8,1.00,5.000,358.2 --4.9,1.00,3.670,22.6 --5.2,1.00,1.820,354.0 --4.8,1.00,2.690,1.1 --5.4,1.00,2.540,354.2 --5.7,1.00,2.230,354.1 --5.9,1.01,1.490,352.4 --6.6,1.01,0.690,42.6 --6.6,1.01,0.810,121.1 --6.1,1.01,1.210,135.9 --6.1,1.01,1.600,140.1 --6.6,1.01,2.600,146.2 --7.1,1.01,3.280,143.6 --7.2,1.01,3.860,141.5 --7.1,1.01,3.870,143.1 --7.1,1.01,3.310,135.9 --6.1,1.01,3.130,134.0 --5.4,1.01,2.790,137.3 --5.0,1.01,2.730,135.3 --4.7,1.01,2.670,138.9 --4.4,1.01,2.600,143.2 --3.6,1.01,3.290,134.6 --3.7,1.01,3.620,149.4 --3.7,1.01,3.750,154.4 --4.1,1.01,4.010,156.0 --4.0,1.01,4.150,158.7 --4.8,1.01,4.930,151.5 --5.4,1.01,5.590,149.4 --5.3,1.01,6.420,153.6 --5.6,1.01,6.790,155.6 --6.0,1.01,7.450,154.3 --6.2,1.01,7.930,157.4 --6.2,1.01,7.610,161.0 --6.5,1.01,6.990,159.9 --6.5,1.01,7.070,157.4 --6.3,1.01,7.620,157.2 --6.1,1.01,7.830,156.0 --5.9,1.01,7.820,160.3 --5.1,1.01,8.880,154.5 --4.1,1.01,7.800,150.6 --3.2,1.01,8.130,153.3 --3.0,1.01,8.130,151.0 --2.8,1.01,7.890,148.8 --2.7,1.01,8.000,148.4 --2.5,1.01,7.720,149.3 --2.5,1.01,7.530,152.1 --2.6,1.01,7.210,152.5 --2.7,1.01,6.640,152.9 --2.6,1.01,6.170,155.6 --2.6,1.01,5.730,154.7 --2.6,1.01,5.330,158.4 --2.6,1.01,5.450,157.2 --2.6,1.01,5.850,157.1 --2.6,1.01,5.990,160.8 --2.5,1.01,6.080,155.7 --2.4,1.01,5.970,155.5 --2.3,1.01,5.910,153.4 --2.1,1.01,5.980,151.4 --2.0,1.01,5.980,149.6 --2.0,1.01,5.750,149.0 --2.0,1.01,5.840,147.7 --1.9,1.01,5.870,147.1 --1.7,1.01,5.860,146.8 --1.3,1.01,6.020,145.0 --0.9,1.01,5.960,140.0 --0.5,1.01,5.670,141.7 --0.2,1.01,5.360,144.2 -0.2,1.01,5.490,142.2 -0.8,1.01,5.470,142.3 -0.4,1.01,5.440,149.0 -0.3,1.00,5.370,150.3 -0.6,1.00,5.600,157.0 -0.8,1.00,5.270,165.8 -0.3,1.00,4.090,162.9 -0.0,1.00,3.270,152.4 -0.1,1.00,3.360,145.7 -0.1,1.00,3.590,145.4 -0.4,1.00,3.700,142.7 -0.2,1.00,3.670,140.2 --0.0,1.00,3.720,138.7 -0.1,1.00,3.760,140.4 -0.3,1.00,3.590,132.3 -0.0,1.00,3.400,131.9 -0.0,1.00,3.580,133.4 --0.4,1.00,3.060,132.5 -0.0,1.00,2.510,139.6 -0.3,1.00,2.760,148.2 -0.3,1.00,3.220,143.6 -0.8,1.00,4.060,141.0 -1.5,1.00,4.890,139.1 -1.5,1.00,5.660,139.4 -1.4,1.00,4.110,145.1 -1.2,1.00,3.650,158.1 -1.4,1.00,2.730,160.7 -0.7,1.00,1.530,164.1 -0.6,1.00,0.720,209.7 -0.7,1.00,1.410,140.9 -1.1,1.00,1.670,138.0 -0.8,1.00,1.770,134.0 -1.4,1.00,2.650,131.2 -2.1,1.00,3.180,137.2 -1.9,1.00,2.760,147.2 -1.7,0.99,2.200,152.8 -1.5,0.99,3.290,170.9 -0.9,0.99,3.240,186.6 -1.7,0.99,5.840,176.7 -1.1,0.99,3.160,166.6 -2.1,0.99,3.030,164.3 -3.4,0.99,5.020,172.3 -4.1,0.99,13.110,159.9 -4.7,0.99,17.090,164.1 -4.6,0.99,15.820,172.1 -4.4,0.99,14.460,168.7 -4.6,0.99,14.070,168.5 -4.8,0.99,12.850,166.3 -5.0,0.99,11.740,166.9 -4.7,0.99,10.480,159.8 -4.3,0.99,9.700,158.2 -4.3,0.99,9.000,154.4 -4.4,0.99,8.550,147.2 -4.3,0.99,10.630,149.1 -4.3,0.99,9.990,158.9 -4.3,0.99,9.120,149.0 -4.3,0.99,8.450,149.9 -4.3,0.99,7.000,159.2 -3.3,1.00,5.280,167.6 -2.9,1.00,4.590,151.1 -1.9,1.00,4.370,159.7 -3.4,1.00,5.970,157.1 -3.5,1.00,5.490,155.0 -3.7,1.00,4.980,151.2 -3.6,1.00,4.940,153.2 -3.5,1.00,5.370,150.4 -3.5,1.00,6.250,150.5 -3.6,1.00,5.920,157.3 -3.6,1.00,6.350,159.3 -3.9,1.00,6.180,158.4 -3.9,1.00,6.120,156.9 -4.0,1.00,5.860,156.2 -4.5,1.00,5.930,149.0 -4.4,1.00,6.120,155.2 -4.4,1.00,5.540,149.9 -5.0,1.00,5.550,151.7 -5.1,1.00,4.090,146.6 -5.3,1.00,3.740,139.4 -5.3,1.00,3.000,156.3 -5.2,1.00,2.720,166.7 -5.0,1.00,3.610,159.7 -4.9,1.00,3.970,158.8 -5.2,1.00,4.620,158.4 -5.6,1.00,5.750,164.3 -5.6,1.00,5.940,156.4 -5.6,1.00,6.570,166.8 -5.9,1.00,8.700,153.6 -6.0,1.00,8.310,160.3 -6.1,1.00,9.230,156.7 -6.0,1.00,8.240,155.3 -6.0,1.00,7.340,168.3 -5.5,1.00,4.560,195.7 -5.9,1.00,6.880,159.2 -6.0,1.00,7.670,159.2 -6.2,1.00,10.350,157.8 -6.7,1.00,9.790,156.5 -6.8,1.00,11.650,157.8 -6.8,1.00,10.080,159.4 -6.9,1.00,8.520,183.0 -6.8,1.00,9.230,161.9 -6.7,1.00,10.790,141.5 -6.5,1.00,12.350,152.3 -6.5,1.00,12.320,148.3 -6.5,1.00,12.430,150.5 -6.5,1.00,9.720,161.6 -6.8,1.00,8.880,159.0 -6.7,1.00,6.670,159.3 -5.2,1.00,4.590,168.1 -4.5,1.00,3.350,165.8 -4.2,1.00,3.140,166.0 -5.2,1.00,4.470,152.0 -5.2,1.00,4.090,153.6 -5.9,1.00,4.340,143.7 -5.7,1.00,3.660,128.1 -5.9,1.00,5.420,120.3 -5.6,1.00,5.690,128.9 -5.4,1.00,5.280,128.0 -5.3,1.00,5.590,134.7 -6.5,1.00,5.560,140.1 -7.0,1.00,6.100,150.8 -7.3,1.00,5.410,151.4 -7.7,1.00,4.330,143.3 -7.3,1.00,3.890,141.1 -7.3,1.00,3.510,139.1 -7.5,1.00,2.170,163.7 -8.7,1.00,2.410,198.1 -7.4,1.00,1.300,64.5 -8.1,1.00,1.930,60.8 -8.1,1.01,0.350,39.3 -7.2,1.01,2.460,327.3 -5.8,1.01,2.130,309.5 -5.6,1.01,1.540,241.1 -5.4,1.01,3.890,179.9 -6.2,1.01,5.000,187.4 -6.5,1.01,4.380,184.3 -6.0,1.01,4.150,171.7 -5.8,1.01,4.470,176.7 -6.0,1.01,4.650,174.3 -6.4,1.01,5.800,189.9 -6.4,1.01,5.080,179.8 -6.4,1.01,6.280,167.3 -6.7,1.01,6.970,149.6 -6.2,1.01,6.570,146.2 -6.1,1.00,4.810,138.8 -6.2,1.00,4.090,129.8 -6.3,1.00,2.660,141.7 -6.6,1.00,2.550,113.0 -6.1,1.00,1.380,70.8 -4.7,1.00,1.090,348.1 -3.1,1.00,2.360,322.1 -2.9,1.00,3.170,302.5 -2.7,1.00,3.540,296.6 -2.4,1.00,2.970,306.4 -2.8,1.00,2.740,317.5 -2.4,1.00,2.510,292.3 -2.4,1.00,1.270,296.4 -2.3,1.00,0.910,277.1 -2.3,1.00,0.810,221.3 -2.4,1.00,1.330,171.8 -2.1,1.00,2.010,166.3 -1.8,1.00,3.210,165.8 -1.9,1.00,4.160,163.0 -1.8,1.00,4.650,162.4 -1.6,1.00,6.160,155.4 -1.4,1.00,8.720,154.1 -1.3,1.00,8.850,152.0 -1.2,1.00,8.410,154.1 -1.3,1.00,6.800,157.8 -1.5,0.99,7.440,152.7 -1.6,0.99,7.040,151.8 -2.0,0.99,7.270,149.2 -2.4,0.99,6.640,146.8 -2.6,0.99,4.110,149.7 -3.3,0.99,4.040,145.4 -3.7,0.99,4.470,148.9 -4.2,0.99,4.450,157.3 -5.2,0.99,3.080,197.2 -5.2,0.99,3.290,246.3 -5.4,0.99,3.900,271.4 -5.4,0.99,7.410,297.0 -5.6,0.99,10.140,305.1 -5.8,0.99,10.810,303.0 -5.9,0.99,9.280,294.1 -5.3,0.99,6.350,299.3 -5.3,0.99,8.430,295.2 -4.4,0.99,5.630,320.8 -4.4,0.99,5.890,327.7 -4.3,0.99,5.220,327.6 -4.7,0.99,6.050,333.9 -4.0,0.99,7.220,324.1 -4.0,0.99,4.180,300.3 -4.3,0.99,3.900,357.9 -4.7,0.99,3.120,335.4 -4.3,0.99,1.590,347.8 -3.4,0.99,5.910,5.9 -3.1,0.99,9.660,333.9 -2.4,0.99,10.030,316.1 -2.5,0.99,10.500,331.8 -2.3,1.00,8.550,7.5 -2.9,1.00,10.890,359.1 -1.8,1.00,10.550,4.4 -1.8,1.00,11.040,7.8 -1.9,1.00,11.310,10.8 -2.5,1.00,12.380,18.4 -2.3,1.00,14.450,20.1 -0.4,1.00,12.100,351.7 -0.5,1.00,12.970,336.1 -0.1,1.00,9.370,327.0 --0.7,1.00,9.210,314.6 --0.7,1.00,8.990,354.1 --0.3,1.00,8.470,2.3 --0.2,1.00,6.180,351.6 -0.3,1.00,4.690,343.2 -0.5,1.00,3.700,347.8 -0.7,1.00,3.410,349.1 -0.3,1.00,1.470,326.9 -0.6,1.00,1.050,286.8 -1.1,1.00,0.440,305.6 -1.3,1.00,1.030,145.6 -1.0,1.00,2.380,159.9 -0.9,1.00,4.150,169.6 -1.0,1.00,5.000,171.4 -1.2,1.00,5.890,171.4 -1.0,1.00,6.170,164.5 -1.2,1.00,6.180,161.1 -0.6,1.00,4.540,141.5 -1.3,1.00,6.190,161.7 -0.3,1.00,6.920,143.7 -0.2,0.99,9.870,141.2 --0.9,0.99,10.980,146.9 --1.9,0.99,12.510,140.3 --1.8,0.99,14.710,155.3 --1.5,0.99,14.780,144.7 --1.3,0.99,14.740,138.2 --1.1,0.99,16.180,142.7 --0.8,0.99,15.020,138.3 --0.7,0.98,13.050,144.4 --0.2,0.98,11.880,148.4 -0.5,0.98,10.030,147.5 -0.7,0.98,9.650,143.8 -1.0,0.98,9.250,145.7 -1.3,0.98,8.020,146.8 -1.7,0.98,6.280,149.3 -1.9,0.98,4.670,164.5 -2.2,0.98,2.260,183.4 -1.7,0.98,1.980,283.0 -1.7,0.98,3.140,287.5 -2.0,0.98,3.660,292.4 -1.8,0.99,2.120,349.2 -2.2,0.99,1.780,352.6 -2.1,0.99,2.570,352.6 -1.8,0.99,2.410,18.1 -1.6,0.99,3.270,13.3 -1.9,0.99,2.940,13.2 -1.6,0.99,2.210,348.6 -1.7,0.99,2.380,330.3 -2.3,0.99,2.810,359.3 -2.8,0.99,3.870,4.8 -2.6,0.99,3.120,15.0 -2.0,0.99,1.860,48.7 -1.7,0.99,1.810,109.1 -1.9,0.99,1.090,101.3 -2.2,0.99,1.720,68.5 -2.5,0.99,1.350,88.5 -2.8,0.99,0.970,103.9 -3.1,1.00,2.400,128.0 -3.2,1.00,3.010,147.5 -3.1,1.00,3.240,155.8 -2.9,1.00,3.800,155.2 -3.1,1.00,3.750,156.3 -3.5,1.00,3.600,155.7 -3.6,1.00,4.210,160.8 -4.1,1.00,3.740,156.8 -3.7,1.00,4.600,158.5 -3.8,1.00,4.300,157.0 -3.8,1.00,4.580,150.8 -3.8,1.00,4.600,146.4 -3.7,1.00,4.510,140.9 -4.0,1.00,4.820,139.0 -4.2,1.00,4.930,143.2 -4.0,1.00,4.150,148.2 -3.8,1.00,3.490,147.8 -4.1,1.00,3.810,147.7 -4.2,1.00,3.000,146.8 -4.2,1.00,2.990,147.8 -4.7,1.00,3.450,146.7 -5.0,1.00,3.500,145.8 -5.4,1.00,3.790,148.3 -5.5,1.00,4.250,154.6 -5.6,1.00,4.570,157.3 -5.5,1.00,4.440,154.8 -5.4,1.00,4.390,146.7 -5.3,1.00,4.830,138.0 -5.3,1.00,4.350,135.0 -5.5,1.00,3.650,131.2 -5.9,1.00,2.230,158.5 -6.1,1.00,3.790,169.5 -6.8,1.00,3.620,161.7 -7.8,1.00,7.470,143.8 -8.1,1.00,9.460,169.1 -7.8,1.00,5.840,177.8 -8.4,1.00,8.380,159.0 -8.6,0.99,7.650,167.1 -8.1,1.00,1.740,283.4 -6.9,1.00,1.710,255.2 -7.6,1.00,0.680,234.1 -7.6,1.00,0.390,135.9 -8.4,1.00,6.600,278.5 -7.2,1.00,16.950,291.8 -7.3,1.00,20.360,289.2 -7.8,1.00,19.080,289.0 -7.8,1.00,16.580,281.9 -7.4,1.00,10.850,281.9 -7.4,1.00,15.180,284.3 -7.0,1.00,15.870,280.9 -6.9,1.00,15.600,280.7 -6.9,1.00,15.660,282.2 -6.8,1.00,13.480,284.0 -6.4,1.01,10.430,284.6 -5.9,1.01,7.420,278.0 -5.6,1.01,4.210,233.5 -3.7,1.01,5.450,195.1 -3.9,1.01,6.640,187.9 -4.5,1.01,6.920,190.9 -4.2,1.01,7.250,186.1 -4.2,1.01,8.450,179.7 -3.6,1.01,9.090,173.3 -3.4,1.01,7.430,185.0 -4.0,1.01,7.040,184.4 -3.9,1.01,7.350,171.6 -3.5,1.01,6.100,175.2 -3.6,1.01,5.740,170.0 -3.6,1.01,5.980,166.4 -3.5,1.01,6.400,165.6 -3.5,1.01,4.920,160.0 -3.5,1.01,4.960,169.7 -3.2,1.01,4.870,159.3 -2.9,1.01,3.950,164.4 -2.9,1.01,3.020,176.8 -3.2,1.01,1.640,193.7 -2.3,1.01,0.460,287.6 -1.0,1.01,1.900,306.2 -0.4,1.01,1.680,323.4 -0.8,1.01,2.050,318.8 -1.2,1.01,2.680,321.3 --0.2,1.01,2.150,30.1 -0.0,1.01,2.420,5.2 -0.1,1.01,3.000,355.1 --0.1,1.01,3.470,354.1 --0.2,1.01,3.040,337.0 --0.1,1.01,3.420,339.1 --0.3,1.01,3.210,331.3 --0.3,1.01,3.530,312.4 --0.1,1.01,3.500,311.4 --0.4,1.01,2.830,302.4 --0.1,1.01,1.490,296.9 --0.3,1.01,0.220,285.3 --0.3,1.01,0.440,215.2 --0.1,1.01,0.440,158.8 --0.3,1.01,1.680,138.6 --0.0,1.01,2.010,132.4 --0.4,1.00,3.400,158.8 --0.1,1.00,3.880,154.3 --0.2,1.00,4.610,158.1 -0.1,1.00,4.390,158.2 -0.4,1.00,4.490,151.0 -0.4,1.00,4.300,150.1 --0.1,1.00,4.220,151.7 --0.1,1.00,4.600,151.6 --0.1,1.00,5.090,159.9 -0.2,1.00,4.200,159.5 -0.6,1.00,4.620,153.5 -0.8,1.00,4.840,154.2 -0.8,1.00,4.860,158.0 -0.9,1.00,4.990,156.3 -1.2,1.00,5.130,157.1 -1.2,1.00,5.660,156.3 -1.7,1.00,6.030,157.2 -2.1,1.00,6.070,157.1 -2.4,1.00,6.040,158.7 -2.6,1.00,5.760,154.6 -3.6,1.00,6.330,155.8 -3.9,1.00,5.700,159.5 -4.0,1.00,4.850,155.4 -3.6,1.00,4.120,151.2 -3.9,1.00,4.130,153.9 -3.9,1.00,4.020,149.1 -3.4,1.00,3.850,147.7 -2.6,1.00,4.000,153.9 -3.0,1.00,4.150,153.1 -3.1,1.00,4.480,159.0 -3.4,1.00,4.260,157.3 -3.2,1.00,4.270,156.5 -2.9,1.00,4.060,158.1 -2.7,1.00,4.450,161.6 -3.0,1.00,4.860,155.6 -3.7,1.00,5.310,157.9 -3.1,1.00,6.220,154.3 -2.7,1.00,4.800,164.4 -2.1,1.00,3.560,168.1 -2.2,1.00,3.140,170.3 -2.5,1.00,3.150,170.7 -2.7,1.00,3.230,175.8 -2.8,1.00,3.570,162.1 -2.9,1.00,5.630,151.7 -3.1,1.00,1.310,206.6 -3.8,1.00,3.770,139.6 -4.0,1.00,4.250,139.5 -4.7,1.00,4.880,140.2 -4.9,1.00,2.580,151.5 -6.6,1.00,3.780,291.4 -6.3,1.00,8.390,296.5 -6.2,1.00,10.030,295.4 -6.0,1.00,7.970,295.8 -6.0,1.00,6.360,296.4 -6.2,1.00,4.150,294.3 -4.8,1.00,1.040,199.8 -3.8,1.00,2.850,146.4 -4.8,1.00,3.040,161.6 -3.9,1.01,2.750,188.2 -4.0,1.01,2.420,165.6 -5.3,1.01,1.750,16.3 -4.2,1.01,0.600,84.0 -3.2,1.01,0.790,58.1 -1.1,1.01,1.850,89.7 -1.0,1.01,1.220,58.5 -2.5,1.01,0.570,98.1 -2.3,1.01,1.170,151.8 -1.5,1.01,2.620,146.2 -1.8,1.01,1.460,138.5 -2.1,1.01,0.980,159.0 -2.2,1.01,0.800,349.8 -2.4,1.01,1.500,73.0 -2.6,1.01,0.400,340.3 -3.0,1.01,0.550,267.9 -3.2,1.01,0.390,299.8 -2.7,1.01,0.690,133.6 -2.5,1.01,0.790,207.4 -2.4,1.01,1.320,147.5 -2.7,1.01,1.880,130.1 -2.8,1.01,2.160,158.8 -2.6,1.01,2.480,171.8 -1.8,1.01,2.430,165.7 -1.1,1.01,4.360,155.9 -1.0,1.01,5.010,154.5 -1.4,1.01,5.450,156.5 -1.1,1.01,4.880,162.9 -0.8,1.01,4.990,161.0 -0.8,1.01,4.870,161.2 -0.7,1.00,4.900,157.4 -0.4,1.00,4.360,154.1 -0.3,1.00,4.960,147.2 -0.5,1.00,4.890,148.9 -0.6,1.00,4.140,150.5 -0.7,1.00,3.450,152.3 -1.8,1.00,3.190,156.8 -2.1,1.00,3.540,161.5 -2.4,1.00,3.280,157.6 -2.2,1.00,2.970,153.2 -2.5,1.00,2.830,157.3 -2.3,1.00,3.330,160.5 -2.9,1.00,3.050,154.9 -2.8,1.00,3.040,150.5 -3.2,1.00,2.890,148.9 -3.2,1.00,2.900,137.9 -3.0,1.00,2.880,139.0 -2.8,1.00,3.000,134.1 -2.9,1.00,2.960,137.2 -2.8,1.00,2.760,133.4 -2.4,1.00,2.890,127.1 -2.4,1.00,3.390,143.0 -2.5,1.00,3.060,139.3 -2.9,1.00,3.180,151.9 -3.3,1.00,4.800,144.7 -2.8,1.00,4.510,133.9 -3.2,1.00,3.090,169.3 -3.2,1.00,1.160,164.8 -3.9,1.00,1.510,157.4 -4.1,1.00,3.190,165.1 -3.9,1.00,2.010,220.8 -3.8,1.00,5.080,159.1 -3.5,1.00,5.940,161.0 -3.5,1.00,5.860,150.5 -3.6,1.00,5.150,145.4 -3.6,1.00,2.240,170.6 -3.9,1.00,1.580,130.3 -4.3,1.00,1.240,115.6 -4.3,1.00,2.700,87.7 -3.9,1.00,2.780,95.7 -3.8,1.00,3.630,109.1 -3.7,1.00,4.610,126.0 -3.5,1.00,4.670,141.2 -3.4,1.00,4.400,147.7 -3.4,1.00,5.070,142.1 -3.5,1.00,6.270,150.5 -3.5,1.00,5.990,149.7 -3.5,1.00,5.600,148.8 -3.6,1.00,5.600,145.6 -3.9,1.00,5.830,150.3 -4.0,1.00,4.840,146.9 -4.0,1.00,3.880,136.1 -4.0,1.00,4.300,147.0 -4.1,1.00,3.550,146.2 -4.4,1.00,3.720,134.5 -4.6,1.00,3.580,128.5 -0.0,1.00,2.420,304.2 -1.1,1.00,3.360,320.0 -1.9,1.00,3.690,314.6 -1.9,1.00,2.780,344.0 -2.1,1.00,3.060,353.0 -1.9,1.00,2.870,337.2 -1.8,1.00,2.510,339.8 -1.7,1.01,2.380,337.9 diff --git a/examples/11_hybrid_energy_plant/driver_config.yaml b/examples/11_hybrid_energy_plant/driver_config.yaml deleted file mode 100644 index eaf7431a9..000000000 --- a/examples/11_hybrid_energy_plant/driver_config.yaml +++ /dev/null @@ -1,54 +0,0 @@ -name: driver_config -description: Optimize a wind, PV, and battery hybrid plant using native H2I models -general: - folder_output: wind_pv_battery_run -driver: - optimization: - flag: true - tol: 0.01 - max_iter: 6 - solver: COBYLA - rhobeg: 0.2 - debug_print: true - autoscaler: bounds -design_variables: - wind: - wind_turbine_rating: - flag: true - lower: 0.0 - upper: 5000.0 - units: kW - solar: - system_capacity_DC: - flag: true - lower: 0.0 - upper: 22000.0 - units: kW - battery: - max_charge_rate: - flag: true - lower: 0.0 - upper: 22000.0 - units: kW - storage_capacity: - flag: true - lower: 0.0 - upper: 22000.0 - units: kW*h -constraints: - electrical_load_demand: - percent_load_missed: - flag: true - lower: 20. - units: percent - curtailment_percent: - flag: true - lower: 20.0 - units: percent - battery: - storage_duration: - flag: true - upper: 10 - units: h -objective: - name: finance_subgroup_default.LCOE diff --git a/examples/11_hybrid_energy_plant/plant_config.yaml b/examples/11_hybrid_energy_plant/plant_config.yaml deleted file mode 100644 index 3ed6238db..000000000 --- a/examples/11_hybrid_energy_plant/plant_config.yaml +++ /dev/null @@ -1,68 +0,0 @@ -name: plant_config -description: Wind, PV, and battery hybrid plant located in TX, USA near Sheffield -sites: - site: - latitude: 30.6617 - longitude: -101.7096 - resources: - wind_resource: - resource_model: WTKNLRDeveloperAPIWindResource - resource_parameters: - resource_year: 2013 - resource_filename: 30.6617_-101.7096_2013_wtk_v2_60min_utc_tz.csv - solar_resource: - resource_model: GOESAggregatedSolarAPI - resource_parameters: - resource_year: 2013 - resource_filename: 30.6617_-101.7096_psmv3_60_2013.csv -plant: - plant_life: 30 - simulation: - n_timesteps: 8760 - dt: 3600 -technology_interconnections: - - [wind, combiner, electricity, cable] - - [solar, combiner, electricity, cable] - - [combiner, battery, electricity, cable] - - [battery, elec_combiner, electricity, cable] - - [combiner, elec_combiner, electricity, cable] - - [elec_combiner, electrical_load_demand, electricity, cable] -tech_to_dispatch_connections: - - [combiner, battery] - - [battery, battery] -site_to_tech_connections: - - [site.wind_resource, wind, wind_resource_data] - - [site.solar_resource, solar, solar_resource_data] -finance_parameters: - finance_groups: - default: - finance_model: ProFastLCO - model_inputs: - params: - analysis_start_year: 2032 - installation_time: 12 # months - inflation_rate: 0.025 - discount_rate: 0.089 # nominal return, from 2024 ATB land-based wind - debt_equity_ratio: 2.1746031746031744 # 72.4% debt, from 2024 ATB - property_tax_and_insurance: 0.015 - total_income_tax_rate: 0.257 - capital_gains_tax_rate: 0.15 - sales_tax_rate: 0.045 - debt_interest_rate: 0.06 - debt_type: Revolving debt - loan_period_if_used: 0 - cash_onhand_months: 1 - admin_expense: 0.01 - capital_items: - depr_type: MACRS - depr_period: 5 - refurb: [0.] - finance_subgroups: - default: - commodity: electricity - commodity_stream: elec_combiner - finance_groups: [default] - technologies: [wind, solar, battery] - cost_adjustment_parameters: - cost_year_adjustment_inflation: 0.025 - target_dollar_year: 2025 diff --git a/examples/11_hybrid_energy_plant/run_wind_pv_battery.py b/examples/11_hybrid_energy_plant/run_wind_pv_battery.py deleted file mode 100644 index 941a21689..000000000 --- a/examples/11_hybrid_energy_plant/run_wind_pv_battery.py +++ /dev/null @@ -1,9 +0,0 @@ -from h2integrate import H2IntegrateModel - - -h2i = H2IntegrateModel("wind_pv_battery.yaml") - -# Run the model -h2i.run() - -h2i.post_process() diff --git a/examples/11_hybrid_energy_plant/tech_config.yaml b/examples/11_hybrid_energy_plant/tech_config.yaml deleted file mode 100644 index 4c119d2e5..000000000 --- a/examples/11_hybrid_energy_plant/tech_config.yaml +++ /dev/null @@ -1,100 +0,0 @@ -name: technology_config -description: Wind, solar PV, and battery hybrid energy plant using native H2I models -technologies: - wind: - performance_model: - model: PYSAMWindPlantPerformanceModel - cost_model: - model: ATBWindPlantCostModel - model_inputs: - performance_parameters: - num_turbines: 2 - turbine_rating_kw: 1500.0 # kW per turbine, initial value (design variable) - rotor_diameter: 82.0 - hub_height: 80.0 - create_model_from: default - config_name: WindPowerSingleOwner - pysam_options: {} - run_recalculate_power_curve: true - layout: - layout_mode: basicgrid - layout_options: - row_D_spacing: 7.0 - turbine_D_spacing: 7.0 - rotation_angle_deg: 0.0 - row_phase_offset: 0.0 - layout_shape: square - cost_parameters: - capex_per_kW: 1228.308 # $/kW, from 2024 ATB land-based wind class 1 moderate year 2030 - opex_per_kW_per_year: 29.2638 # $/kW/year, from 2024 ATB - cost_year: 2022 - solar: - performance_model: - model: PYSAMSolarPlantPerformanceModel - cost_model: - model: ATBUtilityPVCostModel - model_inputs: - performance_parameters: - pv_capacity_kWdc: 2500.0 # kW-DC, initial value (design variable) - dc_ac_ratio: 1.3 - create_model_from: default - config_name: PVWattsSingleOwner - tilt_angle_setting: lat-func - pysam_options: {} - cost_parameters: - capex_per_kWac: 1043.702 # $/kW-AC, from 2024 ATB utility PV moderate year 2030 - opex_per_kWac_per_year: 17.9907 # $/kW-AC/year, from 2024 ATB - cost_year: 2022 - combiner: - performance_model: - model: GenericCombinerPerformanceModel - dispatch_rule_set: - model: PyomoDispatchGenericConverter - model_inputs: - shared_parameters: - commodity: electricity - commodity_rate_units: kW - battery: - performance_model: - model: PySAMBatteryPerformanceModel - cost_model: - model: ATBBatteryCostModel - dispatch_rule_set: - model: PyomoRuleStorageBaseclass - control_strategy: - model: HeuristicLoadFollowingStorageController - model_inputs: - shared_parameters: - commodity: electricity - commodity_rate_units: kW - max_charge_rate: 2000.0 # kW, initial value (design variable) - max_capacity: 10000.0 # kWh, initial value (design variable) - init_soc_fraction: 0.9 - max_soc_fraction: 1.0 - min_soc_fraction: 0.2 - performance_parameters: - chemistry: LFPGraphite - demand_profile: !include tech_inputs/desired_schedules/desired_schedule_hourly_kw.yaml - cost_parameters: - cost_year: 2022 - energy_capex: 245.692 # $/kWh, from 2024 ATB utility-scale battery year 2030 - power_capex: 317.384 # $/kW, from 2024 ATB utility-scale battery year 2030 - opex_fraction: 0.025 - control_parameters: - system_commodity_interface_limit: 1.0e12 - n_control_window_hours: 24 - elec_combiner: - performance_model: - model: GenericCombinerPerformanceModel - model_inputs: - performance_parameters: - commodity: electricity - commodity_rate_units: kW - electrical_load_demand: - performance_model: - model: GenericDemandComponent - model_inputs: - performance_parameters: - commodity: electricity - commodity_rate_units: kW - demand_profile: !include tech_inputs/desired_schedules/desired_schedule_hourly_kw.yaml diff --git a/examples/11_hybrid_energy_plant/tech_inputs/desired_schedules/desired_schedule_hourly_kw.yaml b/examples/11_hybrid_energy_plant/tech_inputs/desired_schedules/desired_schedule_hourly_kw.yaml deleted file mode 100644 index 97e441cb7..000000000 --- a/examples/11_hybrid_energy_plant/tech_inputs/desired_schedules/desired_schedule_hourly_kw.yaml +++ /dev/null @@ -1,8760 +0,0 @@ -- 1374.7355705258792 -- 1366.1057497042643 -- 1376.4912200641252 -- 1390.9053351916205 -- 1401.24506224077 -- 1408.3000766392415 -- 1410.7811082993933 -- 1410.0683686403602 -- 1407.5420690812768 -- 1404.5824210412775 -- 1402.5696359394972 -- 1402.88392519507 -- 1406.9055002271316 -- 1476.5325271734234 -- 1437.8323003062303 -- 1425.9305842901035 -- 1426.8876841706003 -- 1420.946873328795 -- 1444.325317308149 -- 1472.5406465944993 -- 1502.8699005974781 -- 1532.5901187267202 -- 1562.5863626611406 -- 1508.2307762090288 -- 1449.449654354851 -- 1355.7718199999506 -- 1359.845131379042 -- 1473.8923653826694 -- 1405.9567633088134 -- 1439.368044289739 -- 1451.0812829137872 -- 1461.7403059176022 -- 1474.0070816078662 -- 1493.454495618451 -- 1562.6716839765306 -- 1628.648358457574 -- 1505.3818308798059 -- 1491.581520913475 -- 1443.3788746586654 -- 1402.1878022131834 -- 1396.7425708524665 -- 1402.1563726538188 -- 1410.322895927001 -- 1444.041711385271 -- 1518.984020019734 -- 1583.7017109551364 -- 1507.410830169102 -- 1595.01971542282 -- 1494.7662010737263 -- 1357.208265737854 -- 1350.2563645796897 -- 1388.3834860452616 -- 1402.2342038573106 -- 1539.2710901830058 -- 1441.779595220175 -- 1520.6356211166053 -- 1583.1438407241246 -- 1568.2935543184196 -- 1536.3429802088465 -- 1477.9420488560322 -- 1466.4198531962106 -- 1343.7470472993107 -- 1334.2674816071615 -- 1375.5985541621628 -- 1337.3077692084585 -- 1362.6014184740945 -- 1410.378380134182 -- 1404.4654209363337 -- 1509.648551034044 -- 1506.0736493735703 -- 1443.118717169288 -- 1445.4019371870052 -- 1297.7155392729335 -- 1276.8775574667322 -- 1272.6298363114495 -- 1329.5502183312865 -- 1290.1763904025004 -- 1310.220375578714 -- 1351.2536721135032 -- 1485.8475414441202 -- 1510.0441128560915 -- 1370.3520079322516 -- 1395.9632470584113 -- 1376.3582346386363 -- 1313.5475665646675 -- 1317.7052637611632 -- 1300.194903048289 -- 1283.9551700582717 -- 1324.035014353087 -- 1313.738473474085 -- 1347.850805050085 -- 1421.1211638890697 -- 1367.5151442370695 -- 1468.3242106644418 -- 1329.8401500086823 -- 1299.081602846339 -- 1276.2812819786395 -- 1248.331926066802 -- 1239.1361636871686 -- 1258.8658898183403 -- 1262.1114259333892 -- 1292.0007679487333 -- 1330.2489357836535 -- 1416.8215109220757 -- 1343.2453297361697 -- 1324.8724328230683 -- 1388.8972592341386 -- 1354.7948496891945 -- 1282.725357573454 -- 1399.9066065523048 -- 1282.4453265741195 -- 1280.9392062968395 -- 1281.8670587990391 -- 1321.6964819146922 -- 1311.8729536400665 -- 1313.3095646110253 -- 1336.5820878102031 -- 1444.661064241349 -- 1285.648391523511 -- 1328.5377468295133 -- 1253.534552314975 -- 1239.9715518035594 -- 1220.7182782309706 -- 1226.065029291822 -- 1238.4121285401932 -- 1327.220192576385 -- 1292.3410411626446 -- 1320.749505814244 -- 1340.1615073670932 -- 1326.9660467111537 -- 1347.4761237904966 -- 1315.2325415913224 -- 1336.5298085983256 -- 1371.9637765792795 -- 1280.6055922774935 -- 1299.5420692970488 -- 1305.5178923056105 -- 1311.93224206471 -- 1327.2878519165558 -- 1459.9257733035977 -- 1333.1387691072555 -- 1393.2323969556617 -- 1248.9631441766678 -- 1220.6297809455145 -- 1206.6458994896004 -- 1190.7452108620791 -- 1187.4594922063304 -- 1175.4099422586887 -- 1179.512009536193 -- 1182.25792214019 -- 1196.817802420962 -- 1215.2888468224546 -- 1244.8586399987244 -- 1274.2651819812377 -- 1457.9931098158324 -- 1313.7146915660717 -- 1402.783424525017 -- 1311.9496299813918 -- 1245.9422053375172 -- 1289.757421608125 -- 1262.5959949134956 -- 1292.9545135619212 -- 1326.3115738964423 -- 1373.3461572633503 -- 1423.7027531151914 -- 1398.2831610531518 -- 1231.509005043068 -- 1224.757573735669 -- 1203.8853198119687 -- 1213.7106331360137 -- 1220.0345664593224 -- 1231.906505697634 -- 1356.5360078594315 -- 1256.0742469304803 -- 1431.328169349269 -- 1437.2120733008642 -- 1444.0493461569247 -- 1314.848161369748 -- 1312.848386380639 -- 1269.3842710285594 -- 1279.6110406193793 -- 1276.721056118082 -- 1277.625009197685 -- 1279.7343461566677 -- 1283.7381082227494 -- 1320.5920525876727 -- 1336.9747574182682 -- 1459.3588130210235 -- 1436.268282838088 -- 1328.4783077479171 -- 1404.9716729630852 -- 1370.63225807266 -- 1355.5185736171834 -- 1227.7479606526927 -- 1232.1450011670509 -- 1232.202565595579 -- 1239.2002352435625 -- 1359.7587688977328 -- 1406.3325901353073 -- 1431.544878970322 -- 1441.7966528644201 -- 1418.045239378939 -- 1301.3227110005048 -- 1332.9377032302036 -- 1278.4452495530256 -- 1264.9606712700718 -- 1260.8482490852832 -- 1306.1350051503084 -- 1280.6421258714533 -- 1287.6187295328252 -- 1274.4349929716398 -- 1317.807468164187 -- 1409.2955636051645 -- 1303.6889204108004 -- 1290.202409238947 -- 1237.846625359062 -- 1219.752637069984 -- 1205.9353707757507 -- 1192.6483408854315 -- 1195.6172516144015 -- 1201.632051659531 -- 1217.8872840698602 -- 1278.690169141219 -- 1409.9425282593852 -- 1275.3689882217761 -- 1290.2731185291323 -- 1368.6801590968396 -- 1257.9970797380888 -- 1315.5206305350544 -- 1246.1793141994458 -- 1248.7476155734755 -- 1264.3063114463166 -- 1256.3631012771543 -- 1256.6479592641945 -- 1257.7420470302143 -- 1302.6341269281024 -- 1363.4344930845891 -- 1332.3103280714229 -- 1257.1952481231397 -- 1217.8337563227328 -- 1196.5710870064806 -- 1263.2399413878136 -- 1190.0949965452776 -- 1309.6706533044241 -- 1192.984784369608 -- 1319.191723947622 -- 1251.672786656429 -- 1392.3949966419636 -- 1296.643876555453 -- 1290.9445973948364 -- 1417.45236216518 -- 1284.1703101348246 -- 1258.219078006339 -- 1356.111168668819 -- 1264.1613821868332 -- 1374.9601907406943 -- 1381.479728601869 -- 1316.134294886645 -- 1308.4075777054982 -- 1354.634007574574 -- 1393.0228048655333 -- 1313.5283884579137 -- 1398.8875808151136 -- 1372.6164070081022 -- 1246.467282353539 -- 1310.8378356017367 -- 1243.0608746682776 -- 1260.7262259149677 -- 1271.698851078569 -- 1300.6769486520907 -- 1333.2926400590534 -- 1467.4535448627464 -- 1371.0172628984114 -- 1347.5429114142985 -- 1494.895975101663 -- 1375.849321948416 -- 1306.8734204199704 -- 1290.2960406137108 -- 1277.601755243104 -- 1266.8696676585944 -- 1317.083354963486 -- 1306.6509622709784 -- 1303.3720316132749 -- 1408.8201790817686 -- 1473.8869149621366 -- 1338.354507017224 -- 1315.0174530375475 -- 1384.0676940498977 -- 1326.3086825431471 -- 1265.746395194103 -- 1262.7937383517972 -- 1267.593960777546 -- 1263.9046800774947 -- 1274.8067260982043 -- 1329.46622494422 -- 1348.6957660887856 -- 1351.6700614441015 -- 1357.5733245760928 -- 1383.8374352352866 -- 1356.2586258956048 -- 1355.844160408551 -- 1339.7452383158684 -- 1419.7462971330178 -- 1435.0105978657923 -- 1469.862515750156 -- 1365.2303404817933 -- 1367.4901209135874 -- 1369.9240723139553 -- 1331.6279682837348 -- 1440.6822388517965 -- 1301.423123480596 -- 1279.594898566601 -- 1257.3159018268282 -- 1237.1947273389737 -- 1228.4407349213475 -- 1307.284231538703 -- 1217.9699512830487 -- 1238.6025992399818 -- 1244.128294129135 -- 1258.6070290401365 -- 1386.7745283868705 -- 1410.2172296729073 -- 1309.7680365582007 -- 1288.1890790836214 -- 1370.0548271278894 -- 1368.7736904992762 -- 1260.8930826237793 -- 1398.427277569969 -- 1359.3177468191984 -- 1290.0452412443947 -- 1317.9476952002321 -- 1418.0442431859842 -- 1328.8802641461916 -- 1289.4922469380294 -- 1368.4036803938216 -- 1233.4661837948659 -- 1236.861763428626 -- 1239.1517416300835 -- 1242.391123875303 -- 1247.1867431359728 -- 1370.8587804178892 -- 1270.3220252883455 -- 1300.0439378799076 -- 1325.0990290153065 -- 1443.6510300716157 -- 1306.8718074211956 -- 1322.1953224528609 -- 1315.658754532205 -- 1389.1220592501081 -- 1266.7736161506095 -- 1277.3679454035043 -- 1260.0311979138182 -- 1383.0073078309122 -- 1304.9354155486785 -- 1446.2902142998105 -- 1383.6244758855382 -- 1532.2541655587906 -- 1399.5942014651148 -- 1410.3002833437502 -- 1360.4188217286676 -- 1354.2995753510722 -- 1392.1234103678087 -- 1306.2473534774967 -- 1330.1984177493443 -- 1334.9870321726403 -- 1457.927565663 -- 1434.4661601977393 -- 1435.553256245752 -- 1549.783102843958 -- 1483.7101391381455 -- 1628.4907516843684 -- 1550.0092140172494 -- 1517.4567704607832 -- 1469.6366606018535 -- 1405.9641858895232 -- 1386.6291191859616 -- 1391.9105021958921 -- 1376.4403095083178 -- 1408.3797679225304 -- 1560.8479769275473 -- 1504.7565764493506 -- 1486.0469950740423 -- 1456.599534506653 -- 1526.7366275461145 -- 1486.0803543030643 -- 1318.0761890747892 -- 1311.084842257821 -- 1356.867183123049 -- 1340.552247574984 -- 1491.8824381145032 -- 1411.3011498230346 -- 1432.804361443745 -- 1527.3601080530602 -- 1529.064321368927 -- 1550.814070677847 -- 1489.2728076717042 -- 1471.476561051801 -- 1394.9778625008007 -- 1388.297552993516 -- 1432.436539764077 -- 1360.54565008692 -- 1365.1860783322581 -- 1421.6273036713062 -- 1468.4785398657789 -- 1469.3617399695302 -- 1452.5015125064178 -- 1425.5463973564154 -- 1497.2095310945215 -- 1359.6485771959517 -- 1393.5239612236485 -- 1376.1414724153074 -- 1283.9505174063358 -- 1298.3552938879768 -- 1318.573637325007 -- 1330.945681579675 -- 1483.564981149641 -- 1499.651965134877 -- 1367.2765091807703 -- 1457.2024585056154 -- 1427.8318926800202 -- 1338.9622750134029 -- 1429.2739280441435 -- 1295.8336596907968 -- 1448.6949906303028 -- 1305.2245315870443 -- 1304.2037601887519 -- 1296.7731899993782 -- 1346.8137249867436 -- 1380.2049329767635 -- 1364.532384926508 -- 1547.8560974006793 -- 1331.8299829910472 -- 1302.355947460003 -- 1420.200523067361 -- 1276.7981494995156 -- 1287.5582525657335 -- 1301.2848725245542 -- 1416.9048859920836 -- 1371.0024346172365 -- 1510.948377313516 -- 1495.864938663483 -- 1461.8658931517366 -- 1418.490518745247 -- 1377.4963447927869 -- 1423.8087674371475 -- 1276.3861072114196 -- 1291.4603579474815 -- 1290.9263272330536 -- 1282.2440372940418 -- 1316.0836377325022 -- 1377.3109433796694 -- 1347.0555278118547 -- 1337.0128994586178 -- 1352.9727504402495 -- 1297.941480304413 -- 1267.9538383278723 -- 1252.992242472107 -- 1242.1304299446192 -- 1235.7865184507903 -- 1235.6564635024977 -- 1227.6197340335054 -- 1301.4906933702262 -- 1261.6887326277895 -- 1288.5123821228578 -- 1303.763208070878 -- 1478.5951206348968 -- 1327.0679123447837 -- 1396.6984809725022 -- 1300.9057185686922 -- 1404.24125848139 -- 1400.219497378689 -- 1290.8993156579372 -- 1387.2646309851111 -- 1450.9689658829402 -- 1329.8262324151447 -- 1461.541677063557 -- 1326.2840692857278 -- 1297.6041621499562 -- 1381.8661015559935 -- 1226.4164905469554 -- 1206.2003250706236 -- 1183.959535135107 -- 1263.8493504074652 -- 1183.0563443139051 -- 1180.0995275943117 -- 1196.487527139866 -- 1205.7641150082313 -- 1216.727209519858 -- 1268.355045584625 -- 1299.8722349205623 -- 1307.5501138039715 -- 1334.330458566043 -- 1339.0677773353382 -- 1262.8986908404863 -- 1250.1957089939476 -- 1262.0493281379256 -- 1270.6413428463816 -- 1293.48248651164 -- 1287.0912454249415 -- 1452.9548397090628 -- 1340.76651137185 -- 1376.9045387218512 -- 1356.1312736330165 -- 1214.852649641523 -- 1215.1926567918017 -- 1213.4467306202018 -- 1220.8752237819085 -- 1228.4392158798162 -- 1225.2595745966792 -- 1243.58029321203 -- 1288.3811677856581 -- 1432.6564248412828 -- 1384.2035557497918 -- 1309.6108480136672 -- 1391.1164471763161 -- 1311.7726045527806 -- 1317.6358551893259 -- 1391.753553842196 -- 1263.1190508801292 -- 1256.797719550983 -- 1253.4761171655211 -- 1361.6011125570815 -- 1350.8901281330766 -- 1421.3619141716176 -- 1425.4285841610395 -- 1422.250849005181 -- 1405.059301106953 -- 1391.3597371543347 -- 1251.925281385843 -- 1246.7983184630343 -- 1244.7048888220522 -- 1260.324432330099 -- 1276.9385293301152 -- 1290.0940526793183 -- 1466.9448036326125 -- 1492.8425188586186 -- 1382.235714955408 -- 1350.5716915917458 -- 1374.0010011324002 -- 1339.4170656176364 -- 1301.625152982219 -- 1297.4628556202229 -- 1267.1871165019274 -- 1263.9588731547162 -- 1296.966761232012 -- 1310.1473497491918 -- 1320.4564840575335 -- 1353.5241849421895 -- 1360.774204739585 -- 1381.1233015709756 -- 1316.4694631261623 -- 1277.4826547900036 -- 1263.219953757606 -- 1241.8038615611335 -- 1246.330034786364 -- 1257.5496013351944 -- 1271.8113813429156 -- 1310.6696183543083 -- 1446.8478746294415 -- 1472.373341354783 -- 1495.0915344998357 -- 1445.3753991715205 -- 1359.1120833651555 -- 1446.1712839025004 -- 1418.6177610077477 -- 1282.0210170366515 -- 1281.517055146962 -- 1349.8826184940185 -- 1289.4211387969915 -- 1303.382756345718 -- 1302.7658795622042 -- 1305.7077588526577 -- 1354.429128178234 -- 1338.5039406554615 -- 1325.4955438320392 -- 1383.0015593064436 -- 1253.9309216597221 -- 1238.9061339431255 -- 1239.1269881572905 -- 1226.7303540869614 -- 1233.0722106018943 -- 1346.9176225093784 -- 1279.7774107240075 -- 1439.664723085206 -- 1406.367297110267 -- 1325.0172486972979 -- 1359.3314001387257 -- 1336.7532598248588 -- 1291.2023958653929 -- 1269.9119794939206 -- 1313.1499120723427 -- 1298.2430209765196 -- 1279.2784449006317 -- 1294.4748991227593 -- 1278.1576630935065 -- 1344.0174123345043 -- 1371.1296153253813 -- 1449.1269099098763 -- 1403.2970484091911 -- 1252.942651990807 -- 1360.0999964363014 -- 1341.0098776236819 -- 1337.9507060360495 -- 1258.3851936436445 -- 1212.2206601056891 -- 1224.685996396769 -- 1258.882128723322 -- 1402.1387955659354 -- 1412.9465300482484 -- 1349.6180431578737 -- 1380.336125005356 -- 1422.9107309493975 -- 1415.6557255211242 -- 1391.476852069825 -- 1376.7951403602365 -- 1368.9500684735979 -- 1360.6889873912326 -- 1262.5136363136344 -- 1400.2238481764455 -- 1382.1051959175722 -- 1325.0359715688628 -- 1453.9991293039843 -- 1423.1982607293596 -- 1345.2503534428063 -- 1211.3960201146276 -- 1192.8856104684728 -- 1184.095172656611 -- 1188.4838173583544 -- 1194.8644942064432 -- 1213.8630516494886 -- 1233.2975127946013 -- 1247.0285937364367 -- 1417.2951053917955 -- 1421.3402098182326 -- 1383.0933664647932 -- 1286.2189680953786 -- 1282.4015133771804 -- 1265.4639875249425 -- 1270.2289158886506 -- 1253.8084164251338 -- 1258.524128971842 -- 1259.9331322308365 -- 1292.2309106921614 -- 1431.3870857459685 -- 1300.7629131575184 -- 1385.136260000567 -- 1296.5628492205242 -- 1341.5778095323433 -- 1241.226846662072 -- 1205.1730402114356 -- 1214.3253556950108 -- 1211.5005758447517 -- 1212.6864533956843 -- 1232.079456851693 -- 1238.2080456662259 -- 1269.316244629886 -- 1306.4282298425892 -- 1344.0270581541579 -- 1284.7205356144668 -- 1274.1290012880395 -- 1380.5929992930903 -- 1271.5359031643666 -- 1398.951956770525 -- 1274.081552200284 -- 1383.8035487182965 -- 1414.3833138735624 -- 1319.7226463646712 -- 1317.74451157147 -- 1306.3944526731427 -- 1342.9579029539598 -- 1347.4432877130478 -- 1214.2223015826198 -- 1226.4146870914828 -- 1229.8937800321378 -- 1240.9633535778398 -- 1257.588642807896 -- 1251.658830763952 -- 1267.5157629159937 -- 1431.8320632894533 -- 1518.203546131326 -- 1470.0998859053848 -- 1451.288544275105 -- 1487.779563159783 -- 1459.7578546532889 -- 1450.7505733378944 -- 1291.571761801588 -- 1282.3974536494006 -- 1386.1704676281684 -- 1293.1251107203057 -- 1315.6101639285698 -- 1320.5893275152891 -- 1451.4647890008293 -- 1358.3518806334544 -- 1451.2610260137565 -- 1273.8877651061098 -- 1267.7951553727055 -- 1241.2430170945495 -- 1236.6461929060172 -- 1234.857182836302 -- 1227.4163009200406 -- 1239.0268849130366 -- 1379.8312493049461 -- 1415.1483715575625 -- 1424.7502912061373 -- 1437.6627768101162 -- 1313.6942382178083 -- 1434.308723565831 -- 1352.0859544266245 -- 1290.98024859488 -- 1265.8456653156804 -- 1268.5964474326074 -- 1356.303081756972 -- 1277.0660601929815 -- 1292.5258456309623 -- 1278.6042530063974 -- 1302.78374992843 -- 1331.9547064510818 -- 1317.7027325380297 -- 1355.784370899557 -- 1238.0321217378173 -- 1256.6097699663615 -- 1211.7136046917112 -- 1209.9309291620227 -- 1315.7525729471904 -- 1217.9556373444332 -- 1233.316818399707 -- 1320.8106250066337 -- 1385.664557094911 -- 1397.6221964063525 -- 1291.4447662889324 -- 1275.227190496846 -- 1353.7906973323802 -- 1260.45139218921 -- 1264.0047439237387 -- 1262.0302858730402 -- 1276.360070786873 -- 1300.9461003262143 -- 1307.5515250104982 -- 1397.8349283890996 -- 1426.128509824268 -- 1314.6895621041565 -- 1409.9413886575708 -- 1254.029949915657 -- 1224.3472787387743 -- 1208.0508881803787 -- 1203.7761510182722 -- 1196.5829194004555 -- 1189.9867835286304 -- 1194.1205155645025 -- 1204.6175106829817 -- 1332.7151554103175 -- 1386.0980147360515 -- 1249.504513336506 -- 1260.2802843170937 -- 1273.80325036756 -- 1385.3092388036825 -- 1304.1882482983326 -- 1246.3974731724636 -- 1242.8981873730188 -- 1247.392530476566 -- 1272.7861094803845 -- 1354.0945475667236 -- 1245.774270279811 -- 1313.2982147319403 -- 1331.2554795025576 -- 1347.372455584758 -- 1399.5821745160654 -- 1328.508072185427 -- 1255.5130757626187 -- 1224.1802435244356 -- 1224.5894912449426 -- 1232.3389918855712 -- 1242.1635033516493 -- 1249.4780165164154 -- 1268.9844728236544 -- 1374.0865760184856 -- 1448.629782765036 -- 1301.0681363159629 -- 1320.2270528797792 -- 1404.79053050123 -- 1289.6393853112638 -- 1267.4923148140338 -- 1267.1246339954255 -- 1290.8151058038252 -- 1400.7917047901183 -- 1265.8845874771303 -- 1395.783903897459 -- 1292.4202736144177 -- 1313.386410405161 -- 1331.3876660583564 -- 1284.3184751295687 -- 1364.9475637393964 -- 1233.7915234092104 -- 1212.5209112113466 -- 1212.8682413534436 -- 1204.256382191549 -- 1207.4621986631503 -- 1250.632405661903 -- 1246.8988987354685 -- 1274.6001211996706 -- 1393.0365196679109 -- 1437.529692228211 -- 1427.0056265483174 -- 1422.0205769375955 -- 1298.443685348082 -- 1364.990141120489 -- 1279.5146207670382 -- 1414.5862740889613 -- 1372.983912723502 -- 1366.8335605525472 -- 1370.891538326395 -- 1405.7786787531636 -- 1444.1152098455418 -- 1346.7059276764887 -- 1244.0316868757086 -- 1307.7881470029795 -- 1209.045854423362 -- 1202.9965004183448 -- 1186.00003779259 -- 1193.0699858393316 -- 1198.9722343701985 -- 1191.3192770037006 -- 1219.296076442263 -- 1264.7052920399867 -- 1394.0625770281347 -- 1399.3047933692428 -- 1394.291881597198 -- 1264.7112149419124 -- 1344.5419762423458 -- 1262.4901875842309 -- 1282.3049689685338 -- 1320.3471444190914 -- 1344.767563674044 -- 1302.0000712446408 -- 1329.977874882864 -- 1326.132321986297 -- 1474.747465626457 -- 1407.5870755234484 -- 1365.6412922128013 -- 1344.7737854439931 -- 1346.6707166039764 -- 1233.2465831376226 -- 1230.2537090128899 -- 1252.1786178524594 -- 1246.7403719150402 -- 1391.4628045657867 -- 1353.4781660168114 -- 1460.6202948126702 -- 1458.8486102031604 -- 1346.4735817354638 -- 1352.3456030335547 -- 1452.2432654702836 -- 1319.7987226593923 -- 1398.3861240299275 -- 1415.5960501818136 -- 1313.7385152327577 -- 1298.5427687722795 -- 1324.2286277207104 -- 1369.9892978203782 -- 1323.4797875120375 -- 1440.4646997512932 -- 1327.4205259105202 -- 1270.731272684626 -- 1348.275464509595 -- 1343.5811251905307 -- 1296.285773417655 -- 1215.7811172274703 -- 1201.6333895983985 -- 1206.045592908269 -- 1258.373560699936 -- 1240.365251833376 -- 1404.2922329124624 -- 1424.5320940301435 -- 1423.346020787074 -- 1332.1059410305327 -- 1386.4486477662174 -- 1373.3356215266404 -- 1365.203447852626 -- 1273.1371377932294 -- 1257.5970567179456 -- 1267.271098582966 -- 1263.9705056005523 -- 1394.9555779417178 -- 1357.981194815145 -- 1353.0271459301096 -- 1338.952886887748 -- 1356.4678500067266 -- 1272.3857428264284 -- 1262.9548461610664 -- 1254.3872385733723 -- 1261.329233933511 -- 1260.2988630100022 -- 1390.0407283236411 -- 1287.0924149402579 -- 1414.873223408502 -- 1484.3860792112034 -- 1359.1839263820345 -- 1440.572526318217 -- 1391.4478995508123 -- 1469.4275181246483 -- 1304.7114157374704 -- 1281.8431056728027 -- 1288.8046775183657 -- 1402.8508375067508 -- 1325.574256924851 -- 1321.3134856248962 -- 1320.63129229324 -- 1360.1447404596668 -- 1367.921180745739 -- 1359.8591173765255 -- 1315.802745394775 -- 1332.1985862373044 -- 1272.462013293786 -- 1259.3287587500274 -- 1262.0334163217265 -- 1274.2718590224185 -- 1275.1680485537272 -- 1378.457527788398 -- 1340.9451330717818 -- 1495.9199229037993 -- 1364.093078681583 -- 1336.2105766881868 -- 1455.4818369705756 -- 1439.3718467943295 -- 1422.7706978981832 -- 1414.6744176693874 -- 1291.0194201357601 -- 1412.8978038974174 -- 1418.1807573948552 -- 1307.0290897312439 -- 1309.992584410824 -- 1344.31968750263 -- 1358.0486739344092 -- 1470.9262705798428 -- 1454.465627733043 -- 1355.5339605454776 -- 1248.0158280542314 -- 1238.6154333105749 -- 1228.9099360968312 -- 1233.4625475632718 -- 1225.9184814038827 -- 1253.61060795771 -- 1406.3066632821688 -- 1431.2779731562794 -- 1342.4996992877261 -- 1430.6307606442274 -- 1414.933453608355 -- 1278.8778355162597 -- 1367.0702003680285 -- 1267.8067510593835 -- 1258.0560389106283 -- 1279.5564998800216 -- 1296.9420043669088 -- 1309.2931255079866 -- 1308.307466323667 -- 1381.5719955070015 -- 1336.9947907723888 -- 1320.3414021439976 -- 1260.2053679068767 -- 1315.5386951408632 -- 1219.3962668303488 -- 1216.2928771682275 -- 1227.3829639548733 -- 1217.032227278705 -- 1216.537816634812 -- 1222.5817425175785 -- 1240.981603547037 -- 1380.122767527352 -- 1449.0858169543096 -- 1355.630362801423 -- 1450.4421404140544 -- 1312.138966493768 -- 1407.0598852071332 -- 1284.7689892764593 -- 1362.8806291473534 -- 1290.3725263204892 -- 1371.0547345421762 -- 1318.1686305577027 -- 1361.657311008836 -- 1355.8096591710437 -- 1420.3223800318794 -- 1310.582973128854 -- 1276.193213233219 -- 1309.0789901554872 -- 1260.6182197077962 -- 1234.146178602337 -- 1255.0908218271077 -- 1262.7344850777538 -- 1279.7649321407318 -- 1283.8350597468489 -- 1297.009709228019 -- 1313.651840663135 -- 1369.1041360972642 -- 1505.8149204859417 -- 1416.7838136981738 -- 1402.281579144787 -- 1365.258012051258 -- 1320.9662201316853 -- 1296.1662330677163 -- 1302.4440934615013 -- 1304.5859684052775 -- 1385.0425691765183 -- 1361.7098194523742 -- 1352.1619503517795 -- 1374.9528972051367 -- 1350.7932935684707 -- 1311.5996158446471 -- 1284.919135309397 -- 1370.1366923133064 -- 1275.9583252665104 -- 1274.8617453822487 -- 1290.571754329029 -- 1300.8486104187284 -- 1359.783295404868 -- 1454.663521906916 -- 1487.1476796129311 -- 1498.8791586045395 -- 1490.5139299642906 -- 1405.7348241371774 -- 1468.8102907331438 -- 1374.5425136548909 -- 1315.0301856079752 -- 1306.2516600624367 -- 1330.6889911834046 -- 1304.9080150468126 -- 1311.1174491881025 -- 1355.5359888162754 -- 1372.9555992129542 -- 1393.2124541399417 -- 1473.9862352287028 -- 1354.1902024807293 -- 1275.367236721026 -- 1249.176597948213 -- 1229.439226813728 -- 1231.202365060586 -- 1251.471047997018 -- 1230.1174082278678 -- 1243.9593057381214 -- 1274.5386362475351 -- 1426.563836124659 -- 1373.879511896502 -- 1289.888770605393 -- 1313.923829251071 -- 1303.3766756033913 -- 1260.7997451293097 -- 1312.1241583456997 -- 1271.2162814761552 -- 1280.9743667541065 -- 1282.2469215808637 -- 1267.0114208774517 -- 1353.885352415383 -- 1325.6645987391792 -- 1340.5033432053065 -- 1383.8358362012 -- 1393.9103220979393 -- 1346.4582984776407 -- 1201.7017875976665 -- 1216.3089171793947 -- 1189.4611152576404 -- 1189.0189195565197 -- 1196.3318916578346 -- 1201.9326446920445 -- 1249.4142416080622 -- 1335.8793517163294 -- 1383.0803770534383 -- 1247.9402432154072 -- 1250.7429909891177 -- 1303.3524722995692 -- 1249.279168042053 -- 1243.2334972557942 -- 1308.7043575630707 -- 1267.7014997458812 -- 1278.949378102949 -- 1294.8688598750334 -- 1454.6826926870965 -- 1433.1414930943317 -- 1425.5147499806285 -- 1359.0462997047969 -- 1378.4392734105732 -- 1223.4417685998508 -- 1197.5276975491295 -- 1187.3881975276283 -- 1175.7898026743876 -- 1174.150612366935 -- 1177.4758789818284 -- 1185.2727512298723 -- 1244.1718550153407 -- 1375.1292982955579 -- 1398.4230975446003 -- 1325.1116464904576 -- 1250.7102929105956 -- 1362.6702295488435 -- 1372.0987810948834 -- 1249.7695817499664 -- 1285.968246459397 -- 1308.1374098027447 -- 1284.7752293113847 -- 1281.594094683385 -- 1337.9138458179268 -- 1342.5533546759193 -- 1327.4357241449934 -- 1326.2592425475764 -- 1271.3931105060465 -- 1241.7430773996095 -- 1197.0482793390568 -- 1187.2916514969568 -- 1212.3269682800224 -- 1180.8354028517522 -- 1174.223227400855 -- 1176.6551507438871 -- 1191.518889708425 -- 1222.5017445281396 -- 1263.1821335177583 -- 1247.7322313761902 -- 1357.06525288412 -- 1382.3213838338688 -- 1370.4223662493798 -- 1366.0396240312368 -- 1350.4363166123503 -- 1344.5294400111645 -- 1230.804323273181 -- 1348.3005184286387 -- 1280.4759197130882 -- 1292.2669043154217 -- 1308.2465171059177 -- 1363.4668288581602 -- 1260.4773883118266 -- 1346.9508252467874 -- 1207.1898776783537 -- 1206.818343975858 -- 1200.2557812114105 -- 1196.0159919307123 -- 1201.2577749789957 -- 1203.8613112475869 -- 1215.8147984995355 -- 1225.3271913027459 -- 1331.0853447116576 -- 1416.0669898265348 -- 1346.039569008201 -- 1413.081635317314 -- 1293.7500615400224 -- 1425.5189001913384 -- 1283.9028166297373 -- 1312.486461566501 -- 1316.5698330584378 -- 1345.9847599996156 -- 1358.2653953502231 -- 1352.580035735075 -- 1333.189604151151 -- 1398.5316595674537 -- 1266.6466516497571 -- 1328.567148203145 -- 1197.4904189830231 -- 1175.7793145651167 -- 1169.4562986509343 -- 1168.4588724818254 -- 1171.4349682027032 -- 1182.0508886522832 -- 1236.458357267386 -- 1217.495860364296 -- 1259.718455253754 -- 1285.09691088249 -- 1259.297827921108 -- 1351.545920296659 -- 1268.9674421817656 -- 1382.1943059088178 -- 1308.954895532776 -- 1313.8419401998408 -- 1301.4799729362708 -- 1298.7443263794612 -- 1436.1964713501739 -- 1335.205877775724 -- 1337.4911644612473 -- 1306.5903974292657 -- 1225.957072317528 -- 1276.3523228632696 -- 1185.2593126367688 -- 1181.5719320680178 -- 1181.5450751650415 -- 1194.1763920009473 -- 1193.7044794221106 -- 1193.7498845772882 -- 1217.9685858086634 -- 1245.2315000722265 -- 1357.5189021533188 -- 1386.9553087071242 -- 1333.9503816858548 -- 1329.491666516079 -- 1381.231065008655 -- 1364.9344927845138 -- 1348.6270638337808 -- 1280.2347981960024 -- 1303.216177018901 -- 1346.046871317406 -- 1289.6223417296642 -- 1356.1961146587475 -- 1271.436958025222 -- 1397.8833224833659 -- 1368.7925586652204 -- 1326.0898630469433 -- 1232.9063786862573 -- 1193.3038354351731 -- 1193.6101214482264 -- 1201.6056184484437 -- 1195.0114053288762 -- 1190.7354398869902 -- 1213.9061649665211 -- 1364.403545669475 -- 1388.355823025484 -- 1290.8236155281468 -- 1272.9385520889016 -- 1337.918213222651 -- 1263.2297955936365 -- 1375.3855221775395 -- 1336.4633524988105 -- 1299.0204820733377 -- 1354.2693962552155 -- 1452.9578863290951 -- 1301.740282374881 -- 1380.574478341955 -- 1329.629476774072 -- 1298.550675113231 -- 1277.2283795214269 -- 1370.7362913719865 -- 1216.7401699206239 -- 1223.1271062301691 -- 1227.9651268350212 -- 1236.6862663306756 -- 1262.7516054332655 -- 1267.0223505681336 -- 1441.3411982500475 -- 1465.219705334239 -- 1531.6564492694504 -- 1463.8395403762765 -- 1510.8383634605425 -- 1470.1606095967795 -- 1312.7997669890026 -- 1334.5115624458851 -- 1382.283930050725 -- 1330.5652157173804 -- 1401.2760266934963 -- 1324.6143864540684 -- 1343.841474444789 -- 1373.5593393700594 -- 1527.317350790754 -- 1389.4482346402199 -- 1474.27396543037 -- 1313.0815742163666 -- 1400.203687863762 -- 1263.8817934196345 -- 1266.1404704044269 -- 1294.360157388189 -- 1307.549692945903 -- 1326.1513321149578 -- 1340.5071440343145 -- 1456.5856381166352 -- 1502.2993369010296 -- 1383.5911750989305 -- 1420.9176520058897 -- 1404.831340633097 -- 1425.158515187364 -- 1340.9142154446947 -- 1347.54223255955 -- 1308.8972370872225 -- 1313.9180255054507 -- 1317.5461426281615 -- 1342.030026330731 -- 1358.0929028799842 -- 1502.674809249895 -- 1453.6565445197982 -- 1406.5354800543055 -- 1407.7371795183203 -- 1387.9646949133737 -- 1250.7068292517029 -- 1236.0289383291395 -- 1235.7881459235116 -- 1251.473099436231 -- 1270.4431297251754 -- 1408.9237578271268 -- 1448.0050908198455 -- 1338.1160311559954 -- 1311.6254444764252 -- 1336.3105897700893 -- 1316.7315775069321 -- 1286.1441487045117 -- 1275.519876549249 -- 1264.5030616830272 -- 1276.6462733837564 -- 1282.7933405989957 -- 1290.8721868816137 -- 1358.8016567853078 -- 1360.3355491437364 -- 1353.3479885009776 -- 1455.0367323807402 -- 1273.0185930299585 -- 1241.0336392752636 -- 1345.2206776015014 -- 1240.1019714887052 -- 1199.7731101468075 -- 1233.426210135509 -- 1213.844803800228 -- 1210.5311620964173 -- 1232.4059435094618 -- 1261.4535385081122 -- 1326.881296463894 -- 1357.1137172031572 -- 1307.106149999322 -- 1311.6856352005107 -- 1417.7606266374778 -- 1309.358787779358 -- 1299.2415834903247 -- 1454.6007111213482 -- 1295.4691910301153 -- 1341.5577782305743 -- 1311.0281602031548 -- 1317.4198170516743 -- 1296.50062318463 -- 1298.400760879417 -- 1371.167344780525 -- 1217.900403361745 -- 1194.4977421917963 -- 1195.5879474164408 -- 1187.3931582786493 -- 1187.5955031803994 -- 1188.3131784667091 -- 1241.9069887995552 -- 1219.755075184167 -- 1235.364492131279 -- 1249.6157712227061 -- 1399.7065594021662 -- 1386.4854580455242 -- 1273.3703748681098 -- 1392.735451538108 -- 1393.073601703114 -- 1263.3398519277991 -- 1400.8294065147386 -- 1339.517604237507 -- 1329.4391475230427 -- 1439.886626142478 -- 1308.2066342701391 -- 1409.479174696133 -- 1270.241236040491 -- 1257.894609304614 -- 1217.0979612762521 -- 1213.2557648292063 -- 1217.0291964135502 -- 1210.6803600943674 -- 1220.3489425858281 -- 1237.1435711269976 -- 1251.5305270942117 -- 1287.2393239687374 -- 1340.9753455027421 -- 1442.2227120511936 -- 1296.5605137693194 -- 1286.8350945197258 -- 1263.1747151346442 -- 1305.3681165992741 -- 1417.531860448847 -- 1270.6392732132997 -- 1255.2174861003655 -- 1260.7111524148108 -- 1276.83501397056 -- 1327.8147679677627 -- 1450.6684613342884 -- 1337.9578848171016 -- 1315.2653624499558 -- 1389.196627803441 -- 1355.2081230984058 -- 1226.4811777864165 -- 1299.7954659769355 -- 1197.5818551804073 -- 1197.1287015256594 -- 1195.1284510001938 -- 1184.787914073894 -- 1331.2019133417193 -- 1378.6739745874154 -- 1383.2477850482187 -- 1257.897935944876 -- 1359.2444938395406 -- 1282.106533728191 -- 1315.22455545952 -- 1244.4255371849424 -- 1245.5716382405622 -- 1265.5696157502448 -- 1265.0843057834736 -- 1380.8144270814612 -- 1275.1596116315409 -- 1343.4433840464453 -- 1378.8493146460496 -- 1327.325226866695 -- 1263.6556108547268 -- 1234.913488574122 -- 1196.622188522041 -- 1188.2563891862485 -- 1183.6282416131705 -- 1200.742845388723 -- 1223.6478289725378 -- 1183.7610270470348 -- 1351.4545766813376 -- 1368.2136330499063 -- 1363.381034467573 -- 1432.7189143609828 -- 1430.4784582451266 -- 1352.7590351060674 -- 1382.0092892072134 -- 1385.631227944495 -- 1274.9736778389877 -- 1398.8266007207596 -- 1286.8809938498302 -- 1356.8172310652676 -- 1367.0831463289785 -- 1450.9787711493632 -- 1448.0905541637924 -- 1407.855464143301 -- 1360.9869035743943 -- 1329.0390368443807 -- 1288.2009732412662 -- 1195.8099330209816 -- 1194.729262035851 -- 1188.9223575235408 -- 1321.8111786112504 -- 1211.777843378747 -- 1239.115198914177 -- 1398.1184625868411 -- 1393.7707514245283 -- 1248.3469432144175 -- 1243.6868089666693 -- 1247.0159153397997 -- 1330.6809518036073 -- 1240.7361669255074 -- 1238.0075949663378 -- 1246.858850137071 -- 1264.726490834838 -- 1273.8807221479567 -- 1272.299476041901 -- 1286.8638834666958 -- 1269.6941986055574 -- 1330.117065509749 -- 1236.0046111236704 -- 1222.5147005211697 -- 1323.287212115183 -- 1203.723911237447 -- 1203.672287734177 -- 1324.8079660022936 -- 1213.5841374845243 -- 1236.8778990661983 -- 1365.3134286859763 -- 1397.8026603524065 -- 1392.3125456466457 -- 1395.802632300328 -- 1371.6716814705924 -- 1379.9759108067826 -- 1372.9462891234505 -- 1375.7744531721112 -- 1252.9661789307465 -- 1337.5269802347257 -- 1288.9327439843848 -- 1289.565944119462 -- 1306.0632035000394 -- 1306.781092093892 -- 1281.1043518453841 -- 1302.7239374726987 -- 1255.3724309778188 -- 1313.0637436661807 -- 1201.1822810749472 -- 1192.198314912359 -- 1177.3114483288705 -- 1170.6019157841038 -- 1170.9244921621118 -- 1178.1791838060685 -- 1255.271763988861 -- 1221.6413190355331 -- 1241.812209606161 -- 1306.7855121779337 -- 1246.1455279973911 -- 1271.9618485155597 -- 1247.0067556293923 -- 1372.6654069560507 -- 1256.9297088154885 -- 1293.0430178726374 -- 1264.1119060592646 -- 1293.1483697935928 -- 1291.3477504385094 -- 1300.1076045000689 -- 1298.4285041408286 -- 1263.8211368384095 -- 1223.1145071748463 -- 1189.8855072860488 -- 1168.6255353824931 -- 1162.5008226701416 -- 1159.7369739217097 -- 1173.6646975915985 -- 1167.1836678692027 -- 1161.6594526160031 -- 1175.0078226255077 -- 1227.0754681761143 -- 1219.3658514680076 -- 1236.8879046417449 -- 1298.734625938919 -- 1244.2215428955328 -- 1263.6586266895472 -- 1285.3745297479284 -- 1309.8392821019497 -- 1341.6708972229835 -- 1389.2281452762322 -- 1481.2850024472575 -- 1426.6774957583893 -- 1372.6222051975917 -- 1364.1426563738535 -- 1277.754520236265 -- 1392.2659791223373 -- 1331.7748657568245 -- 1188.5455265016337 -- 1187.7856123930835 -- 1186.32036550705 -- 1201.3440592234433 -- 1187.6598063566273 -- 1184.5435540002236 -- 1208.9730265396088 -- 1316.239752452741 -- 1353.6855032888116 -- 1344.2262393114029 -- 1312.4765021268745 -- 1330.143476731809 -- 1253.5966037566623 -- 1243.787718462617 -- 1333.8066900232548 -- 1288.8868362989183 -- 1271.329663203379 -- 1286.0745129996303 -- 1277.1848884760655 -- 1304.2785690686237 -- 1385.7791256469075 -- 1317.4438497037052 -- 1297.0022035310642 -- 1215.4397698181629 -- 1328.0217736576676 -- 1200.000049621967 -- 1204.1947683297967 -- 1212.9220421490077 -- 1274.7010974156328 -- 1214.2462346296084 -- 1371.6879054191324 -- 1272.9375627625077 -- 1386.045688517005 -- 1246.255440205921 -- 1256.4367635164654 -- 1255.2037818136894 -- 1261.721583445097 -- 1383.3552852684197 -- 1254.9189036874823 -- 1250.1270978852024 -- 1273.5917748621737 -- 1296.2939717478682 -- 1299.917288987085 -- 1399.777745786006 -- 1385.835587143672 -- 1284.9831450252582 -- 1364.7412763415623 -- 1230.437322199618 -- 1215.6467332250347 -- 1205.0113616999051 -- 1250.1702552417848 -- 1206.9489878255001 -- 1318.5265370381435 -- 1222.8256804622551 -- 1267.0526091377246 -- 1278.8508862177919 -- 1399.6586388575488 -- 1264.2145409945438 -- 1270.3002495440082 -- 1266.7917219763156 -- 1365.217134798739 -- 1375.5055592438894 -- 1297.7252136999587 -- 1238.1625946784852 -- 1358.9191888321318 -- 1280.1034646312198 -- 1273.7166687498484 -- 1304.9412937998136 -- 1418.2909537912965 -- 1299.653228373206 -- 1249.2204798958978 -- 1212.5917233380023 -- 1277.0012316588684 -- 1197.8446230176419 -- 1203.873576034187 -- 1194.289414103285 -- 1198.1575801913802 -- 1310.9366540852698 -- 1247.301589966003 -- 1351.7890582198174 -- 1389.4751995413992 -- 1385.7188641329963 -- 1355.9036577238003 -- 1379.184845437498 -- 1376.5627959839103 -- 1239.0801127085372 -- 1325.8837208007415 -- 1355.4701707509942 -- 1287.1165925439368 -- 1299.8149570784267 -- 1360.835686854017 -- 1454.8358391120385 -- 1299.3841208265544 -- 1289.8884731753326 -- 1250.7720897577105 -- 1337.4060914669694 -- 1298.8158134574878 -- 1197.037634150221 -- 1195.2927053109443 -- 1203.7682512388978 -- 1191.5277657513059 -- 1198.040643393285 -- 1304.9033663227 -- 1387.6257302582271 -- 1384.5768449098773 -- 1290.1452077549052 -- 1359.7305283933233 -- 1243.2360576036288 -- 1248.7770815655942 -- 1266.3498293399257 -- 1363.9761802244864 -- 1295.2235112482704 -- 1303.06978187192 -- 1343.0278640696533 -- 1370.1140660659216 -- 1322.0496718238799 -- 1433.3236689524529 -- 1417.5552710939778 -- 1410.878426083457 -- 1236.0822532142631 -- 1220.9579069093065 -- 1175.7290510335774 -- 1170.242698944183 -- 1180.3786648312275 -- 1178.3764524070518 -- 1299.7477916013752 -- 1245.321798222495 -- 1209.5181925390773 -- 1268.1269859921397 -- 1225.8203258104695 -- 1267.2656722768438 -- 1391.8328262699902 -- 1306.3659702334971 -- 1416.1397736100403 -- 1456.3775444198227 -- 1347.787164558723 -- 1431.356536222204 -- 1363.947397277062 -- 1342.1481891915873 -- 1447.5696214063232 -- 1380.8092007078876 -- 1422.399144380337 -- 1369.0434746370931 -- 1258.4600889396268 -- 1207.5310554946884 -- 1195.3934011389645 -- 1181.7937831298561 -- 1179.7317382119927 -- 1180.372574219461 -- 1179.8222009400004 -- 1196.5549330427823 -- 1211.7165063418136 -- 1240.850587887257 -- 1372.2685755102164 -- 1366.9304355165789 -- 1362.7161216456493 -- 1370.875190415393 -- 1370.7118567390828 -- 1249.4460930522894 -- 1346.8858208820511 -- 1268.1382013585892 -- 1276.5515266979708 -- 1297.8393577984095 -- 1278.3723595302995 -- 1367.1339866928897 -- 1244.327771671819 -- 1274.2797977439377 -- 1203.939286287665 -- 1301.5632512194436 -- 1189.3083441534743 -- 1199.4304192892296 -- 1198.612016851078 -- 1223.0966424879543 -- 1242.027037218715 -- 1366.0169428891045 -- 1387.1300738485618 -- 1363.1656566359638 -- 1381.7558572860678 -- 1261.1811864063368 -- 1378.0604864577044 -- 1271.1848595218692 -- 1239.795197378272 -- 1242.8735581064707 -- 1325.787169238391 -- 1241.6406307205689 -- 1259.1425201204006 -- 1264.5119951708944 -- 1287.851983657306 -- 1308.201859166379 -- 1293.5994805161981 -- 1317.5332273176698 -- 1233.9973452044364 -- 1276.742919900647 -- 1212.391828438645 -- 1217.8230692553318 -- 1211.8916543762712 -- 1301.1173687491219 -- 1270.5594264862493 -- 1380.3701661919217 -- 1429.7726020944904 -- 1432.517269618056 -- 1388.4778789654395 -- 1275.6186741437155 -- 1404.6798122760713 -- 1345.6280538365024 -- 1253.6320559566684 -- 1253.0551753556244 -- 1253.8323860844584 -- 1268.9557768330271 -- 1284.9228360315362 -- 1276.7954342082144 -- 1289.0745307577508 -- 1283.6705715262972 -- 1292.5685398522342 -- 1238.6777154992578 -- 1340.7307800679177 -- 1207.942288311425 -- 1200.221591011399 -- 1202.625715899345 -- 1293.397761656615 -- 1218.455995942232 -- 1245.1125514994258 -- 1247.5456428267048 -- 1461.8823876165627 -- 1393.7327512466513 -- 1382.6459214642255 -- 1375.2654275831726 -- 1271.224080478793 -- 1312.2081471524739 -- 1251.112161362443 -- 1310.6147825798341 -- 1252.3758403963798 -- 1264.4546566681904 -- 1303.1669201189775 -- 1315.2047537956105 -- 1284.5694051886787 -- 1299.1047447290368 -- 1413.760277254732 -- 1386.4539343396254 -- 1222.1440989677349 -- 1327.0285997194887 -- 1313.9041332388347 -- 1187.2947530125086 -- 1189.3558020979278 -- 1210.4878231368675 -- 1205.1165045358518 -- 1319.270288093944 -- 1370.8097588731343 -- 1238.362024985972 -- 1238.577803834399 -- 1245.538831027737 -- 1242.5547030710036 -- 1356.2972130033804 -- 1245.7796390897931 -- 1265.7553677265914 -- 1286.2420379989665 -- 1286.108418468481 -- 1304.0812390534331 -- 1284.0818778917733 -- 1301.744087238908 -- 1301.6573419450797 -- 1415.2578613651262 -- 1267.5062731595633 -- 1214.5181199607507 -- 1196.0716022294928 -- 1193.5491073577066 -- 1190.5099325964989 -- 1201.1383914754947 -- 1196.515059120544 -- 1204.5344548004496 -- 1335.5961406650993 -- 1365.698118655794 -- 1372.5261513116784 -- 1368.3172920407412 -- 1372.8982098259878 -- 1273.2359884851023 -- 1283.461093624083 -- 1284.3635120477572 -- 1293.5362296400133 -- 1292.4942383160767 -- 1332.9513701739202 -- 1315.1613137100637 -- 1295.7347336853575 -- 1295.4795369931144 -- 1321.6945565661308 -- 1296.4581507381547 -- 1291.291332405107 -- 1196.5040130511904 -- 1182.9481607653302 -- 1186.6210471695042 -- 1170.834502578992 -- 1188.5343591470546 -- 1180.0558886228969 -- 1239.8530510367082 -- 1188.9802008252695 -- 1211.474988544993 -- 1234.0200934277582 -- 1240.795443369591 -- 1299.8163822529918 -- 1275.257346437212 -- 1352.5707897308819 -- 1280.7621873942915 -- 1442.6706235486567 -- 1457.773771312392 -- 1348.7549073498203 -- 1360.4011517538993 -- 1336.983930675724 -- 1328.3998721870103 -- 1314.244795183857 -- 1301.435506810415 -- 1335.4315625946424 -- 1207.8102430327485 -- 1190.9046842324026 -- 1190.408910346529 -- 1193.1832254491737 -- 1206.3085921790246 -- 1200.3279008112117 -- 1172.2577460419957 -- 1186.0030688309191 -- 1198.0392519396487 -- 1365.3866261362045 -- 1303.4023116464055 -- 1365.1071935271639 -- 1295.5577646170796 -- 1296.398491441013 -- 1318.9415533178853 -- 1358.3441468271421 -- 1375.5141497989878 -- 1406.1563158577057 -- 1391.5491682366483 -- 1358.6287655606895 -- 1464.5384306118203 -- 1398.1077448002022 -- 1343.871800117666 -- 1331.3841278614452 -- 1326.850637890292 -- 1212.4354468563313 -- 1184.377759958324 -- 1179.8656091992443 -- 1183.9499002720856 -- 1181.9679347698295 -- 1224.3876271614174 -- 1348.247262972484 -- 1228.5419972792527 -- 1293.6906746073569 -- 1356.9769766234288 -- 1298.8046179974538 -- 1284.9047343056743 -- 1299.3886370573905 -- 1262.703821192084 -- 1251.0256045331766 -- 1378.8150059565469 -- 1296.850994930006 -- 1303.507810070741 -- 1289.1900623733486 -- 1392.2806088887635 -- 1409.9710199761332 -- 1289.7909269533163 -- 1332.5153437734054 -- 1208.4411292745847 -- 1202.2749899852267 -- 1200.7478911988967 -- 1200.5561592465613 -- 1205.854507804445 -- 1217.5090425315107 -- 1215.1366004325657 -- 1259.5451814998976 -- 1254.6243212101315 -- 1309.2881382178136 -- 1326.7129603675862 -- 1315.0447897159663 -- 1358.7175080053305 -- 1236.760288208233 -- 1232.6648459025616 -- 1234.841062962821 -- 1248.250054433411 -- 1260.861673759271 -- 1292.323251405238 -- 1294.2257223528238 -- 1306.5265167703085 -- 1310.9286906146542 -- 1408.744038287553 -- 1339.8370499423372 -- 1353.454073409694 -- 1197.2121757705113 -- 1194.3455011818212 -- 1194.556135881492 -- 1201.084457336714 -- 1201.6029173645145 -- 1216.5247573065997 -- 1235.0183141613145 -- 1368.7585073402345 -- 1264.015822072439 -- 1386.1770102968599 -- 1391.9140296845846 -- 1286.258337255572 -- 1254.7935583845428 -- 1306.8987923580735 -- 1290.345004764062 -- 1320.6689390478264 -- 1340.4626499626258 -- 1333.7640479695463 -- 1318.2259732764787 -- 1426.5395973130155 -- 1341.1856248966515 -- 1303.082839809226 -- 1260.221444211293 -- 1222.2069469974424 -- 1208.6807136549944 -- 1197.7681064374249 -- 1272.4900408316255 -- 1197.060865695641 -- 1191.7161324029687 -- 1201.4442691213067 -- 1223.037805942843 -- 1326.2549744490202 -- 1376.769035918267 -- 1263.8063796365564 -- 1255.0735429290955 -- 1405.099267730331 -- 1286.4459515527465 -- 1316.4818015200328 -- 1346.5630673031612 -- 1365.5065781500239 -- 1444.1697750674844 -- 1406.0811842540345 -- 1379.8575101447543 -- 1377.9208229463284 -- 1391.5613498781383 -- 1431.1256071123892 -- 1400.4228619325488 -- 1225.8215753418397 -- 1322.6137056455564 -- 1276.0080263501138 -- 1180.7805483122745 -- 1194.8686651163698 -- 1190.193237997292 -- 1195.5260154559628 -- 1316.200101377057 -- 1387.704982048885 -- 1234.6172722736849 -- 1340.5885830486304 -- 1361.0495717578772 -- 1260.3350389527102 -- 1262.5906299454527 -- 1295.8903530850148 -- 1354.4916765005244 -- 1459.4313888675451 -- 1356.8115231544039 -- 1421.5414556407684 -- 1351.058351210977 -- 1463.7054070015056 -- 1356.3727732845025 -- 1342.3809335000465 -- 1283.4684570711152 -- 1256.7863870906385 -- 1208.2014637930606 -- 1203.5008838413144 -- 1185.3364881095242 -- 1196.5904589119293 -- 1204.3232011529162 -- 1187.6423896015292 -- 1182.5977201017408 -- 1229.5253893104116 -- 1374.4992036701683 -- 1306.0203138970842 -- 1303.5243438154403 -- 1357.4373418952234 -- 1366.3735579124937 -- 1361.7187892608483 -- 1364.9820302821881 -- 1438.9859296480329 -- 1430.9190473958424 -- 1420.5759032205992 -- 1393.8570826306488 -- 1424.2415406196849 -- 1357.1209856216492 -- 1321.3134402110206 -- 1295.6041897669054 -- 1363.2672296629091 -- 1220.2003701534566 -- 1214.9444972073275 -- 1197.6168418293028 -- 1207.3599780969328 -- 1202.490886598542 -- 1182.0857284537417 -- 1204.829292613592 -- 1231.3300077365654 -- 1299.9287518414037 -- 1407.0856837082865 -- 1429.1778348943715 -- 1459.3030500527698 -- 1464.4120491385381 -- 1527.657477375659 -- 1399.5919443026255 -- 1399.2134759849146 -- 1406.3335405992655 -- 1392.0487101291649 -- 1493.5857327954102 -- 1412.854088304395 -- 1382.1849395445213 -- 1288.1476815935232 -- 1244.2125347146996 -- 1237.1043897696745 -- 1216.7684916505443 -- 1208.3133219427466 -- 1199.4201124883043 -- 1220.002575536638 -- 1316.3990638763946 -- 1223.9468259962405 -- 1381.192238710595 -- 1396.5402997762292 -- 1415.7142008118199 -- 1404.7115339659135 -- 1431.7358974747087 -- 1320.0431806862248 -- 1307.168570659846 -- 1316.6117713325352 -- 1333.3095116781717 -- 1327.944260187961 -- 1345.8287550833513 -- 1342.2261746309216 -- 1340.8821162925458 -- 1459.6012864673426 -- 1361.7626997321802 -- 1319.000340051253 -- 1276.2404313504217 -- 1227.6798808995275 -- 1196.829964265754 -- 1238.8578219275773 -- 1184.4800982942904 -- 1196.632341853218 -- 1203.7221837445968 -- 1305.7599706644316 -- 1228.6841992849952 -- 1303.4216151955584 -- 1353.7036865580026 -- 1345.1456892452545 -- 1312.353179787243 -- 1350.6491147701036 -- 1232.6771361253132 -- 1236.7032602408995 -- 1236.451224224541 -- 1231.6828533290584 -- 1231.7504721759728 -- 1252.1741530041672 -- 1241.0846551502764 -- 1294.1663229334217 -- 1277.4990882110844 -- 1279.946486852896 -- 1228.765564978806 -- 1325.9020363910374 -- 1197.4206600354842 -- 1187.7192169465636 -- 1187.428170670275 -- 1198.2572774465416 -- 1201.245822750397 -- 1233.8162148359625 -- 1227.434926612215 -- 1373.6281776899139 -- 1373.8517712928735 -- 1379.107790098147 -- 1248.432119897 -- 1252.1998016652547 -- 1254.4731498188182 -- 1259.1150440111057 -- 1262.159296441394 -- 1289.088424235328 -- 1365.1974132673702 -- 1283.1376493330067 -- 1285.2710349693132 -- 1303.6399647902736 -- 1381.9539973483868 -- 1399.4530936668182 -- 1348.8519902396044 -- 1220.1624654359596 -- 1203.8254228490314 -- 1318.4802585883767 -- 1199.8356277818427 -- 1214.5036710164097 -- 1208.3081659403344 -- 1340.042704724437 -- 1236.5437581281396 -- 1384.252746551093 -- 1255.3478396641526 -- 1247.3041023693086 -- 1254.613118708087 -- 1260.0480513878617 -- 1257.7496684468383 -- 1299.9211385516955 -- 1263.6160985850245 -- 1277.3320133371446 -- 1285.799741501145 -- 1290.751635850932 -- 1301.2044426292234 -- 1336.0047843480806 -- 1334.1049536491844 -- 1303.8700253166755 -- 1371.7505198861263 -- 1219.0400767512833 -- 1202.821866694349 -- 1198.3991030784557 -- 1191.3493425657127 -- 1290.7933286218451 -- 1212.99515173403 -- 1207.0331985846988 -- 1352.344103615968 -- 1338.1881492901118 -- 1249.606180902966 -- 1250.5565109249808 -- 1303.3978614962743 -- 1295.4193210625572 -- 1247.7881746691414 -- 1263.3950917105553 -- 1259.9421781923388 -- 1278.3757657234169 -- 1292.1630525250464 -- 1286.7937847008438 -- 1437.5014460442467 -- 1326.4732706374173 -- 1297.9702787278402 -- 1302.5106185178417 -- 1265.402362229045 -- 1203.933129141864 -- 1180.9258493037146 -- 1176.4175298789135 -- 1170.860966066125 -- 1183.7476680975333 -- 1179.1205482231771 -- 1173.32115964691 -- 1188.5274973955186 -- 1336.6102813319799 -- 1236.0184870097464 -- 1373.788974045299 -- 1386.7552528520823 -- 1408.895465166447 -- 1436.9549016999454 -- 1366.0479644670859 -- 1458.1710492914622 -- 1391.1424630346426 -- 1403.3810432855241 -- 1410.1883838688807 -- 1389.9670969660008 -- 1379.9297369712538 -- 1398.464529913635 -- 1437.6379940751956 -- 1287.6506207061877 -- 1217.6727018064742 -- 1212.9173067473048 -- 1189.9633571308523 -- 1188.7724964758447 -- 1198.254058391918 -- 1206.6472220852743 -- 1182.2659048573362 -- 1246.9254642906828 -- 1240.5273832981766 -- 1242.4236160611501 -- 1246.7247444261154 -- 1254.8317392620945 -- 1256.398274812928 -- 1287.577885252118 -- 1368.27105941935 -- 1306.5576440270447 -- 1331.8243161443627 -- 1333.6732622952368 -- 1347.1300371910306 -- 1416.526521158128 -- 1319.468767202607 -- 1400.5120082391352 -- 1253.2268826446254 -- 1335.6693094104392 -- 1187.9214355330494 -- 1179.9012689638482 -- 1175.8484056258812 -- 1177.9647269169095 -- 1183.076742657531 -- 1184.0899051818535 -- 1208.5672779181104 -- 1221.1728584943814 -- 1245.492644837216 -- 1304.4556972415119 -- 1251.9384344043287 -- 1331.220533072735 -- 1403.2446518079462 -- 1376.5373088562997 -- 1237.4251563339237 -- 1227.285395363646 -- 1231.9668392081417 -- 1257.7641193537263 -- 1320.3190063998898 -- 1309.1456298912933 -- 1385.479221753207 -- 1425.9148795206477 -- 1295.1737045896964 -- 1277.3588606363244 -- 1378.1380092277484 -- 1207.6519866407564 -- 1202.252581212351 -- 1184.7415589456193 -- 1204.9516282242885 -- 1201.0513915243482 -- 1205.315564935471 -- 1241.5155277814456 -- 1245.2740208730643 -- 1386.6164136586683 -- 1452.1850091141835 -- 1432.3975605739336 -- 1431.963960591722 -- 1468.9901877343439 -- 1374.6924206172982 -- 1335.9886687219398 -- 1402.5351530906153 -- 1314.88171393019 -- 1372.3983696958926 -- 1322.9516158812542 -- 1269.3485957225969 -- 1287.3547516256199 -- 1275.8187946737717 -- 1347.831674321786 -- 1306.8958186476657 -- 1185.46349349198 -- 1188.3497514044748 -- 1191.251788831106 -- 1194.0814741381728 -- 1201.992077185301 -- 1223.0355610278073 -- 1359.8407010436881 -- 1260.4372525501894 -- 1271.2544531101582 -- 1365.8916627251688 -- 1379.7134802844103 -- 1378.89548526716 -- 1380.8722762598281 -- 1379.7795941409177 -- 1253.4984843383963 -- 1300.2032732098876 -- 1261.1685392904965 -- 1270.862067402915 -- 1398.5265059945382 -- 1391.96617475376 -- 1274.5565999678677 -- 1411.3006802068533 -- 1241.8014759245996 -- 1204.1512501295128 -- 1195.1616606676916 -- 1186.5800588209513 -- 1181.1311687663888 -- 1179.7463263098002 -- 1183.0860658091817 -- 1320.7381377429358 -- 1210.9855350617647 -- 1328.154828838854 -- 1378.4097168287049 -- 1254.8360713138075 -- 1229.0672586012092 -- 1346.195569261782 -- 1266.221463804799 -- 1301.4644895349109 -- 1328.1279490214524 -- 1340.3383419627462 -- 1397.9302059856277 -- 1366.6940158797304 -- 1318.1339583056208 -- 1338.9552773826274 -- 1329.7399675501385 -- 1348.570562844495 -- 1285.1504287266941 -- 1250.129914946122 -- 1203.0898779980214 -- 1190.8453914688052 -- 1184.715251043234 -- 1180.793109127582 -- 1196.002807382429 -- 1202.673803414131 -- 1228.567061930066 -- 1259.2901310125903 -- 1305.191910487916 -- 1288.1216660368216 -- 1295.3012819478236 -- 1304.3611122293335 -- 1320.1263423872908 -- 1341.44598861224 -- 1437.8252881059216 -- 1354.2573883828625 -- 1363.6836706329166 -- 1365.1349712581168 -- 1350.6580989757667 -- 1354.0371177069985 -- 1343.9962142970767 -- 1404.3133972777794 -- 1288.7087101795744 -- 1221.423458746533 -- 1197.0886291516053 -- 1190.6309256120073 -- 1196.6004679650741 -- 1208.4877585160527 -- 1253.9401648242128 -- 1247.2192064656404 -- 1367.8139117320848 -- 1257.8645274121113 -- 1337.458407644432 -- 1285.658080713299 -- 1271.7657554866078 -- 1281.5249030640682 -- 1272.6150575219365 -- 1255.8150968082507 -- 1252.0400596956856 -- 1266.0614990984611 -- 1275.6142552629617 -- 1363.9829564499414 -- 1280.9027048087776 -- 1324.8444992201764 -- 1290.9944193179597 -- 1238.8523834402197 -- 1224.8396227780386 -- 1209.1370548415578 -- 1198.2943871649074 -- 1188.4857913551518 -- 1187.538195874458 -- 1189.3235461361514 -- 1226.1634262427335 -- 1326.4407043464528 -- 1349.7843468336353 -- 1359.8980783327747 -- 1396.282406350675 -- 1354.2586506362918 -- 1361.836601797661 -- 1241.4654861134695 -- 1279.8715039016063 -- 1297.8211462098081 -- 1351.3486587002867 -- 1387.4704440689598 -- 1420.4353657911438 -- 1413.7870037189311 -- 1454.4785831752383 -- 1391.7862495263191 -- 1362.4654650140826 -- 1300.169371506313 -- 1318.522506133558 -- 1215.6153990127377 -- 1179.6601648356846 -- 1175.423849655142 -- 1170.6179022179822 -- 1174.0090703521143 -- 1181.6590633105031 -- 1338.0802193402276 -- 1239.1866722270943 -- 1279.4957756932947 -- 1353.685996215535 -- 1233.6175992321027 -- 1342.8572761843554 -- 1225.76152709453 -- 1241.764327215887 -- 1220.8323880639282 -- 1204.5127045910667 -- 1218.6264689484965 -- 1231.6552407332447 -- 1261.0998285095172 -- 1246.8488020166092 -- 1385.7512544161968 -- 1261.647251970476 -- 1358.9607514205052 -- 1283.0872106437337 -- 1184.8226100158004 -- 1238.7762369345357 -- 1187.781241316358 -- 1196.0816895955525 -- 1207.9642825398116 -- 1225.5662493765396 -- 1241.8751904772698 -- 1345.7885434210236 -- 1247.851126173611 -- 1350.460992712645 -- 1269.0009459726023 -- 1276.735885163459 -- 1258.303173225884 -- 1250.778572930772 -- 1258.4947342147202 -- 1272.7778521639455 -- 1290.3214923823427 -- 1325.5106613909054 -- 1332.3230735372606 -- 1365.8795936177344 -- 1359.4106836437684 -- 1322.7497795138345 -- 1362.645126716218 -- 1226.505951106679 -- 1208.530696796342 -- 1186.9605548009788 -- 1181.8334884646015 -- 1178.7822950698514 -- 1181.592197621144 -- 1185.0764541550511 -- 1326.0340868412381 -- 1248.4771110804725 -- 1241.3662382525745 -- 1366.1979962801709 -- 1286.8295856687437 -- 1387.1770379765096 -- 1290.6486427554476 -- 1364.5398225739993 -- 1341.4318901352565 -- 1350.3264148538608 -- 1494.868820945874 -- 1503.6049351961087 -- 1407.5844232522747 -- 1393.5293426072444 -- 1506.5071499638725 -- 1339.2741483425225 -- 1286.1064944296631 -- 1231.388651572907 -- 1212.641738262274 -- 1195.380494970798 -- 1193.9026913210248 -- 1225.6647128567706 -- 1342.655233492774 -- 1287.5978787213603 -- 1236.6073353293684 -- 1241.2196069148818 -- 1260.7185719391377 -- 1296.41831783886 -- 1257.9147923353432 -- 1276.2650497177176 -- 1357.1669845815395 -- 1383.154523832854 -- 1395.1279356362345 -- 1420.1048662792866 -- 1437.9150669862627 -- 1488.7169992535257 -- 1468.7415094330865 -- 1530.833997960252 -- 1447.2088431579366 -- 1422.5303779807398 -- 1489.7759918366282 -- 1299.6577329572883 -- 1261.4907615950244 -- 1232.5640299075737 -- 1225.2461602998412 -- 1230.5913015457961 -- 1220.455129740707 -- 1212.3087029802732 -- 1361.905247825689 -- 1255.5071847747486 -- 1355.8584328802929 -- 1327.0292676006645 -- 1387.7771414329434 -- 1356.7216114543583 -- 1424.808769845675 -- 1439.8653208691032 -- 1345.8339114777204 -- 1341.4061434648054 -- 1348.7158735307835 -- 1317.1166118188082 -- 1336.5014389396752 -- 1319.2356754725456 -- 1315.5637592459416 -- 1389.3748050651172 -- 1379.7426410838355 -- 1327.905995259467 -- 1186.7847783667507 -- 1182.4679751445578 -- 1173.179788480981 -- 1168.5603066925044 -- 1169.316389469722 -- 1170.0904095312908 -- 1180.4746364436212 -- 1208.948039955179 -- 1211.0944223799788 -- 1370.3685259315664 -- 1404.111198324892 -- 1363.463920136248 -- 1261.3865933442532 -- 1341.662828120827 -- 1241.3449544599482 -- 1379.0971902280755 -- 1261.3944466742034 -- 1403.053658736527 -- 1286.3818682288447 -- 1446.6062494982984 -- 1289.0515162917734 -- 1277.4181027604 -- 1248.0147369444767 -- 1250.0497690989278 -- 1298.779162035598 -- 1169.000747474461 -- 1173.3354527793915 -- 1206.2885756115686 -- 1182.981137044643 -- 1188.1881415916098 -- 1219.480483594905 -- 1213.9270101237844 -- 1238.094185866452 -- 1257.3034338556604 -- 1324.2295065021174 -- 1274.5979594304854 -- 1252.323637577421 -- 1264.5248265293003 -- 1265.2650277316245 -- 1393.3113870650307 -- 1304.0436385988476 -- 1377.775899100848 -- 1350.0337810243814 -- 1386.7639839939304 -- 1325.567646241869 -- 1317.0933105327244 -- 1370.3134720974565 -- 1213.4390464047692 -- 1180.5602649931113 -- 1187.8004715191828 -- 1189.62364280781 -- 1190.4176227337805 -- 1182.3815838874216 -- 1189.353794138654 -- 1328.7902640840725 -- 1366.2040335504648 -- 1250.608987547337 -- 1252.7708261683729 -- 1258.412397495766 -- 1382.7075243663571 -- 1274.213250537509 -- 1314.5842111857758 -- 1410.1439152753019 -- 1344.816107151221 -- 1415.9208864831703 -- 1445.8387240915592 -- 1422.6348592437043 -- 1404.374200554244 -- 1446.7877024733882 -- 1494.077152014507 -- 1288.678896737256 -- 1241.4062987239297 -- 1215.177228727711 -- 1208.340112791561 -- 1212.0694246658031 -- 1220.638698299112 -- 1204.1593897079765 -- 1193.1828001911852 -- 1328.8967867826007 -- 1251.725988086597 -- 1388.97516591465 -- 1384.988187460786 -- 1342.506545626311 -- 1288.5571281462255 -- 1307.5338981885445 -- 1355.8556360964312 -- 1454.0401289238641 -- 1417.1844008684131 -- 1483.8167678816399 -- 1438.1191410493423 -- 1516.4404000439179 -- 1441.4333762716344 -- 1394.6144424159816 -- 1420.1153193418108 -- 1328.7055704004113 -- 1284.323273775526 -- 1246.4287042236556 -- 1228.2120232988505 -- 1197.9225982186485 -- 1204.1747552251745 -- 1201.8804621679772 -- 1305.7030167819123 -- 1200.7855081767284 -- 1297.026056765644 -- 1269.1443013852788 -- 1315.2369925611265 -- 1251.6207808113147 -- 1251.3002253178092 -- 1245.4251549064695 -- 1255.107152165083 -- 1248.9971961773824 -- 1266.397786489602 -- 1270.6755296289425 -- 1292.1663706177405 -- 1305.2399808700793 -- 1301.7668355540377 -- 1298.87001302579 -- 1276.8201450102524 -- 1280.7182689789763 -- 1248.3096493056105 -- 1205.1085854084695 -- 1178.5004679227059 -- 1178.1182599652684 -- 1185.0658733140265 -- 1199.7273518704396 -- 1187.60865879807 -- 1214.0001835228886 -- 1383.3481309748831 -- 1368.7052868035923 -- 1249.0658181052775 -- 1321.714852621118 -- 1259.08340771206 -- 1253.3690089602712 -- 1254.8152156048725 -- 1265.8482025516576 -- 1268.3365934416547 -- 1283.8254948107347 -- 1318.3525421780364 -- 1366.5864586716643 -- 1459.3872247480447 -- 1369.9690141894393 -- 1462.9032603406124 -- 1300.271695719401 -- 1224.3078696569676 -- 1288.7779590870566 -- 1187.2619082844408 -- 1176.3024109599442 -- 1170.3845711008073 -- 1174.5370098159467 -- 1242.6621994265095 -- 1302.686971119501 -- 1348.7481455229413 -- 1302.3857025879222 -- 1397.258561115106 -- 1263.145519566736 -- 1343.0555761242147 -- 1281.0157933263765 -- 1227.0558007712652 -- 1232.89557954439 -- 1253.3769405023172 -- 1241.1183930955754 -- 1241.1548203465989 -- 1234.756623397204 -- 1266.3478075803844 -- 1261.019525065218 -- 1310.5411503701553 -- 1238.0844698886667 -- 1278.1942291497191 -- 1191.134739918743 -- 1238.15430500146 -- 1174.3600520431564 -- 1178.5268180330474 -- 1179.7865740167529 -- 1174.8521173546696 -- 1298.2549299981927 -- 1214.4574802808604 -- 1243.2103415895083 -- 1248.4780709594183 -- 1259.9403684512718 -- 1288.0077863619265 -- 1341.398332556015 -- 1304.9300152583357 -- 1311.0493419948807 -- 1352.5847707289197 -- 1342.788798439811 -- 1370.3979943004028 -- 1352.0443037881505 -- 1331.0871343796784 -- 1433.9777787112853 -- 1318.7566773101353 -- 1344.7724226179296 -- 1209.746454853847 -- 1199.8120718033915 -- 1222.7791682514378 -- 1188.1653003749873 -- 1187.6091959518644 -- 1185.272375772246 -- 1180.639976452871 -- 1191.9112311325655 -- 1205.1858767794981 -- 1343.1653853639828 -- 1239.9000406200523 -- 1265.445304349619 -- 1257.45441254884 -- 1283.6263646943598 -- 1256.4713262479697 -- 1350.191957137681 -- 1318.0487900222513 -- 1308.5510498331917 -- 1324.7247735539038 -- 1338.8620531147978 -- 1424.7401132919829 -- 1317.2109820516255 -- 1382.611403404395 -- 1248.6023758783865 -- 1220.3128675407766 -- 1190.5702532593664 -- 1177.5309942285483 -- 1175.719583273103 -- 1173.044320335319 -- 1178.2328836229835 -- 1181.6730516884625 -- 1353.2480030496952 -- 1364.3682885553008 -- 1252.1062626882547 -- 1240.4762562692795 -- 1241.403979423222 -- 1251.8843405499028 -- 1250.5392473636136 -- 1258.9640540679732 -- 1274.6485887676824 -- 1288.3713435774239 -- 1301.8879178536156 -- 1348.5148347139675 -- 1340.1463409340924 -- 1385.1979938237807 -- 1379.390494058495 -- 1346.3769587783477 -- 1390.5671699907487 -- 1349.1977274352244 -- 1211.8288910569493 -- 1204.4873849728006 -- 1284.9533292619146 -- 1204.026066589796 -- 1196.1299943869483 -- 1183.1549486285228 -- 1214.397980435773 -- 1276.533998209901 -- 1358.6553636669466 -- 1309.3853015699729 -- 1298.7560198779915 -- 1309.0110439188472 -- 1332.6809956932718 -- 1393.0972898958628 -- 1368.5353020133855 -- 1394.9204319730352 -- 1436.0331348682055 -- 1463.6370682791423 -- 1449.9744713468383 -- 1453.4271238170807 -- 1411.0904672625968 -- 1389.577667323284 -- 1407.3562733678784 -- 1407.1622459744488 -- 1238.841892104226 -- 1290.9186129089055 -- 1191.3894661035451 -- 1177.0775586624927 -- 1175.071847884303 -- 1180.0169702886817 -- 1207.6145059801331 -- 1342.947568397632 -- 1288.5167498450369 -- 1253.7590358994346 -- 1235.4437082640475 -- 1230.7828977514673 -- 1273.0801870833761 -- 1233.3087049966357 -- 1250.626224053337 -- 1317.11014040451 -- 1228.2572509310135 -- 1320.2727586183955 -- 1251.56692421382 -- 1248.2485858080925 -- 1263.116971734853 -- 1384.591654702099 -- 1223.2742625609976 -- 1205.921689570881 -- 1314.3578076897156 -- 1200.904623976898 -- 1191.5283813705623 -- 1200.594887230513 -- 1272.3748781895326 -- 1221.5480426723389 -- 1244.6636067239617 -- 1269.687280796601 -- 1277.2621674621862 -- 1247.8504752227586 -- 1385.7179924742436 -- 1271.8554074780727 -- 1375.4820526254828 -- 1271.3863829852053 -- 1289.525931033452 -- 1285.5783450213373 -- 1320.2826659433445 -- 1465.3815304125762 -- 1402.4689738331465 -- 1377.6772880745236 -- 1347.23449089988 -- 1358.386256750218 -- 1318.9557021055746 -- 1253.659701402014 -- 1209.7816290634455 -- 1193.6403836154602 -- 1185.5263369193772 -- 1340.749623723857 -- 1188.842920873546 -- 1186.8385184914007 -- 1196.3273006868963 -- 1309.7135651263054 -- 1371.7419197985928 -- 1279.397305368636 -- 1242.2142761170312 -- 1242.5178891089672 -- 1232.4073250973781 -- 1245.959287320885 -- 1243.3817294406551 -- 1261.8807006582108 -- 1276.9076693092459 -- 1337.2018898843455 -- 1354.1152624139122 -- 1424.3360224149235 -- 1321.056382095228 -- 1292.695401311725 -- 1360.7643036537675 -- 1228.2734161462943 -- 1193.7773553703785 -- 1183.7902937713063 -- 1171.479505898906 -- 1187.3480921596645 -- 1194.043116677124 -- 1178.8171279698786 -- 1183.64332842628 -- 1205.1977237180042 -- 1266.7789821659342 -- 1293.6444527988092 -- 1297.3174175824765 -- 1388.3371364418927 -- 1305.9051131305512 -- 1426.8237876113653 -- 1329.4051542652198 -- 1333.2091635387276 -- 1356.9800845915415 -- 1392.321858334725 -- 1362.0976571484287 -- 1369.484777535428 -- 1357.106741172753 -- 1321.8445247048978 -- 1272.4449593296326 -- 1363.90290886673 -- 1212.312919923309 -- 1193.4357979975441 -- 1187.67420637739 -- 1196.300251644933 -- 1219.415381010194 -- 1173.8391561503088 -- 1192.4987811308133 -- 1212.9890232788205 -- 1234.429566945466 -- 1257.526950999145 -- 1315.5020061294397 -- 1296.2505721807445 -- 1295.5207750642867 -- 1376.7474402744729 -- 1339.6904161874122 -- 1370.4498377920318 -- 1400.5355925230224 -- 1408.19585291047 -- 1502.6663890255634 -- 1344.4345112195924 -- 1444.8669805099823 -- 1322.5405838935942 -- 1389.159986396165 -- 1345.7417209728044 -- 1208.0011445815344 -- 1195.3155121481245 -- 1180.9901337852173 -- 1184.6191381474757 -- 1203.7063981911979 -- 1187.288483286701 -- 1223.052521041702 -- 1277.5794255371802 -- 1244.9154098993713 -- 1287.202655397992 -- 1250.3680563201203 -- 1326.7477432931469 -- 1265.9873348136728 -- 1307.0027634802866 -- 1309.8802766639976 -- 1314.364513706077 -- 1348.6667918798871 -- 1392.6614966711447 -- 1380.271665862575 -- 1459.6564104073482 -- 1434.5617520134942 -- 1419.0751226040672 -- 1416.7426388743977 -- 1250.4206299911666 -- 1221.340166615387 -- 1208.5470640453707 -- 1207.2583287373993 -- 1228.174158176574 -- 1230.3566680455876 -- 1204.9719662806378 -- 1353.7869348960182 -- 1404.2374809738358 -- 1298.630531685416 -- 1330.244344435672 -- 1379.651709072124 -- 1419.3063241833154 -- 1337.395799916485 -- 1361.7932497413253 -- 1400.0056384884126 -- 1375.5339180740239 -- 1400.9596505799225 -- 1424.3238902358098 -- 1373.923138066029 -- 1492.9600478430807 -- 1518.6621247604426 -- 1499.2469092213216 -- 1335.4825906665912 -- 1301.1073330212912 -- 1268.330427883125 -- 1257.5817163705192 -- 1253.702863820929 -- 1261.3853632357732 -- 1254.4805027999475 -- 1232.5088775010227 -- 1254.9592256910196 -- 1343.158359694186 -- 1434.3755415270189 -- 1313.5666848666476 -- 1338.2719380185283 -- 1487.5840014870232 -- 1404.2945939859599 -- 1451.460956647838 -- 1580.8737023740389 -- 1461.1465493849948 -- 1469.531153886326 -- 1529.152585009559 -- 1392.9822973762004 -- 1514.325973274372 -- 1378.0769790330396 -- 1363.964815478849 -- 1304.6333973747485 -- 1298.3330310834303 -- 1258.2384922693489 -- 1246.4352643295565 -- 1244.371898504442 -- 1243.3604356126439 -- 1347.6234271306066 -- 1217.7912637403892 -- 1257.8551165574652 -- 1408.1279970516737 -- 1471.5968718820498 -- 1442.2199161781534 -- 1363.1396924578296 -- 1328.2556421322897 -- 1326.1751100552551 -- 1356.495825387391 -- 1353.3591220058674 -- 1357.9461247047495 -- 1394.6745176259692 -- 1417.2414301159683 -- 1396.6462674137717 -- 1381.2902068915944 -- 1422.7424264196356 -- 1366.2875284815298 -- 1325.127613738057 -- 1326.6408356218358 -- 1247.984658321006 -- 1221.6319794386914 -- 1198.5567696269973 -- 1188.767701944833 -- 1189.9149120299764 -- 1274.3815324829336 -- 1225.9935048767416 -- 1322.645435448733 -- 1369.1132316019123 -- 1345.4687330039083 -- 1245.330384194109 -- 1244.6045220256306 -- 1244.1696650454467 -- 1262.6931302784333 -- 1326.8815173210912 -- 1291.1230825982946 -- 1290.932612209443 -- 1327.0736724635974 -- 1457.3481298142854 -- 1536.4708890791148 -- 1350.6551988812917 -- 1326.0083987411758 -- 1309.9059657302944 -- 1233.7521381122478 -- 1201.1762860644387 -- 1195.406613773719 -- 1179.598450532443 -- 1191.992191508438 -- 1191.9927997800783 -- 1179.3919110613165 -- 1186.122032296773 -- 1331.9562128158004 -- 1242.3985428615265 -- 1250.2604381506424 -- 1286.3367048679488 -- 1303.4473051682241 -- 1315.122534865353 -- 1359.3446832927418 -- 1417.2421568381712 -- 1361.8109888870217 -- 1389.0793233903992 -- 1422.035029734291 -- 1508.8119530303184 -- 1408.246380252082 -- 1496.702757581658 -- 1480.1443052378029 -- 1282.9305770322637 -- 1250.1578902530916 -- 1226.2602319388795 -- 1211.6778815726389 -- 1207.9250158152897 -- 1207.8692851445949 -- 1198.7345540461322 -- 1179.51158721977 -- 1186.4749543038645 -- 1302.8430622264268 -- 1258.0200701154558 -- 1279.4937232561629 -- 1298.3231600277743 -- 1315.8866216600109 -- 1465.1466952719547 -- 1362.0230447790932 -- 1487.4236926730541 -- 1522.6241572328563 -- 1522.880779785158 -- 1554.3598611768284 -- 1450.3972695010898 -- 1440.0254343684646 -- 1421.0257275682022 -- 1340.6331760898772 -- 1313.3836816284427 -- 1263.5789469204437 -- 1228.9094619371087 -- 1212.8163836885792 -- 1208.6656926416126 -- 1219.3858116440429 -- 1208.077711678722 -- 1206.2770273464423 -- 1231.4972928235738 -- 1380.7352812595846 -- 1391.9900707325562 -- 1289.9949330717309 -- 1311.5094732977586 -- 1319.949880958727 -- 1344.318645934659 -- 1522.9515677404509 -- 1406.2878788511762 -- 1488.2361389664952 -- 1465.1845726487209 -- 1516.213370571679 -- 1455.5785144055703 -- 1439.492091087758 -- 1398.019926405085 -- 1414.7433254397656 -- 1454.8503308371112 -- 1298.8150950766371 -- 1251.1842407592064 -- 1268.8080974292009 -- 1232.903602826034 -- 1250.6774645749226 -- 1246.1709484800178 -- 1227.923853933838 -- 1249.039857998949 -- 1425.917469639933 -- 1314.1368741197375 -- 1307.3648371941022 -- 1356.9950932415682 -- 1362.7227736266755 -- 1370.925860843848 -- 1427.3356305853104 -- 1435.5812552759664 -- 1427.7882806130863 -- 1464.1551398191507 -- 1483.9263482669212 -- 1457.1065248431262 -- 1481.9037776762186 -- 1462.520023401513 -- 1408.9361458810865 -- 1357.986526390434 -- 1299.3513103258695 -- 1274.9798448175748 -- 1247.2546808937605 -- 1230.9119796777434 -- 1245.9298009980798 -- 1229.2977743707631 -- 1220.6066956786224 -- 1230.8097546693662 -- 1342.588371682047 -- 1337.4087997086635 -- 1342.0066283064111 -- 1317.7545241401435 -- 1411.7130748561265 -- 1452.1849160376582 -- 1403.1038914770902 -- 1399.4973393912655 -- 1478.1520624112068 -- 1475.2126102694358 -- 1505.9716525367232 -- 1440.9958999090522 -- 1496.0907346177735 -- 1487.4973238449538 -- 1532.5564003946736 -- 1472.4793459933542 -- 1422.6866601744553 -- 1256.9766411465278 -- 1254.0056950601072 -- 1245.1150469368781 -- 1264.9594722990928 -- 1378.7762369876334 -- 1243.1646990875213 -- 1348.8225468753712 -- 1420.1840777538548 -- 1296.4196101087564 -- 1315.1071891222423 -- 1334.0802447504964 -- 1353.4740943103827 -- 1444.9244290838476 -- 1411.0086714788258 -- 1512.7291044223984 -- 1456.927807460146 -- 1476.9415449249143 -- 1508.4989210738083 -- 1494.1573909131178 -- 1446.4957072782063 -- 1449.3497300304916 -- 1458.58303467077 -- 1367.2986235275491 -- 1316.9596729503614 -- 1304.9231585580694 -- 1257.504973742807 -- 1232.959727976408 -- 1240.5086892699885 -- 1221.8982662704357 -- 1221.936600336573 -- 1350.1629053423524 -- 1266.9164499647982 -- 1274.4128986859766 -- 1314.2865681223689 -- 1307.495746580477 -- 1452.950399528158 -- 1355.5764883791703 -- 1505.3307174742556 -- 1428.1821798615103 -- 1464.281074825341 -- 1485.8759316217083 -- 1533.4755535358433 -- 1617.307561886706 -- 1520.7495444001793 -- 1556.5207060675557 -- 1470.386975383439 -- 1510.7515270821254 -- 1320.0577517119937 -- 1283.9668803969366 -- 1319.6174190425136 -- 1251.5101946806556 -- 1268.1150260411641 -- 1259.3597643327369 -- 1230.9171876024382 -- 1238.8440849244305 -- 1266.9385726748544 -- 1403.32998646568 -- 1331.7735542733628 -- 1356.9979830795871 -- 1389.228377726444 -- 1477.6542636494894 -- 1511.6742844937457 -- 1480.2362065016844 -- 1450.041205989731 -- 1512.4781858537801 -- 1506.0162535404859 -- 1602.9622786291811 -- 1477.1075476102121 -- 1484.7647897321535 -- 1415.612788249658 -- 1361.1836285978509 -- 1343.7577384012072 -- 1293.6488084651571 -- 1278.4982183932616 -- 1264.9618717406142 -- 1283.2376485643163 -- 1287.123852177843 -- 1237.8669265013277 -- 1250.8024221869928 -- 1281.2666748024126 -- 1327.333122400924 -- 1434.7021057946606 -- 1359.18356452145 -- 1387.8812822543837 -- 1441.3386152851683 -- 1461.8832766916887 -- 1464.8778955032776 -- 1462.0595717692522 -- 1523.1895136545015 -- 1565.9289999603575 -- 1578.656858604312 -- 1461.0213376865636 -- 1550.330067651519 -- 1441.375879535111 -- 1364.2637249879626 -- 1320.77710991781 -- 1287.6019204433105 -- 1272.3049206769595 -- 1369.7400535375039 -- 1284.719069760833 -- 1274.2268653962888 -- 1370.9063855316867 -- 1399.2589998534372 -- 1297.4652178826734 -- 1311.69887850228 -- 1450.7212721058695 -- 1356.247085206372 -- 1355.8163429698843 -- 1351.600562146643 -- 1380.5314171351245 -- 1400.0826855606026 -- 1406.1259121669327 -- 1471.421625406283 -- 1455.8811461406526 -- 1449.6056045118257 -- 1455.5670659151087 -- 1460.97555793508 -- 1431.210467400577 -- 1364.0508390390844 -- 1417.3224970992865 -- 1253.8752842663312 -- 1249.5177489195476 -- 1243.7622361052665 -- 1259.16348932806 -- 1256.1621573403281 -- 1230.3408544197378 -- 1272.9604099642936 -- 1403.1041387183757 -- 1328.4276020908385 -- 1346.228662020031 -- 1351.251881982353 -- 1398.983263017631 -- 1405.8958758696037 -- 1449.7628340815938 -- 1449.231796529129 -- 1458.01406933331 -- 1498.243003825049 -- 1613.812345361098 -- 1475.1947163282389 -- 1432.964426655227 -- 1409.6391313662111 -- 1398.1572902881114 -- 1374.5477911047305 -- 1294.86478640982 -- 1273.5504718145125 -- 1230.3014728690564 -- 1216.8297809635758 -- 1218.2663410993334 -- 1235.396379357974 -- 1213.4692642543578 -- 1228.2839478975561 -- 1262.5138237127987 -- 1294.8011810477376 -- 1372.1447009029862 -- 1340.8558631966234 -- 1382.2626870611048 -- 1512.7515316869694 -- 1419.5431066070403 -- 1417.856212753905 -- 1561.8696620300852 -- 1506.5423770443595 -- 1535.5904152286714 -- 1483.697879722785 -- 1588.9910027133449 -- 1474.3361651352873 -- 1424.4703035038408 -- 1340.829620836602 -- 1499.335428275725 -- 1304.9989125386746 -- 1251.7539661048172 -- 1254.1596181570865 -- 1273.722396425242 -- 1256.3339113342897 -- 1223.1850969983261 -- 1262.0672477283954 -- 1306.5018919877107 -- 1353.1243735317785 -- 1320.34842550431 -- 1359.7772100489128 -- 1498.278852348271 -- 1538.9020256535564 -- 1521.7532615704492 -- 1496.2558286125104 -- 1490.5127857523764 -- 1526.1150768363195 -- 1548.7299989782466 -- 1566.63102463574 -- 1499.8845943547208 -- 1486.7514801547884 -- 1422.8830823256446 -- 1371.74231602729 -- 1331.6108571632553 -- 1299.497741227785 -- 1266.9176280635436 -- 1252.3914792785727 -- 1266.7802707074072 -- 1247.1091775682544 -- 1298.2192489271447 -- 1243.9797245285959 -- 1390.6013105916215 -- 1312.563161480265 -- 1362.0510856222454 -- 1350.8733149040543 -- 1373.087512679773 -- 1515.9332045995154 -- 1424.7750991115752 -- 1429.0927847931541 -- 1557.2238427366278 -- 1492.6127513572487 -- 1518.9478843373292 -- 1441.6648031117693 -- 1464.8808406622372 -- 1469.247646085359 -- 1465.6812049141936 -- 1460.376933474041 -- 1357.8946598170537 -- 1290.0026703334568 -- 1265.3849550718626 -- 1253.7185517321097 -- 1270.0776404463643 -- 1258.0306173732245 -- 1224.7044020863782 -- 1229.6243048952256 -- 1305.4126997457415 -- 1284.1282489188877 -- 1304.0318392396316 -- 1433.1121762095718 -- 1457.5725497596516 -- 1481.9747283126198 -- 1408.0233102225127 -- 1402.0347988444714 -- 1412.478016731695 -- 1566.7898878149672 -- 1494.4501255913506 -- 1630.0406227400008 -- 1503.3248241837296 -- 1545.979728278935 -- 1518.525053438774 -- 1483.215429549298 -- 1415.1243560213022 -- 1383.8272513877919 -- 1256.373669818911 -- 1239.6550232695454 -- 1254.1832484255701 -- 1245.6228055332738 -- 1323.560803425576 -- 1202.1675419715837 -- 1250.4193920517373 -- 1258.508823811258 -- 1400.4039855582012 -- 1289.822979607277 -- 1308.0024782470903 -- 1335.184817867866 -- 1470.9753184155143 -- 1350.8578059644515 -- 1352.029568778149 -- 1383.2771725172104 -- 1429.5553485733126 -- 1368.7631595454245 -- 1363.6819273280414 -- 1366.0449652127772 -- 1308.8097371933459 -- 1272.9002947378613 -- 1233.6079657045711 -- 1210.0316317459237 -- 1207.638188053372 -- 1205.8731757180249 -- 1225.0385651039537 -- 1219.0484425571574 -- 1198.222405580756 -- 1321.5955393222168 -- 1269.4499455750288 -- 1388.3039861341417 -- 1267.2495027851585 -- 1271.9303139943768 -- 1298.454901766424 -- 1313.0340358448245 -- 1344.5353120522884 -- 1338.5976942209486 -- 1348.5537028821832 -- 1376.0791673025867 -- 1388.4543343436603 -- 1407.5873310424965 -- 1545.5685278925832 -- 1522.009377752376 -- 1516.0598237568029 -- 1333.3858574376627 -- 1372.632363514618 -- 1334.6522187712883 -- 1238.95527178272 -- 1244.9237018781962 -- 1253.307222348035 -- 1246.4152982837386 -- 1222.474572266391 -- 1319.1297718903277 -- 1331.907646720025 -- 1298.4956273108533 -- 1277.1866804550602 -- 1296.4641707502485 -- 1411.384017013495 -- 1364.2153873405855 -- 1373.3787286211264 -- 1424.8024496811527 -- 1408.8924713155293 -- 1411.4074416243348 -- 1561.2679791823132 -- 1415.3371610399201 -- 1431.0755850266155 -- 1551.3988441750457 -- 1412.8241360671223 -- 1497.5193101488642 -- 1340.2744753274114 -- 1274.8184324035085 -- 1268.5686206114751 -- 1258.9030512746265 -- 1267.5747652388586 -- 1259.5723081926942 -- 1231.2626125686102 -- 1351.3007126806106 -- 1322.4047752343738 -- 1335.161435927245 -- 1351.8989863564016 -- 1401.7089550767944 -- 1474.5586391148388 -- 1398.0958800774467 -- 1589.3826869564643 -- 1431.3246951508581 -- 1434.984597107648 -- 1466.515759029683 -- 1458.1556763751705 -- 1438.1114703517883 -- 1396.7840384172262 -- 1417.0804676502335 -- 1540.514953631335 -- 1560.444462894251 -- 1327.218494042718 -- 1270.8199325990363 -- 1261.4873712654264 -- 1257.0181673867512 -- 1273.201345705096 -- 1366.5751353288836 -- 1254.5968330433059 -- 1263.6823253183277 -- 1421.787413248653 -- 1308.269694770895 -- 1330.4816893416375 -- 1330.2804824311165 -- 1390.448632936949 -- 1348.4525807297757 -- 1396.390814035291 -- 1518.9547761829265 -- 1413.623010369393 -- 1442.489058665796 -- 1464.4463380858099 -- 1455.6008961518442 -- 1449.1772551379458 -- 1477.44774541885 -- 1554.260072518563 -- 1374.7304055756667 -- 1327.5951350420112 -- 1297.3475868957805 -- 1278.0402340772428 -- 1269.2278380266976 -- 1285.829713617315 -- 1346.8604760262513 -- 1244.672511444759 -- 1388.3213514396912 -- 1377.9896669054299 -- 1325.4970433580984 -- 1337.1753401697295 -- 1381.7781768248472 -- 1393.0835629568767 -- 1475.5591215922623 -- 1461.4600630607754 -- 1471.2385556251238 -- 1482.8811229300336 -- 1523.1721068942434 -- 1593.183933156607 -- 1541.7988253251476 -- 1526.5256191375038 -- 1619.630844395969 -- 1498.511144870733 -- 1536.5551992704338 -- 1464.460753200526 -- 1318.585711807306 -- 1302.6075252647875 -- 1291.1729280984855 -- 1306.1958255294828 -- 1288.5923972935848 -- 1255.2212339539465 -- 1273.1112775198087 -- 1309.2439136386101 -- 1352.4187835609544 -- 1384.7020868552795 -- 1408.5341176791242 -- 1479.203262571561 -- 1560.1403323461882 -- 1568.535888335656 -- 1609.674294011061 -- 1568.5950489846455 -- 1575.5378808735568 -- 1575.0361571954588 -- 1578.4687107001619 -- 1676.5072065008233 -- 1567.4270176392135 -- 1483.2347573781835 -- 1402.9974798730423 -- 1351.6376824174347 -- 1330.7219752692347 -- 1432.5862718677913 -- 1292.6083564904511 -- 1312.924874287738 -- 1299.8657196411248 -- 1274.2112581997092 -- 1283.1779241691283 -- 1326.2734616781074 -- 1345.0886939611084 -- 1368.9532822736253 -- 1430.5912144301158 -- 1458.2158400751023 -- 1484.6597193743007 -- 1659.9354893189154 -- 1559.532556035205 -- 1607.432193951281 -- 1599.7185544726212 -- 1615.9095929756647 -- 1556.77514168665 -- 1552.5880130770545 -- 1535.4971392720593 -- 1601.1102782877863 -- 1438.533637187117 -- 1368.146402438279 -- 1337.9108077395103 -- 1336.730415407365 -- 1323.2165747105478 -- 1361.0717645534141 -- 1333.3873929102092 -- 1372.6017127712807 -- 1413.8697286144547 -- 1478.7871650408633 -- 1503.485383648374 -- 1488.478170847902 -- 1418.7171335283488 -- 1434.9532381949234 -- 1445.4180298522374 -- 1516.846927612887 -- 1516.36669893898 -- 1529.9484993674457 -- 1585.7510516277607 -- 1568.328321822986 -- 1533.3583854620756 -- 1552.532572126073 -- 1569.878873684639 -- 1485.3037525404661 -- 1411.960529214845 -- 1379.9656193566077 -- 1339.5226865021907 -- 1434.9353719564888 -- 1304.1571273574575 -- 1320.3736753090902 -- 1304.720135101647 -- 1265.9706910958466 -- 1346.8248005707928 -- 1370.998580268847 -- 1471.0164746838304 -- 1361.8036229307459 -- 1388.0684768619017 -- 1430.691431860258 -- 1540.0588582019125 -- 1618.463398233477 -- 1507.2339104952787 -- 1513.0582298420857 -- 1568.2038000750397 -- 1567.8174397115508 -- 1584.248460496638 -- 1566.198991760152 -- 1523.2702412044034 -- 1505.7666031061917 -- 1470.538902873662 -- 1373.0230629701614 -- 1339.7973726203504 -- 1320.853534827382 -- 1325.0714069718722 -- 1331.7472677407366 -- 1317.9140296572234 -- 1282.5123421939081 -- 1389.326412264501 -- 1327.2606805886683 -- 1364.9019220237037 -- 1449.4526129422245 -- 1450.7077462139152 -- 1512.098401528716 -- 1506.3883611182684 -- 1618.791722543579 -- 1543.8130637981183 -- 1566.2853507357133 -- 1586.6474004858483 -- 1712.8015018891144 -- 1605.4871531244487 -- 1647.734756651951 -- 1604.4734374234038 -- 1537.8126636779364 -- 1485.292435295079 -- 1420.5193120859474 -- 1382.1095009966714 -- 1364.094232788833 -- 1362.5332146906076 -- 1348.7140990603502 -- 1341.4909129407497 -- 1294.9689491957968 -- 1450.811701672084 -- 1468.4316954678636 -- 1382.9329383262361 -- 1408.4545875550264 -- 1433.6579756114231 -- 1425.776895335419 -- 1471.8385725751764 -- 1531.4535089737726 -- 1573.0618547897361 -- 1551.6860404432264 -- 1634.093021142625 -- 1672.2146365003757 -- 1704.618893129392 -- 1582.4479041621103 -- 1586.978890323209 -- 1558.1507214531177 -- 1633.1715644310907 -- 1397.3303187924678 -- 1351.799732925246 -- 1339.3451084866674 -- 1324.2850434498673 -- 1346.2895550619526 -- 1352.6989025953749 -- 1298.2197007215443 -- 1312.9524683796687 -- 1405.342649371963 -- 1418.9984073934684 -- 1414.2452291920763 -- 1442.7888094507666 -- 1455.2749149441017 -- 1470.937188380252 -- 1603.1393134371756 -- 1565.7541369061207 -- 1582.6147391931624 -- 1651.519864207698 -- 1652.1463310630488 -- 1641.6513700016658 -- 1611.6827318834896 -- 1655.2784321648612 -- 1604.0594304401163 -- 1478.8502534392348 -- 1420.0433226207722 -- 1489.2877529533353 -- 1354.5271177833056 -- 1338.0935676945332 -- 1344.5843617165717 -- 1327.852526613759 -- 1284.7825690248528 -- 1307.698116360523 -- 1378.3586059060533 -- 1353.032679641115 -- 1452.1413854561113 -- 1581.1134329440579 -- 1460.0731885264274 -- 1622.7401446896197 -- 1617.179457784443 -- 1569.3706758428955 -- 1588.2789122400475 -- 1613.260526322025 -- 1706.8862576093188 -- 1580.1266861127044 -- 1572.9021054362636 -- 1535.055878660136 -- 1464.879853726613 -- 1503.619440599861 -- 1388.6438661353616 -- 1352.235190382936 -- 1345.002596996072 -- 1330.3599239852801 -- 1343.3170658575882 -- 1454.273937041171 -- 1405.3612175280575 -- 1302.4412265425108 -- 1466.7960739845219 -- 1356.5289485849212 -- 1373.4238362018898 -- 1392.4037599088551 -- 1423.1653099996145 -- 1445.3052872393089 -- 1470.1318626400562 -- 1459.379237855932 -- 1470.8698194318247 -- 1475.6174231550322 -- 1523.0399101009775 -- 1480.1099447635168 -- 1610.388801335167 -- 1468.514292410795 -- 1417.8463964091934 -- 1373.772867917036 -- 1329.509244139247 -- 1289.9747090663182 -- 1256.64257065156 -- 1248.664464097181 -- 1368.2856308428627 -- 1257.1719315401415 -- 1251.1712841571343 -- 1396.8249526779987 -- 1292.471762905073 -- 1355.4977152004544 -- 1438.372461912031 -- 1331.5205947636703 -- 1337.3914188809017 -- 1334.0363582471366 -- 1373.412595423766 -- 1391.6769273100235 -- 1539.034142927605 -- 1433.6370342875766 -- 1465.4640808686397 -- 1455.3989870486037 -- 1492.5712672770999 -- 1608.192893076311 -- 1562.8358259199706 -- 1384.0710878804937 -- 1336.8066932144097 -- 1316.0109942085094 -- 1301.7845282396556 -- 1296.3616805144022 -- 1306.0039773584713 -- 1298.8564398064827 -- 1266.624241737558 -- 1400.7038942842792 -- 1330.5465006066029 -- 1358.8493994737412 -- 1505.2649793831788 -- 1406.283664147442 -- 1524.2556565099574 -- 1564.4661136138552 -- 1516.2008786262822 -- 1616.227576608351 -- 1522.612876714959 -- 1615.2959722493224 -- 1597.6962237485634 -- 1539.536662078553 -- 1587.5499421773532 -- 1574.9612569966162 -- 1501.9971438026087 -- 1434.863208297847 -- 1501.639228068846 -- 1353.5850532985162 -- 1334.5054452777229 -- 1326.7640533069168 -- 1344.4138411986737 -- 1329.1014847382519 -- 1292.5213874502706 -- 1425.8216478487739 -- 1438.7087511929467 -- 1409.3063448866876 -- 1392.2896519254332 -- 1393.995176305009 -- 1418.1211580100594 -- 1461.7156409983588 -- 1634.6928949354567 -- 1569.2431522629247 -- 1535.7871561171992 -- 1653.219860502489 -- 1619.4408902259797 -- 1550.8964563993754 -- 1564.3534619471063 -- 1536.4290296841239 -- 1507.3629637527579 -- 1456.954135975604 -- 1399.8416886049686 -- 1361.1007818282662 -- 1356.5389722931209 -- 1344.0131098701836 -- 1361.0362635255271 -- 1437.5364389690271 -- 1367.4465665920918 -- 1313.0986449353848 -- 1352.140657179281 -- 1380.4611577706648 -- 1546.728099100768 -- 1435.0371188154052 -- 1595.7793952101517 -- 1673.9955007379344 -- 1536.0094601994363 -- 1519.2092752714161 -- 1526.5823494354693 -- 1633.1042228265924 -- 1574.3995380492895 -- 1517.3375741818454 -- 1546.0624662982007 -- 1574.267009759924 -- 1509.628534889626 -- 1465.1365836567295 -- 1407.4887345795466 -- 1360.981786320453 -- 1335.7509002582665 -- 1318.0830863091062 -- 1336.4754620856638 -- 1313.4779227601873 -- 1374.1726607537087 -- 1296.9292403596671 -- 1322.7100013441957 -- 1345.319004738628 -- 1342.4724453716644 -- 1537.2395425326915 -- 1433.6561684800824 -- 1476.2432271954851 -- 1474.0449887668537 -- 1467.8627655123325 -- 1496.3435990285977 -- 1521.0258175401427 -- 1529.6264347568713 -- 1566.1601266133605 -- 1490.9656706791325 -- 1476.6148374058341 -- 1434.5204597791208 -- 1391.166301769567 -- 1357.2693606692055 -- 1320.3304162492382 -- 1308.6064773387843 -- 1310.5804639708076 -- 1440.6911881163305 -- 1308.005767582747 -- 1270.4009113513164 -- 1286.9145364589192 -- 1311.5935788630982 -- 1333.5710897324896 -- 1361.7400454882784 -- 1513.9988707712032 -- 1399.342118001272 -- 1417.4010001709044 -- 1492.887997698651 -- 1596.530104566253 -- 1496.6667132215998 -- 1522.6490184000634 -- 1518.160975617893 -- 1483.6456413104672 -- 1488.3275936457956 -- 1482.6325724050607 -- 1440.843778671261 -- 1387.5683117093238 -- 1342.1994191979595 -- 1404.6133858094784 -- 1317.8594121319481 -- 1301.2666117050203 -- 1321.5364913136261 -- 1305.418805399261 -- 1381.1710804420525 -- 1278.219804522948 -- 1289.585970080503 -- 1442.6448085095867 -- 1372.0660158004584 -- 1415.02697177101 -- 1415.4373191675118 -- 1508.2678364417916 -- 1583.7406415259184 -- 1601.2111510809568 -- 1509.5794031578057 -- 1576.7686421793064 -- 1539.9966477151277 -- 1577.9447380173028 -- 1467.919043697643 -- 1464.410392939075 -- 1450.4206246128933 -- 1392.3098180210218 -- 1339.4288607988933 -- 1316.1643371616751 -- 1312.5652067693381 -- 1307.2867820387319 -- 1329.127290425918 -- 1318.2987547713349 -- 1286.3942825657464 -- 1400.002227162997 -- 1350.894418226089 -- 1377.8038640149837 -- 1496.2175017909835 -- 1449.7428298321568 -- 1560.290855545567 -- 1530.246231128815 -- 1555.0651469030688 -- 1543.8431667457598 -- 1550.0102792958824 -- 1578.3961366450217 -- 1626.074709396764 -- 1711.4927957492225 -- 1623.4745272746727 -- 1575.5061801539607 -- 1527.011791831799 -- 1532.6365900428748 -- 1405.7178360735331 -- 1379.9181800430545 -- 1362.2221443099163 -- 1349.0546463941728 -- 1368.4002252917317 -- 1352.2464656337436 -- 1322.7527188640752 -- 1431.087440355973 -- 1374.8997606689559 -- 1376.656552569034 -- 1404.4372482137082 -- 1427.464492003934 -- 1442.9063943358635 -- 1475.156837893398 -- 1561.7246235959076 -- 1519.0111550152492 -- 1524.5454165309543 -- 1552.458033115592 -- 1649.0875047403804 -- 1511.3386350304766 -- 1570.262886995857 -- 1517.5620816931266 -- 1507.7470545587644 -- 1463.3682152674269 -- 1397.247324362899 -- 1364.3547060771791 -- 1342.04032756873 -- 1327.2563267328933 -- 1345.5325532029212 -- 1331.7653318449659 -- 1291.9502755148524 -- 1314.8072613008824 -- 1467.4064404030228 -- 1388.5485449245693 -- 1412.1001206340259 -- 1396.0221086898523 -- 1391.3352282131818 -- 1392.948832999648 -- 1446.3155462345567 -- 1580.3388689047388 -- 1453.7084056077952 -- 1490.321219190788 -- 1569.1014780135185 -- 1665.8780496173317 -- 1510.7262869296355 -- 1505.9992256845396 -- 1485.8051146274704 -- 1420.6216934121421 -- 1367.8454439427019 -- 1346.5996003089933 -- 1324.1350841382475 -- 1313.220835750759 -- 1331.1291908749695 -- 1403.3982810987811 -- 1307.7030031642607 -- 1315.9900847419042 -- 1327.2537327710013 -- 1358.2702614561697 -- 1423.1572290096292 -- 1394.1799288867228 -- 1427.5945890673795 -- 1575.8706069725129 -- 1494.7644746910782 -- 1516.885036564004 -- 1519.414153117762 -- 1543.9183123243274 -- 1510.6419546911898 -- 1476.8717067664186 -- 1482.519534085815 -- 1503.8885262399592 -- 1447.6964735381443 -- 1388.11857973597 -- 1341.8012053861737 -- 1320.160272893052 -- 1312.6948315521136 -- 1304.0751487132447 -- 1324.6215535231188 -- 1311.0313532342402 -- 1281.1784243781728 -- 1398.1823876678513 -- 1424.6854899564305 -- 1350.4401818527945 -- 1345.3430101356366 -- 1373.22803343521 -- 1382.8864557938891 -- 1399.1106615631832 -- 1464.659806339492 -- 1469.1081831810825 -- 1488.6829446745924 -- 1551.3146060868582 -- 1531.0266050957352 -- 1473.2474812267621 -- 1471.758627754973 -- 1502.5152816224065 -- 1479.9400891796695 -- 1409.190656180818 -- 1368.4852618028162 -- 1322.8754697811548 -- 1318.0173513511274 -- 1306.3896550475567 -- 1317.452352553516 -- 1303.9267815116418 -- 1263.1886393228256 -- 1394.575132209799 -- 1304.3782969776944 -- 1368.3164466747658 -- 1406.0132371302911 -- 1399.1081060270267 -- 1523.4684990222418 -- 1438.1288754392378 -- 1552.001826074256 -- 1479.621677474078 -- 1492.9891970759072 -- 1519.7192200872776 -- 1651.907370837008 -- 1495.5748820962124 -- 1563.516427596718 -- 1405.1640025964518 -- 1408.8924626339167 -- 1339.9011113608353 -- 1300.894753159756 -- 1286.542324636896 -- 1283.2087751444249 -- 1265.2713317572243 -- 1279.9742075170752 -- 1269.912212934394 -- 1241.899409397519 -- 1254.818728927537 -- 1291.5014129043984 -- 1353.4009948454368 -- 1317.3631230753765 -- 1342.9361285297741 -- 1336.1684240771856 -- 1359.4006309076183 -- 1382.959846612804 -- 1358.8893070081572 -- 1375.6104774160399 -- 1403.567437253063 -- 1405.5256342800076 -- 1486.4070895895861 -- 1471.7139425197374 -- 1433.720181704763 -- 1427.482181788928 -- 1371.043236370258 -- 1317.0646038747484 -- 1282.219529499443 -- 1287.663517662331 -- 1280.5538158872075 -- 1303.6307639014365 -- 1286.4274496341172 -- 1385.903960813836 -- 1336.3956796719297 -- 1324.49428154616 -- 1353.7031041367054 -- 1374.5909919007904 -- 1377.6425627379945 -- 1527.2843329632708 -- 1410.992408356937 -- 1455.3154445336725 -- 1452.0484729953237 -- 1459.2429700128403 -- 1475.2000629426964 -- 1519.2985141009249 -- 1473.795469102853 -- 1481.661235662531 -- 1458.9928061208443 -- 1446.1070170260457 -- 1391.0109326200486 -- 1338.1869602290265 -- 1306.3868361294847 -- 1304.938151743798 -- 1294.2894227967756 -- 1307.4974894608206 -- 1292.835146374122 -- 1270.7195345553953 -- 1293.9328183794191 -- 1402.3676025380832 -- 1389.149787887619 -- 1361.127590248097 -- 1386.5998845119148 -- 1366.771681937463 -- 1445.6892990140818 -- 1441.3628374486732 -- 1426.5255316733937 -- 1459.354110424842 -- 1464.724265722099 -- 1495.7079887942991 -- 1455.9624084051588 -- 1484.1817756054993 -- 1492.2477681609507 -- 1523.5839481026592 -- 1401.2925570072555 -- 1364.963715998415 -- 1452.1958572006608 -- 1295.0302660210941 -- 1287.694876925327 -- 1305.1160807233648 -- 1300.2330439712764 -- 1355.573471047186 -- 1281.6390122717535 -- 1345.0443661503234 -- 1483.4067855824446 -- 1374.3083962903738 -- 1437.8987246556255 -- 1387.2564234558538 -- 1380.9301790440657 -- 1418.9060231826988 -- 1413.493255900559 -- 1444.6835648411343 -- 1515.3271285267815 -- 1476.147783223025 -- 1474.152536948412 -- 1452.7629134422748 -- 1591.8599953631438 -- 1439.0992827544883 -- 1391.2554392097377 -- 1462.1101605474705 -- 1331.9387223095978 -- 1310.102558601392 -- 1301.0354437406531 -- 1317.2549950894895 -- 1314.029736685356 -- 1286.2961432377322 -- 1300.4717888178675 -- 1450.1010313716415 -- 1411.459803786514 -- 1447.270390999604 -- 1510.5634982488525 -- 1414.2761310607896 -- 1502.2161897069868 -- 1476.4946593936654 -- 1470.5680940038255 -- 1581.3948836967684 -- 1500.6137253729394 -- 1535.5432225947939 -- 1494.6204720850978 -- 1521.4924435060805 -- 1497.2705557256963 -- 1466.1190891686495 -- 1412.979219828704 -- 1367.6070862360195 -- 1342.6886078207904 -- 1336.0879347970736 -- 1328.6064343069622 -- 1335.4256783293029 -- 1324.7661539814746 -- 1285.5974373780502 -- 1305.647304489968 -- 1446.3550977890884 -- 1391.738732823492 -- 1404.5055127790567 -- 1508.1862404486862 -- 1545.9571891803007 -- 1431.764784131282 -- 1534.6201605523268 -- 1535.5901507947417 -- 1509.3626892200934 -- 1543.4492239296212 -- 1579.578502506523 -- 1619.7677895791144 -- 1580.7605535885743 -- 1560.988669807188 -- 1542.2971362123062 -- 1461.3191016846397 -- 1446.9989376973328 -- 1361.3437768292042 -- 1345.0148890191865 -- 1333.485458423387 -- 1355.1090001958105 -- 1342.2467260514627 -- 1303.2536408248395 -- 1427.6904363692129 -- 1357.429425290533 -- 1375.3798350654765 -- 1403.0949890757104 -- 1490.3944584060037 -- 1590.9509815069691 -- 1504.6670017895694 -- 1583.1234327797017 -- 1557.3147353655352 -- 1632.0165993800817 -- 1617.7660173688134 -- 1634.5766763988481 -- 1673.7591606821018 -- 1593.409590352635 -- 1580.571571885377 -- 1550.2141227695324 -- 1518.0833241993523 -- 1496.1360910432531 -- 1381.288777367878 -- 1380.3171131250522 -- 1370.5102407804084 -- 1390.2569330681831 -- 1373.2382629506824 -- 1374.511952171498 -- 1329.1469210994137 -- 1365.8711044440856 -- 1401.5946094820802 -- 1426.1521093035508 -- 1446.2732156466998 -- 1471.4963756397583 -- 1518.6321339748552 -- 1681.440436874669 -- 1621.9164061379095 -- 1651.478016957657 -- 1667.6894686619135 -- 1678.0247893329447 -- 1626.8141501362245 -- 1626.7233205514174 -- 1648.0854008251995 -- 1584.9805091437245 -- 1511.8170475607221 -- 1431.2588336559013 -- 1389.0973645265242 -- 1374.0618923817792 -- 1360.5890737201544 -- 1370.2191200719228 -- 1481.830816797807 -- 1311.4551834158804 -- 1337.8733926237314 -- 1431.7961214743611 -- 1384.165131317536 -- 1376.2959612905388 -- 1397.7112676392267 -- 1414.982507351169 -- 1492.0031973882294 -- 1497.318444814841 -- 1541.223306880931 -- 1490.9187017671409 -- 1519.273804794132 -- 1612.6820221585197 -- 1523.633361335033 -- 1608.5822137027744 -- 1516.75584950899 -- 1606.5589647886159 -- 1460.0286764952843 -- 1484.5303780340607 -- 1353.9491265286208 -- 1349.7698529610266 -- 1338.3720701475268 -- 1359.848312283002 -- 1338.8785063343753 -- 1297.1423615361757 -- 1368.5002220575955 -- 1359.8597167226924 -- 1432.043121343892 -- 1386.408823767271 -- 1419.2996518029813 -- 1455.9038967132872 -- 1432.877598991634 -- 1491.5196558477671 -- 1503.5735931251236 -- 1547.781846093195 -- 1525.567744924582 -- 1585.6442285598803 -- 1522.111589602901 -- 1527.8228077633394 -- 1540.8661793202882 -- 1508.2775724739583 -- 1454.086143599062 -- 1373.5483552035475 -- 1355.0010524328713 -- 1340.3765493016099 -- 1330.4726456564908 -- 1349.7666994254423 -- 1344.4417321175579 -- 1388.7421433059003 -- 1321.956795384255 -- 1358.3658961870308 -- 1466.9681924680533 -- 1391.8374767885803 -- 1524.5898589507613 -- 1413.1299473788292 -- 1441.0516791850098 -- 1606.9821123696981 -- 1487.2482511084218 -- 1528.9818343061522 -- 1528.4259654138664 -- 1603.730429212779 -- 1520.9959674050692 -- 1554.158110907213 -- 1569.6946647233333 -- 1559.7407259323988 -- 1457.7715945031907 -- 1406.6627847670572 -- 1375.0024613803428 -- 1351.5734855496626 -- 1343.6818103683254 -- 1364.3790329659312 -- 1341.7035503551485 -- 1305.8584083441913 -- 1361.006722553081 -- 1360.23931091871 -- 1373.6840986316888 -- 1394.7254763707967 -- 1399.8687241615885 -- 1423.676394551932 -- 1464.8720489294265 -- 1507.4016943870897 -- 1503.5509533742486 -- 1509.0628346656283 -- 1514.1874005029133 -- 1565.765215184088 -- 1537.2541606846328 -- 1533.6214940166242 -- 1523.753133682202 -- 1552.1527581008463 -- 1510.106118509249 -- 1428.8891713644732 -- 1390.7137098991402 -- 1386.8634960915756 -- 1348.1880965776866 -- 1368.1567964535486 -- 1350.68571985546 -- 1434.147552437799 -- 1450.0105300832004 -- 1386.970688066781 -- 1426.9094805133725 -- 1515.922235822481 -- 1428.9086575652134 -- 1477.3924811923162 -- 1455.4839141065816 -- 1498.0268222803224 -- 1611.6012573940402 -- 1518.891352968365 -- 1541.2866907472571 -- 1610.0056079479857 -- 1520.756626095907 -- 1514.9941275376618 -- 1538.9429389532147 -- 1422.5843430472946 -- 1381.0183145582102 -- 1456.377898389841 -- 1328.6549549873166 -- 1299.7460597806162 -- 1288.9573825857042 -- 1305.746504575828 -- 1398.8884046848434 -- 1277.024024000198 -- 1276.1020493525234 -- 1312.4552440790808 -- 1431.2832549633883 -- 1341.0620340072912 -- 1413.6479289621334 -- 1387.5089680118977 -- 1438.428947165359 -- 1481.2820780314728 -- 1468.920598283853 -- 1482.0908385192536 -- 1509.0475051534922 -- 1607.4667101990026 -- 1514.5393657887964 -- 1485.522221525841 -- 1467.7363947900897 -- 1524.9421226435343 -- 1371.7508343599495 -- 1419.5818387746144 -- 1300.912128336398 -- 1401.9031919067882 -- 1302.1868651375944 -- 1312.456412749158 -- 1302.0294460760817 -- 1268.5672849780008 -- 1284.3882930833497 -- 1361.2033581300025 -- 1344.9442404708147 -- 1530.2697097514308 -- 1450.1218193484776 -- 1543.2438573877544 -- 1492.03081879013 -- 1592.8098830948468 -- 1605.474720395366 -- 1518.8840447524613 -- 1558.238753003584 -- 1629.098687546121 -- 1537.4059890206188 -- 1537.9885744094574 -- 1514.6591621503471 -- 1447.1374837705862 -- 1384.7525351215431 -- 1357.6918268294748 -- 1330.4915185331404 -- 1328.8958843176479 -- 1317.1727136537784 -- 1328.7598757155092 -- 1317.2933100190712 -- 1281.106752714974 -- 1425.0018806878857 -- 1374.321336190105 -- 1389.3040101912754 -- 1515.0495406400712 -- 1400.1107014070196 -- 1426.214964877732 -- 1443.6488354266364 -- 1487.151622635364 -- 1547.421852746555 -- 1532.9950979232647 -- 1572.909405993444 -- 1570.7876405506775 -- 1516.8528688052484 -- 1565.7660203388864 -- 1482.853009555167 -- 1548.4281183985815 -- 1499.152160735695 -- 1342.07593995825 -- 1341.5147783925695 -- 1318.2009148456818 -- 1302.7388766591785 -- 1321.9236331785205 -- 1311.6141904972767 -- 1294.6277495066267 -- 1421.5313580130692 -- 1415.76260072263 -- 1336.2939204181082 -- 1347.9499936152624 -- 1379.2056156465565 -- 1590.6291645549095 -- 1478.8121194983835 -- 1497.1583136363602 -- 1489.257626471681 -- 1504.1576186756035 -- 1514.5256704070453 -- 1554.6417889036618 -- 1552.7763418526495 -- 1562.8685328222114 -- 1537.2417629697125 -- 1497.0892587429353 -- 1501.025785011306 -- 1531.8758212010691 -- 1363.5861463228894 -- 1350.9895320117755 -- 1346.361318123837 -- 1417.1973104209428 -- 1356.7616264069247 -- 1322.6487351345193 -- 1485.4310710514792 -- 1387.2220752355815 -- 1385.3554264944908 -- 1400.8481610584997 -- 1532.4645323350328 -- 1541.0150336399317 -- 1447.3295574830383 -- 1526.4781305847237 -- 1520.943196798293 -- 1540.5098031607538 -- 1650.392679363859 -- 1708.2041326728884 -- 1698.8662173071746 -- 1641.9969597952943 -- 1544.979097999018 -- 1513.5364114136164 -- 1447.3624134266126 -- 1397.7636209968218 -- 1346.3834901519322 -- 1333.062113166815 -- 1305.2866797942256 -- 1317.953460387161 -- 1303.8660410928921 -- 1270.3515344621649 -- 1292.6610286344812 -- 1312.1635207455113 -- 1345.2965167684356 -- 1457.6625689440039 -- 1383.021789677072 -- 1357.8747952691708 -- 1374.8347309397855 -- 1392.2802737348313 -- 1412.871446917425 -- 1496.9495100495994 -- 1464.635561491814 -- 1444.133695281797 -- 1555.7807375481095 -- 1416.8063717784232 -- 1465.1975666735805 -- 1414.154832562526 -- 1345.326438878251 -- 1310.128904912582 -- 1293.4290251691868 -- 1292.9412439895707 -- 1273.4410394973072 -- 1300.2574409880972 -- 1281.6918230276299 -- 1255.0511681567455 -- 1290.0208669540275 -- 1408.7398788303321 -- 1325.6515522051566 -- 1378.2072992882697 -- 1356.469120412597 -- 1366.4465864474103 -- 1385.1762035562663 -- 1444.630479403913 -- 1444.2568239956213 -- 1435.1830116778754 -- 1464.170591084051 -- 1487.249753773994 -- 1449.8263591579812 -- 1422.887009349399 -- 1410.8049822320936 -- 1389.4920281765026 -- 1333.7694955862999 -- 1310.8164497230923 -- 1284.0257618084113 -- 1344.8596589160934 -- 1263.6339024473161 -- 1281.5774753241742 -- 1273.4040370142623 -- 1240.753724565677 -- 1381.8103446154566 -- 1295.9637482611986 -- 1409.4127043605613 -- 1322.923635891171 -- 1437.5470212978403 -- 1335.2146079153006 -- 1455.4473438934215 -- 1394.7971968351048 -- 1575.8663047534264 -- 1443.4129002103743 -- 1480.1073834449119 -- 1496.5547269728058 -- 1459.7780382401488 -- 1469.5974246919334 -- 1439.5584563067907 -- 1389.861031500465 -- 1322.5314249199553 -- 1304.7882521025863 -- 1279.4655739385003 -- 1263.4651179902155 -- 1272.2726297418378 -- 1278.4225207556237 -- 1281.437634338416 -- 1241.1438606862298 -- 1253.5881945791436 -- 1398.0177021845864 -- 1289.2174059716801 -- 1353.6976076926014 -- 1367.578565871823 -- 1374.742628697767 -- 1378.077967216178 -- 1417.964415349559 -- 1557.5359869131382 -- 1466.7924001668594 -- 1465.402342859148 -- 1505.045166138424 -- 1403.718637462935 -- 1392.740821673589 -- 1390.229973437168 -- 1482.5333274747195 -- 1420.7625023855633 -- 1304.1331970700994 -- 1280.097313668841 -- 1376.819702747228 -- 1260.3278966809346 -- 1278.7781074893567 -- 1275.3625563153303 -- 1246.703873860373 -- 1382.8271842015856 -- 1420.0157971897322 -- 1324.2734919772238 -- 1327.034759923567 -- 1350.6179615361075 -- 1402.4879535634643 -- 1477.1744684328298 -- 1418.4603738270293 -- 1523.328551912751 -- 1406.6830421985085 -- 1410.8695194042862 -- 1391.7299859394027 -- 1356.591201158261 -- 1475.6981623230251 -- 1449.2936735446535 -- 1362.988132302358 -- 1329.289277550861 -- 1292.478044863193 -- 1281.7349586384646 -- 1263.2889601158154 -- 1253.7837710241924 -- 1282.8449153639426 -- 1274.8259841169784 -- 1251.4986359212821 -- 1416.6364839332798 -- 1351.3154356129164 -- 1452.9510584471311 -- 1323.2449507487943 -- 1297.5615177440413 -- 1352.425380401167 -- 1343.7380716533773 -- 1395.997335239976 -- 1402.9600385369267 -- 1415.0761842349068 -- 1451.823249404103 -- 1466.9875864564592 -- 1460.6677865468694 -- 1458.6452912590635 -- 1434.695608653967 -- 1426.6698559037684 -- 1493.2500481535342 -- 1321.3231089694925 -- 1328.5911253115894 -- 1287.4876474502614 -- 1268.2298421026785 -- 1297.5567574832617 -- 1290.5682438840915 -- 1264.6325330781165 -- 1371.238523019357 -- 1312.862577057598 -- 1444.0080953822358 -- 1369.4807860194492 -- 1488.5681286548702 -- 1390.846368955537 -- 1516.9906026385504 -- 1455.980072525348 -- 1459.982152443874 -- 1463.8327407407146 -- 1490.1944615212296 -- 1648.6795705462923 -- 1523.9750170855523 -- 1640.4680589792706 -- 1637.4949794010463 -- 1452.3197195404377 -- 1401.0930375499913 -- 1364.9721706813352 -- 1336.0704192287326 -- 1316.8306174048023 -- 1308.1351849353116 -- 1332.1236847867972 -- 1318.5763242005032 -- 1284.1008741023904 -- 1357.6846150820004 -- 1465.1743858845546 -- 1362.8688061693088 -- 1386.8312485299355 -- 1415.7618426368774 -- 1554.102847275959 -- 1410.6461174650483 -- 1446.7689363425825 -- 1450.118550590818 -- 1499.5445815802939 -- 1499.8021152548752 -- 1522.4403107406156 -- 1607.3595765984398 -- 1599.0502910992993 -- 1478.0899556872448 -- 1453.6021570704988 -- 1406.4878490955623 -- 1374.8648469719037 -- 1340.226861994973 -- 1458.9584685224424 -- 1314.2871125798501 -- 1339.7356513685827 -- 1308.771639495707 -- 1279.994707572975 -- 1418.4268426866295 -- 1348.4034630084136 -- 1366.610095549469 -- 1397.6503289592604 -- 1368.4299307973745 -- 1401.1857464948303 -- 1497.3675061818853 -- 1461.1084223438781 -- 1455.6516368258783 -- 1475.1481040861584 -- 1499.2636334781466 -- 1519.5488638053112 -- 1478.2389746645308 -- 1525.535017195267 -- 1651.9457128469714 -- 1478.760304027351 -- 1437.629626957871 -- 1367.5333721434718 -- 1326.047595326903 -- 1320.741498872211 -- 1300.2024859595065 -- 1322.6105905079617 -- 1312.2669060074086 -- 1269.444517391035 -- 1284.4031064688932 -- 1298.0054628573162 -- 1383.3071620907533 -- 1362.691602657593 -- 1387.6160693753461 -- 1407.2260075956985 -- 1419.471902789601 -- 1589.052818288903 -- 1458.3616137187998 -- 1490.598972009522 -- 1558.06023605972 -- 1542.882546654835 -- 1503.9801987642686 -- 1486.1016119641724 -- 1478.973939698723 -- 1447.6361295262986 -- 1418.796304786801 -- 1378.0449561925984 -- 1343.8961207333348 -- 1339.5518876809663 -- 1329.6982397057116 -- 1394.8364243318663 -- 1328.5411956831197 -- 1284.6889911046276 -- 1300.4515939649402 -- 1327.198616672551 -- 1444.20972924208 -- 1381.59765924409 -- 1507.4252753726296 -- 1396.9743050528994 -- 1416.722162645369 -- 1498.1533837788497 -- 1461.5054541405607 -- 1474.5869690672673 -- 1605.1373428633876 -- 1522.1464270317094 -- 1490.2291305918216 -- 1475.4500074995283 -- 1517.8678890429046 -- 1477.4846250657363 -- 1527.77564689095 -- 1473.876074634071 -- 1309.4810853989995 -- 1306.5163157180814 -- 1299.7095346223466 -- 1325.1337674034785 -- 1328.0456103554793 -- 1351.1385920407988 -- 1303.462109389153 -- 1388.7060512343367 -- 1391.8553734814736 -- 1379.2006962409669 -- 1414.616354133846 -- 1428.9862144697786 -- 1414.3783282454121 -- 1481.4525780900094 -- 1464.2996723496985 -- 1507.6884067692858 -- 1448.002333370114 -- 1630.0801973270077 -- 1526.7898455465609 -- 1523.577772391176 -- 1520.9261288453329 -- 1470.4655851722325 -- 1460.0766503327534 -- 1374.9157824018398 -- 1356.12056769376 -- 1326.3172462395823 -- 1314.0437380938033 -- 1334.2034775182717 -- 1321.5639542170895 -- 1306.101159576413 -- 1406.368773338899 -- 1448.6306513828022 -- 1345.2071039615603 -- 1354.2312734422503 -- 1460.5379533053158 -- 1388.4516275611766 -- 1432.199771942126 -- 1478.7855340661483 -- 1475.3821217780583 -- 1480.8773450545748 -- 1514.7207042762432 -- 1643.1414311651258 -- 1505.2344971265068 -- 1504.7832697915555 -- 1502.9485995728373 -- 1506.6041878025342 -- 1457.4997125391849 -- 1417.4553875028741 -- 1351.0694233248407 -- 1335.0328040461548 -- 1327.7460679901847 -- 1346.3858689958688 -- 1332.379721371367 -- 1308.8912975708754 -- 1330.9229861452754 -- 1376.9725080025723 -- 1362.9012974784819 -- 1395.9943309490275 -- 1524.059048797322 -- 1489.8088453554715 -- 1458.9969854366154 -- 1527.592034998721 -- 1521.31346328066 -- 1660.9657331316134 -- 1561.2877519310573 -- 1578.1537027204492 -- 1624.1546519125195 -- 1665.2438840733582 -- 1600.022991554012 -- 1561.624825894005 -- 1490.4476414664819 -- 1441.4723992782394 -- 1357.3262308079823 -- 1367.2999664993397 -- 1353.3378705301573 -- 1376.3074754606657 -- 1431.3266779552043 -- 1318.8302574638792 -- 1416.4250871781912 -- 1356.9076112390428 -- 1393.4792002308725 -- 1390.843747224528 -- 1447.1175015081296 -- 1470.3985771978148 -- 1527.295919422427 -- 1552.0352423652205 -- 1554.141637231095 -- 1643.9360407702047 -- 1589.0302243814353 -- 1613.8127512295391 -- 1589.8150184685908 -- 1629.7220798650212 -- 1571.5764152520724 -- 1539.5526517877067 -- 1460.459225749609 -- 1426.505631501775 -- 1382.909582597094 -- 1374.7525760839044 -- 1366.5519735928726 -- 1378.0288575250736 -- 1357.840047714289 -- 1330.0996259138835 -- 1445.553050967156 -- 1368.165991732286 -- 1385.4584809012213 -- 1400.9686982860103 -- 1421.9082879080322 -- 1461.4814126350884 -- 1505.88637275862 -- 1539.1066274267491 -- 1528.5762746047303 -- 1552.8127893422197 -- 1587.5597618534575 -- 1631.3344616252673 -- 1595.9293161000087 -- 1630.8999620275722 -- 1583.9899792389483 -- 1546.9589524797175 -- 1603.6033176954418 -- 1426.851432749571 -- 1376.3156614957397 -- 1362.0742855410574 -- 1347.1631311721774 -- 1355.1152749174225 -- 1340.9886550485196 -- 1290.4065596667922 -- 1303.1616436782417 -- 1425.3067522211536 -- 1355.2235700204126 -- 1465.131942241766 -- 1434.9096099909495 -- 1590.099392972695 -- 1492.0671228465478 -- 1563.726065996871 -- 1532.1926653479575 -- 1591.872155859884 -- 1697.3419219514801 -- 1599.965139611592 -- 1629.93643143964 -- 1579.1993440243477 -- 1562.4059133959793 -- 1514.6848519571008 -- 1449.335292282357 -- 1477.9781193030237 -- 1357.3671363754906 -- 1344.7257984699565 -- 1332.9538265942583 -- 1342.3287218288904 -- 1336.225688823449 -- 1282.573100303194 -- 1288.006417186008 -- 1308.1120411172092 -- 1339.518651755996 -- 1378.0223026099764 -- 1495.9862056867025 -- 1500.0021783846742 -- 1497.2724032492595 -- 1526.5800530282 -- 1522.5750284769363 -- 1531.1595536790708 -- 1583.9141604309887 -- 1729.9590196811982 -- 1579.4949143427532 -- 1595.2289455046093 -- 1562.979804902085 -- 1591.4137421890055 -- 1439.47846477428 -- 1493.5460272381304 -- 1369.4232015704283 -- 1355.6318572159182 -- 1343.0147223993629 -- 1361.5374061736675 -- 1376.2332145935 -- 1328.3554090741027 -- 1308.310422397913 -- 1360.6161387406103 -- 1493.0361921191527 -- 1444.8357224581175 -- 1418.783204230123 -- 1496.9436355048097 -- 1490.4029914741236 -- 1562.7569247142508 -- 1543.59154510123 -- 1569.0984787097752 -- 1600.2424067639115 -- 1610.1977172979387 -- 1635.5827592998398 -- 1616.642083886001 -- 1681.0416721700997 -- 1578.4513133568043 -- 1525.426212261509 -- 1525.451648198044 -- 1414.8787492181557 -- 1353.5399567546974 -- 1346.5423671382118 -- 1356.6087903393427 -- 1338.9103774193247 -- 1302.4216341793456 -- 1329.1871373716453 -- 1355.7061732018296 -- 1506.374620084247 -- 1374.839933909015 -- 1404.4115907906507 -- 1454.7040518873523 -- 1432.316865066595 -- 1499.5436636881943 -- 1500.0326368572366 -- 1500.2634902903928 -- 1520.862902726519 -- 1532.5019562753575 -- 1503.2573046142234 -- 1495.6038317386726 -- 1519.385762414439 -- 1611.2164551575336 -- 1456.1573459579818 -- 1417.1117804490168 -- 1354.4698649235781 -- 1359.466546127425 -- 1335.615720068527 -- 1354.73756130649 -- 1345.394588719355 -- 1305.7852044669235 -- 1341.5674425225432 -- 1416.4257868843881 -- 1465.7501657843814 -- 1465.745074976633 -- 1405.65263420865 -- 1440.087282932331 -- 1454.5784750006837 -- 1491.4122512369352 -- 1504.8317174490728 -- 1603.6737723659064 -- 1555.5013618125577 -- 1554.4262712621114 -- 1510.945345838949 -- 1511.6974794278372 -- 1535.4989211144302 -- 1535.3963212256297 -- 1471.7591275218842 -- 1509.4313592675635 -- 1434.181376436722 -- 1330.013238590831 -- 1323.0799104753596 -- 1341.321264525135 -- 1327.0613298205128 -- 1292.6926031979651 -- 1411.001232586294 -- 1350.6595856836666 -- 1348.173111112272 -- 1384.092639225388 -- 1450.1029052881352 -- 1508.483042110816 -- 1427.0061545692251 -- 1480.159588388578 -- 1491.507339469456 -- 1643.4162662871584 -- 1508.5976639962937 -- 1582.6723384390568 -- 1511.9706674528493 -- 1501.6207503201747 -- 1630.9611873571678 -- 1511.3991737730648 -- 1420.5897378960817 -- 1500.0144118974217 -- 1355.3865661613818 -- 1332.6505848350264 -- 1329.3147641478713 -- 1378.2936694018406 -- 1334.5594992988804 -- 1296.9085449348051 -- 1448.2452335649054 -- 1390.2533258106196 -- 1354.748186989878 -- 1352.5788030934546 -- 1376.4125383609626 -- 1410.584833302093 -- 1478.545723700568 -- 1453.93021578094 -- 1464.530495468324 -- 1473.370296257742 -- 1493.3737715674413 -- 1527.5419307293762 -- 1488.5648737310423 -- 1479.8392133511945 -- 1472.6792065031439 -- 1462.0461441617913 -- 1415.7860463098377 -- 1400.2343447538217 -- 1383.5528854918923 -- 1357.9568816525855 -- 1314.3907100944289 -- 1326.000464769389 -- 1313.289008757429 -- 1275.887245622059 -- 1289.8082502715847 -- 1311.405244384121 -- 1335.6607599400943 -- 1374.0986028051102 -- 1395.504183652699 -- 1441.017163378859 -- 1471.9767161989455 -- 1514.7643215669957 -- 1487.20836680344 -- 1559.0412826491158 -- 1590.8485715387676 -- 1541.9013507290488 -- 1516.0849082181483 -- 1543.5366707933078 -- 1508.478495087025 -- 1578.500819101683 -- 1412.7903783615122 -- 1365.7214950926725 -- 1375.9641520281561 -- 1326.722615089384 -- 1305.4811235059028 -- 1325.7835777618686 -- 1310.0008190309998 -- 1274.9196090423472 -- 1288.4049208236077 -- 1313.7987777360743 -- 1342.7908809316305 -- 1381.7355594814055 -- 1413.9169317579103 -- 1433.2645526622441 -- 1460.7853907244948 -- 1539.632306314681 -- 1502.7165111243191 -- 1508.1159887356068 -- 1554.9196337864719 -- 1625.8316841047415 -- 1573.7960257693592 -- 1552.3698675869011 -- 1522.7109677152366 -- 1518.4136689171896 -- 1500.1191462067995 -- 1502.0587462174008 -- 1353.1301295776511 -- 1342.027240836232 -- 1334.5538190259097 -- 1361.7526642938733 -- 1357.1402990749773 -- 1316.1728677530218 -- 1346.96256361666 -- 1390.2346106066395 -- 1406.780074535121 -- 1394.3200045249148 -- 1399.1196632245644 -- 1414.4010901958732 -- 1431.2936045022805 -- 1511.4200572370307 -- 1461.0490124727123 -- 1460.2903143212684 -- 1470.6766898284386 -- 1521.6526591796899 -- 1569.864124372438 -- 1502.5049854572421 -- 1485.1404955723265 -- 1493.5863977449048 -- 1416.0739016504776 -- 1342.8455494203367 -- 1318.5664509355433 -- 1378.4853856433065 -- 1283.026406027539 -- 1317.9564988732884 -- 1300.9070132057914 -- 1277.0905447811028 -- 1442.5588130650647 -- 1338.0887492551904 -- 1429.402633917292 -- 1311.5757703868487 -- 1315.4917891226803 -- 1340.3174020829747 -- 1528.7304668594757 -- 1410.3338116348505 -- 1398.8839608509559 -- 1416.9682603683493 -- 1474.5228941241971 -- 1467.0484902746475 -- 1438.3816260241044 -- 1484.2280434906706 -- 1456.775935767011 -- 1476.9714162508806 -- 1376.3259253146418 -- 1371.3327259845605 -- 1299.5712511934294 -- 1284.454122566328 -- 1282.9673259700521 -- 1295.064528267035 -- 1280.2441615223167 -- 1250.1779417123735 -- 1273.3032666761835 -- 1309.374235210832 -- 1343.1879011299886 -- 1333.0275151531578 -- 1351.2146170277892 -- 1397.816962823105 -- 1394.2563934736233 -- 1431.8524681301892 -- 1424.6641538960182 -- 1436.1123290267587 -- 1486.2632554324873 -- 1519.777661367908 -- 1532.6716729055354 -- 1502.2667686541952 -- 1481.8501529893936 -- 1468.18944921677 -- 1531.759331064526 -- 1348.0368587919247 -- 1313.6567699461775 -- 1311.9180489652442 -- 1295.12803753639 -- 1315.3017701614904 -- 1308.4964004840547 -- 1274.9704877943136 -- 1307.7371741083693 -- 1433.0305680326594 -- 1402.053277003297 -- 1339.736536013069 -- 1356.2664406863314 -- 1363.9948820857903 -- 1367.039230047183 -- 1440.6361288317175 -- 1434.971997267016 -- 1439.3269303996478 -- 1472.3287170259769 -- 1554.0870386185684 -- 1498.3107082509894 -- 1521.8198077530706 -- 1495.4709894846706 -- 1558.976008414996 -- 1494.1499096130656 -- 1416.0419823162317 -- 1387.634638584631 -- 1337.9438182061326 -- 1331.442283596946 -- 1335.848801561334 -- 1426.0467443604 -- 1276.700804010271 -- 1299.1099414382038 -- 1302.2205927518266 -- 1340.9949504164626 -- 1344.9115870692726 -- 1376.993246316853 -- 1382.2092913909335 -- 1417.2567322234634 -- 1479.829762472968 -- 1478.9389795142804 -- 1507.5436119342276 -- 1505.2902256015834 -- 1525.0146529519648 -- 1562.2089498561172 -- 1528.169668319365 -- 1528.7716424444639 -- 1601.9657802660008 -- 1434.3055412743668 -- 1390.4149949032974 -- 1330.2259188496587 -- 1373.5061936669329 -- 1309.591212224265 -- 1321.037385678171 -- 1307.6682072658919 -- 1268.7212747378599 -- 1283.0587008294071 -- 1302.008319149095 -- 1351.788559008251 -- 1356.6343700143248 -- 1374.7607558820582 -- 1418.7036406968468 -- 1407.0155243448921 -- 1454.3144132438767 -- 1543.6085374605352 -- 1508.333417865137 -- 1504.3711774612811 -- 1541.0924898754404 -- 1489.2059182308913 -- 1600.417059403181 -- 1487.8609910150499 -- 1483.9230254210545 -- 1377.9628614283047 -- 1329.4449955200396 -- 1317.0961100807892 -- 1301.9398767358 -- 1292.1852327205406 -- 1309.8980431096668 -- 1302.008403878919 -- 1264.9304102411313 -- 1286.831348613553 -- 1419.0594275560788 -- 1334.561365809468 -- 1475.293229000124 -- 1574.2040023536035 -- 1549.6932063284082 -- 1428.019659176343 -- 1478.086601742786 -- 1485.532044737164 -- 1477.1847099327301 -- 1486.69866173109 -- 1570.3111645661418 -- 1513.2336908648408 -- 1484.0329459704562 -- 1596.565470256872 -- 1432.4436069230462 -- 1348.7214489244323 -- 1319.5349774030433 -- 1304.6040992580593 -- 1280.858998077143 -- 1286.56047030222 -- 1304.7356776137608 -- 1294.9053899939775 -- 1265.6204403814968 -- 1282.5381216782696 -- 1438.7366510759464 -- 1341.014788929083 -- 1447.8844930772484 -- 1371.059249911346 -- 1380.2925692685 -- 1514.3481104554724 -- 1418.6284259616423 -- 1427.7176840671464 -- 1449.8695354694446 -- 1486.9314589300975 -- 1494.672737686921 -- 1477.7150829822747 -- 1536.798183844121 -- 1471.236511419068 -- 1443.0724688914113 -- 1472.7906010913807 -- 1390.361245564685 -- 1322.7456601147437 -- 1314.615770603072 -- 1305.1807563907175 -- 1323.223438990438 -- 1307.705289289305 -- 1278.0289045314962 -- 1356.3404242126649 -- 1335.9667339763382 -- 1347.556256881203 -- 1360.363111259391 -- 1390.1870867454631 -- 1408.4126226550743 -- 1425.7229630064846 -- 1517.1166583374725 -- 1484.995056050009 -- 1500.9239723021549 -- 1516.913012908847 -- 1603.9799470188027 -- 1511.7423781925277 -- 1546.724952401873 -- 1518.6519895727793 -- 1483.4890167368724 -- 1439.5649234516359 -- 1388.2500222859792 -- 1341.812110500578 -- 1341.7743643087845 -- 1321.0760427904588 -- 1337.576951794929 -- 1365.1581806561055 -- 1290.3475905686232 -- 1345.062587081363 -- 1493.768683264492 -- 1446.8665853821246 -- 1496.454621308393 -- 1428.7296883196689 -- 1398.6301532110604 -- 1423.6917930189782 -- 1470.9016115783681 -- 1521.3743638212638 -- 1507.9571676555786 -- 1546.3046299831776 -- 1551.9256425502551 -- 1640.8543878134658 -- 1594.2636553596765 -- 1577.8753054408135 -- 1481.6804140497134 -- 1428.6956706839737 -- 1375.5894573345536 -- 1348.848457459835 -- 1320.176000386675 -- 1303.7567554037305 -- 1306.3706024102323 -- 1284.6785758832061 -- 1246.180019811025 -- 1312.403326707802 -- 1430.4104825150732 -- 1309.8813356823325 -- 1292.7765657506266 -- 1305.237508061784 -- 1343.216515678148 -- 1330.5793100573665 -- 1494.5750847095462 -- 1373.9256490014222 -- 1398.0316850290117 -- 1415.6744470453211 -- 1432.9183177504315 -- 1416.6246066045517 -- 1433.3322257260988 -- 1436.9633056613993 -- 1431.9244902482237 -- 1371.5045772869087 -- 1426.4968783668803 -- 1288.8594964152885 -- 1285.5808822602814 -- 1266.1916718709704 -- 1287.13479726746 -- 1391.8342157879324 -- 1285.158774300685 -- 1270.8124344905896 -- 1423.7168048321444 -- 1336.1130702879407 -- 1443.5775952270715 -- 1312.271776939518 -- 1452.3411539159063 -- 1332.5052286342102 -- 1460.7234754240183 -- 1374.8665802154017 -- 1371.4849068537017 -- 1367.80841721799 -- 1374.4565177410445 -- 1343.7517548650499 -- 1360.469787631817 -- 1371.784425225456 -- 1371.0141239787954 -- 1361.646609052849 -- 1278.5579177568839 -- 1328.4316104282204 -- 1243.333326840135 -- 1236.0904805366958 -- 1256.0123755179436 -- 1246.7679231651468 -- 1230.2426286879927 -- 1232.3497169692434 -- 1394.5042309320152 -- 1278.1919004482156 -- 1289.8221866953181 -- 1306.3976075768771 -- 1293.9557361226148 -- 1405.6553650043188 -- 1349.0506236212464 -- 1351.451922270838 -- 1489.2811215479592 -- 1393.2543415241842 -- 1469.6859782841227 -- 1402.5712372519833 -- 1387.5163413424145 -- 1372.9559088114477 -- 1466.956930903598 -- 1283.601588151788 -- 1253.5839305299842 -- 1252.1375470447056 -- 1238.918664906958 -- 1234.8593873973468 -- 1333.4078559161887 -- 1245.1794606722883 -- 1216.9452016325065 -- 1337.6729941312915 -- 1270.827204506834 -- 1267.493701417436 -- 1378.1131155132907 -- 1306.4775849917125 -- 1446.6875627843176 -- 1313.2180386813438 -- 1362.979967230673 -- 1416.9532730732285 -- 1445.3561799841557 -- 1310.7759579626415 -- 1345.4564947141957 -- 1465.847026034135 -- 1397.0675297178304 -- 1332.2413214600897 -- 1314.9175449488498 -- 1282.0296042474924 -- 1267.179261057687 -- 1345.5576666032393 -- 1239.2610674494945 -- 1241.4185373540436 -- 1260.432315835523 -- 1263.9010422089145 -- 1345.8177012259084 -- 1363.4451481870565 -- 1294.8672185000944 -- 1364.1969406682495 -- 1303.0531345366123 -- 1380.248133108388 -- 1306.543314527199 -- 1314.3312010273976 -- 1368.9589855484653 -- 1365.9771357837456 -- 1499.4022180895543 -- 1415.4207779842848 -- 1436.5038647987124 -- 1426.1712090428084 -- 1410.0643632691792 -- 1428.9606911902579 -- 1412.4738999287688 -- 1349.6196452566412 -- 1298.6918329330686 -- 1288.5806051333523 -- 1262.0665333783086 -- 1256.9731892515563 -- 1281.9949092953236 -- 1272.1515039968622 -- 1244.3155050211565 -- 1286.6442509520784 -- 1451.5032901292113 -- 1361.9817550510277 -- 1321.0424407152723 -- 1346.8689307755133 -- 1368.9413214402941 -- 1407.932204671288 -- 1409.3064977757533 -- 1414.1145922600583 -- 1441.8103722944295 -- 1646.4408283443584 -- 1555.8098083412176 -- 1473.8316304944733 -- 1467.8730110243043 -- 1543.1633952061795 -- 1447.1417891174428 -- 1382.471726916844 -- 1438.5673427532076 -- 1308.1574665284684 -- 1405.888295470433 -- 1280.9452536027138 -- 1301.05478747724 -- 1291.2571324287583 -- 1275.556194733737 -- 1298.855887541613 -- 1462.8793299389365 -- 1437.1204729805513 -- 1463.4348685964933 -- 1503.9502179067674 -- 1390.5561837867729 -- 1413.5184092996556 -- 1536.2990642234838 -- 1467.1652224915497 -- 1469.6453528433485 -- 1498.647474847997 -- 1629.3451174455256 -- 1492.6136152748143 -- 1656.3285964787672 -- 1471.7700042394465 -- 1453.2900078198818 -- 1401.1225851567103 -- 1392.590982427881 -- 1341.4713137559463 -- 1318.6247080667229 -- 1307.1391150643162 -- 1314.4259035672055 -- 1303.0966623553954 -- 1275.636133462063 -- 1421.8873803062224 -- 1320.4250710547658 -- 1328.4651797232914 -- 1340.411894705275 -- 1389.3265658987739 -- 1377.3972509101238 -- 1465.7905794974256 -- 1467.4321084718815 -- 1523.8410995452712 -- 1511.0343301496046 -- 1622.3887272151312 -- 1592.1174231767934 -- 1516.0543665596472 -- 1527.0215439502772 -- 1485.2458540676432 -- 1544.0962818584262 -- 1376.9387253758866 -- 1435.1385149533671 -- 1316.0311328319888 -- 1308.3872224963575 -- 1301.873554409688 -- 1325.7080568610927 -- 1314.1925688650015 -- 1366.2011164507674 -- 1308.571034155306 -- 1478.9021143209804 -- 1496.2549453471051 -- 1416.8613499922474 -- 1387.731941553454 -- 1402.4025079761157 -- 1416.114943427263 -- 1480.04053858764 -- 1474.3591743471338 -- 1490.7583527760803 -- 1652.286254506905 -- 1567.794779608677 -- 1522.4936971334096 -- 1525.4512527077804 -- 1516.898073694751 -- 1487.057485867239 -- 1533.0049296315922 -- 1385.575030673737 -- 1348.5798253753305 -- 1329.1380518068772 -- 1320.203326621287 -- 1337.464059853925 -- 1324.3419746931172 -- 1282.601716603404 -- 1294.706674363636 -- 1322.1652656824479 -- 1344.0001914474224 -- 1418.427459536241 -- 1461.4582349144105 -- 1413.0083247586208 -- 1494.524206453616 -- 1486.224105076367 -- 1537.8071836479348 -- 1652.0167012731122 -- 1580.758530758105 -- 1581.82740605285 -- 1518.7881207361547 -- 1606.3126401467518 -- 1467.10205583939 -- 1426.709985667945 -- 1355.4481067479244 -- 1327.990538216614 -- 1313.3143512259828 -- 1307.4981808042248 -- 1300.067944003885 -- 1325.579889390637 -- 1314.7454898258509 -- 1271.532130443092 -- 1330.7309089561809 -- 1310.1956490311863 -- 1324.7272629325582 -- 1357.8309449405017 -- 1373.8321686604538 -- 1377.175032133655 -- 1461.7515980458265 -- 1457.7150244064246 -- 1431.1589512950288 -- 1457.9950361292367 -- 1502.4933459397798 -- 1645.9974367441664 -- 1499.6469624585466 -- 1485.534124859584 -- 1560.4037463210009 -- 1408.2391774424684 -- 1367.2081730107723 -- 1315.7458226202637 -- 1316.7315878086058 -- 1296.177056993638 -- 1289.5781021201817 -- 1313.7920562106472 -- 1303.683680051875 -- 1274.615777344966 -- 1307.963366148614 -- 1340.9989806343256 -- 1449.1473424058795 -- 1369.5858853285138 -- 1343.7637151753988 -- 1344.9506543183497 -- 1351.0099534707779 -- 1429.3899451588404 -- 1453.7392469572205 -- 1425.3227916121396 -- 1463.602907551863 -- 1506.2655839332924 -- 1480.6106023686027 -- 1575.0550296381753 -- 1464.1869632853313 -- 1411.9538952327778 -- 1419.3489769001085 -- 1327.7424610790308 -- 1316.360172235713 -- 1306.4771181198291 -- 1293.7596177536204 -- 1312.816485286267 -- 1306.4761348220968 -- 1279.250136449398 -- 1301.3971754143 -- 1331.772257857556 -- 1449.9888776242567 -- 1361.4137343398427 -- 1393.8871217477747 -- 1405.117552633163 -- 1445.3647695805773 -- 1477.4473081998044 -- 1474.324129786525 -- 1578.4881669026008 -- 1521.93995674126 -- 1573.564152934402 -- 1502.0104488499246 -- 1525.7204073791725 -- 1494.305918078511 -- 1562.1555764895659 -- 1414.1211912727483 -- 1362.9948129627187 -- 1422.62187798678 -- 1310.3014620491056 -- 1357.4075628563935 -- 1317.9411238212995 -- 1329.7866055257039 -- 1287.192226052478 -- 1392.009786321272 -- 1388.4683131331565 -- 1466.0313493180577 -- 1394.573125785726 -- 1375.799819073665 -- 1520.2578488415081 -- 1447.284337087555 -- 1636.0826806295113 -- 1496.451201677982 -- 1606.3584450777867 -- 1514.7539104207613 -- 1634.1812438459724 -- 1522.7572394706165 -- 1529.7683181331001 -- 1493.7769247817573 -- 1466.4459521152123 -- 1403.930137357743 -- 1447.9362351601558 -- 1325.4978183237176 -- 1302.3559875812848 -- 1293.2928906205882 -- 1313.26619564095 -- 1300.7154159120287 -- 1350.4221833251554 -- 1297.15320239264 -- 1337.2481216260976 -- 1345.3074403932983 -- 1434.329132100979 -- 1390.6732036909134 -- 1650.466964217536 -- 1461.1737529766892 -- 1632.4427376113251 -- 1476.0368549358434 -- 1481.5354958521398 -- 1500.1359180893596 -- 1594.160965353279 -- 1518.7363818498313 -- 1654.9198028183855 -- 1509.303986252171 -- 1472.7517260682177 -- 1408.2412327684617 -- 1361.4827183308996 -- 1348.7180194088069 -- 1325.4188225618275 -- 1316.2088027993889 -- 1339.0185978059592 -- 1328.0477856462805 -- 1425.8418580787454 -- 1320.564338669071 -- 1357.5526817701457 -- 1389.5385167639358 -- 1369.5687962085383 -- 1396.6350651531643 -- 1532.3093307214283 -- 1430.2670732354636 -- 1489.5513499371789 -- 1477.6990286031605 -- 1500.7474612322767 -- 1535.4641114949216 -- 1568.0783243637125 -- 1525.42366461783 -- 1547.9703454596802 -- 1505.5765549999169 -- 1602.9649366342649 -- 1438.2450979745208 -- 1373.4719532096285 -- 1349.7253983273836 -- 1329.1972844428287 -- 1377.279673452367 -- 1338.881579812154 -- 1324.0373893560818 -- 1281.0086559185686 -- 1295.254405041972 -- 1317.2655226639374 -- 1337.6278502949165 -- 1374.4789315801816 -- 1412.513886290334 -- 1414.486234079612 -- 1463.9951912399415 -- 1530.154120804418 -- 1509.9721485349805 -- 1552.4249648017358 -- 1517.8220922411751 -- 1555.7008477657662 -- 1620.6250569711428 -- 1552.263769913091 -- 1529.1073689907234 -- 1536.053738045632 -- 1390.0423445034694 -- 1361.872654511127 -- 1327.9048736307186 -- 1305.8236242313556 -- 1309.4484699357865 -- 1440.247692428596 -- 1309.6384253678793 -- 1271.150710906114 -- 1299.9671223149155 -- 1306.3391893923354 -- 1340.500810412347 -- 1350.906863881334 -- 1513.6183632672721 -- 1432.4711674225882 -- 1450.1045819733054 -- 1500.5771333495516 -- 1531.0375300509575 -- 1491.3433810481686 -- 1644.2650853245887 -- 1615.0222743464024 -- 1628.6344282900288 -- 1523.0618415884696 -- 1618.3626635566382 -- 1521.309423887423 -- 1403.607112085362 -- 1480.1956892215849 -- 1333.1005397510935 -- 1327.9447609264107 -- 1311.8300474834823 -- 1338.2956383718806 -- 1328.4253325756276 -- 1301.5876719570824 -- 1432.6545047521129 -- 1384.3348825802013 -- 1408.188691054727 -- 1516.815993538294 -- 1389.4290460068883 -- 1428.2741386709595 -- 1486.118798545305 -- 1504.4825438432842 -- 1484.5072639192638 -- 1503.8928117528887 -- 1551.901384780511 -- 1716.2689905368525 -- 1575.156124487783 -- 1549.7196454735615 -- 1453.7608376466978 -- 1408.1690450512024 -- 1445.123748623696 -- 1336.832174013186 -- 1307.318409379153 -- 1302.3664512779658 -- 1307.535474393523 -- 1331.3708540974162 -- 1319.844775635574 -- 1361.2897812359995 -- 1329.5778954839957 -- 1466.5368457702943 -- 1347.3032847231043 -- 1362.5779130151757 -- 1489.4681337725624 -- 1399.7297599257856 -- 1504.1736374389134 -- 1497.581464270045 -- 1486.4921000641373 -- 1506.9620180014056 -- 1560.7461413237472 -- 1601.7794143398282 -- 1526.1523868570296 -- 1574.595166231284 -- 1506.0672123089714 -- 1491.3962628140512 -- 1544.7873855566372 -- 1389.9809408622098 -- 1352.6340423168447 -- 1335.7932274358393 -- 1335.5331170112295 -- 1349.6087648725322 -- 1336.4193933278016 -- 1301.629029546754 -- 1342.5761163460643 -- 1367.9522637213206 -- 1393.7935447527018 -- 1383.3466884454085 -- 1381.495597059684 -- 1391.56892493845 -- 1413.520867241416 -- 1490.8490698194735 -- 1624.909678222421 -- 1537.380259117717 -- 1523.6675893401914 -- 1671.6056303662729 -- 1515.4244473185508 -- 1506.8487506731449 -- 1488.685429254423 -- 1469.2572514699766 -- 1586.6576844909393 -- 1398.5077985782698 -- 1340.3515553421998 -- 1332.0249741644295 -- 1323.4163531654397 -- 1338.3568394424153 -- 1320.905760351704 -- 1304.6858149716081 -- 1325.0334790160878 -- 1466.7658431735201 -- 1392.7572274331337 -- 1374.9436182554723 -- 1389.7707892703916 -- 1414.017224487852 -- 1422.8647949284375 -- 1492.4595875161444 -- 1486.6138397589625 -- 1506.0227649146111 -- 1523.0575229586675 -- 1564.5058156261523 -- 1520.4854363902125 -- 1636.1549239882465 -- 1480.5405286636292 -- 1452.4294020567017 -- 1405.9314813198494 -- 1366.0089162924342 -- 1346.1807890661096 -- 1320.7935973004935 -- 1312.2320609587757 -- 1332.3965963007204 -- 1324.5864860521306 -- 1293.6808488101808 -- 1426.3031162908267 -- 1461.4992527930308 -- 1358.7230870214605 -- 1364.6675712206827 -- 1402.6133813502654 -- 1411.7380737015264 -- 1525.8191343360327 -- 1488.266670664613 -- 1481.9379855761108 -- 1628.7553610731186 -- 1517.4824689920154 -- 1545.4018663030988 -- 1495.2661379451524 -- 1548.3236073609655 -- 1501.6813867080925 -- 1455.750659058173 -- 1498.7032108495514 -- 1366.4011937584685 -- 1333.1873883878243 -- 1323.6512542038315 -- 1300.920645019799 -- 1317.5965452366163 -- 1302.4192165273844 -- 1265.269687318486 -- 1283.0034777553187 -- 1355.4071914093608 -- 1356.6375847536167 -- 1352.6126127945554 -- 1407.968813150255 -- 1507.449370966618 -- 1433.972825333631 -- 1590.6927940475857 -- 1485.6744934387011 -- 1509.341693687561 -- 1546.9982169102398 -- 1558.426109511794 -- 1640.072430137386 -- 1511.2717742173686 -- 1461.9021699420796 -- 1409.6228573789592 -- 1322.8205506918994 -- 1418.3333454573105 -- 1269.319503849781 -- 1265.801444824716 -- 1276.0643812405121 -- 1283.5122357837115 -- 1272.5100171453496 -- 1245.8878486324868 -- 1347.1105516894947 -- 1288.479291811781 -- 1317.422411252058 -- 1350.6287634723265 -- 1392.6949383218762 -- 1449.871237597637 -- 1418.7111085461029 -- 1565.7718253892967 -- 1467.3651460229653 -- 1458.636690326895 -- 1455.7975648916693 -- 1451.2784657779293 -- 1454.3547796794992 -- 1415.3488112199711 -- 1401.492583419857 -- 1353.847731812323 -- 1444.7640563120908 -- 1290.072710903959 -- 1278.6885211830659 -- 1262.396102810501 -- 1270.8682575956 -- 1285.964275908772 -- 1401.7192199979918 -- 1304.9873344937412 -- 1406.6998273909203 -- 1417.1612666627873 -- 1358.5158524469778 -- 1415.137809648653 -- 1381.7410726550302 -- 1455.139665478352 -- 1346.6472085095809 -- 1369.1136721464359 -- 1382.5337073248782 -- 1433.5973148070177 -- 1426.141575636499 -- 1442.74526137728 -- 1408.5898553423071 -- 1399.537780725849 -- 1430.1295502114301 -- 1511.2965556563386 -- 1454.742620184655 -- 1431.05378860129 -- 1278.3937760917788 -- 1266.6669545309592 -- 1258.670363258839 -- 1280.2570464829098 -- 1276.3017862419422 -- 1244.9513554104892 -- 1267.923852112239 -- 1299.3494490907617 -- 1337.770536117415 -- 1379.9022177537101 -- 1362.12462285945 -- 1338.1175037769883 -- 1356.671877473832 -- 1375.938376073452 -- 1433.0409820757984 -- 1433.4359011610757 -- 1410.1550060029113 -- 1443.40855990819 -- 1441.6744253382903 -- 1439.1358516991625 -- 1461.929104588937 -- 1407.6798766218562 -- 1323.8054870148646 -- 1292.573637940348 -- 1269.4559994863457 -- 1261.087293106313 -- 1260.0387816027676 -- 1280.3197087860344 -- 1269.2271525596036 -- 1353.7803560568157 -- 1293.9512212516277 -- 1408.1164366399857 -- 1335.5285843583194 -- 1378.4039930349961 -- 1347.7981834096654 -- 1352.7317368234162 -- 1440.9053634400368 -- 1453.1886108852668 -- 1378.6253788613376 -- 1395.9033165726473 -- 1417.8002392707233 -- 1431.4705033477271 -- 1410.8745054014375 -- 1562.1994247687776 -- 1413.1865093899003 -- 1406.730038011667 -- 1466.2924598030781 -- 1347.5971620808587 -- 1281.840649971786 -- 1271.8690725474141 -- 1254.2121787843744 -- 1275.7411991996787 -- 1269.4871653505163 -- 1348.4407006997567 -- 1276.4187955711277 -- 1295.034196006171 -- 1299.1308914889046 -- 1414.2749950976724 -- 1314.9218427036053 -- 1418.1109332153128 -- 1432.3515623584963 -- 1345.310911055315 -- 1356.9394184328883 -- 1456.186756460898 -- 1355.4579921623235 -- 1348.122501392325 -- 1319.1210375851392 -- 1345.190861402956 -- 1483.624038170622 -- 1459.3308329659194 -- 1316.35944527615 -- 1361.443385812194 -- 1281.6020052763351 -- 1255.888406318311 -- 1240.2198715070085 -- 1270.2500538341517 -- 1260.019206572647 -- 1243.39920094579 -- 1274.4717061051535 -- 1318.695790948057 -- 1306.9428670423567 -- 1438.599436541955 -- 1309.5346376404655 -- 1287.3433715126775 -- 1438.080051709417 -- 1325.624634693633 -- 1342.7808700075855 -- 1381.7552494925098 -- 1300.6516015335387 -- 1317.6251301783925 -- 1308.0281876152715 -- 1307.1126725358886 -- 1324.9835260460345 -- 1344.4887411319949 -- 1399.868472257442 -- 1257.9448351197925 -- 1257.6261940506768 -- 1238.0543753932873 -- 1215.2953593170907 -- 1231.1821108949082 -- 1223.932954667453 -- 1195.8641268657886 -- 1210.7010084149867 -- 1241.9043931954134 -- 1247.4844699655293 -- 1417.0285190074421 -- 1277.5662231451658 -- 1283.0448816958638 -- 1282.0749587429036 -- 1294.445256035945 -- 1324.998670213812 -- 1293.090730659755 -- 1321.9164063679893 -- 1378.0596576469322 -- 1396.3454513015047 -- 1369.5224581249959 -- 1383.320630673702 -- 1337.57956834179 -- 1262.043942113605 -- 1248.3335317274036 -- 1223.9155934143023 -- 1252.4506949732895 -- 1204.7361087635086 -- 1225.963335724262 -- 1219.3569070244896 -- 1191.0007513987782 -- 1205.9541942344697 -- 1239.4350911810466 -- 1267.4145556968022 -- 1280.0031675382265 -- 1281.403031568806 -- 1292.3613999480488 -- 1324.081090722539 -- 1485.7484920501056 -- 1406.9089730181843 -- 1446.6390685931733 -- 1385.1875850497574 -- 1411.803569400655 -- 1478.1776039728227 -- 1390.8216244554808 -- 1401.937095593344 -- 1341.5002641265153 -- 1404.631167368464 -- 1249.644469872098 -- 1219.5978230883961 -- 1215.9581393979229 -- 1304.0747067808747 -- 1236.339846024946 -- 1246.792622853751 -- 1211.485884184402 -- 1394.1723714055715 -- 1425.202363529891 -- 1389.3226098540042 -- 1405.6663779759126 -- 1422.0714413754092 -- 1309.0895463902398 -- 1320.0841528049477 -- 1341.4821847751505 -- 1394.523006728729 -- 1424.2869243984194 -- 1387.8565255767653 -- 1418.1079739524325 -- 1390.8153418994984 -- 1413.2763351677968 -- 1499.988270115698 -- 1477.3051465624453 -- 1320.1363454414397 -- 1290.4022675901006 -- 1254.462614125224 -- 1233.9314607619726 -- 1231.8859369819695 -- 1249.0240005498763 -- 1239.0235922256902 -- 1214.061590133083 -- 1260.1549175701562 -- 1365.3666023700946 -- 1297.2504642654965 -- 1293.1679541726107 -- 1304.68125094737 -- 1435.8591716331885 -- 1313.016619132026 -- 1352.05340752754 -- 1350.7080653965252 -- 1368.7537689338087 -- 1443.3328157528451 -- 1427.5434938563444 -- 1405.3602273237855 -- 1522.482417272949 -- 1378.1201553793685 -- 1379.3029711921738 -- 1315.5951613476386 -- 1270.6729474659912 -- 1316.4530351215037 -- 1237.3363423229762 -- 1237.3850094156396 -- 1252.6835593537496 -- 1247.812715572515 -- 1220.6085996049703 -- 1271.4376384834977 -- 1272.3900704332832 -- 1436.7556279562125 -- 1313.9607553310884 -- 1309.3064113857288 -- 1300.334850900249 -- 1322.3103618552493 -- 1364.5803140898763 -- 1498.1958917547265 -- 1384.5285213586797 -- 1409.8525963086456 -- 1446.454427250802 -- 1394.8079958771095 -- 1436.2174632364258 -- 1417.5203980747735 -- 1466.7640316317334 -- 1319.9629805343277 -- 1412.3740491147548 -- 1348.930830130298 -- 1254.59476686829 -- 1245.8588738263923 -- 1267.2707375399555 -- 1258.2664824887484 -- 1344.8546276365635 -- 1294.0781306343124 -- 1322.0965650511798 -- 1340.2717252820564 -- 1301.4854281580795 -- 1318.586794597942 -- 1418.9219583063616 -- 1340.1894922406427 -- 1404.1497564791453 -- 1392.5126051259108 -- 1414.0834832505686 -- 1473.3417452008139 -- 1448.0439058583195 -- 1418.4469546884147 -- 1570.7125987412392 -- 1530.2608216380431 -- 1379.6961468301677 -- 1464.9497903193696 -- 1329.970191260751 -- 1267.4126318054325 -- 1258.080103755796 -- 1252.1465984863087 -- 1274.6286409708166 -- 1265.3192323499059 -- 1240.7524681404327 -- 1269.3034288334131 -- 1292.5167716634 -- 1311.054462538489 -- 1315.9362693549092 -- 1315.1339638320107 -- 1331.8870874373674 -- 1407.269100969146 -- 1378.9228864701663 -- 1385.768125052455 -- 1431.067472398009 -- 1473.0512898085667 -- 1542.517035119306 -- 1454.0217068641603 -- 1433.2842057813104 -- 1399.102949805861 -- 1388.9995914750014 -- 1478.2238434022436 -- 1295.9114439073162 -- 1260.9785397471549 -- 1261.0718084343528 -- 1235.502670391282 -- 1251.485494071617 -- 1241.2601682468617 -- 1207.798864319503 -- 1225.519268837149 -- 1246.2774880795998 -- 1272.2435614698632 -- 1295.517941528198 -- 1477.639357747883 -- 1366.1148927525248 -- 1471.6128085623018 -- 1388.3028558128092 -- 1445.7984398371066 -- 1408.0655662049567 -- 1352.7394594623347 -- 1364.537402083303 -- 1317.3942103648483 -- 1355.4795474949588 -- 1353.0992783047814 -- 1335.0403168881408 -- 1311.8319601682192 -- 1257.4557343237316 -- 1228.4810611710043 -- 1308.742872530412 -- 1217.1939788866316 -- 1237.475158654721 -- 1231.940038687727 -- 1202.3628069291083 -- 1218.1078713695706 -- 1242.5121013818207 -- 1278.9064458391485 -- 1300.1500727072173 -- 1296.7958943360754 -- 1296.6764760796789 -- 1326.82860594339 -- 1366.474442317991 -- 1399.9006364882775 -- 1414.8167655955199 -- 1367.773509278722 -- 1424.1962873535178 -- 1468.6797130385232 -- 1398.1154769596058 -- 1376.012960632564 -- 1407.7878862497362 -- 1316.2083402586475 -- 1246.4155913113898 -- 1246.5939940955707 -- 1241.665649748547 -- 1232.257054013041 -- 1257.4393269718744 -- 1254.3839252868463 -- 1340.8717742133113 -- 1265.591771355575 -- 1402.287273096797 -- 1427.5492439111936 -- 1340.7566338634165 -- 1457.3483540386037 -- 1345.5198936108586 -- 1325.7278474537168 -- 1409.095073968547 -- 1383.9188808439467 -- 1461.721921546061 -- 1401.78612185009 -- 1412.8087197632367 -- 1372.0357729001737 -- 1384.899586485732 -- 1377.7914287413907 -- 1390.5784714811834 -- 1315.0654953264018 -- 1292.6300882343835 -- 1271.4430085144936 -- 1265.8848398708385 -- 1251.887724186096 -- 1273.2855737037983 -- 1269.019045950969 -- 1239.9163806611593 -- 1379.2380300435666 -- 1429.1741418509737 -- 1327.2238626725052 -- 1315.8782943579997 -- 1319.0966277787763 -- 1440.8897109146874 -- 1330.62352205861 -- 1377.793701416611 -- 1389.5632336419533 -- 1405.3068480794225 -- 1436.4513572573217 -- 1462.38981884456 -- 1434.2158128405115 -- 1417.8797787817925 -- 1415.794179495864 -- 1416.3651892257333 -- 1351.195354920523 -- 1312.1192008800494 -- 1412.8270611686796 -- 1262.5182222169065 -- 1258.1729393268074 -- 1280.4402399847788 -- 1268.8741971347056 -- 1244.0629147701068 -- 1272.481415401271 -- 1291.7173188156469 -- 1432.823369811047 -- 1337.8508340321303 -- 1434.2495680585457 -- 1454.1581807568846 -- 1371.4427284506503 -- 1418.1297723494454 -- 1408.7980236053556 -- 1426.2449750665157 -- 1435.486684216392 -- 1475.1119700338884 -- 1440.7516307963438 -- 1416.280647536572 -- 1435.5884770751038 -- 1437.9540266162949 -- 1340.0533015559279 -- 1297.7260211229275 -- 1350.941148172693 -- 1263.1873922031316 -- 1252.3941086760153 -- 1273.3131618077807 -- 1369.2188865052615 -- 1239.1413478385762 -- 1399.6418764254317 -- 1425.712291482507 -- 1463.2437759481397 -- 1464.793145246451 -- 1339.9750995433387 -- 1349.5234193107137 -- 1434.9592730676077 -- 1379.3754937526664 -- 1414.8118534329656 -- 1385.957529680856 -- 1398.1035925345718 -- 1408.399078451974 -- 1372.671238732421 -- 1379.0539107699265 -- 1355.4157481652155 -- 1363.376095000302 -- 1323.890844090532 -- 1421.8831207869148 -- 1392.0424828063135 -- 1249.2460946874146 -- 1247.758559971014 -- 1272.5229963331733 -- 1263.20404024119 -- 1236.393724735902 -- 1260.1383788704911 -- 1436.6051876832682 -- 1308.0621820390102 -- 1426.5742563604028 -- 1284.0666779993894 -- 1316.5616971167776 -- 1349.2029976578037 -- 1410.6979521412434 -- 1369.6676756031497 -- 1331.9931491837413 -- 1348.2534046695916 -- 1361.659282487953 -- 1338.5672333630114 -- 1341.2046046107655 -- 1322.9986727993598 -- 1320.2190382267709 -- 1333.009591869828 -- 1239.8724875682258 -- 1225.1329795641184 -- 1199.7609540004935 -- 1183.1503846103365 -- 1195.5354449391082 -- 1195.8730311186077 -- 1163.8314910290828 -- 1169.494859190346 -- 1204.4708807215802 -- 1216.7203866054404 -- 1261.3467242014756 -- 1246.3302199118787 -- 1288.9723221234506 -- 1261.4681996253546 -- 1323.9077106746302 -- 1287.1849042892027 -- 1333.164780204647 -- 1342.6780324968333 -- 1453.433540661944 -- 1307.9610967422868 -- 1347.0878609071106 -- 1326.5921202222173 -- 1326.617300222634 -- 1300.533896880567 -- 1223.0155495698568 -- 1193.0361986049782 -- 1185.4019875897948 -- 1180.5890017230488 -- 1197.4242036828382 -- 1188.8889847563755 -- 1165.6567430604368 -- 1175.1395405794099 -- 1314.8606300738622 -- 1253.5314127026002 -- 1348.6041789504804 -- 1252.9968418054073 -- 1388.473895051265 -- 1298.948697289527 -- 1337.0683529167436 -- 1328.0413228732816 -- 1346.1365994705711 -- 1370.1648189853984 -- 1401.396431830658 -- 1382.7161873860475 -- 1383.4692664604158 -- 1426.898642737778 -- 1296.1063793013934 -- 1296.3842212911181 -- 1307.6357542762119 -- 1222.3184989167673 -- 1193.06015981263 -- 1189.8138846538347 -- 1196.2203363456329 -- 1201.3438004481363 -- 1211.6031139967724 -- 1230.1856436156081 -- 1260.8505457531264 -- 1257.8401193272239 -- 1375.312868083764 -- 1279.585058953602 -- 1318.2854988542965 -- 1332.3715424340135 -- 1370.6675978506487 -- 1437.7054758507938 -- 1557.952775159081 -- 1577.552350731986 -- 1521.626224312533 -- 1529.723977660286 -- 1445.217787811968 -- 1454.6703768632888 -- 1359.438096178576 -- 1281.6840890505048 -- 1246.89872429092 -- 1227.6256484167398 -- 1244.3331730205655 -- 1244.6234150766143 -- 1250.6887026467823 -- 1241.2802341459048 -- 1219.0918908078625 -- 1342.6471673725428 -- 1271.2831664525538 -- 1374.513218615914 -- 1413.4736598625927 -- 1319.6977473360737 -- 1328.71785465101 -- 1349.3148254238429 -- 1418.233967616709 -- 1432.253628987397 -- 1438.4277507903853 -- 1481.7054370358853 -- 1482.6159609982935 -- 1453.2632234437551 -- 1433.7109446484214 -- 1441.505807170499 -- 1552.454755650321 -- 1400.8278229793127 -- 1306.049955779409 -- 1271.6257068169443 -- 1261.5369088316443 -- 1260.6614263148126 -- 1256.0345396143218 -- 1242.1106752024562 -- 1208.5615233343785 -- 1222.2196927874518 -- 1388.9579255583765 -- 1303.773987270001 -- 1238.810245366729 -- 1234.0855348215377 -- 1246.2604655551527 -- 1249.749415449954 -- 1267.2333736094179 -- 1256.5272896616225 -- 1260.051369757694 -- 1369.0345626318194 -- 1278.1510192857847 -- 1378.924416869333 -- 1251.8324442746539 -- 1256.8423187146184 -- 1258.2718302309247 -- 1337.6551671339912 -- 1217.3298151471067 -- 1232.9798535436878 -- 1188.505670586917 -- 1199.8509675233088 -- 1203.1376886429248 -- 1200.9038997658176 -- 1191.5649939910893 -- 1237.961801871389 -- 1241.8090420896567 -- 1270.2694627991202 -- 1363.6960563605862 -- 1361.7067718644562 -- 1251.4007906949782 -- 1250.8013655453021 -- 1258.1374668481085 -- 1273.000448588697 -- 1296.1196591365251 -- 1322.9472461116495 -- 1359.1086165238041 -- 1331.82523226813 -- 1357.4781602252783 -- 1496.6263545354727 -- 1489.1691009990911 -- 1293.5089528751182 -- 1232.1519524731705 -- 1218.503772848162 -- 1212.84169063233 -- 1212.3659347565715 -- 1229.9295067430348 -- 1238.9175240709608 -- 1207.4230992342912 -- 1248.1104053638387 -- 1372.4625822011965 -- 1398.306361231465 -- 1321.5831944688073 -- 1392.5117076472754 -- 1308.63718879471 -- 1352.906644990509 -- 1418.310020775638 -- 1379.3657282196239 -- 1395.4374734551263 -- 1444.475142834877 -- 1508.5902794086435 -- 1423.4543622012347 -- 1427.7337935237408 -- 1472.3994192242956 -- 1380.1022626371791 -- 1315.8157074303301 -- 1275.0065767345088 -- 1247.1742041283612 -- 1225.0498873180359 -- 1218.2478205048365 -- 1236.7963649889075 -- 1229.305436644735 -- 1324.2842617970925 -- 1220.578134401385 -- 1372.818063564702 -- 1271.198713353428 -- 1276.2488954969467 -- 1296.298650803758 -- 1322.6777814889892 -- 1329.3895186881612 -- 1378.669995394605 -- 1364.8439444264652 -- 1545.1347428659187 -- 1438.270227785272 -- 1485.468613707045 -- 1458.4927869710273 -- 1407.9718632594984 -- 1380.9078210059881 -- 1420.556552857743 -- 1304.6472995490515 -- 1282.3800023726105 -- 1239.7966726711043 -- 1233.8990859761752 -- 1228.5034290437045 -- 1248.7131378117792 -- 1239.4950509519142 -- 1212.0866691563006 -- 1239.022121484541 -- 1328.876899658267 -- 1299.3542508581268 -- 1304.420244357972 -- 1311.528733463572 -- 1435.6873154727123 -- 1331.0563690177344 -- 1383.1122211587094 -- 1384.9626789434833 -- 1400.9846604039465 -- 1511.5063838873334 -- 1443.2654883574799 -- 1548.3715538264016 -- 1409.0116222588388 -- 1395.9278342739208 -- 1413.1724611575119 -- 1337.0866621624577 -- 1354.591859885478 -- 1237.505632905542 -- 1235.947500829801 -- 1227.8433387137304 -- 1250.8131667717357 -- 1245.8176288012337 -- 1224.9072932652057 -- 1396.8792014663322 -- 1272.856627795908 -- 1429.4647388163733 -- 1427.472508638437 -- 1322.5605798138747 -- 1362.2898062487427 -- 1348.0013850055066 -- 1384.7744252223597 -- 1484.8546700590266 -- 1411.425855188377 -- 1421.8370039997494 -- 1434.053231507668 -- 1446.5265978556681 -- 1523.6560376263808 -- 1415.3496178347525 -- 1387.568024530604 -- 1323.3253980895863 -- 1282.086338805816 -- 1255.1431840461175 -- 1250.46204781252 -- 1233.4725663853628 -- 1256.2119438065722 -- 1248.7439309367571 -- 1317.2065385907167 -- 1254.016550488147 -- 1292.682485077667 -- 1313.0207748190721 -- 1329.4471953008183 -- 1381.0371674709793 -- 1488.272478696872 -- 1354.4202558783836 -- 1397.7238441525076 -- 1404.8050735967195 -- 1426.3618659363099 -- 1428.3228556131749 -- 1486.87770936718 -- 1410.2659827459395 -- 1531.958605299017 -- 1495.9455039724983 -- 1510.4439719098466 -- 1374.241544058679 -- 1300.6699008360188 -- 1267.906654564959 -- 1255.4195589384099 -- 1250.4538574317821 -- 1269.381594669196 -- 1266.869397932156 -- 1354.9740298273014 -- 1285.577343222258 -- 1425.5416938230853 -- 1524.7762700904832 -- 1483.472790139317 -- 1455.1904911959277 -- 1354.9119240442935 -- 1461.7898103279442 -- 1415.819466330714 -- 1420.6226212619868 -- 1438.0904474259798 -- 1601.607347049479 -- 1499.896787922365 -- 1464.010625957234 -- 1455.6522434633625 -- 1435.4794828988959 -- 1405.5892809094398 -- 1441.0083358276968 -- 1296.5953056916992 -- 1280.9301000473383 -- 1265.3956529955208 -- 1254.7007433209521 -- 1278.8395509897723 -- 1269.1428811769244 -- 1329.7948241204622 -- 1271.419665381826 -- 1318.094624070039 -- 1411.0617868075176 -- 1325.3863757620368 -- 1327.6561141380628 -- 1347.530211333366 -- 1378.4592168324584 -- 1531.8258461575124 -- 1416.6920635673582 -- 1448.2569046646604 -- 1478.7636009314567 -- 1554.127853294921 -- 1498.9679504305668 -- 1495.5173521614447 -- 1577.8632191232052 -- 1456.5855325763023 -- 1373.398781109013 -- 1330.7540017351741 -- 1281.7787482556525 -- 1268.1731317959966 -- 1263.3708911654026 -- 1281.8276693160235 -- 1264.0318529165845 -- 1235.2358892303423 -- 1385.3119827148416 -- 1289.644139856259 -- 1310.0319022925437 -- 1413.1348545204917 -- 1465.1649017083316 -- 1364.6854979565587 -- 1360.9051504946221 -- 1417.1600381708724 -- 1472.7613903515091 -- 1417.2525115163571 -- 1561.5835265890476 -- 1601.7283744469582 -- 1490.1199034693277 -- 1451.990371341763 -- 1512.575477293595 -- 1487.2779002252662 -- 1362.9936517812855 -- 1293.0091916207116 -- 1253.0088737182605 -- 1240.657406323254 -- 1219.5609230149976 -- 1249.3051921952376 -- 1229.8957300900495 -- 1219.5176421794527 -- 1229.4743856833725 -- 1254.0322844043924 -- 1289.4647574702237 -- 1297.8747101209833 -- 1430.1586973680119 -- 1315.6563759522196 -- 1345.3800497582229 -- 1432.6217943878673 -- 1497.9655520294332 -- 1395.7305055746624 -- 1434.2128983950468 -- 1412.031184022563 -- 1380.6443212955553 -- 1453.7862315250586 -- 1349.9363191643408 -- 1352.781325070538 -- 1346.6282320230287 -- 1369.7104235961717 -- 1315.958542893991 -- 1220.332308775521 -- 1220.89722911287 -- 1237.058188153795 -- 1232.3246874082142 -- 1208.086765462195 -- 1229.7641641045352 -- 1290.7190562461274 -- 1278.4834387593046 -- 1307.6795020547079 -- 1302.0050993264158 -- 1360.1412710600762 -- 1366.4297225285966 -- 1399.6138637583667 -- 1352.157147949255 -- 1354.6435068393732 -- 1378.6993214181477 -- 1438.0680372783963 -- 1399.0935250891114 -- 1398.198117267508 -- 1382.422342929244 -- 1348.324201596863 -- 1375.5157608149952 -- 1241.902715270715 -- 1224.478940496058 -- 1223.3248122460575 -- 1216.891093824346 -- 1245.2320972867367 -- 1243.2906957239277 -- 1218.9099027812686 -- 1380.9175650492164 -- 1327.5161364266096 -- 1285.5000835463725 -- 1346.9419001075075 -- 1431.7692280919064 -- 1294.4970940805647 -- 1302.6419448825645 -- 1291.5892758855105 -- 1303.5887121812768 -- 1290.2500422187231 -- 1451.1332234036547 -- 1465.9827876153429 -- 1321.0638400947569 -- 1315.9334894929923 -- 1428.1106166601194 -- 1323.5174863844302 -- 1365.8916134735412 -- 1338.8926131986539 -- 1212.2758796163737 -- 1195.3917349281512 -- 1188.9426615076484 -- 1209.983376005665 -- 1204.9775394774538 -- 1188.0733606734518 -- 1344.2758386241012 -- 1241.1115765288796 -- 1251.2417224042306 -- 1388.4510343500895 -- 1265.9315724512035 -- 1279.712943568552 -- 1292.276151516554 -- 1301.2557196696944 -- 1356.0352934770874 -- 1319.744072141691 -- 1345.3507054329048 -- 1362.300897655933 -- 1338.674287005518 -- 1406.1770616157487 -- 1319.3591300651567 -- 1313.4440792133369 -- 1267.7033322605344 -- 1249.818028650425 -- 1203.658704121207 -- 1200.436285345545 -- 1186.937266608947 -- 1204.186900959649 -- 1200.093668466051 -- 1269.8967084353624 -- 1208.5720311031428 -- 1402.9845930130862 -- 1318.4696684495132 -- 1237.3544930996368 -- 1255.080337707964 -- 1245.406542383437 -- 1360.429672002847 -- 1238.0048812996765 -- 1260.0803325008949 -- 1275.9931892933691 -- 1304.2813703623196 -- 1344.193635512807 -- 1296.605094447664 -- 1331.2526173810315 -- 1318.6219394167006 -- 1460.301664564292 -- 1279.0362528751475 -- 1241.386340674983 -- 1213.4750737913926 -- 1190.8088851803966 -- 1183.993292093265 -- 1198.83158468068 -- 1193.0362618141946 -- 1189.9357004325882 -- 1331.011932687549 -- 1358.6142965539495 -- 1246.0721650713986 -- 1253.2421393925174 -- 1229.5109610534842 -- 1254.3116965760462 -- 1249.2532439288295 -- 1250.8281951740153 -- 1262.9068474166045 -- 1270.1082348097723 -- 1288.1966156995807 -- 1299.7444601392972 -- 1386.565853220445 -- 1304.700263788111 -- 1299.1725395154076 -- 1305.67037705589 -- 1356.540402719195 -- 1325.9273150524868 -- 1319.1789942275973 -- 1188.8743952992265 -- 1184.5215220977768 -- 1201.243553022861 -- 1203.356267793643 -- 1185.3822448073834 -- 1273.758049925669 -- 1369.860147819077 -- 1255.718878514075 -- 1324.5724127038475 -- 1251.0631093964723 -- 1259.6558701515057 -- 1265.4091338156204 -- 1286.2503862834496 -- 1396.4442218606505 -- 1430.863216348071 -- 1326.9561758089892 -- 1351.8161352851373 -- 1316.4326666014122 -- 1440.6835395181859 -- 1482.1851733406354 -- 1374.3312227240385 -- 1319.6341153306694 -- 1316.8214195945811 -- 1217.8032345849865 -- 1199.4896772197214 -- 1195.580067430172 -- 1214.5669577796505 -- 1207.388095745889 -- 1183.7592640772095 -- 1188.2063793159418 -- 1212.8973047934915 -- 1247.7575541130236 -- 1258.6167687790828 -- 1290.2318646824529 -- 1278.2473801921938 -- 1405.9501026739479 -- 1346.2462437671063 -- 1287.1301276083266 -- 1303.960147579895 -- 1347.313587403401 -- 1432.0495250328254 -- 1326.4021968594775 -- 1341.1699248856075 -- 1322.041361268951 -- 1310.3896975727628 -- 1249.0151262814495 -- 1225.5723544290336 -- 1212.323102523137 -- 1205.4391714857584 -- 1189.4943985837965 -- 1209.2629814917264 -- 1206.3297152692578 -- 1193.147655223504 -- 1262.330967791608 -- 1230.291567009831 -- 1383.7679104153751 -- 1294.4953578718114 -- 1339.347593785127 -- 1303.550146486746 -- 1320.087002554417 -- 1359.1971140680737 -- 1481.658316629473 -- 1396.7150079999558 -- 1378.670534728447 -- 1414.839486087757 -- 1321.239116456848 -- 1273.9985603399925 -- 1285.6891194165726 -- 1261.8076414467102 -- 1301.3709021737964 -- 1207.8946651770248 -- 1190.6410433244673 -- 1188.7660645644282 -- 1195.436024351256 -- 1265.1792220902505 -- 1206.552382093916 -- 1221.6082273675243 -- 1381.0002139678445 -- 1266.0134688450116 -- 1276.453355576017 -- 1294.9603070993865 -- 1397.6667160181394 -- 1402.520053659279 -- 1375.644803721581 -- 1265.87937417015 -- 1264.9417547704277 -- 1291.429191272151 -- 1279.0505494745532 -- 1377.0218310201135 -- 1274.7754985567603 -- 1407.318363831557 -- 1401.4181500878556 -- 1297.8046498068043 -- 1259.5530956897765 -- 1235.799136887996 -- 1222.8649098887608 -- 1206.9931169634249 -- 1205.0432036970494 -- 1209.916093424715 -- 1218.044627612457 -- 1228.5145820992925 -- 1343.7791729611113 -- 1373.7975343454657 -- 1347.8219112508148 -- 1300.547626422155 -- 1385.6156535666519 -- 1343.860635022244 -- 1303.3909358782992 -- 1318.154894346904 -- 1279.1198292430875 -- 1296.5196244776862 -- 1281.094946860755 -- 1397.473451813276 -- 1312.5513643597699 -- 1303.946319416667 -- 1315.3605380226838 -- 1437.5207053424965 -- 1391.731577550138 -- 1377.2109006223213 -- 1226.0013420206112 -- 1212.6790975408592 -- 1216.023053629059 -- 1219.9614687183112 -- 1230.9318949733968 -- 1357.5469764119953 -- 1284.2413204612321 -- 1419.4536260215912 -- 1406.8267909899355 -- 1320.018353633244 -- 1294.865182293883 -- 1294.558678490397 -- 1287.6517316563159 -- 1279.1928856858663 -- 1287.0905969467435 -- 1271.617436365966 -- 1294.942141647544 -- 1289.7626241385838 -- 1401.1577344361651 -- 1334.712538137809 -- 1468.7628552483593 -- 1453.9044018445477 -- 1290.6657108862462 -- 1256.0669557612719 -- 1241.482440053184 -- 1235.2745911516367 -- 1219.1237454657473 -- 1217.450919646775 -- 1225.2954944095695 -- 1238.4984116345934 -- 1257.996527904231 -- 1276.9027809959603 -- 1380.8839684096693 -- 1287.0150422381907 -- 1299.95906652209 -- 1295.6007298995858 -- 1392.6071405456414 -- 1268.1760786204884 -- 1270.2171072111094 -- 1258.2811743874483 -- 1286.6599592996831 -- 1308.0051153607803 -- 1260.2810121875336 -- 1300.3786051670838 -- 1331.4811221575487 -- 1319.915213128708 -- 1320.0937813739529 -- 1249.5349460173388 -- 1326.6430538018637 -- 1201.3271911189856 -- 1196.6691180640487 -- 1204.395240528934 -- 1205.3737083526566 -- 1213.3741160837696 -- 1320.6739548512426 -- 1242.949110438393 -- 1255.55040243023 -- 1254.4458457193027 -- 1317.6524245797646 -- 1257.954750290334 -- 1311.9394625902878 -- 1233.8119327255743 -- 1244.5760519331702 -- 1239.2056893123404 -- 1279.0787108534482 -- 1260.97545947232 -- 1276.9609653628745 -- 1293.1549600607002 -- 1298.1359573579703 -- 1305.818707433773 -- 1365.7166870074875 -- 1202.4681746146484 -- 1214.1738658766938 -- 1185.7582274905121 -- 1173.4040735340718 -- 1193.5213291977589 -- 1179.5140840136564 -- 1174.5433796615907 -- 1205.6336471308446 -- 1204.4374969724956 -- 1223.2366911626336 -- 1264.4108936890486 -- 1294.51074801883 -- 1306.7006024545599 -- 1360.1703711966609 -- 1386.6351517889925 -- 1270.543389749618 -- 1256.4124610815684 -- 1246.0123246969251 -- 1391.1405195931125 -- 1372.1721891188004 -- 1280.5058265924238 -- 1291.1752255616134 -- 1240.6104823210849 -- 1199.2493064771108 -- 1184.7164465237408 -- 1287.2181710670955 -- 1167.5587450380478 -- 1163.0289230080368 -- 1185.9567291290355 -- 1170.943408047758 -- 1167.5388531922988 -- 1175.6913156485896 -- 1184.97109115783 -- 1309.9470171691764 -- 1226.845521322427 -- 1230.0461527405778 -- 1235.3330881319114 -- 1342.14887323817 -- 1349.509041215483 -- 1258.9956430821483 -- 1266.4590665800988 -- 1276.2835164367475 -- 1291.0064387087496 -- 1282.3111805072824 -- 1270.5203709035648 -- 1401.9093039078389 -- 1282.7447880655277 -- 1201.1500929835306 -- 1176.0973416652662 -- 1167.4462888212383 -- 1164.340891227367 -- 1165.2200375984023 -- 1180.939906665131 -- 1179.4290067791117 -- 1179.344750006271 -- 1205.7354136513866 -- 1346.5245378160612 -- 1233.427069789367 -- 1233.3186792114514 -- 1289.7671039183588 -- 1236.8035668568295 -- 1290.6376586454357 -- 1236.023376496198 -- 1290.7499811405999 -- 1227.444890468241 -- 1242.162993133044 -- 1222.362224973051 -- 1245.8253517301291 -- 1277.0562347968016 -- 1265.445816308377 -- 1282.9714057794877 -- 1345.8352833729446 -- 1208.9844842469947 -- 1182.9063857529006 -- 1171.9448921777516 -- 1172.107360447741 -- 1183.8104555759494 -- 1184.177551219009 -- 1264.9668484362508 -- 1207.359693498463 -- 1228.8999749903435 -- 1252.894285142223 -- 1241.702282195786 -- 1233.1699242384293 -- 1227.9196922210333 -- 1271.2994286779365 -- 1242.141098276268 -- 1347.2818840336831 -- 1233.3596613869065 -- 1367.794245101728 -- 1251.1605067905903 -- 1277.2688090966292 -- 1265.7324256411948 -- 1263.9519800195937 -- 1281.8050684559614 -- 1264.7329696509212 -- 1196.7314198446227 -- 1185.5160060882822 -- 1173.8130941961315 -- 1173.3650111461811 -- 1182.5144489421364 -- 1179.1916729743484 -- 1188.468970243382 -- 1355.3968052655243 -- 1363.1698947939008 -- 1373.110285507528 -- 1376.722303649691 -- 1368.6516234032401 -- 1349.3159811273667 -- 1242.9739297069482 -- 1235.7146750148531 -- 1355.605562484559 -- 1234.97353077187 -- 1246.717999126702 -- 1221.8926327785591 -- 1230.7887070789557 -- 1320.5149665742215 -- 1280.1152941721805 -- 1428.2606449930934 -- 1241.1696169542413 -- 1198.8893101730844 -- 1192.5188217292389 -- 1184.82111628419 -- 1181.9002708368557 -- 1193.0648100744402 -- 1198.828274929553 -- 1331.309477422453 -- 1356.8840021427695 -- 1284.478446365 -- 1256.7498154307739 -- 1247.5814465009512 -- 1366.7624208811412 -- 1377.2723842193288 -- 1364.9076013966458 -- 1362.9230328604203 -- 1365.850031464246 -- 1259.0384311372957 -- 1284.037470030675 -- 1303.733031491272 -- 1418.854065102713 -- 1292.9412547651764 -- 1285.4218946596484 -- 1413.0728999467135 -- 1250.4975101101759 -- 1230.5133186301916 -- 1315.0678764312713 -- 1200.114668399849 -- 1188.3193355283786 -- 1207.2782928732192 -- 1202.3161113514034 -- 1197.581905686371 -- 1228.8267672311574 -- 1265.3170678719146 -- 1247.4808831980663 -- 1295.603694300435 -- 1297.7487243393414 -- 1249.8672942312664 -- 1363.849781892874 -- 1274.6278455644442 -- 1294.2166144351545 -- 1314.432771320718 -- 1320.3752384100562 -- 1335.7657744938306 -- 1367.2516404787236 -- 1312.4308719097628 -- 1297.4383216076576 -- 1281.247365286115 -- 1260.404690466399 -- 1203.970050580697 -- 1197.8042316735948 -- 1231.3819566504048 -- 1176.3781713225453 -- 1174.802834966482 -- 1188.820966458131 -- 1184.827951500128 -- 1287.851174764479 -- 1307.1739949049208 -- 1223.6500024656568 -- 1351.3922490229227 -- 1368.5026710695251 -- 1385.045746599054 -- 1267.3387868151353 -- 1332.986775240864 -- 1331.7334527866872 -- 1476.5131212099152 -- 1368.3340754889562 -- 1379.5597114385646 -- 1423.4106390891754 -- 1452.274144037014 -- 1323.1147693462603 -- 1461.9550900881036 -- 1311.8198143317754 -- 1260.727358270778 -- 1325.9749424338386 -- 1194.2538140095196 -- 1176.0384859245873 -- 1183.1782406813304 -- 1190.0844105783829 -- 1184.6566945697693 -- 1173.0542033033394 -- 1189.5579104544051 -- 1327.8433422456937 -- 1258.306485044173 -- 1279.9479018160546 -- 1269.0016254641826 -- 1403.9326260843009 -- 1355.3317701695948 -- 1347.30047162372 -- 1382.6862480912391 -- 1386.3375813950327 -- 1388.6795753595347 -- 1421.6838395622565 -- 1377.0780822895313 -- 1338.3044018072706 -- 1322.0020273229975 -- 1313.7342203244416 -- 1229.2233081037393 -- 1337.9265266443765 -- 1234.868401592407 -- 1200.1329880931523 -- 1198.5980463265048 -- 1218.0212407360216 -- 1200.1162361349768 -- 1199.0317615546917 -- 1208.0270414737495 -- 1355.221692797115 -- 1250.8416472958236 -- 1309.4976557733698 -- 1272.573175751258 -- 1283.9406436759864 -- 1277.2709089782968 -- 1333.0432092433962 -- 1347.425918951986 -- 1354.225959046327 -- 1376.6079808017162 -- 1406.5171178553255 -- 1354.4438061561593 -- 1345.906051989191 -- 1315.4243988114838 -- 1448.6402757212431 -- 1396.752614120766 -- 1231.966130684892 -- 1212.7659779079895 -- 1255.88172927185 -- 1194.0995690685318 -- 1329.0948620972838 -- 1207.021176671939 -- 1198.29468343573 -- 1224.337483775822 -- 1256.2483158071013 -- 1409.094466260887 -- 1267.6675580300864 -- 1278.8317653448853 -- 1330.9031776977022 -- 1329.7504802492626 -- 1359.6488973826163 -- 1370.6051734826676 -- 1373.5056546587334 -- 1498.924106331383 -- 1410.267359583313 -- 1344.1897688212023 -- 1340.5947263826774 -- 1446.220768096245 -- 1299.7931674482888 -- 1256.3351777371972 -- 1220.4583645178081 -- 1328.9539127010344 -- 1196.2199379320582 -- 1180.1917358480955 -- 1194.5596214953327 -- 1195.3607124741575 -- 1188.8533703647056 -- 1196.8250472031093 -- 1241.8630938908495 -- 1225.9726757917915 -- 1246.2729970676808 -- 1225.4773161958435 -- 1280.8103825013009 -- 1291.0932036091526 -- 1223.9966337953556 -- 1216.4092787844133 -- 1232.2633030534892 -- 1227.6724797690074 -- 1248.039903484222 -- 1244.8713155635508 -- 1269.1333547543136 -- 1283.2061801607663 -- 1277.5143079090742 -- 1299.4562969104138 -- 1225.7441171873772 -- 1284.1505221249913 -- 1190.7028840434432 -- 1198.2979154183145 -- 1207.4693638933654 -- 1212.840832916353 -- 1227.6483250593103 -- 1292.7048976109936 -- 1274.7140384428662 -- 1273.2152235257984 -- 1248.380045829711 -- 1251.1943282981229 -- 1363.163066506111 -- 1288.2040160083657 -- 1255.6806253298826 -- 1245.7203035977773 -- 1241.1298428796492 -- 1364.8403587511298 -- 1322.8986448286348 -- 1307.8763218454294 -- 1277.2334231138009 -- 1290.7238138167397 -- 1268.5871499541495 -- 1241.9067627649147 -- 1337.0869179206748 -- 1201.3340899883092 -- 1198.269388748673 -- 1205.3201274942635 -- 1248.8565508117158 -- 1229.110240586919 -- 1236.689131785466 -- 1273.6029634487043 -- 1309.4877084449463 -- 1269.4217597590289 -- 1262.2272258401047 -- 1359.3116832476921 -- 1272.4912962536764 -- 1308.804495465626 -- 1305.4090415571916 -- 1283.47841561248 -- 1313.572937444284 -- 1296.8166243592366 -- 1312.889669697884 -- 1291.7318895522533 -- 1287.4378688885156 -- 1371.5300965778529 -- 1289.222678471029 -- 1291.1992985797774 -- 1384.4504231937647 -- 1202.8300485837067 -- 1314.7041023517875 -- 1305.3751347932507 -- 1185.9799940570624 -- 1181.524843310107 -- 1189.901893550993 -- 1197.6017928058784 -- 1296.0585250322315 -- 1246.1601645637008 -- 1328.3436397091593 -- 1251.0263954448415 -- 1250.0062492975214 -- 1377.4153374647751 -- 1397.6353048346828 -- 1310.5004069168351 -- 1314.7797178214805 -- 1338.5667018847462 -- 1506.8292126813517 -- 1320.7757655921102 -- 1418.6129004245574 -- 1448.8105730702257 -- 1253.8395427241626 -- 1214.8487346022252 -- 1200.0508312853633 -- 1295.849696068829 -- 1179.908910730646 -- 1176.139813348798 -- 1188.2428137461854 -- 1177.039993374147 -- 1179.3319362283441 -- 1196.1791014796813 -- 1199.280864364223 -- 1270.7370190758506 -- 1226.4256342891777 -- 1233.2140103953725 -- 1253.077226521388 -- 1248.041535500553 -- 1371.1148194378413 -- 1262.3624529862293 -- 1281.1445805048706 -- 1279.5674296646355 -- 1310.8474096714087 -- 1295.2845503705037 -- 1373.088808849988 -- 1268.9859461176798 -- 1247.6125302858843 -- 1209.6034368871085 -- 1189.8269252351213 -- 1174.8532693700695 -- 1170.8780137433107 -- 1175.2062501348128 -- 1196.6205984879218 -- 1187.1841891652932 -- 1190.7290898950969 -- 1207.324120819412 -- 1370.9977178146285 -- 1303.4380655802663 -- 1252.6182658831071 -- 1224.4544486100237 -- 1233.2401164055343 -- 1265.1825902896223 -- 1292.7481883580062 -- 1308.3930066841162 -- 1327.4176660364421 -- 1349.1230706777135 -- 1338.9787486130576 -- 1462.9072262571685 -- 1303.3620029681501 -- 1290.4564918156616 -- 1287.4275262135648 -- 1250.4623990026987 -- 1213.6459613954123 -- 1198.0419857368663 -- 1192.731993781217 -- 1180.34179765763 -- 1193.8221690850378 -- 1196.7993996101711 -- 1186.0485851109227 -- 1268.0074743499567 -- 1366.5380560341644 -- 1265.2469451603604 -- 1295.4329170783656 -- 1263.2294988125523 -- 1289.675184152024 -- 1410.7155464007099 -- 1330.1955297117124 -- 1370.2056111069417 -- 1346.142203707454 -- 1363.4577722610204 -- 1524.58127756501 -- 1350.3242769168116 -- 1439.8232791764488 -- 1333.5801898400784 -- 1310.9259003660552 -- 1248.0504028222538 -- 1305.7505271126868 -- 1181.9418150265972 -- 1184.4856874248017 -- 1174.4069542586612 -- 1190.0105450574642 -- 1195.7770004713645 -- 1197.4899707291024 -- 1351.5990980824224 -- 1295.1436374190039 -- 1256.9472858631696 -- 1379.5225816609777 -- 1319.6764958281058 -- 1383.391565459902 -- 1385.7588306382502 -- 1316.2945214487638 -- 1277.127247525615 -- 1261.6847281233472 -- 1313.4292075833857 -- 1327.3525211567487 -- 1284.2516502966334 -- 1318.2584555562296 -- 1367.5539048084936 -- 1259.8822411900424 -- 1218.2502265927149 -- 1202.631569739937 -- 1262.5003500205348 -- 1178.8806969694353 -- 1175.8229800891916 -- 1187.7862449612699 -- 1185.7946586518547 -- 1321.7041516119673 -- 1339.290213010949 -- 1280.593459888754 -- 1238.479835147824 -- 1241.2726190473866 -- 1268.9299146280364 -- 1307.0672635980875 -- 1360.7585970811813 -- 1232.6211238959029 -- 1229.0029167034988 -- 1225.202412129639 -- 1242.0117571618832 -- 1255.1833054686426 -- 1295.9119934123298 -- 1307.9505102640433 -- 1294.7510001781145 -- 1260.9909908353522 -- 1230.6802641733502 -- 1218.488881854416 -- 1195.6308359325178 -- 1196.1446012877543 -- 1204.7489787700322 -- 1222.894704933636 -- 1214.9599140938565 -- 1223.7111598555168 -- 1258.9879167450608 -- 1328.3988085562564 -- 1339.3465796469475 -- 1296.301219915782 -- 1282.3339620227855 -- 1288.145811863629 -- 1268.3770337277722 -- 1382.5569135473868 -- 1264.1715358477625 -- 1259.80633623489 -- 1307.3551854064751 -- 1400.1541477502255 -- 1403.285076768555 -- 1290.4357255573445 -- 1401.6708300811772 -- 1401.3998816693052 -- 1255.7649744232112 -- 1236.3050141985082 -- 1211.8024342387966 -- 1212.2155778740137 -- 1252.3987033990215 -- 1204.0927677073976 -- 1208.2587453447186 -- 1216.8545305273717 -- 1260.7423429749183 -- 1249.0653875708012 -- 1320.9640167020107 -- 1301.7536237853883 -- 1270.8751844715157 -- 1303.0148251441385 -- 1283.1865157535156 -- 1332.9662611443175 -- 1271.116386400123 -- 1256.479113440415 -- 1265.3748771126636 -- 1290.1214515813638 -- 1419.0979460486676 -- 1382.21045391953 -- 1296.26974054649 -- 1278.496398761083 -- 1249.1011371018853 -- 1235.3852768382733 -- 1318.78114677801 -- 1186.3483384914289 -- 1194.0745567683573 -- 1180.83632823495 -- 1182.8283195208323 -- 1189.975066353682 -- 1203.6960725324006 -- 1221.6260306153288 -- 1241.748476129113 -- 1254.0650818540234 -- 1251.9612456939224 -- 1245.3994173177887 -- 1234.507694795396 -- 1237.5760774785413 -- 1246.8860851393836 -- 1261.4477414204075 -- 1316.5530933426649 -- 1313.367405599074 -- 1409.8758693425762 -- 1428.5754939025783 -- 1290.6456070600896 -- 1294.9601605580065 -- 1318.1179501618647 -- 1277.3817960183455 -- 1206.6246172457184 -- 1213.3557786784377 -- 1220.1480681723515 -- 1220.572781075948 -- 1225.9241150442908 -- 1245.1752269946905 -- 1390.939449091026 -- 1396.5624658860165 -- 1294.4240075863088 -- 1306.409556966395 -- 1326.1587380448054 -- 1427.1027032666584 -- 1279.9373460649767 -- 1301.366383714887 -- 1327.0912941474487 -- 1309.531169864834 -- 1318.8433166321136 -- 1343.7193865335082 -- 1352.3985319522585 -- 1374.903576589966 -- 1491.910586866647 -- 1466.6464474375884 -- 1326.99368807128 -- 1310.7369078101888 -- 1290.4189998097092 -- 1261.1481483375326 -- 1277.3069588827577 -- 1295.4698870244242 -- 1310.7706783405065 -- 1331.9265324943322 -- 1353.84489325408 -- 1441.6939639452269 -- 1365.2376628337536 -- 1487.1847790347613 -- 1402.4336564998207 -- 1456.9489602191773 -- 1445.8590887834466 -- 1332.013752455457 -- 1426.877329773567 -- 1309.0390795224096 -- 1434.9810093240335 -- 1343.5641742609562 -- 1357.8154043188422 -- 1356.519185351903 -- 1386.7191627708428 -- 1456.1081039703577 -- 1322.1467952848889 -- 1307.801845356015 -- 1282.2646129638022 -- 1373.3935630845922 -- 1268.7225660203273 -- 1280.425825746281 -- 1289.1807344360975 -- 1311.723577804038 -- 1378.2182825927528 -- 1487.4524618337985 -- 1398.8355603547054 -- 1351.327376959097 -- 1331.6878157801061 -- 1317.2395863061822 -- 1299.6896901737498 -- 1353.6949182107041 -- 1407.7866499925824 -- 1301.682829110256 -- 1297.649269155912 -- 1297.7496930823518 -- 1392.9012485590695 -- 1460.9309919691727 -- 1344.702539949858 -- 1428.766872465044 -- 1420.737967300535 -- 1401.344442875849 -- 1242.083810031024 -- 1239.5883258889444 -- 1237.4060474780317 -- 1244.2365041723363 -- 1247.6770069364266 -- 1333.548971937286 -- 1435.0947352857188 -- 1337.1664672688503 -- 1437.144371772577 -- 1302.7913014678081 -- 1307.632504230174 -- 1309.5871622491095 -- 1266.526669149459 -- 1272.732883034788 -- 1296.3690978040268 -- 1329.533891023991 -- 1326.228479986729 -- 1301.9123560943262 -- 1421.3512334297438 -- 1439.20500090357 -- 1351.300025201145 -- 1300.7487280774033 -- 1301.0949021714603 -- 1249.6260828582479 -- 1290.3051705043117 -- 1218.9580480859865 -- 1217.774861444723 -- 1220.6057058985919 -- 1229.5991499110628 -- 1255.8568926277048 -- 1278.880859128181 -- 1313.1506412655979 -- 1292.2089532675927 -- 1448.6674541356665 -- 1344.3931364939044 -- 1403.0601194604767 -- 1283.7609863452158 -- 1281.9670509584396 -- 1260.033940538987 -- 1278.3436405976106 -- 1288.3292460878947 -- 1329.644928498838 -- 1460.223099290189 -- 1318.458034593797 -- 1323.0822935662122 -- 1313.6000840795514 -- 1270.4444481785035 -- 1365.6528746505262 -- 1212.1098462660325 -- 1202.8845333085003 -- 1289.9666943303553 -- 1195.545325815687 -- 1205.6218043296753 -- 1206.151478572416 -- 1222.6071582431207 -- 1238.958704419423 -- 1257.7262626708416 -- 1373.476812193811 -- 1287.5691076772239 -- 1280.5202264609404 -- 1274.6741649389894 -- 1361.2021323142965 -- 1427.2957084618042 -- 1284.1411657461797 -- 1289.727919974269 -- 1294.8542354445772 -- 1314.0190898054482 -- 1332.5577709605373 -- 1416.076645678573 -- 1352.4327265467873 -- 1239.1280502081038 -- 1217.685588538051 -- 1212.757174976592 -- 1192.5086590224892 -- 1191.8177168182076 -- 1190.812479871741 -- 1191.8783656598682 -- 1199.804006652207 -- 1262.173565775863 -- 1241.5263194716204 -- 1274.538288523058 -- 1274.5452931192667 -- 1280.7075764244896 -- 1251.149919646504 -- 1245.871723614701 -- 1241.3473803128577 -- 1336.123731087311 -- 1284.6172543690411 -- 1370.1906259065004 -- 1265.8499566562637 -- 1318.5428197337123 -- 1353.5192136905227 -- 1286.4311772120504 -- 1406.67498200345 -- 1247.56958195875 -- 1301.2061590217222 -- 1201.4115357151995 -- 1205.1808267919575 -- 1207.6800326945831 -- 1215.0501746013565 -- 1220.8043915723438 -- 1241.7339120545319 -- 1257.624265848071 -- 1403.8631424534988 -- 1292.2128949772743 -- 1394.7052298308647 -- 1295.5508268514366 -- 1268.2545125519773 -- 1293.4425006055349 -- 1263.8056748966178 -- 1263.175773900421 -- 1273.7634600330198 -- 1276.7541297573198 -- 1352.3344530538043 -- 1305.9245940134924 -- 1317.8369054517736 -- 1289.0475760957522 -- 1338.784997245171 -- 1268.4246915865633 -- 1332.071320344655 -- 1356.2035107501179 -- 1235.1645124962658 -- 1237.1138221878364 -- 1234.3027687741012 -- 1236.4562983031863 -- 1362.0992158101296 -- 1293.293145728255 -- 1433.1485719226312 -- 1423.371103001947 -- 1311.3980083380561 -- 1296.0525607314692 -- 1279.4728077493064 -- 1376.7651108485563 -- 1270.328049741827 -- 1263.173707388053 -- 1259.4509815570818 -- 1380.0800988869378 -- 1271.855412425254 -- 1277.3521416621816 -- 1308.513503190385 -- 1312.1030142754353 -- 1302.5933984298529 -- 1365.088717724372 -- 1281.3950675436702 -- 1236.4048799220723 -- 1232.4603462023313 -- 1215.379074238321 -- 1218.8002280289663 -- 1259.4964512593444 -- 1242.7778605816284 -- 1262.721244033007 -- 1300.6023176012138 -- 1288.328129052933 -- 1262.3898134307094 -- 1280.6802997282089 -- 1390.4496244785 -- 1350.8986187277317 -- 1261.793599183028 -- 1257.9811703377532 -- 1254.787544391443 -- 1378.7501164639477 -- 1282.6305017483585 -- 1289.1686823594239 -- 1292.6192437815398 -- 1372.4758896653204 -- 1292.4828319206006 -- 1277.8847059340846 -- 1238.7056924482956 -- 1240.728595453353 -- 1224.5275587087176 -- 1220.0112195257361 -- 1223.432361975841 -- 1232.3746954997168 -- 1355.7868115651033 -- 1269.7804345090592 -- 1399.4192157382658 -- 1350.4573452315249 -- 1457.1433091263632 -- 1375.056152996363 -- 1296.1894345739213 -- 1281.7573579573077 -- 1261.1009040894303 -- 1255.2473764412844 -- 1278.9206260446392 -- 1274.7923599878795 -- 1280.856258913933 -- 1298.3352064282494 -- 1352.7468681640732 -- 1314.912137784986 -- 1308.4500701648096 -- 1390.7369851184742 -- 1245.2035892020456 -- 1226.378422381558 -- 1271.976832685851 -- 1211.780328151261 -- 1212.8260428713027 -- 1220.6835830049429 -- 1219.6166856945692 -- 1243.2044149091487 -- 1396.309269686778 -- 1330.5517567696604 -- 1268.774886561663 -- 1379.6989743630495 -- 1263.3595033727083 -- 1381.1734595114376 -- 1388.6603034029272 -- 1262.90334633716 -- 1374.807673304684 -- 1276.923760616181 -- 1404.0817828670324 -- 1304.2298237668624 -- 1322.5830126389508 -- 1307.4667439078464 -- 1383.066664132997 -- 1330.559762720549 -- 1219.7925273771737 -- 1202.6537186889832 -- 1196.0856290618406 -- 1190.411791225111 -- 1193.0491532495398 -- 1195.7260270273384 -- 1314.4352456869763 -- 1225.8602922677244 -- 1244.3441238552273 -- 1386.4476085340023 -- 1268.5211138298866 -- 1272.3689789288323 -- 1382.743043024851 -- 1383.3478743735823 -- 1291.093070751185 -- 1275.6803444078498 -- 1277.0933253248916 -- 1288.947593542713 -- 1349.582590981207 -- 1426.7186386506303 -- 1300.0410182679839 -- 1416.3592042304613 -- 1433.4028678941406 -- 1232.4516686749378 -- 1213.4043970205166 -- 1188.9112611105909 -- 1181.589877097162 -- 1207.3584528129932 -- 1179.2421106637876 -- 1173.127269672098 -- 1170.6017188332044 -- 1184.93183708635 -- 1216.536988806457 -- 1222.814588638173 -- 1339.5340592348143 -- 1254.550353768437 -- 1250.919266756671 -- 1247.7052453481551 -- 1243.4140187030262 -- 1239.090981378204 -- 1244.1792226750351 -- 1244.205434319516 -- 1257.8661317687836 -- 1262.870818313306 -- 1387.5004009636837 -- 1283.5634783201863 -- 1272.8254046138143 -- 1235.998216859733 -- 1240.5076798174914 -- 1221.0140788703739 -- 1226.5023085993362 -- 1275.8338685345898 -- 1231.2169098494512 -- 1237.8944220937683 -- 1351.4452388076134 -- 1407.0951353780795 -- 1312.8479252870613 -- 1422.0529297972014 -- 1304.560459840856 -- 1459.8344962513802 -- 1385.5674454313462 -- 1316.2505833560956 -- 1271.525433891121 -- 1313.983926920891 -- 1320.6442742125623 -- 1277.147165734776 -- 1415.3089244211164 -- 1326.1910657957585 -- 1397.2296587333994 -- 1569.2787384946253 -- 1410.795106663217 -- 1386.811101646639 -- 1257.4665803166267 -- 1228.357716815763 -- 1225.0379045022205 -- 1226.770936133127 -- 1234.4447072975618 -- 1239.70243461276 -- 1373.6788893015903 -- 1409.8713207652106 -- 1442.7734468939311 -- 1441.2023613052174 -- 1421.634836546481 -- 1446.7977485153845 -- 1442.0787948735758 -- 1407.4538104421335 -- 1271.1909024293439 -- 1342.3805760155028 -- 1275.320778100722 -- 1319.403367064577 -- 1288.7045295151975 -- 1276.8257047111779 -- 1285.5664713029553 -- 1383.5792402570478 -- 1435.4293960339044 -- 1294.49671055318 -- 1255.9660973745583 -- 1230.4793977693764 -- 1216.7955717545112 -- 1207.916582677799 -- 1206.27937122856 -- 1213.1039820026276 -- 1306.0576725306626 -- 1263.4289180845901 -- 1408.593024980568 -- 1415.5614604078983 -- 1300.3562444113836 -- 1330.7542441650348 -- 1295.1028956790965 -- 1257.629213513671 -- 1238.6959246011936 -- 1244.382713558481 -- 1279.6888636901485 -- 1292.068998251652 -- 1309.2767285347977 -- 1311.8041270828903 -- 1412.7784062116677 -- 1292.2998123495643 -- 1382.920388580253 -- 1254.8602865663513 -- 1224.0511819698083 -- 1207.7634291200968 -- 1200.785354605946 -- 1198.2176727252856 -- 1199.5502344548788 -- 1193.1782509033028 -- 1200.8185763440913 -- 1235.3357939750297 -- 1370.205692350809 -- 1375.9161719816932 -- 1258.6005279811905 -- 1244.7368769430877 -- 1321.2959907535703 -- 1249.0959250606024 -- 1254.8097424601096 -- 1276.1568340837039 -- 1315.309478127517 -- 1299.8451888962302 -- 1315.6508244468907 -- 1338.932026178037 -- 1318.1096918471076 -- 1291.075602199837 -- 1295.1660963482427 -- 1375.69197727596 -- 1336.9468226189347 -- 1190.816795263871 -- 1181.0987253598817 -- 1178.2348869429134 -- 1186.462740543639 -- 1192.5381270159708 -- 1183.7686367842832 -- 1205.8069647410898 -- 1242.0585647333016 -- 1291.0225369968489 -- 1270.0056802054874 -- 1239.2773808727677 -- 1248.358074439963 -- 1247.3072998080816 -- 1254.2179258512363 -- 1269.8856244115218 -- 1306.6295269772095 -- 1259.5569560294985 -- 1273.6242163952256 -- 1293.9791011580319 -- 1395.1169405094342 -- 1297.8781109995982 -- 1256.9490011476996 -- 1224.2068892681132 -- 1279.3220077959102 -- 1188.6442213622058 -- 1184.4229305313768 -- 1195.368273513201 -- 1167.3866738944566 -- 1179.8948504571135 -- 1241.6156441952626 -- 1302.3014629709253 -- 1229.989001649694 -- 1375.1115442587418 -- 1395.6842801533173 -- 1377.5029193305072 -- 1254.897279282251 -- 1272.943123762988 -- 1389.1116072151765 -- 1315.998611714552 -- 1387.3510685155159 -- 1416.5371840629005 -- 1411.3325986846428 -- 1440.9045503594014 -- 1449.2843699184482 -- 1472.0633960456703 -- 1428.225192226655 -- 1379.2995606886413 -- 1202.4527521894272 -- 1182.2279758386246 -- 1180.006987781283 -- 1182.56402513517 -- 1183.4301892673654 -- 1187.7874526502892 -- 1182.1937787015208 -- 1230.4827960629414 -- 1235.0460514684135 -- 1368.51448606557 -- 1267.9130166797388 -- 1367.8242318738862 -- 1269.0683011367073 -- 1253.5156132056109 -- 1315.3232477834028 -- 1392.1260976331637 -- 1260.3966667070865 -- 1276.733999437354 -- 1462.3657073162326 -- 1302.8192558895403 -- 1295.307024135027 -- 1427.6333958311282 -- 1327.7159336078657 -- 1262.2750544789137 -- 1358.61025216123 -- 1199.685565210138 -- 1200.2256732762785 -- 1204.593445605699 -- 1230.1815760002296 -- 1327.720820773125 -- 1325.8105216429212 -- 1249.5609629653031 -- 1289.0698411200062 -- 1276.492559824723 -- 1268.7035412930102 -- 1294.36951449229 -- 1253.2916785326472 -- 1253.2935327702876 -- 1243.488923525263 -- 1286.7821268693954 -- 1268.267429989442 -- 1401.6176568986803 -- 1411.0199393276093 -- 1363.0596253121307 -- 1443.815554069403 -- 1352.5047266776016 -- 1388.9940129930578 -- 1270.9426911219055 -- 1239.4710598051113 -- 1231.74237251183 -- 1216.738587219879 -- 1251.5555529861851 -- 1226.3312826698161 -- 1236.7080635562013 -- 1268.3572237544788 -- 1359.4451825698789 -- 1430.8609330888378 -- 1308.97665406294 -- 1424.7881397211506 -- 1414.268249084725 -- 1309.5716782647096 -- 1305.8120593111432 -- 1279.7582505810662 -- 1393.5967600492716 -- 1296.1981830352195 -- 1274.659222922258 -- 1298.9299596970636 -- 1309.4752092908043 -- 1336.1180898414073 -- 1322.8208839840147 -- 1392.6636799185535 -- 1413.7120026250461 -- 1245.2907107340013 -- 1233.8326305740509 -- 1230.3945025196767 -- 1218.5165228432602 -- 1221.922798649896 -- 1228.8996770537956 -- 1249.69391500393 -- 1411.4304261836835 -- 1379.0174437811322 -- 1422.2599021969809 -- 1414.156536849795 -- 1305.5514823957321 -- 1415.3466168973457 -- 1285.7337859354009 -- 1291.1937329477819 -- 1360.3611243533928 -- 1407.027839016703 -- 1278.5868821962224 -- 1278.7153167542156 -- 1300.504346424427 -- 1290.8425187042928 -- 1405.6834545760166 -- 1391.0177120861676 -- 1393.387969004491 -- 1271.5801127576033 -- 1228.91310952239 -- 1217.1437846599374 -- 1202.5303730760934 -- 1198.8783081493473 -- 1204.5570123950488 -- 1323.3385626858772 -- 1275.6812042280487 -- 1284.4428458366576 -- 1407.417441069559 -- 1385.9312801805681 -- 1391.1388861681703 -- 1301.4220397686845 -- 1374.637360587158 -- 1261.3732787137667 -- 1265.265764880426 -- 1247.9550031656033 -- 1280.895759266138 -- 1262.5623420546883 -- 1361.1847961643425 -- 1315.4867340859769 -- 1442.2932144143126 -- 1356.4578227413042 -- 1405.1738285067881 -- 1252.4841577786372 -- 1226.655799132812 -- 1221.2945153924122 -- 1213.4184532351098 -- 1220.5265694201773 -- 1232.0984437445393 -- 1256.409012598686 -- 1399.7960996522402 -- 1334.740099805486 -- 1327.7451638398709 -- 1427.6117458229892 -- 1370.2232299109282 -- 1424.589147188417 -- 1421.1815320094952 -- 1359.06550031399 -- 1296.0753479175467 -- 1303.875243116385 -- 1309.380342712592 -- 1308.1312339347344 -- 1331.4848418557933 -- 1464.6362850737405 -- 1367.882616090845 -- 1388.9692601855957 -- 1497.4212909394591 -- 1307.31250149829 -- 1293.4619283420002 -- 1259.5464521182334 -- 1259.6419624436833 -- 1260.155115346975 -- 1265.2061593645976 -- 1280.670020425775 -- 1296.2748305128696 -- 1358.9020742859695 -- 1381.1902181081891 -- 1385.7901527418512 -- 1370.7379607530966 -- 1368.0101873313804 -- 1359.0998653658503 -- 1394.5767123943426 -- 1510.1514363196059 -- 1346.770728400756 -- 1346.8165689545658 -- 1380.4741095613092 -- 1532.8739173952997 -- 1396.9518720374879 -- 1480.0871102232095 -- 1315.355121597174 -- 1422.1388638224184 -- 1385.9336027236502 -- 1335.5902080299463 -- 1234.592970763878 -- 1227.1565474176816 -- 1236.807799982031 -- 1243.962589428386 -- 1255.7522020757117 -- 1268.8624368627675 -- 1352.4508434729298 -- 1306.4525802988899 -- 1306.3638419367237 -- 1357.111366224369 -- 1326.4380897798635 -- 1307.5910025490548 -- 1336.591602524466 -- 1417.765427468253 -- 1288.6138662039123 -- 1297.695193966706 -- 1296.9855412911643 -- 1321.5806475370998 -- 1418.8174105656296 -- 1443.4577277136802 -- 1322.0412062674384 -- 1297.7475170825016 -- 1275.8352669046558 -- 1301.1716729813427 -- 1268.5871969962495 -- 1262.2340997216422 -- 1280.5349401175797 -- 1306.274723851666 -- 1320.9086751554728 -- 1461.2939735571026 -- 1497.610050904811 -- 1481.1200794160034 -- 1490.9182514812476 -- 1360.967140725873 -- 1334.900912463887 -- 1300.6509158940512 -- 1288.8517866770603 -- 1294.2379611364056 -- 1417.2299660759356 -- 1298.5273302364365 -- 1306.3256669349996 -- 1306.6328714240199 -- 1397.5225219760748 -- 1373.0068794416416 -- 1357.8649582278651 -- 1385.8077509772638 -- 1376.5725599326768 -- 1256.583185772424 -- 1236.844784451371 -- 1234.5928175666468 -- 1236.146743640411 -- 1246.2165586871688 -- 1384.9576912786047 -- 1422.7371238896485 -- 1432.3388963981258 -- 1332.1286512717443 -- 1341.6595524596125 -- 1457.2604857381752 -- 1321.8579638624724 -- 1303.6930823171779 -- 1411.0048804962591 -- 1268.1342803000518 -- 1358.457115429352 -- 1429.5157798573669 -- 1415.0411917198323 -- 1308.6788712123432 -- 1344.2800903863997 -- 1509.0516051903187 -- 1373.3410086772608 -- 1313.7031944563198 -- 1261.4408481470184 -- 1353.9108991457827 -- 1219.6198429494623 -- 1214.2620387962647 -- 1203.605752541651 -- 1211.3846654496747 -- 1218.9162005380385 -- 1376.802961535149 -- 1396.963695613318 -- 1284.136290112735 -- 1289.1505304806362 -- 1282.180714991765 -- 1353.2675920102708 -- 1260.4439392490729 -- 1364.8826574641269 -- 1284.8986983625002 -- 1240.1886176230641 -- 1241.4018815808643 -- 1258.5377231240545 -- 1277.6565451211004 -- 1277.3083949173117 -- 1293.4343159500022 -- 1415.47685175265 -- 1415.7498547813907 -- 1390.7287402148177 -- 1226.2945277857016 -- 1225.1671447701158 -- 1219.51055348835 -- 1214.2447359761577 -- 1221.6658911476516 -- 1233.6620739439363 -- 1261.186092357244 -- 1281.3701920210246 -- 1305.857355546922 -- 1436.5498383452764 -- 1452.6085291793624 -- 1469.0539511093473 -- 1311.5334343330971 -- 1305.0171139893157 -- 1305.3915039540173 -- 1431.2392032915182 -- 1384.6973394149181 -- 1334.6511762444186 -- 1453.3928706957404 -- 1386.3663015964758 -- 1483.4828815251947 -- 1336.5536733539811 -- 1309.190199903896 -- 1361.9972999390598 -- 1266.7553215177074 -- 1247.2031465303608 -- 1258.6987654153008 -- 1295.964876333078 -- 1276.9004273779951 -- 1305.425665585301 -- 1453.9255158896908 -- 1487.8402162427274 -- 1341.8884315448277 -- 1487.7704481794826 -- 1502.366293482419 -- 1482.8920895860138 -- 1395.7392909490463 -- 1287.3908417990456 -- 1290.0984083805452 -- 1394.6654805746725 -- 1276.180365500097 -- 1343.2367618353587 -- 1311.9107986450874 -- 1353.5004050738432 -- 1362.8128960662693 -- 1369.6102000900087 -- 1450.419887756043 -- 1356.7495927327448 -- 1407.2343128907612 -- 1255.0502126965616 -- 1247.6179262790681 -- 1257.5400700899245 -- 1263.3245661731914 -- 1271.2669284194176 -- 1298.2316640417262 -- 1467.6271325212824 -- 1481.8599006902127 -- 1364.7941366568084 -- 1349.3576536435194 -- 1456.477784814683 -- 1386.8952741313308 -- 1439.566389787139 -- 1473.7562553189275 -- 1320.1260374356698 -- 1342.5980416085945 -- 1381.7853288290598 -- 1364.1417609067503 -- 1365.7555599883674 -- 1356.9120442539313 -- 1335.6411392049235 -- 1286.027268661341 -- 1251.7668069474855 -- 1229.4223578117276 -- 1213.627387528617 -- 1192.1228833800512 -- 1185.1236730651792 -- 1187.722221119118 -- 1195.3723747178271 -- 1201.8555844795223 -- 1312.8748662438886 -- 1393.0997793104987 -- 1298.4296295129761 -- 1276.2365043920752 -- 1280.0348936562884 -- 1286.3541766368885 -- 1311.3216889987207 -- 1264.0128871903792 -- 1350.1538421998787 -- 1396.851043554881 -- 1322.0114605907227 -- 1362.5177005817463 -- 1298.90530961196 -- 1296.156514956776 -- 1247.5965092946337 -- 1355.0706925824584 -- 1341.132798791611 -- 1221.1675767411245 -- 1219.1632890187009 -- 1222.3748531552642 -- 1225.544810829611 -- 1236.7645240282964 -- 1341.356805487251 -- 1385.3772111987523 -- 1421.8710111122523 -- 1465.3788907623677 -- 1369.7108933375634 -- 1299.597354845546 -- 1288.8749041023289 -- 1388.5067281314232 -- 1276.284326526433 -- 1254.9880401022795 -- 1265.417795876893 -- 1277.4685123545612 -- 1285.2241921120003 -- 1284.224268164128 -- 1291.146051325974 -- 1377.1122070442052 -- 1325.1936160017137 -- 1286.2474657303667 -- 1347.9230359651542 -- 1232.0398939348743 -- 1230.1713586073774 -- 1221.077458615109 -- 1225.4022250109388 -- 1309.7985779379171 -- 1242.9271727464065 -- 1274.8257308191482 -- 1393.5240688977783 -- 1437.059839823536 -- 1424.8225255091743 -- 1301.894152672948 -- 1282.903142964745 -- 1305.7358102318478 -- 1398.281319549106 -- 1260.9670171851851 -- 1277.0250292864503 -- 1280.740045089358 -- 1292.2818924278267 -- 1343.0808946421205 -- 1310.002040330331 -- 1396.922205558665 -- 1326.1147706365607 -- 1304.7422625023 -- 1378.4431788523802 -- 1351.8011632290393 -- 1222.504131641143 -- 1212.2635656632876 -- 1206.5524316933197 -- 1212.9563333719457 -- 1223.170510776617 -- 1359.3490613742902 -- 1324.4362505284705 -- 1291.3135690239499 -- 1284.1205344223142 -- 1301.7045507880089 -- 1350.7370923088258 -- 1381.089953383339 -- 1254.0912015247218 -- 1267.6087061513279 -- 1394.355092860247 -- 1264.6582498159412 -- 1269.0341659720686 -- 1265.6570303686317 -- 1294.2436343961458 -- 1295.9374730851719 -- 1377.3396580464575 -- 1279.6310924122972 -- 1351.9577148723251 -- 1358.986910197692 -- 1216.3353579785871 -- 1230.4953658933327 -- 1203.8436630997408 -- 1214.9046786343308 -- 1219.609103590277 -- 1244.1038552504874 -- 1273.5884361314238 -- 1380.4986160637116 -- 1258.2959605529725 -- 1296.9089355111444 -- 1383.2438818972932 -- 1246.726220865913 -- 1362.7815303027915 -- 1232.7522239525879 -- 1322.1684081559015 -- 1374.8301019309044 -- 1250.8632435994862 -- 1270.8896097791007 -- 1331.6972436227168 -- 1392.2597106220073 -- 1327.6710240536836 -- 1287.2672633328482 -- 1257.7403937845604 -- 1230.0486558941122 -- 1219.9058355130796 -- 1222.3182295808763 -- 1241.9296806379052 -- 1245.9407146244932 -- 1375.3343397588103 -- 1317.5316654675048 -- 1310.1156370035035 -- 1343.8289000166837 -- 1425.5529681399344 -- 1423.2217318354317 -- 1405.5600257519432 -- 1429.4691351473964 -- 1398.9949841878586 -- 1282.1993559421608 -- 1270.4573796438137 -- 1318.757383171154 -- 1371.692077884033 -- 1293.0243430210728 -- 1397.735687907666 -- 1440.2026766049946 -- 1320.9119101143635 -- 1267.2474780509758 -- 1244.1137350398988 -- 1348.907013144132 -- 1214.3300258238744 -- 1202.6840709888786 -- 1200.9496031446101 -- 1198.1972498316288 -- 1207.3970817931859 -- 1343.7291016309061 -- 1264.6938491885755 -- 1425.1075071042428 -- 1405.7686328589864 -- 1363.4409523857826 -- 1266.605050862437 -- 1304.6817311770747 -- 1259.5391837614634 -- 1330.8105778339568 -- 1295.704226246076 -- 1331.2215209064088 -- 1352.6795929257876 -- 1368.4330207809667 -- 1466.4273515756754 -- 1346.3513567137923 -- 1309.207065444883 -- 1256.3141074319958 -- 1241.1471897746262 -- 1230.3411727851103 -- 1195.8478335655038 -- 1192.9675350447567 -- 1193.7847958133775 -- 1183.740421285218 -- 1189.6352588102511 -- 1213.7219990924527 -- 1230.758168069133 -- 1317.8294649682803 -- 1386.7326539958794 -- 1256.0633945656314 -- 1302.1221491217207 -- 1288.8653586837488 -- 1270.0335177655502 -- 1277.1466630427128 -- 1376.3885727830925 -- 1253.664680202115 -- 1399.0262578852432 -- 1326.4310733475195 -- 1325.8469321555601 -- 1311.179814345693 -- 1340.640855910714 -- 1233.3068568403517 -- 1271.5950653109696 -- 1222.5335185341967 -- 1209.6949332322497 -- 1220.5400777970715 -- 1253.0140463459643 -- 1236.5575855535653 -- 1388.05871950282 -- 1409.4342554245902 -- 1311.5604172040455 -- 1294.030581880939 -- 1299.7263846768583 -- 1352.4583877382443 -- 1323.682659934584 -- 1267.1571342741008 -- 1262.8602573532878 -- 1266.676038834774 -- 1262.5766701271816 -- 1276.423276804982 -- 1306.0544201875741 -- 1379.9417899493842 -- 1349.5443813741038 -- 1329.2675911652518 -- 1435.5186859749733 -- 1269.7409911582988 -- 1241.6180644842514 -- 1226.3644711912088 -- 1225.103273512557 -- 1210.6972996523893 -- 1205.3637768133515 -- 1214.5028600215132 -- 1230.287598954287 -- 1391.5450563827983 -- 1425.8010354999738 -- 1334.360755573521 -- 1271.870424110658 -- 1271.48191545686 -- 1397.2337698285103 -- 1341.4207743876036 -- 1281.208780399888 -- 1245.5534125132197 -- 1330.031148239759 -- 1298.4015066527347 -- 1285.2976085094308 -- 1290.7721704039188 -- 1320.731338289043 -- 1306.1458877681987 -- 1313.3425037378754 -- 1287.8588522014138 -- 1227.308111136162 -- 1216.5824658627891 -- 1199.9251261311977 -- 1195.4500311166744 -- 1182.4779487920525 -- 1182.8825656158645 -- 1256.546507652736 -- 1207.7858820409815 -- 1344.9961334287218 -- 1259.6329360917682 -- 1341.4019124732117 -- 1320.1294506709132 -- 1375.94453406194 -- 1370.081150192873 -- 1250.9014121135986 -- 1245.4778128336213 -- 1241.936615326478 -- 1376.1851646055181 -- 1393.1454963257563 -- 1265.0883432627234 -- 1310.2288351500322 -- 1307.0578668347325 -- 1416.5937897912647 -- 1286.603394150227 -- 1231.0036948884851 -- 1228.4561679337173 -- 1202.141837453785 -- 1209.3652015844991 -- 1218.5452709245658 -- 1297.4589305652062 -- 1224.3668985137376 -- 1379.6103543163415 -- 1277.6781303641126 -- 1323.1588370137579 -- 1282.224092485909 -- 1401.26688330527 -- 1287.3232996136367 -- 1275.242409886568 -- 1252.4209162624702 -- 1255.3664060449207 -- 1247.267396250134 -- 1248.6127484268252 -- 1256.1582177885878 -- 1303.0100259553944 -- 1312.0773335408762 -- 1290.507144456347 -- 1334.915835458116 -- 1380.6897231453386 -- 1363.4818769211927 -- 1234.003919491561 -- 1320.7121579043262 -- 1227.060846680998 -- 1237.540934055645 -- 1264.292075058761 -- 1272.8198792352648 -- 1365.6274393030806 -- 1359.7211111656366 -- 1458.2842259955157 -- 1337.0595201035835 -- 1445.1610548458477 -- 1410.826817404087 -- 1465.7128483719994 -- 1355.221966333796 -- 1311.3418397602197 -- 1304.1281254094472 -- 1305.1520254040975 -- 1321.5714444755517 -- 1350.1624026112372 -- 1376.816567568355 -- 1421.5216859701036 -- 1387.2027757000997 -- 1451.3748916009038 -- 1423.6860264785291 -- 1442.8451593888137 -- 1281.5984247535005 -- 1283.5462910636113 -- 1280.3658730056598 -- 1284.1406240151875 -- 1295.921636239224 -- 1321.7632488027748 -- 1329.4945672551864 -- 1350.790718071113 -- 1376.240719437127 -- 1448.4354526669158 -- 1434.8175068698017 -- 1415.1754842262317 -- 1372.199004524879 -- 1341.2347839169404 -- 1459.8645995158845 -- 1358.4975504868326 -- 1406.1720832379283 -- 1381.8238503000925 -- 1425.1898192536985 -- 1383.601192326425 -- 1366.6881594588929 -- 1341.1153892927305 -- 1305.4422280541585 -- 1393.9238538432078 -- 1241.4245393632082 -- 1243.3743411765838 -- 1235.6783995911228 -- 1225.1611915588862 -- 1234.3595837561845 -- 1340.383054933762 -- 1307.7799552315676 -- 1300.3797485990883 -- 1344.992873623249 -- 1341.4551805694987 -- 1320.3831797289408 -- 1292.3838834145663 -- 1293.9888560820966 -- 1280.5548250588336 -- 1384.4811504103836 -- 1289.327501039815 -- 1449.575348291974 -- 1386.6719737766055 -- 1439.113703265766 -- 1344.2150379680759 -- 1441.0932952367737 -- 1422.1436321491385 -- 1261.6066428282631 -- 1271.156086486122 -- 1261.9442318978502 -- 1272.47341243795 -- 1302.4358773609722 -- 1406.9544981489473 -- 1333.7655343354645 -- 1425.5420526235823 -- 1370.1351402883874 -- 1502.8538557133008 -- 1465.6661443084236 -- 1495.8386686461226 -- 1517.1077095623161 -- 1322.8860252522009 -- 1314.849910408884 -- 1296.1501934332841 -- 1294.0201840511156 -- 1298.2275747446372 -- 1320.345584030776 -- 1309.5056510545694 -- 1341.9946211460858 -- 1351.284912766025 -- 1376.574318325345 -- 1353.2393047901853 -- 1307.2940982039752 diff --git a/examples/11_hybrid_energy_plant/wind_pv_battery.yaml b/examples/11_hybrid_energy_plant/wind_pv_battery.yaml deleted file mode 100644 index 0ae487f2b..000000000 --- a/examples/11_hybrid_energy_plant/wind_pv_battery.yaml +++ /dev/null @@ -1,5 +0,0 @@ -name: H2Integrate_config -system_summary: Wind, PV, Battery hybrid plant (native H2I models) -driver_config: driver_config.yaml -technology_config: tech_config.yaml -plant_config: plant_config.yaml diff --git a/examples/13_dispatch_for_electrolyzer/run_dispatch_for_electrolyzer.py b/examples/13_dispatch_for_electrolyzer/run_dispatch_for_electrolyzer.py index 5c2ad309f..73a89e4e6 100644 --- a/examples/13_dispatch_for_electrolyzer/run_dispatch_for_electrolyzer.py +++ b/examples/13_dispatch_for_electrolyzer/run_dispatch_for_electrolyzer.py @@ -1,15 +1,9 @@ from pathlib import Path -from h2integrate import ( - EXAMPLE_DIR, - H2IntegrateModel, - load_tech_yaml, - load_plant_yaml, - load_driver_yaml, -) +from h2integrate import H2IntegrateModel, load_tech_yaml, load_plant_yaml, load_driver_yaml -this_dir = EXAMPLE_DIR / "13_dispatch_for_electrolyzer" +this_dir = Path(__file__).parent tech_config = load_tech_yaml(this_dir / "tech_config.yaml") plant_config = load_plant_yaml(this_dir / "plant_config.yaml") driver_config = load_driver_yaml(this_dir / "driver_config.yaml") diff --git a/examples/14_wind_hydrogen_dispatch/hydrogen_dispatch.py b/examples/14_wind_hydrogen_dispatch/run_hydrogen_dispatch.py similarity index 100% rename from examples/14_wind_hydrogen_dispatch/hydrogen_dispatch.py rename to examples/14_wind_hydrogen_dispatch/run_hydrogen_dispatch.py diff --git a/examples/18_pyomo_heuristic_dispatch/run_pyomo_heuristic_dispatch.py b/examples/18_pyomo_heuristic_dispatch/run_pyomo_heuristic_dispatch.py index 2c617f700..7a24a1028 100644 --- a/examples/18_pyomo_heuristic_dispatch/run_pyomo_heuristic_dispatch.py +++ b/examples/18_pyomo_heuristic_dispatch/run_pyomo_heuristic_dispatch.py @@ -41,13 +41,17 @@ ) ax[1].plot( range(start_hour, end_hour), - model.prob.get_val("battery.unused_electricity_out", units="MW")[start_hour:end_hour], + model.prob.get_val("electrical_load_demand.unused_electricity_out", units="MW")[ + start_hour:end_hour + ], linestyle=":", label="Unused Electricity (MW)", ) ax[1].plot( range(start_hour, end_hour), - model.prob.get_val("battery.unmet_electricity_demand_out", units="MW")[start_hour:end_hour], + model.prob.get_val("electrical_load_demand.unmet_electricity_demand_out", units="MW")[ + start_hour:end_hour + ], linestyle=":", label="Unmet Electrical Demand (MW)", ) @@ -59,7 +63,10 @@ ) ax[1].plot( range(start_hour, end_hour), - model.prob.get_val("battery.battery_electricity", units="MW")[start_hour:end_hour], + ( + model.prob.get_val("battery.storage_electricity_discharge", units="MW") + + model.prob.get_val("battery.storage_electricity_charge", units="MW") + )[start_hour:end_hour], linestyle="-.", label="Battery Electricity Out (MW)", ) diff --git a/examples/19_simple_dispatch/run_wind_battery.py b/examples/19_simple_dispatch/run_wind_battery.py index 79328e4be..c5855748e 100644 --- a/examples/19_simple_dispatch/run_wind_battery.py +++ b/examples/19_simple_dispatch/run_wind_battery.py @@ -16,9 +16,9 @@ start_hour = 0 end_hour = 200 -total_time_steps = model.prob.get_val("battery.electricity_soc", units="unitless").size +total_time_steps = model.prob.get_val("battery.SOC", units="percent").size demand_profile = [ - model.technology_config["technologies"]["battery"]["model_inputs"]["control_parameters"][ + model.technology_config["technologies"]["battery"]["model_inputs"]["shared_parameters"][ "demand_profile" ] * 1e-3 @@ -26,7 +26,7 @@ ax[0].plot( range(start_hour, end_hour), - model.prob.get_val("battery.electricity_soc", units="percent")[start_hour:end_hour], + model.prob.get_val("battery.SOC", units="percent")[start_hour:end_hour], label="SOC", linewidth=2, ) @@ -42,14 +42,18 @@ ) ax[1].plot( range(start_hour, end_hour), - model.prob.get_val("battery.unused_electricity_out", units="MW")[start_hour:end_hour], + model.prob.get_val("electrical_load_demand.unused_electricity_out", units="MW")[ + start_hour:end_hour + ], linestyle=":", label="Unused Electricity commodity (MW)", linewidth=2, ) ax[1].plot( range(start_hour, end_hour), - model.prob.get_val("battery.unmet_electricity_demand_out", units="MW")[start_hour:end_hour], + model.prob.get_val("electrical_load_demand.unmet_electricity_demand_out", units="MW")[ + start_hour:end_hour + ], linestyle=":", label="Electricity Unmet Demand (MW)", linewidth=2, diff --git a/examples/21_iron_examples/iron_dri_nrri/plant_config.yaml b/examples/21_iron_examples/iron_dri/plant_config_nrri.yaml similarity index 100% rename from examples/21_iron_examples/iron_dri_nrri/plant_config.yaml rename to examples/21_iron_examples/iron_dri/plant_config_nrri.yaml diff --git a/examples/21_iron_examples/iron_dri/run_iron.py b/examples/21_iron_examples/iron_dri/run_iron.py index 467401c94..663e5ef6a 100644 --- a/examples/21_iron_examples/iron_dri/run_iron.py +++ b/examples/21_iron_examples/iron_dri/run_iron.py @@ -2,7 +2,12 @@ In this example, iron ore pellets are produced at different iron mine locations in NE Minnesota. These mines send processed ore pellets to a separate iron DRI plant located outside Chicago. -Four different cases are generated for four different iron mine setups in the `test_inputs.csv`. +The example is run with two iron mine models: the simple mine model (`single_site_iron.yaml`) and +the NRRI mine model (`single_site_iron_nrri.yaml`), which also models the natural gas and diesel +consumed at the mine. + +For each mine model, four different cases are generated for four different iron mine setups in +`test_inputs.csv` (simple mine model) or `test_inputs_nrri.csv` (NRRI mine model). The first two cases generate standard blast furnace grade pellets at two different mine locations. The second two cases generate DR grade pellets at the same location, with the output capacity varied to show how the capacity of the mine does not affect the levelized cost of iron_ore pellets @@ -13,21 +18,12 @@ from pathlib import Path -import numpy as np import pandas as pd from h2integrate import H2IntegrateModel from h2integrate.tools.run_cases import modify_tech_config, load_tech_config_cases -# Create H2Integrate model -model = H2IntegrateModel("single_site_iron.yaml") - -# Load cases -case_file = Path("test_inputs.csv") -cases = load_tech_config_cases(case_file) - -# Modify and run the model for different cases casenames = [ "Standard Iron - Hibbing", "Standard Iron - Northshore", @@ -35,92 +31,6 @@ "DR Grade Iron - Northshore (adjusted)", ] -# Create empty lists to store the costs -lcois_ore = [] -capexes_ore = [] -fopexes_ore = [] -vopexes_ore = [] -production_ore = [] -lcois_iron = [] -capexes_iron = [] -fopexes_iron = [] -vopexes_iron = [] -production_ore = [] -lcois_steel = [] -capexes_steel = [] -fopexes_steel = [] -vopexes_steel = [] - -model.run() -model.post_process() - -for casename in casenames: - model = modify_tech_config(model, cases[casename]) - model.run() - lcois_ore.append( - float(model.model.get_val("finance_subgroup_iron_ore.price_iron_ore", units="USD/kg")[0]) - ) - capexes_ore.append( - float(model.model.get_val("finance_subgroup_iron_ore.total_capex_adjusted", units="USD")[0]) - ) - fopexes_ore.append( - float( - model.model.get_val("finance_subgroup_iron_ore.total_opex_adjusted", units="USD/year")[ - 0 - ] - ) - ) - vopexes_ore.append( - float( - model.model.get_val( - "finance_subgroup_iron_ore.total_varopex_adjusted", units="USD/year" - )[0] - ) - ) - lcois_iron.append( - float( - model.model.get_val("finance_subgroup_sponge_iron.price_sponge_iron", units="USD/kg")[0] - ) - ) - capexes_iron.append( - float( - model.model.get_val("finance_subgroup_sponge_iron.total_capex_adjusted", units="USD")[0] - ) - ) - fopexes_iron.append( - float( - model.model.get_val( - "finance_subgroup_sponge_iron.total_opex_adjusted", units="USD/year" - )[0] - ) - ) - vopexes_iron.append( - float( - model.model.get_val( - "finance_subgroup_sponge_iron.total_varopex_adjusted", units="USD/year" - )[0] - ) - ) - lcois_steel.append( - float(model.model.get_val("finance_subgroup_steel.price_steel", units="USD/kg")[0]) - ) - capexes_steel.append( - float(model.model.get_val("finance_subgroup_steel.total_capex_adjusted", units="USD")[0]) - ) - fopexes_steel.append( - float( - model.model.get_val("finance_subgroup_steel.total_opex_adjusted", units="USD/year")[0] - ) - ) - vopexes_steel.append( - float( - model.model.get_val("finance_subgroup_steel.total_varopex_adjusted", units="USD/year")[ - 0 - ] - ) - ) - -# Compare the Capex, Fixed Opex, and Variable Opex across the 4 cases columns = pd.MultiIndex.from_tuples( [ ("Levelized Cost", "[USD/kg]"), @@ -129,28 +39,51 @@ ("Variable Opex", "[USD/year]"), ] ) -print() -df_ore = pd.DataFrame( - np.transpose(np.vstack([lcois_ore, capexes_ore, fopexes_ore, vopexes_ore])), - index=casenames, - columns=columns, -) -print(df_ore) -print() -df_iron = pd.DataFrame( - np.transpose(np.vstack([lcois_iron, capexes_iron, fopexes_iron, vopexes_iron])), - index=casenames, - columns=columns, -) -print(df_iron) +def get_costs(model: H2IntegrateModel, product: str) -> list[float]: + """Return the levelized cost, CapEx, fixed OpEx, and variable OpEx of one product.""" + subgroup = f"finance_subgroup_{product}" + return [ + float(model.model.get_val(f"{subgroup}.price_{product}", units="USD/kg")[0]), + float(model.model.get_val(f"{subgroup}.total_capex_adjusted", units="USD")[0]), + float(model.model.get_val(f"{subgroup}.total_opex_adjusted", units="USD/year")[0]), + float(model.model.get_val(f"{subgroup}.total_varopex_adjusted", units="USD/year")[0]), + ] -print() -df_steel = pd.DataFrame( - np.transpose(np.vstack([lcois_steel, capexes_steel, fopexes_steel, vopexes_steel])), - index=casenames, - columns=columns, -) -df_steel = df_steel.iloc[2:] -print(df_steel) + +def run_mine_cases(config_file: str, case_file: str) -> dict[str, pd.DataFrame]: + """Run each mine case and tabulate the costs of the iron ore, sponge iron, and steel.""" + model = H2IntegrateModel(config_file) + model.run() + model.post_process() + + cases = load_tech_config_cases(Path(case_file)) + costs = {"iron_ore": [], "sponge_iron": [], "steel": []} + for casename in casenames: + model = modify_tech_config(model, cases[casename]) + model.run() + for product, product_costs in costs.items(): + product_costs.append(get_costs(model, product)) + + return { + product: pd.DataFrame(product_costs, index=casenames, columns=columns) + for product, product_costs in costs.items() + } + + +mine_models = { + "Simple iron mine model": ("single_site_iron.yaml", "test_inputs.csv"), + "NRRI iron mine model": ("single_site_iron_nrri.yaml", "test_inputs_nrri.csv"), +} + +# Compare the Capex, Fixed Opex, and Variable Opex across the 4 cases for each mine model +for mine_model, (config_file, case_file) in mine_models.items(): + tables = run_mine_cases(config_file, case_file) + print(f"\n{mine_model}\n") + print(tables["iron_ore"]) + print() + print(tables["sponge_iron"]) + print() + # Steel results are only reported for the DR grade pellet cases + print(tables["steel"].iloc[2:]) diff --git a/examples/21_iron_examples/iron_dri/single_site_iron_nrri.yaml b/examples/21_iron_examples/iron_dri/single_site_iron_nrri.yaml new file mode 100644 index 000000000..8002a6638 --- /dev/null +++ b/examples/21_iron_examples/iron_dri/single_site_iron_nrri.yaml @@ -0,0 +1,6 @@ +name: H2Integrate_config +system_summary: An iron mine modeled with the NRRI mine model producing ore pellets and a separately-located iron plant performing + direct reduction. +driver_config: driver_config.yaml +technology_config: tech_config_nrri.yaml +plant_config: plant_config_nrri.yaml diff --git a/examples/21_iron_examples/iron_dri_nrri/tech_config.yaml b/examples/21_iron_examples/iron_dri/tech_config_nrri.yaml similarity index 99% rename from examples/21_iron_examples/iron_dri_nrri/tech_config.yaml rename to examples/21_iron_examples/iron_dri/tech_config_nrri.yaml index bdcf8f9ec..4896d505b 100644 --- a/examples/21_iron_examples/iron_dri_nrri/tech_config.yaml +++ b/examples/21_iron_examples/iron_dri/tech_config_nrri.yaml @@ -1,5 +1,5 @@ name: technology_config -description: This hybrid plant produces iron +description: This hybrid plant produces iron using the NRRI iron mine model technologies: mine_electricity_feedstock: # electricity feedstock for iron ore performance_model: diff --git a/examples/21_iron_examples/iron_dri_nrri/test_inputs.csv b/examples/21_iron_examples/iron_dri/test_inputs_nrri.csv similarity index 100% rename from examples/21_iron_examples/iron_dri_nrri/test_inputs.csv rename to examples/21_iron_examples/iron_dri/test_inputs_nrri.csv diff --git a/examples/21_iron_examples/iron_dri_nrri/driver_config.yaml b/examples/21_iron_examples/iron_dri_nrri/driver_config.yaml deleted file mode 100644 index 1f8d10de9..000000000 --- a/examples/21_iron_examples/iron_dri_nrri/driver_config.yaml +++ /dev/null @@ -1,4 +0,0 @@ -name: driver_config -description: Simply setting up an outputs folder, nothing fancy -general: - folder_output: outputs diff --git a/examples/21_iron_examples/iron_dri_nrri/run_iron.py b/examples/21_iron_examples/iron_dri_nrri/run_iron.py deleted file mode 100644 index 467401c94..000000000 --- a/examples/21_iron_examples/iron_dri_nrri/run_iron.py +++ /dev/null @@ -1,156 +0,0 @@ -"""Example of an iron mine sending processed ore pellets to a separate DRI iron plant and EAF - -In this example, iron ore pellets are produced at different iron mine locations in NE Minnesota. -These mines send processed ore pellets to a separate iron DRI plant located outside Chicago. -Four different cases are generated for four different iron mine setups in the `test_inputs.csv`. -The first two cases generate standard blast furnace grade pellets at two different mine locations. -The second two cases generate DR grade pellets at the same location, with the output capacity -varied to show how the capacity of the mine does not affect the levelized cost of iron_ore pellets -(LCOI), nor does it affect the final cost of the sponge_iron produced by DRI (LCOS) or the cost -of the steel produced by the EAF (LCOS). - -""" - -from pathlib import Path - -import numpy as np -import pandas as pd - -from h2integrate import H2IntegrateModel -from h2integrate.tools.run_cases import modify_tech_config, load_tech_config_cases - - -# Create H2Integrate model -model = H2IntegrateModel("single_site_iron.yaml") - -# Load cases -case_file = Path("test_inputs.csv") -cases = load_tech_config_cases(case_file) - -# Modify and run the model for different cases -casenames = [ - "Standard Iron - Hibbing", - "Standard Iron - Northshore", - "DR Grade Iron - Northshore", - "DR Grade Iron - Northshore (adjusted)", -] - -# Create empty lists to store the costs -lcois_ore = [] -capexes_ore = [] -fopexes_ore = [] -vopexes_ore = [] -production_ore = [] -lcois_iron = [] -capexes_iron = [] -fopexes_iron = [] -vopexes_iron = [] -production_ore = [] -lcois_steel = [] -capexes_steel = [] -fopexes_steel = [] -vopexes_steel = [] - -model.run() -model.post_process() - -for casename in casenames: - model = modify_tech_config(model, cases[casename]) - model.run() - lcois_ore.append( - float(model.model.get_val("finance_subgroup_iron_ore.price_iron_ore", units="USD/kg")[0]) - ) - capexes_ore.append( - float(model.model.get_val("finance_subgroup_iron_ore.total_capex_adjusted", units="USD")[0]) - ) - fopexes_ore.append( - float( - model.model.get_val("finance_subgroup_iron_ore.total_opex_adjusted", units="USD/year")[ - 0 - ] - ) - ) - vopexes_ore.append( - float( - model.model.get_val( - "finance_subgroup_iron_ore.total_varopex_adjusted", units="USD/year" - )[0] - ) - ) - lcois_iron.append( - float( - model.model.get_val("finance_subgroup_sponge_iron.price_sponge_iron", units="USD/kg")[0] - ) - ) - capexes_iron.append( - float( - model.model.get_val("finance_subgroup_sponge_iron.total_capex_adjusted", units="USD")[0] - ) - ) - fopexes_iron.append( - float( - model.model.get_val( - "finance_subgroup_sponge_iron.total_opex_adjusted", units="USD/year" - )[0] - ) - ) - vopexes_iron.append( - float( - model.model.get_val( - "finance_subgroup_sponge_iron.total_varopex_adjusted", units="USD/year" - )[0] - ) - ) - lcois_steel.append( - float(model.model.get_val("finance_subgroup_steel.price_steel", units="USD/kg")[0]) - ) - capexes_steel.append( - float(model.model.get_val("finance_subgroup_steel.total_capex_adjusted", units="USD")[0]) - ) - fopexes_steel.append( - float( - model.model.get_val("finance_subgroup_steel.total_opex_adjusted", units="USD/year")[0] - ) - ) - vopexes_steel.append( - float( - model.model.get_val("finance_subgroup_steel.total_varopex_adjusted", units="USD/year")[ - 0 - ] - ) - ) - -# Compare the Capex, Fixed Opex, and Variable Opex across the 4 cases -columns = pd.MultiIndex.from_tuples( - [ - ("Levelized Cost", "[USD/kg]"), - ("Capex", "[USD]"), - ("Fixed Opex", "[USD/year]"), - ("Variable Opex", "[USD/year]"), - ] -) -print() - -df_ore = pd.DataFrame( - np.transpose(np.vstack([lcois_ore, capexes_ore, fopexes_ore, vopexes_ore])), - index=casenames, - columns=columns, -) -print(df_ore) - -print() -df_iron = pd.DataFrame( - np.transpose(np.vstack([lcois_iron, capexes_iron, fopexes_iron, vopexes_iron])), - index=casenames, - columns=columns, -) -print(df_iron) - -print() -df_steel = pd.DataFrame( - np.transpose(np.vstack([lcois_steel, capexes_steel, fopexes_steel, vopexes_steel])), - index=casenames, - columns=columns, -) -df_steel = df_steel.iloc[2:] -print(df_steel) diff --git a/examples/21_iron_examples/iron_dri_nrri/single_site_iron.yaml b/examples/21_iron_examples/iron_dri_nrri/single_site_iron.yaml deleted file mode 100644 index 2403fa15c..000000000 --- a/examples/21_iron_examples/iron_dri_nrri/single_site_iron.yaml +++ /dev/null @@ -1,5 +0,0 @@ -name: H2Integrate_config -system_summary: An iron mine producing ore pellets and a separately-located iron plant performing direct reduction. -driver_config: driver_config.yaml -technology_config: tech_config.yaml -plant_config: plant_config.yaml diff --git a/examples/21_iron_examples/iron_mapping/run_iron.py b/examples/21_iron_examples/iron_mapping/run_iron.py index 33fd1bf88..1594fb819 100644 --- a/examples/21_iron_examples/iron_mapping/run_iron.py +++ b/examples/21_iron_examples/iron_mapping/run_iron.py @@ -8,7 +8,9 @@ """ -from h2integrate import ROOT_DIR, EXAMPLE_DIR, H2IntegrateModel +from pathlib import Path + +from h2integrate import ROOT_DIR, H2IntegrateModel from h2integrate.postprocess.mapping import ( plot_geospatial_point_heat_map, plot_straight_line_shipping_routes, @@ -28,8 +30,8 @@ model.post_process(summarize_sql=True) # Define filepaths -ex_dir = EXAMPLE_DIR / "21_iron_examples/iron_mapping" -ex_out_dir = EXAMPLE_DIR / "21_iron_examples/iron_mapping/ex_out" +ex_dir = Path(__file__).parent +ex_out_dir = ex_dir / "ex_out" save_plot_filepath = ex_out_dir / "example_iron_map.png" save_plot_filepath.unlink(missing_ok=True) case_results_filepath = ex_out_dir / "cases.csv" diff --git a/examples/23_solar_wind_ng_demand/run_solar_wind_ng_demand.py b/examples/23_solar_wind_ng_demand/run_solar_wind_ng_demand.py index c74d75357..19560a4d4 100644 --- a/examples/23_solar_wind_ng_demand/run_solar_wind_ng_demand.py +++ b/examples/23_solar_wind_ng_demand/run_solar_wind_ng_demand.py @@ -34,7 +34,7 @@ total_time_steps = model.prob.get_val("wind.electricity_out", units="MW").size demand_profile = [ model.technology_config["technologies"]["electrical_load_demand"]["model_inputs"][ - "control_parameters" + "performance_parameters" ]["demand_profile"] * 1e-3 ] * total_time_steps diff --git a/examples/24_solar_battery_grid/README.md b/examples/24_solar_battery_grid/README.md index cce607d62..9c3e32ee7 100644 --- a/examples/24_solar_battery_grid/README.md +++ b/examples/24_solar_battery_grid/README.md @@ -1,4 +1,4 @@ -# Example 23: Solar Battery Grid System +# Example 24: Solar Battery Grid System ## Overview @@ -89,3 +89,11 @@ technology_interconnections: [ - `grid_buy` only sets `electricity_buy_price` (the other is None by default) - `grid_sell` only sets `electricity_sell_price` (the other is None by default) - Both prices are optional - set only what you need + +## Running the Example + +- `run_solar_battery_grid.py` runs the plant over all 8760 timesteps at once. +- `run_time_marching.py` runs the same plant twice: once over all timesteps at once, and once with + `n_timesteps_window: 24` so that all technologies advance together one 24-hour window at a time. + It then compares the inputs and outputs of the two runs, which match. See the + [time-marching simulation guide](../../docs/user_guide/time_marching_simulation.md) for details. diff --git a/examples/24_solar_battery_grid/plant_config.yaml b/examples/24_solar_battery_grid/plant_config.yaml index f354d7f92..2bbd991d4 100644 --- a/examples/24_solar_battery_grid/plant_config.yaml +++ b/examples/24_solar_battery_grid/plant_config.yaml @@ -39,6 +39,7 @@ plant: plant_life: 30 simulation: n_timesteps: 8760 + # Add `n_timesteps_window: 24` to simulate 24 timesteps at a time (see run_time_marching.py) finance_parameters: finance_groups: profast_model: diff --git a/examples/24_solar_battery_grid/run_time_marching.py b/examples/24_solar_battery_grid/run_time_marching.py new file mode 100644 index 000000000..784bcceb0 --- /dev/null +++ b/examples/24_solar_battery_grid/run_time_marching.py @@ -0,0 +1,97 @@ +"""Compare a standard simulation of example 24 with a time-marching simulation of the same plant. + +The standard simulation runs each subsystem over all timesteps at once. The time-marching +simulation sets `n_timesteps_window` so that all subsystems advance together one 24-timestep +window at a time. The results of the two simulations match. +""" + +import time +import pprint +from copy import deepcopy +from pathlib import Path + +import yaml + +from h2integrate import H2IntegrateModel, load_tech_yaml, load_plant_yaml, load_driver_yaml +from h2integrate.core.dict_utils import percent_diff_dicts, find_nonzero_percent_diffs + + +# Run one or both simulation paradigms by changing the flags in this dict +run_dict = { + "run_sequential": True, + "run_time_marching": True, +} + +# Load config files into dict +config_root = Path(__file__).parent +config_path = config_root / "solar_battery_grid.yaml" + +# Load top level config +with Path(config_path).open() as f: + config = yaml.safe_load(f) + +config["driver_config"] = load_driver_yaml(config_root / config["driver_config"]) +config["technology_config"] = load_tech_yaml(config_root / config["technology_config"]) +config["plant_config"] = load_plant_yaml(config_root / config["plant_config"]) + +# Run simulation sequentially one subsystem at a time +if run_dict.get("run_sequential", False): + config_seq = deepcopy(config) + config_seq["plant_config"]["plant"]["simulation"].pop("n_timesteps_window", None) + + # Create an H2I model for standard year-long simulation + h2i_seq = H2IntegrateModel(config_seq) + + t0 = time.time() + # Run the model + h2i_seq.run() + t1 = time.time() + + print(f"Sequential took {t1 - t0:.3f} seconds") + + # Post-process the results + h2i_seq.post_process(print_results=False) + + inputs_seq = dict(h2i_seq.model.list_inputs(out_stream=None)) + outputs_seq = dict(h2i_seq.model.list_outputs(out_stream=None)) + + lcoe_seq = outputs_seq[ + "plant.finance_subgroup_renewables.electricity_finance_profast_model.LCOE" + ] + + +# Run the time-marching simulation, advancing all subsystems one window of timesteps at a time +if run_dict.get("run_time_marching", False): + config_tm = deepcopy(config) + + config_tm["plant_config"]["plant"]["simulation"]["n_timesteps"] = 8760 + config_tm["plant_config"]["plant"]["simulation"]["n_timesteps_window"] = 24 + + # Create an H2I model for time-marching simulation + h2i_tm = H2IntegrateModel(config_tm) + + t0 = time.time() + # Run the model + h2i_tm.run() + t1 = time.time() + + print(f"Time-marching took {t1 - t0:.3f} seconds") + + # Post-process the results + h2i_tm.post_process(print_results=False) + + inputs_tm = dict(h2i_tm.model.list_inputs(out_stream=None)) + outputs_tm = dict(h2i_tm.model.list_outputs(out_stream=None)) + + lcoe_tm = outputs_tm["plant.finance_subgroup_renewables.electricity_finance_profast_model.LCOE"] + +# Compare results +if run_dict.get("run_sequential", False) and run_dict.get("run_time_marching", False): + inputs_pd_dict = percent_diff_dicts(inputs_seq, inputs_tm) + outputs_pd_dict = percent_diff_dicts(outputs_seq, outputs_tm) + + in_abs, in_rel = find_nonzero_percent_diffs(inputs_pd_dict, dict(inputs_seq)) + out_abs, out_rel = find_nonzero_percent_diffs(outputs_pd_dict, dict(outputs_seq)) + + pprint.pprint(in_abs) + pprint.pprint(out_abs) diff --git a/examples/30_pyomo_optimized_dispatch/run_pyomo_optimized_dispatch.py b/examples/30_pyomo_optimized_dispatch/run_pyomo_optimized_dispatch.py index 51de68b12..5830287ea 100644 --- a/examples/30_pyomo_optimized_dispatch/run_pyomo_optimized_dispatch.py +++ b/examples/30_pyomo_optimized_dispatch/run_pyomo_optimized_dispatch.py @@ -43,13 +43,17 @@ ) ax[1].plot( range(start_hour, end_hour), - model.prob.get_val("battery.unused_electricity_out", units="MW")[start_hour:end_hour], + model.prob.get_val("electrical_load_demand.unused_electricity_out", units="MW")[ + start_hour:end_hour + ], linestyle=":", label="Unused Electricity (MW)", ) ax[1].plot( range(start_hour, end_hour), - model.prob.get_val("battery.unmet_electricity_demand_out", units="MW")[start_hour:end_hour], + model.prob.get_val("electrical_load_demand.unmet_electricity_demand_out", units="MW")[ + start_hour:end_hour + ], linestyle=":", label="Unmet Electrical Demand (MW)", ) @@ -61,7 +65,10 @@ ) ax[1].plot( range(start_hour, end_hour), - model.prob.get_val("battery.battery_electricity", units="MW")[start_hour:end_hour], + ( + model.prob.get_val("battery.storage_electricity_discharge", units="MW") + + model.prob.get_val("battery.storage_electricity_charge", units="MW") + )[start_hour:end_hour], linestyle="-.", label="Battery Electricity Out (MW)", ) diff --git a/examples/35_system_level_control/battery_with_controller/driver_config.yaml b/examples/35_system_level_control/battery_with_controller/driver_config.yaml deleted file mode 100644 index 74ae9ca19..000000000 --- a/examples/35_system_level_control/battery_with_controller/driver_config.yaml +++ /dev/null @@ -1,4 +0,0 @@ -name: driver_config -description: This analysis runs a battery with a simple controller -general: - folder_output: outputs diff --git a/examples/35_system_level_control/battery_with_controller/plant_config.yaml b/examples/35_system_level_control/battery_with_controller/plant_config.yaml deleted file mode 100644 index 09f1b57f4..000000000 --- a/examples/35_system_level_control/battery_with_controller/plant_config.yaml +++ /dev/null @@ -1,110 +0,0 @@ -name: plant_config -description: This plant is located in Texas, USA. -sites: - site: - latitude: 30.6617 - longitude: -101.7096 - resources: - wind_resource: - resource_model: WTKNLRDeveloperAPIWindResource - resource_parameters: - resource_year: 2013 -# array of arrays containing left-to-right technology -# interconnections; can support bidirectional connections -# with the reverse definition. -# this will naturally grow as we mature the interconnected tech -technology_interconnections: - # connect NG feedstock to NG plant - - [ng_feedstock, natural_gas_plant, natural_gas, pipe] - # wind output available for battery charging (electricity_in) - - [wind, battery, electricity, cable] - # wind to combined output - - [wind, combiner, electricity, cable] - # battery output to combined output - - [battery, combiner, electricity, cable] - # NG to combined output - - [natural_gas_plant, combiner, electricity, cable] - # combined supply to demand - - [combiner, electrical_load_demand, electricity, cable] -site_to_tech_connections: - # connect the wind resource to the wind technology - - [site.wind_resource, wind, wind_resource_data] -plant: - plant_life: 30 - simulation: - n_timesteps: 8760 - dt: 3600 -system_level_control: - control_strategy: DemandFollowingControl - demand_component: electrical_load_demand - solver_options: - solver_name: gauss_seidel - max_iter: 20 - convergence_tolerance: 1.0e-6 -finance_parameters: - finance_groups: - profast_lco: - finance_model: ProFastLCO - model_inputs: - params: - analysis_start_year: 2032 - installation_time: 36 # months - inflation_rate: 0.0 # 0 for nominal analysis - discount_rate: 0.09 # nominal return based on 2024 ATB baseline workbook for land-based wind - debt_equity_ratio: 2.62 # 2024 ATB uses 72.4% debt for land-based wind - property_tax_and_insurance: 0.03 # percent of CAPEX estimated based on https://www.nlr.gov/docs/fy25osti/91775.pdf https://www.house.mn.gov/hrd/issinfo/clsrates.aspx - total_income_tax_rate: 0.257 # 0.257 tax rate in 2024 atb baseline workbook, value here is based on federal (21%) and state in MN (9.8) - capital_gains_tax_rate: 0.15 # H2FAST default - sales_tax_rate: 0.07375 # total state and local sales tax in St. Louis County https://taxmaps.state.mn.us/salestax/ - debt_interest_rate: 0.07 # based on 2024 ATB nominal interest rate for land-based wind - debt_type: Revolving debt # can be "Revolving debt" or "One time loan". Revolving debt is H2FAST default and leads to much lower LCOH - loan_period_if_used: 0 # H2FAST default, not used for revolving debt - cash_onhand_months: 1 # H2FAST default - admin_expense: 0.00 # percent of sales H2FAST default - capital_items: - depr_type: MACRS # can be "MACRS" or "Straight line" - depr_period: 5 # 5 years - for clean energy facilities as specified by the IRS MACRS schedule https://www.irs.gov/publications/p946#en_US_2020_publink1000107507 - refurb: [0.] - profast_npv: - finance_model: ProFastNPV - model_inputs: - commodity_sell_price: 0.05167052 - commodity_sell_price_units: USD/(kW*h) - params: - analysis_start_year: 2032 - installation_time: 36 # months - inflation_rate: 0.0 # 0 for nominal analysis - discount_rate: 0.09 # nominal return based on 2024 ATB baseline workbook for land-based wind - debt_equity_ratio: 2.62 # 2024 ATB uses 72.4% debt for land-based wind - property_tax_and_insurance: 0.03 # percent of CAPEX estimated based on https://www.nlr.gov/docs/fy25osti/91775.pdf https://www.house.mn.gov/hrd/issinfo/clsrates.aspx - total_income_tax_rate: 0.257 # 0.257 tax rate in 2024 atb baseline workbook, value here is based on federal (21%) and state in MN (9.8) - capital_gains_tax_rate: 0.15 # H2FAST default - sales_tax_rate: 0.07375 # total state and local sales tax in St. Louis County https://taxmaps.state.mn.us/salestax/ - debt_interest_rate: 0.07 # based on 2024 ATB nominal interest rate for land-based wind - debt_type: Revolving debt # can be "Revolving debt" or "One time loan". Revolving debt is H2FAST default and leads to much lower LCOH - loan_period_if_used: 0 # H2FAST default, not used for revolving debt - cash_onhand_months: 1 # H2FAST default - admin_expense: 0.00 # percent of sales H2FAST default - capital_items: - depr_type: MACRS # can be "MACRS" or "Straight line" - depr_period: 5 # 5 years - for clean energy facilities as specified by the IRS MACRS schedule https://www.irs.gov/publications/p946#en_US_2020_publink1000107507 - refurb: [0.] - finance_subgroups: - renewables: - commodity: electricity - commodity_stream: wind - finance_groups: [profast_lco, profast_npv] - technologies: [wind] - natural_gas: - commodity: electricity - commodity_stream: natural_gas_plant - finance_groups: [profast_lco] - technologies: [natural_gas_plant, ng_feedstock] - electricity: - commodity: electricity - commodity_stream: combiner - finance_groups: [profast_lco] - technologies: [wind, battery, natural_gas_plant, ng_feedstock] - cost_adjustment_parameters: - cost_year_adjustment_inflation: 0.025 # used to adjust modeled costs to target_dollar_year - target_dollar_year: 2022 diff --git a/examples/35_system_level_control/battery_with_controller/run_wind_ng_demand.py b/examples/35_system_level_control/battery_with_controller/run_wind_ng_demand.py deleted file mode 100644 index e3eedabbd..000000000 --- a/examples/35_system_level_control/battery_with_controller/run_wind_ng_demand.py +++ /dev/null @@ -1,67 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt - -from h2integrate.core.h2integrate_model import H2IntegrateModel - - -################################## -# Create an H2I model with a fixed electricity load demand -h2i = H2IntegrateModel("wind_ng_demand.yaml") - -# Run the model -h2i.run() - -# Post-process the results -h2i.post_process() - -# Plot the first 168 hours (1 week) -n_hours = 168 -hours = np.arange(n_hours) - -wind_out = h2i.prob.get_val("plant.wind.electricity_out")[:n_hours] -ng_out = h2i.prob.get_val("plant.natural_gas_plant.electricity_out", units="kW")[:n_hours] -batt_discharge = h2i.prob.get_val("plant.battery.storage_electricity_discharge")[:n_hours] -batt_soc = h2i.prob.get_val("plant.battery.SOC")[:n_hours] -demand = h2i.prob.get_val("plant.electrical_load_demand.electricity_demand")[:n_hours] -curtailed = h2i.prob.get_val("plant.electrical_load_demand.unused_electricity_out")[:n_hours] - -fig, axes = plt.subplots(3, 1, figsize=(12, 10), sharex=True) - -# Stacked bar chart: wind + battery discharge + NG = total supply -axes[0].bar(hours, wind_out, width=1.0, color="tab:blue", label="Wind", align="edge") -axes[0].bar( - hours, - batt_discharge, - width=1.0, - bottom=wind_out, - color="tab:purple", - label="Battery Discharge", - align="edge", -) -axes[0].bar( - hours, - ng_out, - width=1.0, - bottom=wind_out + batt_discharge, - color="tab:orange", - label="Natural Gas", - align="edge", -) -axes[0].plot(hours, demand, color="black", linewidth=1.5, linestyle="--", label="Demand") -axes[0].set_ylabel("Power (kW)") -axes[0].set_title("System-Level Control: First 168 Hours") -axes[0].legend() - -axes[1].plot(hours, batt_soc, color="tab:cyan") -axes[1].set_ylabel("Battery SOC (%)") - -axes[2].bar(hours, curtailed, width=1.0, color="tab:red", align="edge") -axes[2].set_ylabel("Curtailed (kW)") -axes[2].set_xlabel("Hour") - -for ax in axes: - ax.grid(True, alpha=0.3) - -plt.tight_layout() -plt.savefig("slc_results.png", dpi=150) -plt.show() diff --git a/examples/35_system_level_control/no_battery/driver_config.yaml b/examples/35_system_level_control/demand_following/driver_config.yaml similarity index 100% rename from examples/35_system_level_control/no_battery/driver_config.yaml rename to examples/35_system_level_control/demand_following/driver_config.yaml diff --git a/examples/35_system_level_control/no_battery/plant_config.yaml b/examples/35_system_level_control/demand_following/plant_config.yaml similarity index 100% rename from examples/35_system_level_control/no_battery/plant_config.yaml rename to examples/35_system_level_control/demand_following/plant_config.yaml diff --git a/examples/35_system_level_control/yes_battery/plant_config.yaml b/examples/35_system_level_control/demand_following/plant_config_battery.yaml similarity index 100% rename from examples/35_system_level_control/yes_battery/plant_config.yaml rename to examples/35_system_level_control/demand_following/plant_config_battery.yaml diff --git a/examples/35_system_level_control/demand_following/run_demand_following.py b/examples/35_system_level_control/demand_following/run_demand_following.py new file mode 100644 index 000000000..a3e34c25b --- /dev/null +++ b/examples/35_system_level_control/demand_following/run_demand_following.py @@ -0,0 +1,88 @@ +"""Demand-following system-level control with and without battery storage. + +The same wind and natural gas plant meets a fixed electrical load demand in three configurations: + +- `wind_ng_demand.yaml`: wind and natural gas only. +- `wind_ng_battery_demand.yaml`: adds a battery that the system-level controller dispatches. +- `wind_ng_battery_controller_demand.yaml`: the battery also has its own open-loop controller. + +Each case is run, post-processed, and plotted for the first week of the year. +""" + +import numpy as np +import matplotlib.pyplot as plt + +from h2integrate.core.h2integrate_model import H2IntegrateModel + + +cases = { + "no_battery": "wind_ng_demand.yaml", + "battery": "wind_ng_battery_demand.yaml", + "battery_with_controller": "wind_ng_battery_controller_demand.yaml", +} + +# Plot the first 168 hours (1 week) +n_hours = 168 +hours = np.arange(n_hours) + +for case, config_file in cases.items(): + h2i = H2IntegrateModel(config_file) + h2i.run() + h2i.post_process() + + has_battery = "battery" in h2i.technology_config["technologies"] + + wind_out = h2i.prob.get_val("plant.wind.electricity_out")[:n_hours] + ng_out = h2i.prob.get_val("plant.natural_gas_plant.electricity_out", units="kW")[:n_hours] + demand = h2i.prob.get_val("plant.electrical_load_demand.electricity_demand")[:n_hours] + curtailed = h2i.prob.get_val("plant.electrical_load_demand.unused_electricity_out")[:n_hours] + if has_battery: + batt_discharge = h2i.prob.get_val("plant.battery.storage_electricity_discharge")[:n_hours] + batt_soc = h2i.prob.get_val("plant.battery.SOC")[:n_hours] + else: + batt_discharge = np.zeros(n_hours) + + n_rows = 3 if has_battery else 2 + fig, axes = plt.subplots(n_rows, 1, figsize=(12, 2 + 8 * n_rows / 3), sharex=True) + + # Stacked bar chart: wind + battery discharge + NG = total supply + axes[0].bar(hours, wind_out, width=1.0, color="tab:blue", label="Wind", align="edge") + if has_battery: + axes[0].bar( + hours, + batt_discharge, + width=1.0, + bottom=wind_out, + color="tab:purple", + label="Battery Discharge", + align="edge", + ) + axes[0].bar( + hours, + ng_out, + width=1.0, + bottom=wind_out + batt_discharge, + color="tab:orange", + label="Natural Gas", + align="edge", + ) + axes[0].plot(hours, demand, color="black", linewidth=1.5, linestyle="--", label="Demand") + axes[0].set_ylabel("Power (kW)") + axes[0].set_title(f"System-Level Control ({case.replace('_', ' ')}): First 168 Hours") + axes[0].legend(loc="upper right") + + if has_battery: + axes[1].plot(hours, batt_soc, color="tab:cyan") + axes[1].set_ylabel("Battery SOC (%)") + + axes[-1].bar(hours, curtailed, width=1.0, color="tab:red", align="edge") + axes[-1].set_ylabel("Curtailed (kW)") + axes[-1].set_xlabel("Hour") + + for ax in axes: + ax.grid(True, alpha=0.3) + + fig.tight_layout() + fig.savefig(f"slc_results_{case}.png", dpi=150) + +plt.show() diff --git a/examples/35_system_level_control/no_battery/tech_config.yaml b/examples/35_system_level_control/demand_following/tech_config.yaml similarity index 100% rename from examples/35_system_level_control/no_battery/tech_config.yaml rename to examples/35_system_level_control/demand_following/tech_config.yaml diff --git a/examples/35_system_level_control/yes_battery/tech_config.yaml b/examples/35_system_level_control/demand_following/tech_config_battery.yaml similarity index 100% rename from examples/35_system_level_control/yes_battery/tech_config.yaml rename to examples/35_system_level_control/demand_following/tech_config_battery.yaml diff --git a/examples/35_system_level_control/battery_with_controller/tech_config.yaml b/examples/35_system_level_control/demand_following/tech_config_battery_controller.yaml similarity index 100% rename from examples/35_system_level_control/battery_with_controller/tech_config.yaml rename to examples/35_system_level_control/demand_following/tech_config_battery_controller.yaml diff --git a/examples/35_system_level_control/demand_following/wind_ng_battery_controller_demand.yaml b/examples/35_system_level_control/demand_following/wind_ng_battery_controller_demand.yaml new file mode 100644 index 000000000..b5586d87c --- /dev/null +++ b/examples/35_system_level_control/demand_following/wind_ng_battery_controller_demand.yaml @@ -0,0 +1,6 @@ +name: H2Integrate_config +system_summary: This example uses wind, a battery with its own open-loop controller, and a natural gas power plant to meet + a fixed electrical load demand. +driver_config: driver_config.yaml +technology_config: tech_config_battery_controller.yaml +plant_config: plant_config_battery.yaml diff --git a/examples/35_system_level_control/demand_following/wind_ng_battery_demand.yaml b/examples/35_system_level_control/demand_following/wind_ng_battery_demand.yaml new file mode 100644 index 000000000..a28a6ae1e --- /dev/null +++ b/examples/35_system_level_control/demand_following/wind_ng_battery_demand.yaml @@ -0,0 +1,5 @@ +name: H2Integrate_config +system_summary: This example uses wind, a battery, and a natural gas power plant to meet a fixed electrical load demand. +driver_config: driver_config.yaml +technology_config: tech_config_battery.yaml +plant_config: plant_config_battery.yaml diff --git a/examples/35_system_level_control/battery_with_controller/wind_ng_demand.yaml b/examples/35_system_level_control/demand_following/wind_ng_demand.yaml similarity index 100% rename from examples/35_system_level_control/battery_with_controller/wind_ng_demand.yaml rename to examples/35_system_level_control/demand_following/wind_ng_demand.yaml diff --git a/examples/35_system_level_control/no_battery/run_wind_ng_demand.py b/examples/35_system_level_control/no_battery/run_wind_ng_demand.py deleted file mode 100644 index 38a7b16c4..000000000 --- a/examples/35_system_level_control/no_battery/run_wind_ng_demand.py +++ /dev/null @@ -1,53 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt - -from h2integrate.core.h2integrate_model import H2IntegrateModel - - -################################## -# Create an H2I model with a fixed electricity load demand -h2i = H2IntegrateModel("wind_ng_demand.yaml") - -# Run the model -h2i.run() - -# Post-process the results -h2i.post_process() - -# Plot the first 100 hours -n_hours = 100 -hours = np.arange(n_hours) - -demand = h2i.prob.get_val("plant.electrical_load_demand.electricity_demand")[:n_hours] -wind_out = h2i.prob.get_val("plant.wind.electricity_out")[:n_hours] -ng_out = h2i.prob.get_val("plant.natural_gas_plant.electricity_out", units="kW")[:n_hours] -curtailed = h2i.prob.get_val("plant.electrical_load_demand.unused_electricity_out")[:n_hours] - -fig, axes = plt.subplots(2, 1, figsize=(12, 8), sharex=True) - -# Stacked bar chart of supply per hour with demand overlay -axes[0].bar(hours, wind_out, width=1.0, color="tab:blue", label="Wind", align="edge") -axes[0].bar( - hours, - ng_out, - width=1.0, - bottom=wind_out, - color="tab:orange", - label="Natural Gas", - align="edge", -) -axes[0].plot(hours, demand, color="black", linewidth=1.5, linestyle="--", label="Demand") -axes[0].set_ylabel("Power (kW)") -axes[0].set_title("System-Level Control: First 100 Hours") -axes[0].legend(loc="upper right") - -axes[1].bar(hours, curtailed, width=1.0, color="tab:red", align="edge") -axes[1].set_ylabel("Curtailed (kW)") -axes[1].set_xlabel("Hour") - -for ax in axes: - ax.grid(True, alpha=0.3) - -plt.tight_layout() -plt.savefig("slc_results.png", dpi=150) -plt.show() diff --git a/examples/35_system_level_control/no_battery/wind_ng_demand.yaml b/examples/35_system_level_control/no_battery/wind_ng_demand.yaml deleted file mode 100644 index f2b5599a0..000000000 --- a/examples/35_system_level_control/no_battery/wind_ng_demand.yaml +++ /dev/null @@ -1,5 +0,0 @@ -name: H2Integrate_config -system_summary: This example uses wind, solar and a natural gas power plant to meet a fixed electrical load demand. -driver_config: driver_config.yaml -technology_config: tech_config.yaml -plant_config: plant_config.yaml diff --git a/examples/35_system_level_control/upstream_demand/run_multi_demand.py b/examples/35_system_level_control/upstream_demand/run_multi_demand.py index 12f430501..b964265a7 100644 --- a/examples/35_system_level_control/upstream_demand/run_multi_demand.py +++ b/examples/35_system_level_control/upstream_demand/run_multi_demand.py @@ -1,10 +1,10 @@ import os +from pathlib import Path -from h2integrate import EXAMPLE_DIR from h2integrate.core.h2integrate_model import H2IntegrateModel -os.chdir(EXAMPLE_DIR / "35_system_level_control" / "upstream_demand") +os.chdir(Path(__file__).parent) ################################## # Create an H2I model with a fixed electricity load demand diff --git a/examples/35_system_level_control/yes_battery/driver_config.yaml b/examples/35_system_level_control/yes_battery/driver_config.yaml deleted file mode 100644 index 5b6b7e05a..000000000 --- a/examples/35_system_level_control/yes_battery/driver_config.yaml +++ /dev/null @@ -1,4 +0,0 @@ -name: driver_config -description: This analysis runs a natural gas power plant -general: - folder_output: outputs diff --git a/examples/35_system_level_control/yes_battery/run_wind_ng_demand.py b/examples/35_system_level_control/yes_battery/run_wind_ng_demand.py deleted file mode 100644 index e3eedabbd..000000000 --- a/examples/35_system_level_control/yes_battery/run_wind_ng_demand.py +++ /dev/null @@ -1,67 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt - -from h2integrate.core.h2integrate_model import H2IntegrateModel - - -################################## -# Create an H2I model with a fixed electricity load demand -h2i = H2IntegrateModel("wind_ng_demand.yaml") - -# Run the model -h2i.run() - -# Post-process the results -h2i.post_process() - -# Plot the first 168 hours (1 week) -n_hours = 168 -hours = np.arange(n_hours) - -wind_out = h2i.prob.get_val("plant.wind.electricity_out")[:n_hours] -ng_out = h2i.prob.get_val("plant.natural_gas_plant.electricity_out", units="kW")[:n_hours] -batt_discharge = h2i.prob.get_val("plant.battery.storage_electricity_discharge")[:n_hours] -batt_soc = h2i.prob.get_val("plant.battery.SOC")[:n_hours] -demand = h2i.prob.get_val("plant.electrical_load_demand.electricity_demand")[:n_hours] -curtailed = h2i.prob.get_val("plant.electrical_load_demand.unused_electricity_out")[:n_hours] - -fig, axes = plt.subplots(3, 1, figsize=(12, 10), sharex=True) - -# Stacked bar chart: wind + battery discharge + NG = total supply -axes[0].bar(hours, wind_out, width=1.0, color="tab:blue", label="Wind", align="edge") -axes[0].bar( - hours, - batt_discharge, - width=1.0, - bottom=wind_out, - color="tab:purple", - label="Battery Discharge", - align="edge", -) -axes[0].bar( - hours, - ng_out, - width=1.0, - bottom=wind_out + batt_discharge, - color="tab:orange", - label="Natural Gas", - align="edge", -) -axes[0].plot(hours, demand, color="black", linewidth=1.5, linestyle="--", label="Demand") -axes[0].set_ylabel("Power (kW)") -axes[0].set_title("System-Level Control: First 168 Hours") -axes[0].legend() - -axes[1].plot(hours, batt_soc, color="tab:cyan") -axes[1].set_ylabel("Battery SOC (%)") - -axes[2].bar(hours, curtailed, width=1.0, color="tab:red", align="edge") -axes[2].set_ylabel("Curtailed (kW)") -axes[2].set_xlabel("Hour") - -for ax in axes: - ax.grid(True, alpha=0.3) - -plt.tight_layout() -plt.savefig("slc_results.png", dpi=150) -plt.show() diff --git a/examples/35_system_level_control/yes_battery/wind_ng_demand.yaml b/examples/35_system_level_control/yes_battery/wind_ng_demand.yaml deleted file mode 100644 index f2b5599a0..000000000 --- a/examples/35_system_level_control/yes_battery/wind_ng_demand.yaml +++ /dev/null @@ -1,5 +0,0 @@ -name: H2Integrate_config -system_summary: This example uses wind, solar and a natural gas power plant to meet a fixed electrical load demand. -driver_config: driver_config.yaml -technology_config: tech_config.yaml -plant_config: plant_config.yaml diff --git a/examples/35_system_level_control/yes_hydrogen/run_multi_commodity.py b/examples/35_system_level_control/yes_hydrogen/run_multi_commodity.py index d6662e37c..98a4f7b3b 100644 --- a/examples/35_system_level_control/yes_hydrogen/run_multi_commodity.py +++ b/examples/35_system_level_control/yes_hydrogen/run_multi_commodity.py @@ -1,10 +1,10 @@ import os +from pathlib import Path -from h2integrate import EXAMPLE_DIR from h2integrate.core.h2integrate_model import H2IntegrateModel -os.chdir(EXAMPLE_DIR / "35_system_level_control" / "yes_hydrogen") +os.chdir(Path(__file__).parent) ################################## # Create an H2I model with a fixed electricity load demand diff --git a/examples/37_paper_mill/run_paper_mill_mn.py b/examples/37_paper_mill/run_paper_mill_mn.py index b2fd81c0d..c0843bb9d 100644 --- a/examples/37_paper_mill/run_paper_mill_mn.py +++ b/examples/37_paper_mill/run_paper_mill_mn.py @@ -10,14 +10,15 @@ """ import os +from pathlib import Path -from h2integrate import EXAMPLE_DIR from h2integrate.core.h2integrate_model import H2IntegrateModel from h2integrate.postprocess.sql_to_csv import convert_sql_to_csv_summary from h2integrate.postprocess.sql_timeseries_to_csv import save_case_timeseries_as_csv -os.chdir(EXAMPLE_DIR / "37_paper_mill") +this_dir = Path(__file__).parent +os.chdir(this_dir) model = H2IntegrateModel("37_paper_mill_mn.yaml") @@ -25,7 +26,7 @@ model.run() model.post_process() -sql_fpath = EXAMPLE_DIR / "37_paper_mill" / "outputs" / "cases.sql" +sql_fpath = this_dir / "outputs" / "cases.sql" convert_sql_to_csv_summary(sql_fpath) save_case_timeseries_as_csv(sql_fpath) diff --git a/examples/test/test_all_examples.py b/examples/test/test_all_examples.py index 1ab4a2221..0c1d76ff9 100644 --- a/examples/test/test_all_examples.py +++ b/examples/test/test_all_examples.py @@ -8,14 +8,21 @@ import pytest import openmdao.api as om -from h2integrate import ROOT_DIR, H2IntegrateModel, load_yaml, load_plant_yaml +from h2integrate import ( + ROOT_DIR, + H2IntegrateModel, + load_yaml, + load_tech_yaml, + load_plant_yaml, + load_driver_yaml, +) ROOT = Path(__file__).parents[1] # docs fencepost start: DO NOT REMOVE -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize("example_folder,resource_example_folder", [("01_onshore_steel_mn", None)]) def test_steel_example(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -127,7 +134,7 @@ def test_steel_example(subtests, temp_copy_of_example): ) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize("example_folder,resource_example_folder", [("02_texas_ammonia", None)]) def test_simple_ammonia_example(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -260,7 +267,7 @@ def test_simple_ammonia_example(subtests, temp_copy_of_example): ) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize("example_folder,resource_example_folder", [("12_ammonia_synloop", None)]) def test_ammonia_synloop_example(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -383,7 +390,7 @@ def test_ammonia_synloop_example(subtests, temp_copy_of_example): ) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize("example_folder,resource_example_folder", [("03_methanol/smr", None)]) def test_smr_methanol_example(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -404,7 +411,7 @@ def test_smr_methanol_example(subtests, temp_copy_of_example): ) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("03_methanol/co2_hydrogenation", None)] ) @@ -454,7 +461,7 @@ def test_co2h_methanol_example(subtests, temp_copy_of_example): ) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("03_methanol/co2_hydrogenation_doc", None)] ) @@ -480,7 +487,7 @@ def test_doc_methanol_example(subtests, temp_copy_of_example): ) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize("example_folder,resource_example_folder", [("05_wind_h2_opt", None)]) def test_wind_h2_opt_example(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -580,7 +587,7 @@ def test_wind_h2_opt_example(subtests, temp_copy_of_example): ) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize("example_folder,resource_example_folder", [("06_custom_tech", None)]) def test_paper_example(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -601,7 +608,7 @@ def test_paper_example(subtests, temp_copy_of_example): ) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("09_co2/direct_ocean_capture", None)] ) @@ -638,7 +645,7 @@ def test_wind_wave_doc_example(subtests, temp_copy_of_example): ) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("17_splitter_wind_doc_h2", None)] ) @@ -795,7 +802,7 @@ def test_splitter_wind_doc_h2_example(subtests, temp_copy_of_example): ) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("07_run_of_river_plant", None)] ) @@ -823,55 +830,12 @@ def test_hydro_example(subtests, temp_copy_of_example): ) -@pytest.mark.integration -@pytest.mark.parametrize( - "example_folder,resource_example_folder", [("11_hybrid_energy_plant", None)] -) -def test_hybrid_energy_plant_example(subtests, temp_copy_of_example): - example_folder = temp_copy_of_example - - # Create a H2Integrate model - model = H2IntegrateModel(example_folder / "wind_pv_battery.yaml") - - # Run the model - model.run() - - model.post_process() - - # Subtests for checking specific values - with subtests.test("Check LCOE is positive"): - lcoe = model.prob.get_val("finance_subgroup_default.LCOE", units="USD/(MW*h)")[0] - assert lcoe <= 69 - - with subtests.test("Check wind rated production"): - wind_rated = model.prob.get_val("wind.rated_electricity_production", units="kW")[0] - assert wind_rated <= 3005 - - with subtests.test("Check solar rated production"): - solar_rated = model.prob.get_val("solar.rated_electricity_production", units="kW")[0] - assert solar_rated <= 1925 - - with subtests.test("Check percent_load_missed"): - pct_missed = model.prob.get_val("electrical_load_demand.percent_load_missed")[0] - assert pct_missed <= 18.7 - - with subtests.test("Check curtailment_percent"): - curtailment = model.prob.get_val("electrical_load_demand.curtailment_percent")[0] - assert curtailment <= 16.5 - - with subtests.test("Check delivered total electricity produced"): - load_total = model.prob.get_val( - "electrical_load_demand.total_electricity_produced", units="kW*h" - )[0] - assert load_total <= 9700000 - - -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("13_dispatch_for_electrolyzer", None)] ) def test_electrolyzer_demand(subtests, temp_copy_of_example): - from h2integrate import load_tech_yaml, load_plant_yaml, load_driver_yaml + from h2integrate import load_plant_yaml, load_driver_yaml example_folder = temp_copy_of_example @@ -966,7 +930,7 @@ def test_electrolyzer_demand(subtests, temp_copy_of_example): assert pytest.approx(226775.45532130863, rel=1e-6) == electricity_to_electrolyzer -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("14_wind_hydrogen_dispatch", None)] ) @@ -1052,7 +1016,7 @@ def test_hydrogen_dispatch_example(subtests, temp_copy_of_example): ) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("09_co2/ocean_alkalinity_enhancement", None)] ) @@ -1091,16 +1055,16 @@ def test_wind_wave_oae_example(subtests, temp_copy_of_example): ) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", - [("09_co2/ocean_alkalinity_enhancement_financials", None)], + [("09_co2/ocean_alkalinity_enhancement", None)], ) def test_wind_wave_oae_example_with_finance(subtests, temp_copy_of_example): example_folder = temp_copy_of_example # Create a H2Integrate model - model = H2IntegrateModel(example_folder / "offshore_plant_oae.yaml") + model = H2IntegrateModel(example_folder / "offshore_plant_oae_financials.yaml") # Run the model model.run() @@ -1124,10 +1088,8 @@ def test_wind_wave_oae_example_with_finance(subtests, temp_copy_of_example): ) -@pytest.mark.integration -@pytest.mark.parametrize( - "example_folder,resource_example_folder", [("16_natural_gas", "11_hybrid_energy_plant")] -) +@pytest.mark.examples +@pytest.mark.parametrize("example_folder,resource_example_folder", [("16_natural_gas", None)]) def test_natural_gas_example(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -1299,10 +1261,10 @@ def test_natural_gas_example(subtests, temp_copy_of_example): assert pytest.approx(ng_cf, rel=1e-6) == 0.5676562763739097 -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", - [("15_wind_solar_electrolyzer", "11_hybrid_energy_plant/")], + [("15_wind_solar_electrolyzer", None)], ) def test_wind_solar_electrolyzer_example(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -1381,7 +1343,7 @@ def test_wind_solar_electrolyzer_example(subtests, temp_copy_of_example): assert pytest.approx(total_generation.sum(), rel=1e-5) == total_energy_to_electrolyzer.sum() -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize("example_folder,resource_example_folder", [("10_electrolyzer_om", None)]) def test_electrolyzer_om_example(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -1406,7 +1368,7 @@ def test_electrolyzer_om_example(subtests, temp_copy_of_example): assert pytest.approx(lcoh_with_lcoe_finance, rel=2e-3) == 10.3360027653 -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize("example_folder,resource_example_folder", [("08_wind_electrolyzer", None)]) def test_wombat_electrolyzer_example(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -1440,7 +1402,7 @@ def test_wombat_electrolyzer_example(subtests, temp_copy_of_example): assert pytest.approx(lcoe_with_profast_model, rel=1e-5) == 59.0962084 -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("18_pyomo_heuristic_dispatch", None)] ) @@ -1541,7 +1503,7 @@ def test_pyomo_heuristic_dispatch_example(subtests, temp_copy_of_example): model = H2IntegrateModel(model_config) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize("example_folder,resource_example_folder", [("19_simple_dispatch", None)]) def test_simple_dispatch_example(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -1602,7 +1564,7 @@ def test_simple_dispatch_example(subtests, temp_copy_of_example): npv = model.prob.get_val( "finance_subgroup_electricity.NPV_electricity_all_electricity_npv", units="USD" )[0] - assert pytest.approx(npv, rel=1e-6) == 3791194.71 + assert pytest.approx(npv, rel=1e-5) == 3791194.71 # Subtest for ProFAST NPV with subtests.test("Check NPV value (profast)"): @@ -1610,7 +1572,7 @@ def test_simple_dispatch_example(subtests, temp_copy_of_example): "finance_subgroup_electricity.NPV_electricity_all_electricity_profast_npv", units="USD", )[0] - assert pytest.approx(npv, rel=1e-6) == 7518969.18 + assert pytest.approx(npv, rel=1e-5) == 7518969.18 # Subtest for total electricity produced with subtests.test("Check total electricity produced"): @@ -1700,7 +1662,7 @@ def test_simple_dispatch_example(subtests, temp_copy_of_example): assert pytest.approx(electricity_lcoe, rel=1e-6) == 78.01723 -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.skipif(importlib.util.find_spec("ard") is None, reason="ard is not installed") @pytest.mark.parametrize("example_folder,resource_example_folder", [("29_wind_ard", None)]) def test_windard_pv_battery_dispatch_example(subtests, temp_copy_of_example): @@ -1786,7 +1748,7 @@ def test_windard_pv_battery_dispatch_example(subtests, temp_copy_of_example): assert electricity_missed_load == pytest.approx(1403.5372787817894) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("20_solar_electrolyzer_doe", None)] ) @@ -1797,7 +1759,7 @@ def test_csvgen_parameter_sweep(subtests, temp_copy_of_example): model = H2IntegrateModel(example_folder / "20_solar_electrolyzer_doe.yaml") assert "There may be issues with the csv file csv_doe_cases.csv" in str(excinfo.value) - from h2integrate import write_yaml, load_driver_yaml + from h2integrate import write_yaml from h2integrate.core.dict_utils import update_defaults from h2integrate.core.file_utils import check_file_format_for_csv_generator @@ -1933,7 +1895,7 @@ def test_csvgen_parameter_sweep(subtests, temp_copy_of_example): new_csv_filename.unlink() -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize("example_folder,resource_example_folder", [("22_site_doe", None)]) def test_sweeping_solar_sites_doe(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -2043,10 +2005,10 @@ def test_sweeping_solar_sites_doe(subtests, temp_copy_of_example): assert all(k for k in lcoe_comp) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", - [("23_solar_wind_ng_demand", "11_hybrid_energy_plant/")], + [("23_solar_wind_ng_demand", None)], ) def test_ng_demand_example(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -2072,7 +2034,7 @@ def test_ng_demand_example(subtests, temp_copy_of_example): with subtests.test("Renewables LCOE with generic demand"): assert pytest.approx(65.25367747, rel=1e-6) == lcoe_renewables_generic[0] with subtests.test("Renewables NPV with generic demand"): - assert pytest.approx(-36.0408322, rel=1e-6) == npv_renewables_generic[0] + assert pytest.approx(-36.0408322, rel=1e-5) == npv_renewables_generic[0] with subtests.test("Natural gas LCOE with generic demand"): assert pytest.approx(60.30971126, rel=1e-6) == lcoe_ng_generic[0] with subtests.test("Electricity LCOE with generic demand"): @@ -2099,14 +2061,14 @@ def test_ng_demand_example(subtests, temp_copy_of_example): with subtests.test("Renewables LCOE with flexible demand"): assert pytest.approx(65.25367747, rel=1e-6) == lcoe_renewables_flexible[0] with subtests.test("Renewables NPV with flexible demand"): - assert pytest.approx(-36.0408322, rel=1e-6) == npv_renewables_flexible[0] + assert pytest.approx(-36.0408322, rel=1e-5) == npv_renewables_flexible[0] with subtests.test("Natural gas LCOE with flexible demand"): assert pytest.approx(115.92792486, rel=1e-6) == lcoe_ng_flexible[0] with subtests.test("Electricity LCOE with flexible demand"): assert pytest.approx(76.39162926, rel=1e-6) == lcoe_electricity_flexible[0] -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize("example_folder,resource_example_folder", [("26_floris", None)]) def test_floris_example(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -2252,9 +2214,9 @@ def test_floris_example(subtests, temp_copy_of_example): ) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( - "example_folder,resource_example_folder", [("24_solar_battery_grid", "11_hybrid_energy_plant/")] + "example_folder,resource_example_folder", [("24_solar_battery_grid", None)] ) def test_24_solar_battery_grid_example(subtests, temp_copy_of_example): # NOTE: would be good to compare LCOE against the same example without grid selling @@ -2303,7 +2265,7 @@ def test_24_solar_battery_grid_example(subtests, temp_copy_of_example): assert pytest.approx(lcoe, rel=1e-4) == 91.7057887 -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("21_iron_examples/iron_mapping", None)] ) @@ -2464,7 +2426,7 @@ def test_iron_mapping_example(subtests, temp_copy_of_example): ex_png_fpath.unlink(missing_ok=True) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize("example_folder,resource_example_folder", [("04_geo_h2", None)]) def test_natural_geoh2(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -2542,7 +2504,7 @@ def test_natural_geoh2(subtests, temp_copy_of_example): assert pytest.approx(surface_adjusted_opex, rel=1e-6) == 4798691.865 -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize("example_folder,resource_example_folder", [("04_geo_h2", None)]) def test_stimulated_geoh2(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -2588,7 +2550,7 @@ def test_stimulated_geoh2(subtests, temp_copy_of_example): assert pytest.approx(adjusted_opex, rel=1e-6) == 215100.7857875 -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("21_iron_examples/iron_dri", None)] ) @@ -2612,14 +2574,14 @@ def test_iron_dri_eaf_example(subtests, temp_copy_of_example): assert pytest.approx(lcos, rel=1e-4) == 531.5842266865 -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( - "example_folder,resource_example_folder", [("21_iron_examples/iron_dri_nrri", None)] + "example_folder,resource_example_folder", [("21_iron_examples/iron_dri", None)] ) def test_iron_dri_nrri_example(subtests, temp_copy_of_example): example_folder = temp_copy_of_example - h2i = H2IntegrateModel(example_folder / "single_site_iron.yaml") + h2i = H2IntegrateModel(example_folder / "single_site_iron_nrri.yaml") h2i.run() @@ -2636,7 +2598,7 @@ def test_iron_dri_nrri_example(subtests, temp_copy_of_example): assert pytest.approx(lcos, rel=1e-4) == 520.417 -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("21_iron_examples/iron_electrowinning", None)] ) @@ -2683,7 +2645,7 @@ def test_iron_electrowinning_example(subtests, temp_copy_of_example): assert pytest.approx(lcoi, rel=1e-4) == 2.266210286641621 -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize("example_folder,resource_example_folder", [("27_site_doe_diff", None)]) def test_sweeping_different_resource_sites_doe(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -2797,7 +2759,7 @@ def test_sweeping_different_resource_sites_doe(subtests, temp_copy_of_example): assert len(list(set(res_df["combiner LCOE"].to_list()))) == len(res_df) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("30_pyomo_optimized_dispatch", None)] ) @@ -2893,7 +2855,7 @@ def test_pyomo_optimized_dispatch_example(subtests, temp_copy_of_example): assert price == pytest.approx(0.134, rel=1e-3) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize("example_folder,resource_example_folder", [("31_tidal", None)]) def test_tidal_example(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -2921,7 +2883,7 @@ def test_tidal_example(subtests, temp_copy_of_example): assert lcoe == pytest.approx(0.287, rel=1e-4) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("32_multivariable_streams", None)] ) @@ -3003,7 +2965,7 @@ def test_multivariable_streams_example(subtests, temp_copy_of_example): assert avg_pres[0] == pytest.approx(10.40, rel=1e-3) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("21_iron_examples/iron_cmu/scrap_only", None)], @@ -3030,7 +2992,7 @@ def test_cmu_eaf_scrap_only_example(subtests, temp_copy_of_example): assert pytest.approx(lcos, rel=1e-4) == 0.2233330 -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("21_iron_examples/iron_cmu/dri", None)], @@ -3057,7 +3019,7 @@ def test_cmu_eaf_dri_example(subtests, temp_copy_of_example): assert pytest.approx(lcos, rel=1e-4) == 1.4932 -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("33_peak_load_management_heuristics/plm_storage", None)], @@ -3104,7 +3066,7 @@ def test_peak_load_management_example(subtests, temp_copy_of_example): assert battery_unmet_demand.sum() == pytest.approx(grid_purchase.sum(), rel=1e-3) -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder,expected", [ @@ -3153,7 +3115,7 @@ def test_plm_converter_heuristic_example(subtests, temp_copy_of_example, expecte assert np.sum(command > 0) == expected["nonzero_dispatch"] -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("34_plm_optimized_dispatch", None)] ) @@ -3200,7 +3162,7 @@ def test_plm_optimized_dispatch_example(subtests, temp_copy_of_example): assert pytest.approx(total_energy_charged, rel=1e-3) == -10656.7036 -@pytest.mark.integration +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("36_nuclear_reactor_htse", None)] ) @@ -3253,7 +3215,66 @@ def test_nuclear_reactor_htse_example(subtests, temp_copy_of_example): assert pytest.approx(unused_electricity.sum(), rel=1e-6) == grid_electricity_in.sum() -@pytest.mark.integration +@pytest.mark.examples +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("24_solar_battery_grid", None)] +) +def test_time_marching_simulation_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Load config files into dict + config_root = example_folder + config_path = config_root / "solar_battery_grid.yaml" + + # Load top level config + config = load_yaml(config_path) + config["plant_config"] = load_plant_yaml(config_root / config["plant_config"]) + + # Run simulation sequentially, one subsystem at a time + h2i_seq = H2IntegrateModel(config) + h2i_seq.run() + h2i_seq.post_process(print_results=False) + + inputs_seq = dict(h2i_seq.model.list_inputs(out_stream=None)) + outputs_seq = dict(h2i_seq.model.list_outputs(out_stream=None)) + + # Run time-marching simulation for all subsystems, one window of timesteps at a time + config["plant_config"]["plant"]["simulation"]["n_timesteps"] = 8760 + config["plant_config"]["plant"]["simulation"]["n_timesteps_window"] = 24 + + # Create an H2I model for time-marching simulation + h2i_con = H2IntegrateModel(config) + h2i_con.run() + h2i_con.post_process(print_results=False) + + inputs_con = dict(h2i_con.model.list_inputs(out_stream=None)) + outputs_con = dict(h2i_con.model.list_outputs(out_stream=None)) + + from h2integrate.core.dict_utils import percent_diff_dicts + + inputs_pd_dict = percent_diff_dicts(inputs_seq, inputs_con) + outputs_pd_dict = percent_diff_dicts(outputs_seq, outputs_con) + + with subtests.test("Component iter_count reflects expected number of calls"): + assert h2i_seq.plant.battery.StoragePerformanceModel.iter_count == 1 + assert h2i_con.plant.battery.StoragePerformanceModel.iter_count == 365 + + with subtests.test("Sequential and time-marching H2I inputs are consistent"): + for k, v in inputs_pd_dict.items(): + if k.endswith(".timestep_index"): + continue + assert v <= 1e-8, ( + f"H2I input: {k}, is not consistent between sequential and time-marching runs" + ) + + with subtests.test("Sequential and time-marching H2I outputs are consistent"): + for k, v in outputs_pd_dict.items(): + assert v <= 1e-8, ( + f"H2I output: {k}, is not consistent between sequential and time-marching runs" + ) + + +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("37_paper_mill", None)], diff --git a/examples/test/test_example_scripts.py b/examples/test/test_example_scripts.py new file mode 100644 index 000000000..fd0f4fcdc --- /dev/null +++ b/examples/test/test_example_scripts.py @@ -0,0 +1,109 @@ +"""Run every example script the same way a user would from the command line. + +The value checks for each example live in ``test_all_examples.py``. These tests only confirm that +each ``run_*.py`` script, including its plotting and post-processing code, finishes without error. +""" + +import os +import sys +import shutil +import subprocess +import importlib.util + +import pytest + +from h2integrate import EXAMPLE_DIR + + +# Python modules under examples/ that are imported by a run script or referenced from a config +HELPER_MODULES = { + "03_methanol/co2_hydrogenation_doc/plot_co2h_methanol.py", + "06_custom_tech/user_defined_model/paper_mill.py", + "08_wind_electrolyzer/user_finance_model/simple_lco.py", +} + +# Scripts that need an optional dependency, mapped to the module that must be importable +OPTIONAL_DEPENDENCIES = { + "21_iron_examples/iron_mapping/run_iron.py": "geopandas", + "29_wind_ard/run_windard_pv_battery.py": "ard", +} + +SCRIPT_TIMEOUT_SECONDS = 3600 + + +def _relative(path): + return path.relative_to(EXAMPLE_DIR).as_posix() + + +def _example_python_files(pattern): + return { + _relative(path) + for path in EXAMPLE_DIR.rglob(pattern) + if path.relative_to(EXAMPLE_DIR).parts[0] != "test" and "__pycache__" not in path.parts + } + + +EXAMPLE_SCRIPTS = sorted(_example_python_files("run_*.py")) + + +def _script_param(script): + module = OPTIONAL_DEPENDENCIES.get(script) + marks = [] + if module is not None: + marks.append( + pytest.mark.skipif( + importlib.util.find_spec(module) is None, reason=f"{module} is not installed" + ) + ) + return pytest.param(script, marks=marks, id=script) + + +@pytest.mark.unit +def test_example_scripts_are_discoverable(): + """Every example Python file is either a ``run_*.py`` script or a known helper module.""" + python_files = _example_python_files("*.py") + unexpected = sorted(python_files - set(EXAMPLE_SCRIPTS) - HELPER_MODULES) + assert not unexpected, ( + "Name example scripts `run_*.py` so they are tested, or add helper modules to " + f"HELPER_MODULES in {__file__}: {unexpected}" + ) + missing_helpers = sorted(HELPER_MODULES - python_files) + assert not missing_helpers, f"Remove deleted files from HELPER_MODULES: {missing_helpers}" + + +@pytest.mark.examples +@pytest.mark.parametrize("script", [_script_param(script) for script in EXAMPLE_SCRIPTS]) +def test_example_script_runs(script, tmp_path): + """Run the script from a temporary copy of its top-level example folder.""" + example_root = script.split("/")[0] + shutil.copytree( + EXAMPLE_DIR / example_root, + tmp_path / example_root, + ignore=shutil.ignore_patterns("__pycache__"), + ) + script_path = tmp_path / script + + env = os.environ.copy() + env["MPLBACKEND"] = "Agg" + env["OPENMDAO_REPORTS"] = "none" + env["PYTHONIOENCODING"] = "utf-8" + # Import the h2integrate source tree under test, even when the package is installed elsewhere + python_path = [str(EXAMPLE_DIR.parent), env.get("PYTHONPATH", "")] + env["PYTHONPATH"] = os.pathsep.join(path for path in python_path if path) + + result = subprocess.run( + [sys.executable, script_path.name], + cwd=script_path.parent, + env=env, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + timeout=SCRIPT_TIMEOUT_SECONDS, + check=False, + ) + assert result.returncode == 0, ( + f"{script} exited with code {result.returncode}\n" + f"--- stdout (last 3000 characters) ---\n{result.stdout[-3000:]}\n" + f"--- stderr (last 6000 characters) ---\n{result.stderr[-6000:]}" + ) diff --git a/h2integrate/control/control_rules/pyomo_rule_baseclass.py b/h2integrate/control/control_rules/pyomo_rule_baseclass.py index 021596b50..cb319c7ef 100644 --- a/h2integrate/control/control_rules/pyomo_rule_baseclass.py +++ b/h2integrate/control/control_rules/pyomo_rule_baseclass.py @@ -26,6 +26,9 @@ class PyomoRuleBaseClass(om.ExplicitComponent): 3600, 3600, ) # (min, max) time step lengths (in seconds) compatible with this model + # (min, max) permitted simulation duration in years (assuming a year is 8760 hours); + # default is annual-only. + _simulation_duration_bounds = (1.0, 1.0) def initialize(self): self.options.declare("driver_config", types=dict) diff --git a/h2integrate/control/control_strategies/openloop_control_baseclass.py b/h2integrate/control/control_strategies/openloop_control_baseclass.py index eaea8d251..c18564250 100644 --- a/h2integrate/control/control_strategies/openloop_control_baseclass.py +++ b/h2integrate/control/control_strategies/openloop_control_baseclass.py @@ -164,6 +164,12 @@ class OpenLoopControlBase(om.ExplicitComponent): 3600, 3600, ) # (min, max) time step lengths (in seconds) compatible with this model + # (min, max) permitted simulation duration in years; default is annual-only. + _simulation_duration_bounds = (1.0, 1.0) + + # Flag to indicate steppability. This flag should be overwritten by a subclass when that + # subclass controller is capable of steppable simulation. + _is_steppable = False def initialize(self): self.options.declare("driver_config", types=dict) @@ -172,11 +178,20 @@ def initialize(self): def setup(self): self.n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) + # n_timesteps_window is the number of timesteps simulated per compute call + self.n_timesteps_window = int( + self.options["plant_config"]["plant"]["simulation"].get( + "n_timesteps_window", self.n_timesteps + ) + ) commodity = self.config.commodity demand_data = self.config.demand_profile + # The index to start the simulation slice when compute is called. + self.add_input("timestep_index", val=0, desc="Time step index") + self.add_input( f"{commodity}_set_point", val=demand_data if not isinstance(demand_data, dict) else demand_data["demand"], @@ -201,6 +216,20 @@ def setup(self): desc=f"Dispatch commands for {commodity} storage", ) + def _get_compute_time_range(self, time_index): + """ + This method gets the range of timestep indices that are simulated in a + single call to compute. + + Args: + time_index (numpy array): Starting time index of the simulation range. + + Returns: + range: range of time indices + """ + ti = int(time_index[0]) + return range(ti, ti + self.n_timesteps_window) + def compute(): """This method must be implemented by subclasses to define the controller. diff --git a/h2integrate/control/control_strategies/passthrough_controller.py b/h2integrate/control/control_strategies/passthrough_controller.py index 589e7a598..732fc89a2 100644 --- a/h2integrate/control/control_strategies/passthrough_controller.py +++ b/h2integrate/control/control_strategies/passthrough_controller.py @@ -19,6 +19,8 @@ class PassthroughController(om.ExplicitComponent): _time_step_bounds = (1, float("inf")) + _is_steppable = True + def initialize(self): self.options.declare("commodity", types=str) self.options.declare("n_timesteps", types=int) diff --git a/h2integrate/control/control_strategies/pyomo_storage_controller_baseclass.py b/h2integrate/control/control_strategies/pyomo_storage_controller_baseclass.py index 526efbe1d..01239ec99 100644 --- a/h2integrate/control/control_strategies/pyomo_storage_controller_baseclass.py +++ b/h2integrate/control/control_strategies/pyomo_storage_controller_baseclass.py @@ -78,6 +78,8 @@ class PyomoStorageControllerBaseClass(om.ExplicitComponent): 3600, 3600, ) # (min, max) time step lengths (in seconds) compatible with this model + # (min, max) permitted simulation duration in years; default is annual-only. + _simulation_duration_bounds = (1.0, 1.0) def initialize(self): """ diff --git a/h2integrate/control/control_strategies/storage/demand_openloop_storage_controller.py b/h2integrate/control/control_strategies/storage/demand_openloop_storage_controller.py index f9b6190fb..835a4967a 100644 --- a/h2integrate/control/control_strategies/storage/demand_openloop_storage_controller.py +++ b/h2integrate/control/control_strategies/storage/demand_openloop_storage_controller.py @@ -51,6 +51,8 @@ class DemandOpenLoopStorageController(OpenLoopControlBase): 3600, ) # (min, max) time step lengths (in seconds) compatible with this model + _is_steppable = True + def setup(self): self.config = DemandOpenLoopStorageControllerConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "control"), @@ -59,7 +61,15 @@ def setup(self): ) super().setup() - self.n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] + # Ensure _soc_timeseries is consistent length with simulation + self._soc_timeseries = np.zeros(self.n_timesteps) + + # Initialize soc to the value in the config, if given or halfway between + # the minimum and maximum limits otherwise. + if hasattr(self.config, "init_soc_fraction"): + self.soc_init = self.config.init_soc_fraction + else: + self.soc_init = self.config.min_soc_fraction # Design constraints of storage system self.add_input( @@ -101,6 +111,7 @@ def compute(self, inputs, outputs): * ``_set_point``: Timeseries set-point profile. * ``max_charge_rate``: Maximum charge rate permitted. * ``max_capacity``: Maximum total storage capacity. + * ``timestep_index``: Starting index for the component simulation. Outputs populated: * ``_command_value``: Dispatch command to storage, @@ -119,6 +130,10 @@ def compute(self, inputs, outputs): Returns: None """ + # Range object for the slice of the total simulation to run in this + # compute call + simulation_range = self._get_compute_time_range(inputs["timestep_index"]) + commodity = self.config.commodity self.common_checks_needed_in_compute(inputs) @@ -133,23 +148,26 @@ def compute(self, inputs, outputs): soc_max = self.config.max_soc_fraction soc_min = self.config.min_soc_fraction - init_soc_fraction = self.config.init_soc_fraction charge_eff = float(self.config.charge_efficiency) discharge_eff = float(self.config.discharge_efficiency) # Initialize time-step state of charge prior to loop so the loop starts with # the previous time step's value - soc = deepcopy(init_soc_fraction) + if simulation_range.start == 0: + soc = deepcopy(self.soc_init) + else: + soc = self._soc_timeseries[simulation_range.start - 1] demand_profile = inputs[f"{commodity}_set_point"] # initialize outputs - soc_array = np.zeros(self.n_timesteps) set_point_array = np.zeros(self.n_timesteps) + combined_output_array = np.zeros(self.n_timesteps) # Loop through each time step - for t, demand_t in enumerate(demand_profile): + for t in simulation_range: + demand_t = demand_profile[t] # Get the input flow at the current time step input_flow = inputs[f"{commodity}_in"][t] @@ -192,6 +210,6 @@ def compute(self, inputs, outputs): soc = max(soc_min, min(soc_max, soc)) # Record the SOC for the current time step - soc_array[t] = deepcopy(soc) + self._soc_timeseries[t] = deepcopy(soc) - outputs[f"{commodity}_command_value"] = set_point_array + outputs[f"{commodity}_command_value"][simulation_range] = set_point_array[simulation_range] diff --git a/h2integrate/control/control_strategies/storage/optimized_pyomo_controller.py b/h2integrate/control/control_strategies/storage/optimized_pyomo_controller.py index e335dc7ab..78a784a71 100644 --- a/h2integrate/control/control_strategies/storage/optimized_pyomo_controller.py +++ b/h2integrate/control/control_strategies/storage/optimized_pyomo_controller.py @@ -268,6 +268,7 @@ def pyomo_dispatch_solver( self.storage_dispatch_commands, **performance_model_kwargs, sim_start_index=t, + sim_end_index=t + self.n_control_window_hours, ) # update SOC for next time window self.updated_initial_soc = soc_control_window[-1] / 100 # turn into ratio diff --git a/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py b/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py index 9e3c9ca5c..bf7b0f03a 100644 --- a/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py +++ b/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py @@ -565,6 +565,7 @@ def pyomo_dispatch_solver( self._get_storage_dispatch_commands(), **performance_model_kwargs, sim_start_index=window_start, + sim_end_index=window_start + window_len, ) # Performance model returns SOC in percent. diff --git a/h2integrate/control/control_strategies/system_level/system_level_control_baseclass.py b/h2integrate/control/control_strategies/system_level/system_level_control_baseclass.py index e5613cd32..173544ba7 100644 --- a/h2integrate/control/control_strategies/system_level/system_level_control_baseclass.py +++ b/h2integrate/control/control_strategies/system_level/system_level_control_baseclass.py @@ -139,6 +139,10 @@ class SystemLevelControlBase(om.ExplicitComponent): and divide by the dispatchable tech's annualized total production. """ + # Flag to indicate steppability. This flag should be overwritten by a subclass when that + # system level controller is capable of steppable simulation. + _is_steppable = False + def initialize(self): self.options.declare("driver_config", types=dict) self.options.declare("plant_config", types=dict) diff --git a/h2integrate/control/control_strategies/system_level/test/test_slc_examples.py b/h2integrate/control/control_strategies/system_level/test/test_slc_examples.py index 85dceb615..e046ae37f 100644 --- a/h2integrate/control/control_strategies/system_level/test/test_slc_examples.py +++ b/h2integrate/control/control_strategies/system_level/test/test_slc_examples.py @@ -4,9 +4,9 @@ from h2integrate.core.h2integrate_model import H2IntegrateModel -@pytest.mark.unit +@pytest.mark.examples @pytest.mark.parametrize( - "example_folder,resource_example_folder", [("35_system_level_control/no_battery", None)] + "example_folder,resource_example_folder", [("35_system_level_control/demand_following", None)] ) def test_slc_no_battery(subtests, temp_copy_of_example): example_folder = temp_copy_of_example @@ -58,19 +58,20 @@ def test_slc_no_battery(subtests, temp_copy_of_example): "finance_subgroup_electricity.LCOE", units="USD/(MW*h)" ) with subtests.test("Wind NPV"): - assert pytest.approx(-38.5777102298, rel=1e-6) == model.prob.get_val( + # NPV amplifies ~1e-7 wind resource parsing differences (pandas 3.0 vs 3.1 read_csv) ~80x + assert pytest.approx(-38.5777102298, rel=1e-5) == model.prob.get_val( "finance_subgroup_renewables.NPV_electricity__profast_npv", units="MUSD" ) -@pytest.mark.unit +@pytest.mark.examples @pytest.mark.parametrize( - "example_folder,resource_example_folder", [("35_system_level_control/yes_battery", None)] + "example_folder,resource_example_folder", [("35_system_level_control/demand_following", None)] ) def test_slc_yes_battery(subtests, temp_copy_of_example): example_folder = temp_copy_of_example - model = H2IntegrateModel(example_folder / "wind_ng_demand.yaml") + model = H2IntegrateModel(example_folder / "wind_ng_battery_demand.yaml") model.run() @@ -126,12 +127,13 @@ def test_slc_yes_battery(subtests, temp_copy_of_example): "finance_subgroup_electricity.LCOE", units="USD/(MW*h)" ) with subtests.test("Wind NPV"): - assert pytest.approx(-38.5777102298, rel=1e-6) == model.prob.get_val( + # NPV amplifies ~1e-7 wind resource parsing differences (pandas 3.0 vs 3.1 read_csv) ~80x + assert pytest.approx(-38.5777102298, rel=1e-5) == model.prob.get_val( "finance_subgroup_renewables.NPV_electricity__profast_npv", units="MUSD" ) -@pytest.mark.unit +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("35_system_level_control/profit_maximization", None)], @@ -166,7 +168,7 @@ def test_slc_profit_max(subtests, temp_copy_of_example): assert wind_out.sum() > 0 -@pytest.mark.unit +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("35_system_level_control/yes_hydrogen", None)] ) @@ -187,19 +189,19 @@ def test_slc_yes_hydrogen(subtests, temp_copy_of_example): pytest.approx( model.prob.get_val("finance_subgroup_hydrogen.LCOH", units="USD/kg"), rel=1e-6 ) - == 14.878096642042243 + == 16.52990277 ) -@pytest.mark.unit +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", - [("35_system_level_control/battery_with_controller", None)], + [("35_system_level_control/demand_following", None)], ) def test_slc_battery_with_controller(subtests, temp_copy_of_example): example_folder = temp_copy_of_example - model = H2IntegrateModel(example_folder / "wind_ng_demand.yaml") + model = H2IntegrateModel(example_folder / "wind_ng_battery_controller_demand.yaml") model.run() @@ -221,7 +223,7 @@ def test_slc_battery_with_controller(subtests, temp_copy_of_example): ) -@pytest.mark.unit +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("35_system_level_control/complex_profit_max", None)], @@ -310,7 +312,7 @@ def test_slc_complex_profit_max(subtests, temp_copy_of_example): assert grid_out.sum() == 0 -@pytest.mark.unit +@pytest.mark.examples @pytest.mark.parametrize( "example_folder,resource_example_folder", [("35_system_level_control/upstream_demand", None)] ) diff --git a/h2integrate/converters/ammonia/ammonia_synloop_performance.py b/h2integrate/converters/ammonia/ammonia_synloop_performance.py index d242292be..53f199e25 100644 --- a/h2integrate/converters/ammonia/ammonia_synloop_performance.py +++ b/h2integrate/converters/ammonia/ammonia_synloop_performance.py @@ -184,6 +184,7 @@ class AmmoniaSynLoopPerformanceModel(ResizeablePerformanceModelBaseClass): hydrogen_in (array): Hourly hydrogen feed to the synthesis loop [kg/h]. nitrogen_in (array): Hourly nitrogen feed to the synthesis loop [kg/h]. electricity_in (array): Hourly electricity supplied to the synthesis loop [MW]. + ammonia_command_value (array): Hourly SLC ammonia production set point [kg/h]. Outputs: ammonia_out (array): Hourly ammonia produced by the synthesis loop [kg/h]. @@ -208,9 +209,9 @@ class AmmoniaSynLoopPerformanceModel(ResizeablePerformanceModelBaseClass): total_nitrogen_consumed (float): Total nitrogen consumed over the modeled period [kg/year]. total_electricity_consumed (float): Total electricity consumed over the modeled period [kWh/year]. - limiting_output (array of int): Limiting factor indicator per timestep [-], + limiting_input (array of int): Limiting factor indicator per timestep [-], where 0 = nitrogen-limited, 1 = hydrogen-limited, 2 = electricity-limited, - and 3 = capacity-limited. + 3 = capacity-limited, and 4 = ammonia-command-limited. max_hydrogen_capacity (float): Maximum rate of hydrogen consumption [kg/h]. ammonia_capacity_factor (float): Ratio of ammonia produced to the maximum production capacity [-]. @@ -250,6 +251,15 @@ def setup(self): self.add_input("ramp_up_rate", val=self.config.ramp_up_rate_fraction, units="unitless") self.add_input("ramp_down_rate", val=self.config.ramp_down_rate_fraction, units="unitless") + if "system_level_control" in self.options["plant_config"]: + self.add_input( + "ammonia_command_value", + val=0.0, + shape=self.n_timesteps, + units=self.commodity_rate_units, + desc="Command value for ammonia production from SLC", + ) + if self.config.include_warm_start: self.add_input("off_time_warm_start", val=self.config.off_hours_warm_start, units="h") self.add_input("warm_start_delay", val=self.config.warm_start_delay_hours, units="h") @@ -300,10 +310,10 @@ def apply_dynamic_operation(self, inputs, nh3_production): tuple: - nh3_production (np.ndarray): production profile after ramping and start-up losses have been applied. - - consumption_multiplier (np.ndarray): on/off-gated production profile - used to scale input-commodity consumption. Feedstocks are consumed during - start-up delays (when ammonia output is zeroed) so the consumption - multiplier is taken *before* start-up losses are applied. + - consumption_multiplier (np.ndarray): final production profile used to + scale input-commodity consumption. Feedstocks are consumed only in + proportion to the ammonia produced, so start-up delay hours consume + nothing and positive ramp-down output below turndown still consumes feed. """ rated_capacity = float(inputs["ammonia_production_capacity"][0]) @@ -340,10 +350,14 @@ def apply_dynamic_operation(self, inputs, nh3_production): # Clamp pre-constraint production into the physically allowed range. nh3_production = np.clip(nh3_production, a_min=0.0, a_max=rated_capacity) - # 1. Set production to zero when producing less than the min operating point + # 1. Set production to zero when producing less than the min operating point. + # Turndown is the steady-state minimum only. It is not re-applied after ramping, so + # production can be below turndown while ramping up or down (ramp rates take precedence). nh3_production = np.where(nh3_production < minimum_production, 0.0, nh3_production) # 2. Apply ramping limits on the per-timestep change in production. + # Ramping can leave positive output below turndown (ramping down toward zero, or + # ramping up from zero when the ramp-up rate is below turndown). nh3_production = apply_ramping_limits( nh3_production, dt_seconds=self.dt, @@ -398,11 +412,12 @@ def apply_dynamic_operation(self, inputs, nh3_production): commodity_amount_units=self.commodity_amount_units, ) - # 5. Compute the consumption multiplier from the post-dynamic profile. - # in current behavior the consumption is based on the nh3 produced during - # that timestep. TODO: develop function to allow for varied consumption based - # on behavior in that time step (e.g., ramping down uses less n2 and h2 but more power) - consumption_multiplier = np.where(nh3_production >= minimum_production, nh3_production, 0) + # 5. Compute the consumption multiplier from the post-dynamic profile. Consumption + # follows the ammonia produced in that timestep, including positive output below + # turndown during ramp-down. + # TODO: develop function to allow for varied consumption based on behavior in that + # time step (e.g., ramping down uses less n2 and h2 but more power) + consumption_multiplier = np.maximum(nh3_production, 0.0) return nh3_production, consumption_multiplier @@ -459,15 +474,23 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): nh3_from_elec = elec_in / energy_demand # kg nh3 / hr # Limiting NH3 production per hour by each input - nh3_prod = np.minimum.reduce([nh3_from_n2, nh3_from_h2, nh3_from_elec]) - limiters = np.argmin([nh3_from_n2, nh3_from_h2, nh3_from_elec], axis=0) + production_limits = [nh3_from_n2, nh3_from_h2, nh3_from_elec] + system_level_control_enabled = "system_level_control" in self.options["plant_config"] + if system_level_control_enabled: + # The command joins the feed limits as the fourth candidate under SLC. + production_limits.append(inputs["ammonia_command_value"]) + + nh3_prod = np.minimum.reduce(production_limits) + limiters = np.argmin(production_limits, axis=0) + if system_level_control_enabled: + limiters = np.where(limiters == 3, 4, limiters) # Limiting NH3 production per hour by capacity nh3_prod = np.minimum.reduce([nh3_prod, np.full(len(nh3_prod), nh3_cap)]) cap_lim = 1 - np.argmax([nh3_prod, list(np.full(len(nh3_prod), nh3_cap))], axis=0) - # Determine what the limiting factor is for each hour - limiters = np.maximum.reduce([cap_lim * 3, limiters]) + # Capacity takes precedence over a command at or above rated production. + limiters = np.where(cap_lim == 1, 3, limiters) outputs["limiting_input"] = limiters # Apply dynamic operation diff --git a/h2integrate/converters/ammonia/test/test_ammonia_synloop_dynamics.py b/h2integrate/converters/ammonia/test/test_ammonia_synloop_dynamics.py index f1dc91586..d93b2a260 100644 --- a/h2integrate/converters/ammonia/test/test_ammonia_synloop_dynamics.py +++ b/h2integrate/converters/ammonia/test/test_ammonia_synloop_dynamics.py @@ -3,6 +3,8 @@ import openmdao.api as om from pytest import fixture +from h2integrate.tools.constants import H_MW, N_MW +from h2integrate.core.h2integrate_model import H2IntegrateModel from h2integrate.converters.ammonia.ammonia_synloop_performance import ( AmmoniaSynLoopPerformanceModel, ) @@ -87,6 +89,17 @@ def make_production_sequence(min_prod, max_prod, onoff_sequence, n_timesteps, st return production +def stoichiometric_feedstock_rates(performance): + feed_mw = performance["feed_gas_x_h2"] * H_MW * 2 + performance["feed_gas_x_n2"] * N_MW * 2 + hydrogen_rate = ( + performance["feed_gas_x_h2"] * H_MW * 2 / feed_mw * performance["feed_gas_mass_ratio"] + ) + nitrogen_rate = ( + performance["feed_gas_x_n2"] * N_MW * 2 / feed_mw * performance["feed_gas_mass_ratio"] + ) + return hydrogen_rate, nitrogen_rate + + @pytest.mark.regression @pytest.mark.parametrize("dt,n_timesteps", [(3600, 40)]) def test_ammonia_subdt_offtime_subdt_delay( @@ -160,6 +173,10 @@ def test_ammonia_subdt_offtime_subdt_delay( nh3_produced = nh3_out[:n_timesteps_test].sum() assert pytest.approx(nh3_produced, rel=1e-6) == total_elec_consumed + with subtests.test("Consumption follows partial ammonia output during a startup delay"): + assert elec_consumed[2] == pytest.approx(nh3_out[2]) + assert nh3_out[2] > 0.0 + # Confirm losses repeat consistently across all full sequence repeats in the sim n_repeats = (n_timesteps - 1) // len(on_off_sequence) n_check = 1 + n_repeats * len(on_off_sequence) @@ -240,12 +257,20 @@ def test_ammonia_subdt_offtime_multidt_delay( # h2_consumed = prob.get_val("comp.hydrogen_consumed", units="kg/h") # n2_consumed = prob.get_val("comp.nitrogen_consumed", units="kg/h") elec_consumed = prob.get_val("comp.electricity_consumed", units="kW") + hydrogen_consumed = prob.get_val("comp.hydrogen_consumed", units="kg/h") with subtests.test(f"Electricity consumption for first {n_timesteps_test} timesteps"): total_elec_consumed = elec_consumed[:n_timesteps_test].sum() nh3_produced = nh3_out[:n_timesteps_test].sum() assert pytest.approx(nh3_produced, rel=1e-6) == total_elec_consumed + with subtests.test("No feed is consumed during zero-output startup delay hours"): + startup_delay_hours = (nh3_out == 0.0) & (nh3_no_dynamics >= min_nh3) + assert np.any(startup_delay_hours) + assert np.all(hydrogen_consumed[startup_delay_hours] == 0.0) + assert np.all(elec_consumed[startup_delay_hours] == 0.0) + assert np.all(hydrogen_consumed[nh3_out > 0.0] > 0.0) + # Confirm losses repeat consistently across all full sequence repeats in the sim n_repeats = (n_timesteps - 1) // len(on_off_sequence) n_check = 1 + n_repeats * len(on_off_sequence) @@ -641,7 +666,7 @@ def test_ammonia_multidt_delay_fraction( ) assert pytest.approx(nh3_produced, rel=1e-6) == expected_nh3 - prob.get_val("comp.electricity_consumed", units="kW") + elec_consumed = prob.get_val("comp.electricity_consumed", units="kW") with subtests.test( f"Electricity consumption for first {n_timesteps_test} timesteps (subdt offtime)" ): @@ -1159,6 +1184,228 @@ def test_ammonia_turndown_enforced_without_startup( assert np.all(elec_consumed[3:7] == pytest.approx(0.0, abs=1e-9)) +@pytest.mark.unit +@pytest.mark.parametrize("dt,n_timesteps", [(3600, 4)]) +def test_ammonia_consumption_tracks_positive_rampdown_output( + plant_config, synloop_config, dynamics_config, n_timesteps, subtests +): + rated_capacity = 4000.0 + synloop_config["model_inputs"]["shared_parameters"]["production_capacity"] = rated_capacity + dynamics_config["turndown_ratio"] = 0.2 + dynamics_config["ramp_down_rate_fraction"] = 0.6 + synloop_config["model_inputs"]["performance_parameters"] = ( + synloop_config["model_inputs"]["performance_parameters"] | dynamics_config + ) + + production_profile = np.array([2000.0, 3000.0, 3000.0, 0.0]) + performance = synloop_config["model_inputs"]["performance_parameters"] + hydrogen_rate, nitrogen_rate = stoichiometric_feedstock_rates(performance) + hydrogen_in = production_profile * hydrogen_rate + nitrogen_in = production_profile * nitrogen_rate + electricity_in = production_profile * performance["energy_demand"] + + prob = om.Problem() + comp = AmmoniaSynLoopPerformanceModel( + plant_config=plant_config, + tech_config=synloop_config, + driver_config={}, + ) + prob.model.add_subsystem("comp", comp, promotes=["*"]) + prob.setup() + prob.set_val("comp.hydrogen_in", hydrogen_in, units="kg/h") + prob.set_val("comp.nitrogen_in", nitrogen_in, units="kg/h") + prob.set_val("comp.electricity_in", electricity_in, units="kW") + prob.run_model() + + ammonia_out = prob.get_val("comp.ammonia_out", units="kg/h") + hydrogen_consumed = prob.get_val("comp.hydrogen_consumed", units="kg/h") + nitrogen_consumed = prob.get_val("comp.nitrogen_consumed", units="kg/h") + electricity_consumed = prob.get_val("comp.electricity_consumed", units="kW") + + with subtests.test("Ramp-down output remains positive below turndown"): + assert 0.0 < ammonia_out[2] < rated_capacity * dynamics_config["turndown_ratio"] + assert ammonia_out[2] == pytest.approx(600.0) + + with subtests.test("Consumption follows the positive ramp-down production"): + np.testing.assert_allclose(hydrogen_consumed, ammonia_out * hydrogen_rate) + np.testing.assert_allclose(nitrogen_consumed, ammonia_out * nitrogen_rate) + np.testing.assert_allclose(electricity_consumed, ammonia_out * performance["energy_demand"]) + assert np.all(hydrogen_consumed[ammonia_out > 0.0] > 0.0) + + with subtests.test("Leftover feed stays non-negative"): + assert np.all(prob.get_val("comp.hydrogen_out", units="kg/h") >= -1e-9) + assert np.all(prob.get_val("comp.nitrogen_out", units="kg/h") >= -1e-9) + assert np.all(prob.get_val("comp.electricity_out", units="kW") >= -1e-9) + + +@pytest.mark.unit +@pytest.mark.parametrize("dt,n_timesteps", [(3600, 5)]) +def test_ammonia_slc_command_limits_production( + plant_config, synloop_config, dynamics_config, n_timesteps, subtests +): + plant_config["system_level_control"] = {} + dynamics_config["turndown_ratio"] = 0.2 + synloop_config["model_inputs"]["performance_parameters"] = ( + synloop_config["model_inputs"]["performance_parameters"] | dynamics_config + ) + performance = synloop_config["model_inputs"]["performance_parameters"] + hydrogen_rate, nitrogen_rate = stoichiometric_feedstock_rates(performance) + hydrogen_in = np.full(n_timesteps, 100.0 * hydrogen_rate) + nitrogen_in = np.full(n_timesteps, 100.0 * nitrogen_rate) + electricity_in = np.full(n_timesteps, 100.0) + command = np.array([20.0, 30.0, 40.0, 60.0, 50.0]) + expected_ammonia = np.minimum( + command, synloop_config["model_inputs"]["shared_parameters"]["production_capacity"] + ) + + prob = om.Problem() + comp = AmmoniaSynLoopPerformanceModel( + plant_config=plant_config, + tech_config=synloop_config, + driver_config={}, + ) + prob.model.add_subsystem("comp", comp, promotes=["*"]) + prob.setup() + prob.set_val("comp.hydrogen_in", hydrogen_in, units="kg/h") + prob.set_val("comp.nitrogen_in", nitrogen_in, units="kg/h") + prob.set_val("comp.electricity_in", electricity_in, units="kW") + prob.set_val("comp.ammonia_command_value", command, units="kg/h") + prob.run_model() + + ammonia_out = prob.get_val("comp.ammonia_out", units="kg/h") + hydrogen_consumed = prob.get_val("comp.hydrogen_consumed", units="kg/h") + nitrogen_consumed = prob.get_val("comp.nitrogen_consumed", units="kg/h") + electricity_consumed = prob.get_val("comp.electricity_consumed", units="kW") + + with subtests.test("Ammonia output follows the SLC command"): + np.testing.assert_allclose(ammonia_out, expected_ammonia) + assert np.all(ammonia_out <= command) + np.testing.assert_array_equal(prob.get_val("comp.limiting_input"), [4, 4, 4, 3, 3]) + + with subtests.test("Feed consumption follows commanded production"): + np.testing.assert_allclose(hydrogen_consumed, expected_ammonia * hydrogen_rate) + np.testing.assert_allclose(nitrogen_consumed, expected_ammonia * nitrogen_rate) + np.testing.assert_allclose( + electricity_consumed, expected_ammonia * performance["energy_demand"] + ) + + with subtests.test("Leftover feed equals input minus commanded consumption"): + np.testing.assert_allclose( + prob.get_val("comp.hydrogen_out", units="kg/h"), hydrogen_in - hydrogen_consumed + ) + np.testing.assert_allclose( + prob.get_val("comp.nitrogen_out", units="kg/h"), nitrogen_in - nitrogen_consumed + ) + np.testing.assert_allclose( + prob.get_val("comp.electricity_out", units="kW"), electricity_in - electricity_consumed + ) + + +@pytest.mark.unit +@pytest.mark.parametrize("dt,n_timesteps", [(3600, 4)]) +def test_ammonia_slc_command_below_turndown_shuts_down( + plant_config, synloop_config, dynamics_config, n_timesteps, subtests +): + plant_config["system_level_control"] = {} + dynamics_config["turndown_ratio"] = 0.2 + synloop_config["model_inputs"]["performance_parameters"] = ( + synloop_config["model_inputs"]["performance_parameters"] | dynamics_config + ) + performance = synloop_config["model_inputs"]["performance_parameters"] + hydrogen_rate, nitrogen_rate = stoichiometric_feedstock_rates(performance) + + prob = om.Problem() + comp = AmmoniaSynLoopPerformanceModel( + plant_config=plant_config, + tech_config=synloop_config, + driver_config={}, + ) + prob.model.add_subsystem("comp", comp, promotes=["*"]) + prob.setup() + prob.set_val("comp.hydrogen_in", np.full(n_timesteps, 100.0 * hydrogen_rate), units="kg/h") + prob.set_val("comp.nitrogen_in", np.full(n_timesteps, 100.0 * nitrogen_rate), units="kg/h") + prob.set_val("comp.electricity_in", np.full(n_timesteps, 100.0), units="kW") + prob.set_val("comp.ammonia_command_value", np.full(n_timesteps, 5.0), units="kg/h") + prob.run_model() + + with subtests.test("Command below turndown produces no ammonia"): + assert np.all(prob.get_val("comp.ammonia_out", units="kg/h") == 0.0) + + with subtests.test("Command below turndown consumes no feed"): + assert np.all(prob.get_val("comp.hydrogen_consumed", units="kg/h") == 0.0) + assert np.all(prob.get_val("comp.nitrogen_consumed", units="kg/h") == 0.0) + assert np.all(prob.get_val("comp.electricity_consumed", units="kW") == 0.0) + + +@pytest.mark.unit +@pytest.mark.parametrize("dt,n_timesteps", [(3600, 4)]) +def test_ammonia_without_slc_retains_develop_baseline( + plant_config, synloop_config, n_timesteps, subtests +): + performance = synloop_config["model_inputs"]["performance_parameters"] + hydrogen_rate, nitrogen_rate = stoichiometric_feedstock_rates(performance) + hydrogen_in = np.full(n_timesteps, 100.0 * hydrogen_rate) + nitrogen_in = np.full(n_timesteps, 100.0 * nitrogen_rate) + electricity_in = np.array([35.0, 60.0, 25.0, 50.0]) + + prob = om.Problem() + comp = AmmoniaSynLoopPerformanceModel( + plant_config=plant_config, + tech_config=synloop_config, + driver_config={}, + ) + prob.model.add_subsystem("comp", comp, promotes=["*"]) + prob.setup() + prob.set_val("comp.hydrogen_in", hydrogen_in, units="kg/h") + prob.set_val("comp.nitrogen_in", nitrogen_in, units="kg/h") + prob.set_val("comp.electricity_in", electricity_in, units="kW") + prob.run_model() + + with subtests.test("No ammonia command input is registered without SLC"): + input_names = {name for name, _ in prob.model.list_inputs(out_stream=None)} + assert "comp.ammonia_command_value" not in input_names + + with subtests.test("Non-SLC output matches the develop baseline"): + np.testing.assert_allclose( + prob.get_val("comp.ammonia_out", units="kg/h"), [35.0, 50.0, 25.0, 50.0] + ) + + +@pytest.mark.unit +@pytest.mark.parametrize("dt,n_timesteps", [(3600, 4)]) +def test_ammonia_passthrough_controller_connects_command(plant_config, synloop_config, n_timesteps): + plant_config["system_level_control"] = {} + performance = synloop_config["model_inputs"]["performance_parameters"] + hydrogen_rate, nitrogen_rate = stoichiometric_feedstock_rates(performance) + + perf_comp = AmmoniaSynLoopPerformanceModel( + plant_config=plant_config, + tech_config=synloop_config, + driver_config={}, + ) + tech_group = om.Group() + tech_group.add_subsystem("performance", perf_comp, promotes=["*"]) + + model = object.__new__(H2IntegrateModel) + model.plant_config = plant_config + model.control_strategies = [] + model._add_passthrough_controller(tech_group, perf_comp, {}) + + prob = om.Problem(model=tech_group) + prob.setup() + command = np.full(n_timesteps, 30.0) + prob.set_val("ammonia_set_point", command, units="kg/h") + prob.set_val("hydrogen_in", np.full(n_timesteps, 100.0 * hydrogen_rate), units="kg/h") + prob.set_val("nitrogen_in", np.full(n_timesteps, 100.0 * nitrogen_rate), units="kg/h") + prob.set_val("electricity_in", np.full(n_timesteps, 100.0), units="kW") + prob.run_model() + + np.testing.assert_allclose( + prob.get_val("performance.ammonia_command_value", units="kg/h"), command + ) + np.testing.assert_allclose(prob.get_val("performance.ammonia_out", units="kg/h"), command) + + @pytest.mark.regression @pytest.mark.parametrize("dt,n_timesteps", [(3600, 20)]) def test_ammonia_warm_cold_mutual_exclusion( diff --git a/h2integrate/converters/grid/grid.py b/h2integrate/converters/grid/grid.py index b051edfe2..d842fb5c4 100644 --- a/h2integrate/converters/grid/grid.py +++ b/h2integrate/converters/grid/grid.py @@ -56,6 +56,8 @@ class GridPerformanceModel(PerformanceModelBaseClass): ) # (min, max) time step lengths (in seconds) compatible with this model _control_classifier = "dispatchable" + _is_steppable = True + def initialize(self): super().initialize() self.commodity = "electricity" @@ -146,29 +148,56 @@ def setup(self): ) def compute(self, inputs, outputs): + # Range object for the slice of the total simulation to run in this + # compute call + simulation_range = self._get_compute_time_range(inputs["timestep_index"]) + + # Scalar inputs interconnection_size = inputs["interconnection_size"] # Selling: electricity flows into grid, limited by interconnection size - electricity_sold = np.clip(inputs["electricity_in"], 0, interconnection_size) - outputs["electricity_sold"] = electricity_sold + electricity_sold = np.clip( + inputs["electricity_in"][simulation_range.start : simulation_range.stop], + 0, + interconnection_size, + ) + outputs["electricity_sold"][simulation_range.start : simulation_range.stop] = ( + electricity_sold + ) - # Buying: electricity flows out of grid to meet command value, limited by interconnection - electricity_bought = np.clip(inputs["electricity_command_value"], 0, interconnection_size) - outputs["electricity_out"] = electricity_bought + # Buying: electricity flows out of grid to meet command value, + # limited by interconnection + electricity_bought = np.clip( + inputs["electricity_command_value"][simulation_range.start : simulation_range.stop], + 0, + interconnection_size, + ) + outputs["electricity_out"][simulation_range.start : simulation_range.stop] = ( + electricity_bought + ) # Unmet demand if command value exceeds interconnection size - outputs["electricity_unmet_demand"] = ( - inputs["electricity_command_value"] - electricity_bought + outputs["electricity_unmet_demand"][simulation_range.start : simulation_range.stop] = ( + inputs["electricity_command_value"][simulation_range.start : simulation_range.stop] + - electricity_bought ) # Not sold electricity if demand exceeds interconnection size - outputs["electricity_excess"] = inputs["electricity_in"] - electricity_sold + outputs["electricity_excess"][simulation_range.start : simulation_range.stop] = ( + inputs["electricity_in"][simulation_range.start : simulation_range.stop] + - electricity_sold + ) + # Scalar outputs max_production = ( inputs["interconnection_size"] * len(outputs["electricity_out"]) * (self.dt / 3600) ) - outputs["electricity_sell_headroom"] = interconnection_size - electricity_sold - outputs["electricity_headroom"] = interconnection_size - electricity_bought + outputs["electricity_sell_headroom"][simulation_range] = ( + interconnection_size - electricity_sold + ) + outputs["electricity_headroom"][simulation_range] = ( + interconnection_size - electricity_bought + ) outputs["rated_electricity_production"] = inputs["interconnection_size"] outputs["total_electricity_produced"] = np.sum(outputs["electricity_out"]) * ( self.dt / 3600 @@ -178,7 +207,7 @@ def compute(self, inputs, outputs): 1 / self.fraction_of_year_simulated ) - total_electricity_sold = np.sum(electricity_sold) * (self.dt / 3600) + total_electricity_sold = np.sum(outputs["electricity_sold"]) * (self.dt / 3600) outputs["annual_electricity_sold"] = total_electricity_sold * ( 1 / self.fraction_of_year_simulated ) diff --git a/h2integrate/converters/hydrogen/pem_electrolyzer.py b/h2integrate/converters/hydrogen/pem_electrolyzer.py index 08912fddf..225a1737e 100644 --- a/h2integrate/converters/hydrogen/pem_electrolyzer.py +++ b/h2integrate/converters/hydrogen/pem_electrolyzer.py @@ -175,7 +175,8 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): outputs["electricity_consumed"] = h2_ts.loc["Power Consumed [kWh]"] # 1 gal H2O = 3.79 kg H2O outputs["water_consumed"] = 3.79 * H2_Results["Water Hourly Consumption [kg/hr]"] - outputs["total_hydrogen_produced"] = outputs["hydrogen_out"].sum() + # outputs[...] is a view into the OpenMDAO vector, so copy before it is overwritten below. + unclipped_hydrogen_production = outputs["hydrogen_out"].copy() if not np.isfinite(H2_Results["Sim: Average Efficiency [%-HHV]"]).all(): # if no hydrogen is produced, then set efficiency to 0 rather than a NaN @@ -223,8 +224,33 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): "Annual O2 Production [kg/year]" ] - # Apply command_value from system-level controller if present + # Apply the SLC command to reported production; the PEM calculation still uses full input. if "system_level_control" in self.options["plant_config"]: command_value = inputs[f"{self.commodity}_command_value"] - commodity_out_key = f"{self.commodity}_out" - outputs[commodity_out_key] = np.minimum(outputs[commodity_out_key], command_value) + clipped_hydrogen_production = np.minimum(unclipped_hydrogen_production, command_value) + # Keep hourly consumption and coproducts proportional to the hydrogen delivered. + ts_scale = np.ones_like(unclipped_hydrogen_production, dtype=float) + # Fills ts_scale in place with clipped/unclipped; hours with no hydrogen stay at 1.0. + np.divide( + clipped_hydrogen_production, + unclipped_hydrogen_production, + out=ts_scale, + where=unclipped_hydrogen_production > 0.0, + ) + unclipped_total = np.sum(unclipped_hydrogen_production) + # Annual schedules summarize total production, so use the production-weighted clip. + annual_scale = ( + np.sum(clipped_hydrogen_production) / unclipped_total + if unclipped_total > 0.0 + else 1.0 + ) + + outputs["hydrogen_out"] = clipped_hydrogen_production + outputs["electricity_consumed"] = outputs["electricity_consumed"] * ts_scale + outputs["oxygen_out"] = outputs["oxygen_out"] * ts_scale + outputs["water_consumed"] = outputs["water_consumed"] * ts_scale + outputs["annual_hydrogen_produced"] = outputs["annual_hydrogen_produced"] * annual_scale + outputs["annual_oxygen_produced"] = outputs["annual_oxygen_produced"] * annual_scale + outputs["capacity_factor"] = outputs["capacity_factor"] * annual_scale + + outputs["total_hydrogen_produced"] = outputs["hydrogen_out"].sum() diff --git a/h2integrate/converters/hydrogen/test/test_pem_electrolyzer_performance.py b/h2integrate/converters/hydrogen/test/test_pem_electrolyzer_performance.py index ee0db019c..86e9e64dc 100644 --- a/h2integrate/converters/hydrogen/test/test_pem_electrolyzer_performance.py +++ b/h2integrate/converters/hydrogen/test/test_pem_electrolyzer_performance.py @@ -39,6 +39,20 @@ def tech_config(): return config +def _run_electrolyzer(plant_config, tech_config, power_profile, command_value=None): + prob = om.Problem() + comp = ECOElectrolyzerPerformanceModel( + plant_config=plant_config, tech_config=tech_config, driver_config={} + ) + prob.model.add_subsystem("comp", comp, promotes=["*"]) + prob.setup() + prob.set_val("comp.electricity_in", power_profile, units="MW") + if command_value is not None: + prob.set_val("comp.hydrogen_command_value", command_value, units="kg/h") + prob.run_model() + return prob + + @pytest.mark.unit def test_electrolyzer_outputs(tech_config, plant_config, subtests): plant_life = int(plant_config["plant"]["plant_life"]) @@ -243,3 +257,102 @@ def test_electrolyzer_outputs_never_operated(tech_config, plant_config, subtests with subtests.test("Zero capacity factor"): assert prob.get_val("comp.capacity_factor").sum() == 0.0 + + +@pytest.mark.unit +def test_electrolyzer_slc_clips_hydrogen_linked_outputs(tech_config, plant_config, subtests): + n_timesteps = plant_config["plant"]["simulation"]["n_timesteps"] + power_profile = np.full(n_timesteps, 32.0) + reference_prob = _run_electrolyzer(plant_config, tech_config, power_profile) + reference_hydrogen = reference_prob.get_val("comp.hydrogen_out", units="kg/h") + + command_value = np.full(n_timesteps, 1e9) + command_value[:24] = reference_hydrogen[:24] * 0.5 + slc_plant_config = plant_config | {"system_level_control": {}} + slc_prob = _run_electrolyzer( + slc_plant_config, tech_config, power_profile, command_value=command_value + ) + + hydrogen_out = slc_prob.get_val("comp.hydrogen_out", units="kg/h") + clipped_hours = (np.arange(n_timesteps) < 24) & (reference_hydrogen > 0.0) + annual_scale = hydrogen_out.sum() / reference_hydrogen.sum() + + with subtests.test("Commanded hydrogen is clipped"): + assert np.any(clipped_hours) + np.testing.assert_allclose(hydrogen_out[clipped_hours], command_value[clipped_hours]) + assert np.all(hydrogen_out <= command_value) + + with subtests.test("Electricity per hydrogen matches the unclipped operating point"): + np.testing.assert_allclose( + slc_prob.get_val("comp.electricity_consumed", units="kW")[clipped_hours] + / hydrogen_out[clipped_hours], + reference_prob.get_val("comp.electricity_consumed", units="kW")[clipped_hours] + / reference_hydrogen[clipped_hours], + rtol=1e-12, + ) + assert np.all( + slc_prob.get_val("comp.electricity_consumed", units="kW") + <= slc_prob.get_val("comp.electricity_in", units="kW") + ) + + with subtests.test("Oxygen and water scale with commanded hydrogen"): + np.testing.assert_allclose( + slc_prob.get_val("comp.oxygen_out", units="kg/h")[clipped_hours] + / hydrogen_out[clipped_hours], + reference_prob.get_val("comp.oxygen_out", units="kg/h")[clipped_hours] + / reference_hydrogen[clipped_hours], + rtol=1e-12, + ) + np.testing.assert_allclose( + slc_prob.get_val("comp.water_consumed", units="galUS/h")[clipped_hours] + / hydrogen_out[clipped_hours], + reference_prob.get_val("comp.water_consumed", units="galUS/h")[clipped_hours] + / reference_hydrogen[clipped_hours], + rtol=1e-12, + ) + + with subtests.test("Totals and annual hydrogen-linked schedules follow clipping"): + assert slc_prob.get_val("comp.total_hydrogen_produced", units="kg")[0] == pytest.approx( + hydrogen_out.sum() + ) + for output_name in ( + "annual_hydrogen_produced", + "annual_oxygen_produced", + "capacity_factor", + ): + np.testing.assert_allclose( + slc_prob.get_val(f"comp.{output_name}"), + reference_prob.get_val(f"comp.{output_name}") * annual_scale, + rtol=1e-12, + ) + + +@pytest.mark.unit +def test_electrolyzer_large_slc_command_preserves_outputs(tech_config, plant_config, subtests): + n_timesteps = plant_config["plant"]["simulation"]["n_timesteps"] + power_profile = np.full(n_timesteps, 32.0) + reference_prob = _run_electrolyzer(plant_config, tech_config, power_profile) + slc_plant_config = plant_config | {"system_level_control": {}} + slc_prob = _run_electrolyzer( + slc_plant_config, + tech_config, + power_profile, + command_value=np.full(n_timesteps, 1e9), + ) + + with subtests.test("No command input is registered without SLC"): + input_names = {name for name, _ in reference_prob.model.list_inputs(out_stream=None)} + assert "comp.hydrogen_command_value" not in input_names + + with subtests.test("A nonbinding SLC command leaves every output unchanged"): + reference_outputs = { + name: reference_prob.get_val(name).copy() + for name, _ in reference_prob.model.list_outputs(out_stream=None) + } + slc_outputs = { + name: slc_prob.get_val(name).copy() + for name, _ in slc_prob.model.list_outputs(out_stream=None) + } + assert reference_outputs.keys() == slc_outputs.keys() + for output_name in reference_outputs: + np.testing.assert_array_equal(slc_outputs[output_name], reference_outputs[output_name]) diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index e0d2257a9..d6a3ff6d0 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -126,6 +126,7 @@ class HumbertStinnEwinCostComponent(CostModelBaseClass): ) # (min, max) time step lengths (in seconds) compatible with this model def initialize(self): + super().initialize() self.options.declare("driver_config", types=dict) self.options.declare("plant_config", types=dict) self.options.declare("tech_config", types=dict) diff --git a/h2integrate/converters/iron/iron_transport.py b/h2integrate/converters/iron/iron_transport.py index 89b06f8cd..5bd065bec 100644 --- a/h2integrate/converters/iron/iron_transport.py +++ b/h2integrate/converters/iron/iron_transport.py @@ -177,6 +177,7 @@ class IronTransportCostComponent(CostModelBaseClass): ) # (min, max) time step lengths (in seconds) compatible with this model def initialize(self): + super().initialize() self.options.declare("driver_config", types=dict) self.options.declare("plant_config", types=dict) self.options.declare("tech_config", types=dict) diff --git a/h2integrate/converters/nitrogen/simple_ASU.py b/h2integrate/converters/nitrogen/simple_ASU.py index b328906fb..35fc273ac 100644 --- a/h2integrate/converters/nitrogen/simple_ASU.py +++ b/h2integrate/converters/nitrogen/simple_ASU.py @@ -301,6 +301,7 @@ class SimpleASUCostModel(CostModelBaseClass): ) # (min, max) time step lengths (in seconds) compatible with this model def initialize(self): + super().initialize() self.options.declare("plant_config", types=dict) self.options.declare("tech_config", types=dict) self.options.declare("driver_config", types=dict) diff --git a/h2integrate/converters/solar/atb_utility_pv_cost.py b/h2integrate/converters/solar/atb_utility_pv_cost.py index 2fa7fe4bf..d30e4ed38 100644 --- a/h2integrate/converters/solar/atb_utility_pv_cost.py +++ b/h2integrate/converters/solar/atb_utility_pv_cost.py @@ -32,6 +32,8 @@ class ATBUtilityPVCostModel(CostModelBaseClass): 3600, ) # (min, max) time step lengths (in seconds) compatible with this model + _is_steppable = True + def setup(self): self.config = ATBUtilityPVCostModelConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), diff --git a/h2integrate/converters/solar/solar_pysam.py b/h2integrate/converters/solar/solar_pysam.py index b735048f2..06645980d 100644 --- a/h2integrate/converters/solar/solar_pysam.py +++ b/h2integrate/converters/solar/solar_pysam.py @@ -179,6 +179,8 @@ class PYSAMSolarPlantPerformanceModel(SolarPerformanceBaseClass): 3600, ) # (min, max) time step lengths (in seconds) compatible with this model + _is_steppable = True + def setup(self): super().setup() @@ -229,6 +231,9 @@ def setup(self): # Apply user options before reading the configured angle defaults system_model.assign(design_dict) + # Flag to avoid unnecessary re-calculation of PySAM model + self._PYSAM_model_has_been_executed = False + if self.config.tilt_angle_setting == "input": tilt = self.get_initial_angle_value("tilt", system_model) self.add_input( @@ -432,75 +437,29 @@ def format_resource_data(self, solar_resource_data): reformatted_data.update({new_key: values}) return reformatted_data - def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - if inputs["system_capacity_DC"][0] <= 0: - outputs["electricity_out"] = np.zeros(self.n_timesteps) - outputs["system_capacity_AC"] = 0.0 - outputs["rated_electricity_production"] = 0.0 - outputs["total_electricity_produced"] = 0.0 - outputs["annual_electricity_produced"] = 0.0 - outputs["capacity_factor"] = 0.0 - self.apply_curtailment(outputs) - return - - if self.config.create_model_from == "default": - system_model = Pvwatts.default(self.config.config_name) - elif self.config.create_model_from == "new": - system_model = Pvwatts.new(self.config.config_name) - - system_model.assign(self.design_dict) - - if "tilt_angle" in inputs: - tilt_angle = inputs["tilt_angle"][0] - else: - # calculate the tilt angle based on site latitude (use 0 if site latitude is not input) - tilt = self.calc_tilt_angle(discrete_inputs["solar_resource_data"].get("site_lat", 0)) - # over-write the tilt angle if it was specified in the design dict - tilt_angle = self.design_dict.get("SystemDesign", {}).get("tilt", tilt) - # assign the tilt angle - system_model.value("tilt", tilt_angle) - - if "azimuth_angle" in inputs: - azimuth = inputs["azimuth_angle"][0] - else: - # calculate the azimuth angle based on site latitude or get user input azimuth angle - azimuth = self.calc_azimuth_angle( - discrete_inputs["solar_resource_data"].get("site_lat", 0) - ) - - # assign the azimuth angle - system_model.value("azimuth", azimuth) - - # assign the dc_ac_ratio - system_model.value("dc_ac_ratio", inputs["dc_ac_ratio"][0]) - - # set the system capacity - system_model.value("system_capacity", inputs["system_capacity_DC"][0]) - - solar_resource_data = discrete_inputs["solar_resource_data"] - # format solar resource data into the necessary format for PySAM - solar_resource = self.format_resource_data(solar_resource_data) - system_model.value("solar_resource_data", solar_resource) - - # run the model - system_model.execute(0) + def _compute_outputs(self, inputs, outputs): + # Range object for the slice of the total simulation to run in this + # compute call + simulation_range = self._get_compute_time_range(inputs["timestep_index"]) # assign outputs - pv_capacity_kWdc = system_model.value("system_capacity") - dc_ac_ratio = system_model.value("dc_ac_ratio") + pv_capacity_kWdc = self.system_model.value("system_capacity") + dc_ac_ratio = self.system_model.value("dc_ac_ratio") outputs["system_capacity_AC"] = pv_capacity_kWdc / dc_ac_ratio outputs["rated_electricity_production"] = outputs["system_capacity_AC"] if bool(self.design_dict.get("Lifetime", {}).get("system_use_lifetime_output", 0)): # using lifetime results # split the generation profile to have results per-year - generation_per_year = np.split(np.array(system_model.Outputs.gen), self.plant_life) + generation_per_year = np.split(np.array(self.system_model.Outputs.gen), self.plant_life) # sum the generation per-year aep_per_year = np.array(generation_per_year).sum(axis=1) # get the number of timesteps per year (should be the same for all years) n_timesteps_per_year = np.array([len(k) for k in generation_per_year]) # output the first n_timesteps of the generation profile - outputs["electricity_out"] = np.array(system_model.Outputs.gen)[: self.n_timesteps] + outputs["electricity_out"][simulation_range] = np.array(self.system_model.Outputs.gen)[ + : self.n_timesteps + ][simulation_range.start : simulation_range.stop] # make production is the max production per-year max_production = ( outputs["rated_electricity_production"] * n_timesteps_per_year * (self.dt / 3600) @@ -513,11 +472,13 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): else: # not using lifetime output, use results as-is - outputs["electricity_out"] = system_model.Outputs.gen # kW-AC + outputs["electricity_out"][simulation_range] = self.system_model.Outputs.gen[ + simulation_range.start : simulation_range.stop + ] # kW-AC max_production = ( outputs["rated_electricity_production"] * self.n_timesteps * (self.dt / 3600) ) - outputs["annual_electricity_produced"] = system_model.value("ac_annual") + outputs["annual_electricity_produced"] = self.system_model.value("ac_annual") outputs["total_electricity_produced"] = outputs["electricity_out"].sum() * ( self.dt / 3600 ) @@ -525,3 +486,74 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Apply curtailment based on set_point self.apply_curtailment(outputs) + + def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): + if inputs["system_capacity_DC"][0] <= 0: + outputs["electricity_out"] = np.zeros(self.n_timesteps) + outputs["system_capacity_AC"] = 0.0 + outputs["rated_electricity_production"] = 0.0 + outputs["total_electricity_produced"] = 0.0 + outputs["annual_electricity_produced"] = 0.0 + outputs["capacity_factor"] = 0.0 + self.apply_curtailment(outputs) + return + + simulation_range = self._get_compute_time_range(inputs["timestep_index"]) + + if not self._PYSAM_model_has_been_executed: + assert simulation_range.start == 0, ( + "PYSAM model should only be executed at the start of the simulation" + ) + + if self.config.create_model_from == "default": + self.system_model = Pvwatts.default(self.config.config_name) + elif self.config.create_model_from == "new": + self.system_model = Pvwatts.new(self.config.config_name) + + self.system_model.assign(self.design_dict) + + if "tilt_angle" in inputs: + tilt_angle = inputs["tilt_angle"][0] + else: + # calculate the tilt angle based on site latitude (use 0 if site + # latitude is not input) + tilt = self.calc_tilt_angle( + discrete_inputs["solar_resource_data"].get("site_lat", 0) + ) + # over-write the tilt angle if it was specified in the design dict + tilt_angle = self.design_dict.get("SystemDesign", {}).get("tilt", tilt) + # assign the tilt angle + self.system_model.value("tilt", tilt_angle) + + if "azimuth_angle" in inputs: + azimuth = inputs["azimuth_angle"][0] + else: + # calculate the azimuth angle based on site latitude or get user input azimuth angle + azimuth = self.calc_azimuth_angle( + discrete_inputs["solar_resource_data"].get("site_lat", 0) + ) + + # assign the azimuth angle + self.system_model.value("azimuth", azimuth) + + # assign the dc_ac_ratio + self.system_model.value("dc_ac_ratio", inputs["dc_ac_ratio"][0]) + + # set the system capacity + self.system_model.value("system_capacity", inputs["system_capacity_DC"][0]) + + solar_resource_data = discrete_inputs["solar_resource_data"] + # format solar resource data into the necessary format for PySAM + solar_resource = self.format_resource_data(solar_resource_data) + self.system_model.value("solar_resource_data", solar_resource) + + # run the model + self.system_model.execute(0) + + self._PYSAM_model_has_been_executed = True + + # Re-set model execution flag at the end of the simulation + if simulation_range.stop >= self.n_timesteps: + self._PYSAM_model_has_been_executed = False + + self._compute_outputs(inputs, outputs) diff --git a/h2integrate/converters/water/desal/desalination.py b/h2integrate/converters/water/desal/desalination.py index 7ed45943f..cab36d5a4 100644 --- a/h2integrate/converters/water/desal/desalination.py +++ b/h2integrate/converters/water/desal/desalination.py @@ -1,42 +1,96 @@ +import numpy as np from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.model_baseclass import CostModelBaseConfig -from h2integrate.core.supported_models import register -from h2integrate.converters.water.desal.desalination_baseclass import ( - DesalinationCostBaseClass, - DesalinationPerformanceBaseClass, +from h2integrate.core.model_baseclass import ( + CostModelBaseClass, + CostModelBaseConfig, + PerformanceModelBaseClass, ) +from h2integrate.core.supported_models import register + + +# SWRO: Sea Water Reverse Osmosis, water >18,000 ppm +# BWRO: Brackish Water Reverse Osmosis, water <18,000 ppm +# recovery_ratio (freshwater out / feedwater in) source: +# https://www.usbr.gov/research/dwpr/reportpdfs/report072.pdf +# specific_energy_consumption [kW*h/m**3 of freshwater] source: +# https://www.sciencedirect.com/science/article/pii/S0011916417321057 +# SWRO range is 2.5 to 4.0 kWh/m^3 and BWRO range is 1.0 to 1.5 kWh/m^3 +SALINITY_PROPERTIES = { + "seawater": {"recovery_ratio": 0.5, "specific_energy_consumption": 4.0}, + "brackish": {"recovery_ratio": 0.75, "specific_energy_consumption": 1.5}, +} @define(kw_only=True) class ReverseOsmosisPerformanceModelConfig(BaseConfig): - """Configuration class for the ReverseOsmosisDesalinationPerformanceModel. + """Configuration class for the ReverseOsmosisPerformanceModel. Args: - freshwater_kg_per_hour (float): Desalination plant capacity represented - as maximum freshwater requirements of system [kg/hr] - salinity (str): "seawater" >18,000 ppm or "brackish" <18,000 ppm - freshwater_density (float): Density of the output freshwater [kg/m**3]. - Default = 997. + freshwater_capacity_kg_per_hr (float): Desalination plant capacity, represented as + the maximum freshwater production rate [kg/h]. + salinity (str): "seawater" (>18,000 ppm) or "brackish" (<18,000 ppm). Sets the + recovery ratio (0.5 for seawater, 0.75 for brackish) and specific energy + consumption (4.0 kW*h/m**3 for seawater, 1.5 kW*h/m**3 for brackish). + freshwater_density (float, optional): Density of the output freshwater [kg/m**3]. + Defaults to 997. + mass_per_capacity (float, optional): Mass of the desalination system per unit of + freshwater capacity [kg/(m**3/h)]. Defaults to 346.7. + footprint_per_capacity (float, optional): Footprint of the desalination system per unit + of freshwater capacity [m**2/(m**3/h)]. Defaults to 0.467. """ - freshwater_kg_per_hour: float = field(validator=validators.gt(0)) - salinity: str = field(validator=validators.in_(["seawater", "brackish"])) + freshwater_capacity_kg_per_hr: float = field(validator=validators.gt(0)) + salinity: str = field(validator=validators.in_(list(SALINITY_PROPERTIES))) freshwater_density: float = field(validator=validators.gt(0), default=997) + # Mass and footprint per unit of freshwater capacity. + # Based on commercial industrial RO systems: + # https://www.appliedmembranes.com/s-series-seawater-reverse-osmosis-systems-2000-to-100000-gpd.html + # All mass and footprint estimates are scaled from the largest RO system, S-308F: + # - 436 m^3/day + # - 6330 kg + # - 762 cm (L) x 112 cm (D) x 183 cm (H) + # 436 m^3/day = 18.17 m^3/hr = 8.5 m^2, 6330 kg + # 1 m^3/hr = 0.467 m^2, 346.7 kg + # Voltage codes: 460 or 480 V / 3 phase / 60 Hz + mass_per_capacity: float = field(validator=validators.gt(0), default=346.7) + footprint_per_capacity: float = field(validator=validators.gt(0), default=0.467) + @register -class ReverseOsmosisPerformanceModel(DesalinationPerformanceBaseClass): - """ - An OpenMDAO component that computes the performance of a reverse osmosis desalination system. - Takes plantcapacitykgph input and outputs fresh water and electricity required. +class ReverseOsmosisPerformanceModel(PerformanceModelBaseClass): + """Reverse osmosis desalination performance model. + + Freshwater production at each timestep is limited by the lowest of the plant capacity, + the available electricity, and the available feedwater. + + Inputs: + electricity_in (ndarray): Electricity available to the plant [kW] at each timestep. + feedwater_in (ndarray): Feedwater available to the plant [m**3/h] at each timestep. + freshwater_capacity (float): Maximum freshwater production rate [kg/h]. + freshwater_density (float): Density of the freshwater [kg/m**3]. + + Outputs: + water_out (ndarray): Freshwater produced [m**3/h] at each timestep. + electricity_consumed (ndarray): Electricity consumed [kW] at each timestep. + feedwater_consumed (ndarray): Feedwater consumed [m**3/h] at each timestep. + mass (float): Mass of the desalination system [kg]. + footprint (float): Footprint of the desalination system [m**2]. """ _time_step_bounds = ( - 3600, + 1, 3600, ) # (min, max) time step lengths (in seconds) compatible with this model + _control_classifier = "dispatchable" + + def initialize(self): + super().initialize() + self.commodity = "water" + self.commodity_amount_units = "m**3" + self.commodity_rate_units = "m**3/h" def setup(self): super().setup() @@ -44,103 +98,112 @@ def setup(self): merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), additional_cls_name=self.__class__.__name__, ) - self.add_output( + + self.add_input( "electricity_in", val=0.0, + shape=self.n_timesteps, units="kW", - desc="Electricity required to run desalination plant", + desc="Electricity available to the desalination plant", + ) + self.add_input( + "feedwater_in", + val=0.0, + shape=self.n_timesteps, + units="m**3/h", + desc="Feedwater available to the desalination plant", + ) + self.add_input( + "freshwater_capacity", + val=self.config.freshwater_capacity_kg_per_hr, + units="kg/h", + desc="Maximum freshwater production rate", + ) + self.add_input( + "freshwater_density", + val=self.config.freshwater_density, + units="kg/m**3", + desc="Density of the freshwater", ) - self.add_output("feedwater", val=0.0, units="m**3/h", desc="Feedwater flow rate") + + self.add_output( + "electricity_consumed", + val=0.0, + shape=self.n_timesteps, + units="kW", + desc="Electricity consumed by the desalination plant", + ) + self.add_output( + "feedwater_consumed", + val=0.0, + shape=self.n_timesteps, + units="m**3/h", + desc="Feedwater consumed by the desalination plant", + ) + self.add_output("mass", val=0.0, units="kg", desc="Mass of desalination system") + self.add_output("footprint", val=0.0, units="m**2", desc="Footprint of desalination system") def compute(self, inputs, outputs): - freshwater_m3_per_hr = self.config.freshwater_kg_per_hour / self.config.freshwater_density - - if self.config.salinity == "seawater": - # SWRO: Sea Water Reverse Osmosis, water >18,000 ppm - # Water recovery - recovery_ratio = 0.5 # https://www.usbr.gov/research/dwpr/reportpdfs/report072.pdf - feedwater_m3_per_hr = freshwater_m3_per_hr / recovery_ratio - - # Power required - energy_conversion_factor = ( - 4.0 # [kWh/m^3] SWRO energy_conversion_factor range 2.5 to 4.0 kWh/m^3 - ) - # https://www.sciencedirect.com/science/article/pii/S0011916417321057 - desal_power = freshwater_m3_per_hr * energy_conversion_factor - - if self.config.salinity == "brackish": - # BWRO: Brackish water Reverse Osmosis, water < 18,000 ppm - # Water recovery - recovery_ratio = 0.75 # https://www.usbr.gov/research/dwpr/reportpdfs/report072.pdf - feedwater_m3_per_hr = freshwater_m3_per_hr / recovery_ratio - - # Power required - energy_conversion_factor = ( - 1.5 # [kWh/m^3] BWRO energy_conversion_factor range 1.0 to 1.5 kWh/m^3 - ) - # https://www.sciencedirect.com/science/article/pii/S0011916417321057 - - desal_power = freshwater_m3_per_hr * energy_conversion_factor - - """Note: Mass and Footprint - Based on Commercial Industrial RO Systems - https://www.appliedmembranes.com/s-series-seawater-reverse-osmosis-systems-2000-to-100000-gpd.html - - All Mass and Footprint Estimates are estimated from Largest RO System: - S-308F - -436 m^3/day - -6330 kg - -762 cm (L) x 112 cm (D) x 183 cm (H) - - 436 m^3/day = 18.17 m^3/hr = 8.5 m^2, 6330 kg - 1 m^3/hr = .467 m^2, 346.7 kg - - Voltage Codes - 460 or 480v/ 3ph/ 60 Hz - """ - desal_mass_kg = freshwater_m3_per_hr * 346.7 # [kg] - desal_size_m2 = freshwater_m3_per_hr * 0.467 # [m^2] - - outputs["water_out"] = freshwater_m3_per_hr - outputs["total_water_produced"] = outputs["water_out"].sum() - outputs["rated_water_production"] = outputs["water_out"].max() + # Calculate the rated freshwater production capacity as a rate of volume. + capacity = inputs["freshwater_capacity"] / inputs["freshwater_density"] # m**3/h + + recovery_ratio = SALINITY_PROPERTIES[self.config.salinity]["recovery_ratio"] + specific_energy = SALINITY_PROPERTIES[self.config.salinity]["specific_energy_consumption"] + + # Freshwater possible given the available electricity and feedwater + water_from_electricity = inputs["electricity_in"] / specific_energy + water_from_feedwater = inputs["feedwater_in"] * recovery_ratio + + water_out = np.minimum.reduce( + np.broadcast_arrays(capacity, water_from_electricity, water_from_feedwater) + ).clip(min=0.0) + + outputs["water_out"] = water_out + outputs["electricity_consumed"] = water_out * specific_energy + outputs["feedwater_consumed"] = water_out / recovery_ratio + + hours_per_timestep = self.dt / 3600 # rates are per hour, so scale by timestep length + outputs["total_water_produced"] = water_out.sum() * hours_per_timestep + outputs["rated_water_production"] = capacity outputs["capacity_factor"] = outputs["total_water_produced"] / ( - outputs["rated_water_production"] * len(outputs["water_out"]) + capacity * self.n_timesteps * hours_per_timestep ) - outputs["annual_water_produced"] = outputs["total_water_produced"] * ( - 1 / self.fraction_of_year_simulated + outputs["annual_water_produced"] = outputs["total_water_produced"] / ( + self.fraction_of_year_simulated ) - outputs["electricity_in"] = desal_power - outputs["feedwater"] = feedwater_m3_per_hr - outputs["mass"] = desal_mass_kg - outputs["footprint"] = desal_size_m2 + outputs["mass"] = capacity * self.config.mass_per_capacity + outputs["footprint"] = capacity * self.config.footprint_per_capacity @define(kw_only=True) class ReverseOsmosisCostModelConfig(CostModelBaseConfig): - """Configuration class for the ReverseOsmosisDesalinationCostModel. + """Configuration class for the ReverseOsmosisCostModel. Args: - freshwater_kg_per_hour (float): Desalination plant capacity represented as - maximum freshwater requirements of system [kg/hr] - freshwater_density (float): Density of the output freshwater [kg/m**3]. - Default = 997. + freshwater_capacity_kg_per_hr (float): Desalination plant capacity, represented as + the maximum freshwater production rate [kg/h]. + cost_year (int): Dollar year of the costs. Only 2013 is supported. """ - freshwater_kg_per_hour: float = field(validator=validators.gt(0)) - freshwater_density: float = field(validator=validators.gt(0)) + freshwater_capacity_kg_per_hr: float = field(validator=validators.gt(0)) cost_year: int = field(default=2013, converter=int, validator=validators.in_([2013])) @register -class ReverseOsmosisCostModel(DesalinationCostBaseClass): - """ - An OpenMDAO component that computes the cost of a reverse osmosis desalination system. +class ReverseOsmosisCostModel(CostModelBaseClass): + """Reverse osmosis desalination cost model. + + Inputs: + rated_freshwater_production_capacity (float): Maximum freshwater production rate [kg/s]. + + Outputs: + CapEx (float): Capital expenditure [USD]. + OpEx (float): Fixed operating expenditure [USD/year]. """ _time_step_bounds = ( - 3600, + 1, 3600, ) # (min, max) time step lengths (in seconds) compatible with this model @@ -150,6 +213,12 @@ def setup(self): additional_cls_name=self.__class__.__name__, ) super().setup() + self.add_input( + "rated_freshwater_production_capacity", + val=self.config.freshwater_capacity_kg_per_hr / 3600, + units="kg/s", + desc="Maximum freshwater production rate", + ) def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): """Cost reference: Table 3 of https://www.nlr.gov/docs/fy16osti/66073.pdf. @@ -158,9 +227,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): https://www-pub.iaea.org/MTCD/Publications/PDF/te_1561_web.pdf (Table 6 says 2006 is the currency reference year) """ - desal_capex = 32894 * (self.config.freshwater_kg_per_hour / 3600) # [USD] - - desal_opex = 4841 * (self.config.freshwater_kg_per_hour / 3600) # [USD/yr] + capacity = inputs["rated_freshwater_production_capacity"] - outputs["CapEx"] = desal_capex - outputs["OpEx"] = desal_opex + outputs["CapEx"] = 32894 * capacity # USD + outputs["OpEx"] = 4841 * capacity # USD/year diff --git a/h2integrate/converters/water/desal/desalination_baseclass.py b/h2integrate/converters/water/desal/desalination_baseclass.py deleted file mode 100644 index d02b305a2..000000000 --- a/h2integrate/converters/water/desal/desalination_baseclass.py +++ /dev/null @@ -1,44 +0,0 @@ -from h2integrate.core.model_baseclass import CostModelBaseClass, PerformanceModelBaseClass - - -class DesalinationPerformanceBaseClass(PerformanceModelBaseClass): - _time_step_bounds = ( - 3600, - 3600, - ) # (min, max) time step lengths (in seconds) compatible with this model - _control_classifier = "dispatchable" - - def initialize(self): - super().initialize() - self.commodity = "water" - self.commodity_amount_units = "m**3" - self.commodity_rate_units = "m**3/h" - - def setup(self): - super().setup() - - self.add_output("mass", val=0.0, units="kg", desc="Mass of desalination system") - self.add_output("footprint", val=0.0, units="m**2", desc="Footprint of desalination system") - - def compute(self, inputs, outputs): - """ - Computation for the OM component. - - For a template class this is not implement and raises an error. - """ - - raise NotImplementedError("This method should be implemented in a subclass.") - - -class DesalinationCostBaseClass(CostModelBaseClass): - _time_step_bounds = ( - 3600, - 3600, - ) # (min, max) time step lengths (in seconds) compatible with this model - - def setup(self): - super().setup() - # Inputs for cost model configuration - self.add_input( - "plant_capacity_kgph", val=0.0, units="kg/h", desc="Desired freshwater flow rate" - ) diff --git a/h2integrate/converters/water/desal/test/test_ro_desalination.py b/h2integrate/converters/water/desal/test/test_ro_desalination.py index c160c5635..6d893c731 100644 --- a/h2integrate/converters/water/desal/test/test_ro_desalination.py +++ b/h2integrate/converters/water/desal/test/test_ro_desalination.py @@ -4,42 +4,52 @@ from pytest import approx, fixture from h2integrate.converters.water.desal.desalination import ( + SALINITY_PROPERTIES, ReverseOsmosisCostModel, ReverseOsmosisPerformanceModel, + ReverseOsmosisPerformanceModelConfig, ) +N_TIMESTEPS = 8760 + + @fixture def plant_config(): plant = { "plant_life": 30, "simulation": { "dt": 3600, - "n_timesteps": 8760, + "n_timesteps": N_TIMESTEPS, }, } return {"plant": plant} -@pytest.mark.unit -def test_brackish_desal_outputs(plant_config, subtests): - tech_config = { - "model_inputs": { - "performance_parameters": { - "freshwater_kg_per_hour": 10000, - "salinity": "brackish", - "freshwater_density": 997, - }, - } +def make_problem(plant_config, salinity="brackish", electricity=1e4, feedwater=1e4, **params): + """Set up a performance model with ample (or user-specified) electricity and feedwater.""" + performance_parameters = { + "freshwater_capacity_kg_per_hr": 10000, + "salinity": salinity, + "freshwater_density": 997, + **params, } + tech_config = {"model_inputs": {"performance_parameters": performance_parameters}} prob = om.Problem() comp = ReverseOsmosisPerformanceModel(plant_config=plant_config, tech_config=tech_config) prob.model.add_subsystem("comp", comp, promotes=["*"]) - prob.setup() + prob.set_val("electricity_in", electricity, units="kW") + prob.set_val("feedwater_in", feedwater, units="m**3/h") prob.run_model() + return prob + + +@pytest.mark.unit +def test_brackish_desal_outputs(plant_config, subtests): + prob = make_problem(plant_config) commodity = "water" commodity_amount_units = "m**3" @@ -47,7 +57,6 @@ def test_brackish_desal_outputs(plant_config, subtests): plant_life = int(plant_config["plant"]["plant_life"]) n_timesteps = int(plant_config["plant"]["simulation"]["n_timesteps"]) - # Check that replacement schedule is between 0 and 1 with subtests.test("0 <= replacement_schedule <=1"): assert np.all(prob.get_val("comp.replacement_schedule", units="unitless") >= 0) assert np.all(prob.get_val("comp.replacement_schedule", units="unitless") <= 1) @@ -55,64 +64,44 @@ def test_brackish_desal_outputs(plant_config, subtests): with subtests.test("replacement_schedule length"): assert len(prob.get_val("comp.replacement_schedule", units="unitless")) == plant_life - # Check that capacity factor is between 0 and 1 with units of "unitless" with subtests.test("0 <= capacity_factor (unitless) <=1"): assert np.all(prob.get_val("comp.capacity_factor", units="unitless") >= 0) assert np.all(prob.get_val("comp.capacity_factor", units="unitless") <= 1) - # Check that capacity factor is between 1 and 100 with units of "percent" - with subtests.test("1 <= capacity_factor (percent) <=1"): + with subtests.test("1 <= capacity_factor (percent) <=100"): assert np.all(prob.get_val("comp.capacity_factor", units="percent") >= 1) assert np.all(prob.get_val("comp.capacity_factor", units="percent") <= 100) with subtests.test("capacity_factor length"): assert len(prob.get_val("comp.capacity_factor", units="unitless")) == plant_life - # Test that rated commodity production is greater than zero with subtests.test(f"rated_{commodity}_production > 0"): assert np.all( prob.get_val(f"comp.rated_{commodity}_production", units=commodity_rate_units) > 0 ) - with subtests.test(f"rated_{commodity}_production length"): - assert ( - len(prob.get_val(f"comp.rated_{commodity}_production", units=commodity_rate_units)) == 1 - ) - - # Test that total commodity production is greater than zero with subtests.test(f"total_{commodity}_produced > 0"): assert np.all( prob.get_val(f"comp.total_{commodity}_produced", units=commodity_amount_units) > 0 ) - with subtests.test(f"total_{commodity}_produced length"): - assert ( - len(prob.get_val(f"comp.total_{commodity}_produced", units=commodity_amount_units)) == 1 - ) - - # Test that annual commodity production is greater than zero - with subtests.test(f"annual_{commodity}_produced > 0"): - assert np.all( - prob.get_val(f"comp.annual_{commodity}_produced", units=f"{commodity_amount_units}/yr") - > 0 - ) - with subtests.test(f"annual_{commodity}_produced[1:] == annual_{commodity}_produced[0]"): + with subtests.test(f"annual_{commodity}_produced"): annual_production = prob.get_val( f"comp.annual_{commodity}_produced", units=f"{commodity_amount_units}/yr" ) - assert np.all(annual_production[1:] == annual_production[0]) - - with subtests.test(f"annual_{commodity}_produced length"): assert len(annual_production) == plant_life - - # Test that commodity output has some values greater than zero - with subtests.test(f"Some of {commodity}_out > 0"): - assert np.any(prob.get_val(f"comp.{commodity}_out", units=commodity_rate_units) > 0) + assert np.all(annual_production > 0) + assert np.all(annual_production[1:] == annual_production[0]) with subtests.test(f"{commodity}_out length"): assert len(prob.get_val(f"comp.{commodity}_out", units=commodity_rate_units)) == n_timesteps - # Test default values + with subtests.test("input and output lengths"): + for name in ("electricity_in", "feedwater_in"): + assert len(prob.get_val(f"comp.{name}")) == n_timesteps + for name in ("electricity_consumed", "feedwater_consumed"): + assert len(prob.get_val(f"comp.{name}")) == n_timesteps + with subtests.test("operational_life default value"): assert prob.get_val("comp.operational_life", units="yr") == plant_life with subtests.test("replacement_schedule value"): @@ -120,65 +109,161 @@ def test_brackish_desal_outputs(plant_config, subtests): @pytest.mark.regression -def test_brackish_performance(plant_config, subtests): - tech_config = { - "model_inputs": { - "performance_parameters": { - "freshwater_kg_per_hour": 10000, - "salinity": "brackish", - "freshwater_density": 997, - }, - } - } - - prob = om.Problem() - comp = ReverseOsmosisPerformanceModel(plant_config=plant_config, tech_config=tech_config) - prob.model.add_subsystem("comp", comp, promotes=["*"]) - - prob.setup() - prob.run_model() +@pytest.mark.parametrize( + "salinity,feedwater,electricity", + [("brackish", 13.37, 15.04), ("seawater", 20.06, 40.12)], +) +def test_performance_at_capacity(plant_config, salinity, feedwater, electricity, subtests): + prob = make_problem(plant_config, salinity=salinity) with subtests.test("fresh water"): - assert prob.get_val("water_out", units="m**3/h") == approx(10.03, rel=1e-5) + assert prob.get_val("water_out", units="m**3/h") == approx(10.03, rel=1e-3) with subtests.test("mass"): assert prob.get_val("mass", units="kg") == approx(3477.43, rel=1e-3) with subtests.test("footprint"): assert prob.get_val("footprint", units="m**2") == approx(4.68, rel=1e-3) - with subtests.test("feedwater"): - assert prob.get_val("feedwater", units="m**3/h") == approx(13.37, rel=1e-3) - with subtests.test("electricity"): - assert prob.get_val("electricity_in", units="kW") == approx(15.04, rel=1e-3) + with subtests.test("feedwater consumed"): + assert prob.get_val("feedwater_consumed", units="m**3/h") == approx(feedwater, rel=1e-3) + with subtests.test("electricity consumed"): + assert prob.get_val("electricity_consumed", units="kW") == approx(electricity, rel=1e-3) + with subtests.test("capacity factor"): + assert prob.get_val("capacity_factor", units="unitless") == approx(1.0) @pytest.mark.regression -def test_seawater_performance(plant_config, subtests): - tech_config = { - "model_inputs": { - "performance_parameters": { - "freshwater_kg_per_hour": 10000, - "salinity": "seawater", - "freshwater_density": 997, - }, +def test_limiting_resource(plant_config, subtests): + capacity = 10000 / 997 # m**3/h + + # Seawater: 0.5 recovery and 4 kWh/m^3. Alternate which resource is limiting. + electricity = np.full(N_TIMESTEPS, 1e4) + feedwater = np.full(N_TIMESTEPS, 1e4) + electricity[0] = 20.0 # limits to 5 m^3/h + feedwater[1] = 6.0 # limits to 3 m^3/h + electricity[2] = 0.0 # no electricity, no water + electricity[3] = 20.0 # electricity limits to 5 m^3/h, feedwater would allow 10 m^3/h + feedwater[3] = 20.0 + + prob = make_problem( + plant_config, salinity="seawater", electricity=electricity, feedwater=feedwater + ) + water_out = prob.get_val("water_out", units="m**3/h") + + with subtests.test("electricity limited"): + assert water_out[0] == approx(5.0) + with subtests.test("feedwater limited"): + assert water_out[1] == approx(3.0) + with subtests.test("no electricity"): + assert water_out[2] == 0.0 + with subtests.test("both limiting"): + assert water_out[3] == approx(5.0) + with subtests.test("capacity limited"): + assert water_out[4] == approx(capacity) + + with subtests.test("consumption matches production"): + assert prob.get_val("electricity_consumed", units="kW") == approx(water_out * 4.0) + assert prob.get_val("feedwater_consumed", units="m**3/h") == approx(water_out / 0.5) + + with subtests.test("total production"): + assert prob.get_val("total_water_produced", units="m**3") == approx(water_out.sum()) + with subtests.test("capacity factor"): + assert prob.get_val("capacity_factor", units="unitless") == approx( + water_out.sum() / (capacity * N_TIMESTEPS) + ) + + +@pytest.mark.regression +def test_config_values_are_inputs(plant_config, subtests): + prob = make_problem(plant_config, salinity="seawater") + base_water = prob.get_val("water_out", units="m**3/h")[0] + + with subtests.test("config values are inputs"): + for name in ("freshwater_capacity", "freshwater_density"): + assert name in prob.model.comp._var_rel_names["input"] + + with subtests.test("capacity can be changed"): + prob.set_val("freshwater_capacity", 5000, units="kg/h") + prob.run_model() + assert prob.get_val("water_out", units="m**3/h")[0] == approx(base_water / 2) + assert prob.get_val("mass", units="kg") == approx(3477.43 / 2, rel=1e-3) + + with subtests.test("density can be changed"): + prob.set_val("freshwater_capacity", 10000, units="kg/h") + prob.set_val("freshwater_density", 1994, units="kg/m**3") + prob.run_model() + assert prob.get_val("water_out", units="m**3/h")[0] == approx(base_water / 2) + + +@pytest.mark.regression +@pytest.mark.parametrize("salinity", ["seawater", "brackish"]) +def test_timestep_independence(salinity, subtests): + """Uniform inputs over half a year give the same answers at 5 minute and 1 hour steps.""" + results = {} + for dt, n_timesteps in [(300, 52560), (3600, 4380)]: + config = {"plant": {"plant_life": 30, "simulation": {"dt": dt, "n_timesteps": n_timesteps}}} + # Electricity limits production at 5 m^3/h, below the 10.03 m^3/h capacity + electricity = 5.0 * (4.0 if salinity == "seawater" else 1.5) + prob = make_problem(config, salinity=salinity, electricity=electricity, feedwater=1e4) + results[dt] = { + name: prob.get_val(name, units=units) + for name, units in [ + ("water_out", "m**3/h"), + ("electricity_consumed", "kW"), + ("feedwater_consumed", "m**3/h"), + ("total_water_produced", "m**3"), + ("annual_water_produced", "m**3/year"), + ("capacity_factor", "unitless"), + ("rated_water_production", "m**3/h"), + ] } - } - prob = om.Problem() - comp = ReverseOsmosisPerformanceModel(plant_config=plant_config, tech_config=tech_config) - prob.model.add_subsystem("comp", comp, promotes=["*"]) + fast, slow = results[300], results[3600] - prob.setup() - prob.run_model() + for name in ("electricity_consumed", "feedwater_consumed", "rated_water_production"): + with subtests.test(f"{name} rate"): + assert fast[name][0] == approx(slow[name][0]) + + with subtests.test("water_out rate"): + assert fast["water_out"][0] == approx(5.0) + assert slow["water_out"][0] == approx(5.0) + + with subtests.test("total_water_produced"): + assert fast["total_water_produced"] == approx(slow["total_water_produced"]) + assert slow["total_water_produced"] == approx(5.0 * 4380) + + with subtests.test("annual_water_produced"): + assert fast["annual_water_produced"] == approx(slow["annual_water_produced"]) + assert slow["annual_water_produced"][0] == approx(5.0 * 8760) + + with subtests.test("capacity_factor"): + assert fast["capacity_factor"] == approx(slow["capacity_factor"]) - with subtests.test("fresh water"): - assert prob.get_val("water_out", units="m**3/h") == approx(10.03, rel=1e-5) - with subtests.test("mass"): - assert prob.get_val("mass", units="kg") == approx(3477.43, rel=1e-3) - with subtests.test("footprint"): - assert prob.get_val("footprint", units="m**2") == approx(4.68, rel=1e-3) - with subtests.test("feedwater"): - assert prob.get_val("feedwater", units="m**3/h") == approx(20.06, rel=1e-5) - with subtests.test("electricity"): - assert prob.get_val("electricity_in", units="kW") == approx(40.12, rel=1e-5) + +@pytest.mark.regression +def test_one_second_timestep(subtests): + config = {"plant": {"plant_life": 30, "simulation": {"dt": 1, "n_timesteps": 3600}}} + prob = make_problem(config, salinity="brackish", electricity=7.5, feedwater=1e4) + + with subtests.test("water_out rate"): + assert prob.get_val("water_out", units="m**3/h")[0] == approx(5.0) + with subtests.test("one hour of production"): + assert prob.get_val("total_water_produced", units="m**3") == approx(5.0) + with subtests.test("annual production"): + assert prob.get_val("annual_water_produced", units="m**3/year")[0] == approx(5.0 * 8760) + + +@pytest.mark.unit +def test_config_salinity_values(subtests): + with subtests.test("seawater values"): + assert SALINITY_PROPERTIES["seawater"]["recovery_ratio"] == 0.5 + assert SALINITY_PROPERTIES["seawater"]["specific_energy_consumption"] == 4.0 + + with subtests.test("brackish values"): + assert SALINITY_PROPERTIES["brackish"]["recovery_ratio"] == 0.75 + assert SALINITY_PROPERTIES["brackish"]["specific_energy_consumption"] == 1.5 + + with subtests.test("invalid salinity"): + with pytest.raises(ValueError): + ReverseOsmosisPerformanceModelConfig(freshwater_capacity_kg_per_hr=1, salinity="fresh") @pytest.mark.regression @@ -186,8 +271,7 @@ def test_ro_desalination_cost(subtests): tech_config = { "model_inputs": { "cost_parameters": { - "freshwater_kg_per_hour": 10000, - "freshwater_density": 997, + "freshwater_capacity_kg_per_hr": 10000, }, } } @@ -213,3 +297,8 @@ def test_ro_desalination_cost(subtests): assert prob.get_val("CapEx", units="USD") == approx(91372, rel=1e-2) with subtests.test("opex"): assert prob.get_val("OpEx", units="USD/year") == approx(13447, rel=1e-2) + + with subtests.test("capacity is an input"): + prob.set_val("rated_freshwater_production_capacity", 20000 / 3600, units="kg/s") + prob.run_model() + assert prob.get_val("CapEx", units="USD") == approx(2 * 91372, rel=1e-2) diff --git a/h2integrate/core/annualization.py b/h2integrate/core/annualization.py new file mode 100644 index 000000000..6e2d05d03 --- /dev/null +++ b/h2integrate/core/annualization.py @@ -0,0 +1,455 @@ +"""Annual capacity-factor projection and replacement-cycle calculations. + +The public names (``SECONDS_PER_PLANT_YEAR``, ``VALID_ANNUAL_EXTRAPOLATION_METHODS``, +``time_weighted_annual_means``, ``extrapolate_annual_values``) are shared with the finance +components. Underscore-prefixed helpers implement the degradation and replacement logic and are +meant to be called through +``PerformanceModelBaseClass.calculate_annual_cf_and_replacement_schedule``. +""" + +import math +from itertools import pairwise + +import numpy as np + + +# H2Integrate plant years are always 365 days (8760 h); leap days are not modeled. +SECONDS_PER_PLANT_YEAR = 31_536_000 + +VALID_ANNUAL_EXTRAPOLATION_METHODS = ("tile", "final_sim_value", "time_averaged_sim_value") + + +def extrapolate_annual_values( + simulated_values: np.ndarray, + plant_life: int, + method: str | None, + simulated_durations_years: np.ndarray | None = None, +) -> np.ndarray: + """Extend or truncate simulated annual values to the plant lifetime. + + Preserve the simulated values first, then fill any remaining plant years + using the selected method. The average method weights each supplied value + by the duration it represents, so a partial year contributes proportionally. + + Args: + simulated_values (np.ndarray): Nonempty, one-dimensional array with one + value per full or partial simulated year. + plant_life (int): Positive integer number of plant years to return. + method (str | None): ``"tile"`` to repeat the values cyclically, + ``"final_sim_value"`` to hold the last value, or + ``"time_averaged_sim_value"`` to hold their time-weighted mean. If None, + a method is required only when multiple simulated segments need to + be extended to cover plant life; a single segment repeats naturally. + simulated_durations_years (np.ndarray | None): Positive elapsed duration + in years for each supplied value. Used only by the average method. + Defaults to ``None``, treating all values as equal-duration years. + + Returns: + np.ndarray: One value for each of the ``plant_life`` years, in the + same units as the supplied values. + + Raises: + ValueError: If ``method`` is unsupported or needed but not provided. + """ + if method is not None and method not in VALID_ANNUAL_EXTRAPOLATION_METHODS: + raise ValueError( + f"extrapolation method must be one of {VALID_ANNUAL_EXTRAPOLATION_METHODS}; " + f"got {method!r}." + ) + + n_sim_years = simulated_values.size + if n_sim_years >= plant_life: + return simulated_values[:plant_life].copy() + + if method is None: + if n_sim_years == 1: + method = "tile" + else: + raise ValueError( + "An annual_extrapolation_method is required to extrapolate multiple simulated " + "annual values across the remaining plant life. Set it in " + "plant_config['plant']['simulation']['annual_extrapolation_method']." + ) + + if method == "tile": + n_tiles = math.ceil(plant_life / n_sim_years) + return np.tile(simulated_values, n_tiles)[:plant_life] + + fill_value = ( + simulated_values[-1] + if method == "final_sim_value" + else float(np.average(simulated_values, weights=simulated_durations_years)) + ) + projected = np.full(plant_life, fill_value, dtype=float) + projected[:n_sim_years] = simulated_values[:plant_life] + return projected + + +def _validate_annualization_inputs( + performance_timeseries, + rated_performance, + state_of_health_timeseries, + eol_soh, + no_degradation_extrapolation, + soh_cycle_repetition, +): + """Validate and normalize performance, SOH, and annualization options. + + Validate both extrapolation options regardless of whether SOH is supplied. + When SOH is absent, leave it as ``None`` and do not validate ``eol_soh``. + Timestep duration and plant lifetime are validated by the calling model. + + Args: + performance_timeseries (array-like): Nonempty, finite, one-dimensional + performance rates at equal timesteps. + rated_performance (float): Finite, positive rated performance in the + same units as the performance rates. + state_of_health_timeseries (array-like | None): Finite SOH fractions in + [0, 1], matching the performance array's shape, or ``None`` to + disable degradation and replacement projection. + eol_soh (float | None): EOL threshold in [0, 1]. Required only when SOH + is supplied. + no_degradation_extrapolation (str): ``"tile"``, ``"final_sim_value"``, + or ``"time_averaged_sim_value"`` for extrapolating without SOH. + soh_cycle_repetition (str): ``"all_soh_cycles"`` to repeat all cycles + or ``"final_soh_cycle"`` to repeat the final cycle. + + Returns: + tuple[np.ndarray, float, np.ndarray | None, bool]: Float performance + array, scalar rated performance, float SOH array or ``None``, and a flag + indicating whether SOH was supplied, in that order. + + Raises: + ValueError: If an array is empty, nonfinite, or has an invalid shape; + rated performance is not finite and positive; supplied SOH or its + threshold is invalid; or either extrapolation option is unsupported. + """ + performance_timeseries = np.asarray(performance_timeseries, dtype=float) + if performance_timeseries.size == 0: + raise ValueError("performance_timeseries must contain at least one value.") + if performance_timeseries.ndim != 1 or not np.all(np.isfinite(performance_timeseries)): + raise ValueError("performance_timeseries must be a finite one-dimensional array.") + + rated_performance = float(rated_performance) + if not np.isfinite(rated_performance) or rated_performance <= 0.0: + raise ValueError("rated_performance must be finite and greater than zero.") + + if ( + no_degradation_extrapolation is not None + and no_degradation_extrapolation not in VALID_ANNUAL_EXTRAPOLATION_METHODS + ): + raise ValueError( + "no_degradation_extrapolation must be one of " + f"{VALID_ANNUAL_EXTRAPOLATION_METHODS}; got {no_degradation_extrapolation!r}." + ) + + valid_soh_cycle_repetition_modes = ("all_soh_cycles", "final_soh_cycle") + if soh_cycle_repetition not in valid_soh_cycle_repetition_modes: + raise ValueError( + "soh_cycle_repetition must be one of " + f"{valid_soh_cycle_repetition_modes}; got {soh_cycle_repetition!r}." + ) + + use_degradation = state_of_health_timeseries is not None + if use_degradation: + if eol_soh is None: + raise ValueError("eol_soh is required when state_of_health_timeseries is provided.") + state_of_health_timeseries = np.asarray(state_of_health_timeseries, dtype=float) + if state_of_health_timeseries.size == 0: + raise ValueError( + "state_of_health_timeseries must contain at least one value when provided." + ) + if ( + state_of_health_timeseries.ndim != 1 + or not np.all(np.isfinite(state_of_health_timeseries)) + or state_of_health_timeseries.shape != performance_timeseries.shape + ): + raise ValueError( + "state_of_health_timeseries must be finite, one-dimensional, and match " + "performance_timeseries length." + ) + if not np.isfinite(eol_soh) or not 0.0 <= eol_soh <= 1.0: + raise ValueError("eol_soh must be finite and between zero and one.") + if np.any((state_of_health_timeseries < 0.0) | (state_of_health_timeseries > 1.0)): + raise ValueError("state_of_health_timeseries values must be between zero and one.") + + return performance_timeseries, rated_performance, state_of_health_timeseries, use_degradation + + +def time_weighted_annual_means( + timestep_values: np.ndarray, dt: float, start_year: float = 0.0 +) -> tuple[np.ndarray, np.ndarray]: + """Calculate time-weighted means over fixed 365-day plant-year segments. + + Split the series at integer elapsed plant years, preserving its actual + start and end. A timestep crossing a boundary contributes to both segments + according to its duration in each. Partial-year means use their actual + durations rather than treating the unobserved part of a year as zero. + + Args: + timestep_values (np.ndarray): Nonempty, one-dimensional values, each + constant over its timestep, such as CF or cost per timestep. + dt (float): Positive duration of each timestep in seconds. + start_year (float): Elapsed 365-day plant years at the start of the + series, not a calendar year. Defaults to zero. + + Returns: + tuple[np.ndarray, np.ndarray]: Equally sized arrays of time-weighted + means and durations in years for each segment, in chronological order. + Means retain the input values' units. The first and final segments can + be shorter than one year. + """ + # Keep the series' actual start and end, splitting at intervening plant-year boundaries. + sample_duration = dt / SECONDS_PER_PLANT_YEAR + sample_edges = start_year + (np.arange(timestep_values.size + 1) * dt) / SECONDS_PER_PLANT_YEAR + annual_edges = np.arange(math.floor(start_year) + 1, math.ceil(sample_edges[-1]), dtype=float) + segment_edges = np.concatenate(([start_year], annual_edges, [sample_edges[-1]])) + durations = np.diff(segment_edges) + + # Accumulate values multiplied by elapsed time, starting with zero before the first sample. + cumulative_values = np.concatenate(([0.0], np.cumsum(timestep_values))) * sample_duration + + # Find running totals at segment boundaries and subtract adjacent totals. + # If 4 hours of a 10-hour timestep fall in one year, that year receives 40% + # of its contribution and the next year receives 60%. + segment_totals = np.diff(np.interp(segment_edges, sample_edges, cumulative_values)) + + # Divide by each segment's actual duration to get its mean, including for partial years. + return segment_totals / durations, durations + + +def _degraded_replacement_cycles( + timestep_capacity_factors: np.ndarray, + state_of_health_timeseries: np.ndarray, + eol_soh: float, + dt: float, + plant_life: int, +) -> tuple[list[np.ndarray], list[bool], list[np.ndarray]]: + """Complete timestep-level cycles before aggregating their annual CF segments. + + An upward state-of-health (SOH) jump greater than 1e-9 marks an observed + replacement at the first sample with reset SOH, closing the preceding cycle. + Reaching the end-of-life (EOL) threshold in observed data is not itself a + replacement; the trailing observed cycle always remains open initially. + + Extend that trailing cycle using the nonnegative SOH loss between its last + two samples. Project at timestep resolution until EOL is reached, within a + tolerance of 1e-12, or the remaining plant life is covered. A projected + replacement requires at least one future timestep, even if the last observed + SOH is already at or below EOL. Without measurable loss, extend by one step + only when the last observed SOH is already at or below EOL; otherwise leave + the cycle incomplete. No extension is made if no plant life remains. + + Projected CF scales the last observed timestep's CF by projected SOH divided + by the last observed SOH. Negative projected SOH and CF projected from zero + observed SOH give zero CF. The extension uses whole timesteps, so its final + timestep may extend past retirement; downstream projection clips that excess. + + Aggregate each cycle's CF into time-weighted means, splitting at fixed + 365-day plant-year boundaries and retaining partial-year segment durations. + This helper does not repeat cycles or build the annual replacement schedule. + + Args: + timestep_capacity_factors (np.ndarray): Nonempty, one-dimensional, + dimensionless CF values, each constant over its simulation timestep. + state_of_health_timeseries (np.ndarray): One-dimensional SOH fractions + in [0, 1], aligned with and the same length as the timestep CF array. + eol_soh (float): SOH threshold in [0, 1] used to predict replacement in + projected data, not to detect replacement in observed data. + dt (float): Positive duration of each simulation timestep in seconds. + plant_life (int): Positive plant lifetime in 365-day years, limiting + extension of the final cycle beyond the simulation. + + Returns: + tuple[list[np.ndarray], list[bool], list[np.ndarray]]: Three parallel + lists in cycle order: arrays of segment mean CF, flags indicating an + observed or projected replacement at each cycle's end, and arrays of + segment durations in years. Each duration array matches its CF array + element by element; its sum is the cycle duration. The final replacement + flag can remain false when the cycle cannot be completed. + """ + sample_duration = dt / SECONDS_PER_PLANT_YEAR + simulation_duration = state_of_health_timeseries.size * sample_duration + # The difference index identifies the preceding sample; +1 identifies the first + # sample with reset SOH, which starts the new unit's cycle. + reset_indices = np.flatnonzero(np.diff(state_of_health_timeseries) > 1e-9) + 1 + trailing_start_index = int(reset_indices[-1]) if reset_indices.size else 0 + + # Estimate future SOH loss from the last two samples of the current unit only. + # A single sample or a nondecreasing SOH cannot establish a degradation rate. + trailing_soh = state_of_health_timeseries[trailing_start_index:] + degradation_per_step = ( + max(float(trailing_soh[-2] - trailing_soh[-1]), 0.0) if trailing_soh.size >= 2 else 0.0 + ) + last_observed_soh = float(trailing_soh[-1]) + trailing_has_replacement = False + if degradation_per_step > 0.0 or last_observed_soh <= eol_soh: + # Round up to the first future timestep reaching EOL, never an observed sample. + # Require at least one step even if already below EOL; allow for SOH roundoff. + steps_to_eol = 1 + if degradation_per_step > 0.0: + steps_to_eol = max( + 1, math.ceil((last_observed_soh - eol_soh - 1e-12) / degradation_per_step) + ) + # Extend only as far as the timesteps needed to cover the remaining plant life. + remaining_steps = max(0, math.ceil((plant_life - simulation_duration) / sample_duration)) + projection_steps = min(steps_to_eol, remaining_steps) + projected_soh = last_observed_soh - degradation_per_step * np.arange( + 1, projection_steps + 1 + ) + # Scale the last observed CF by the fraction of its SOH remaining at each step. + # Clip negative SOH to zero; zero starting SOH gives zero CF without division. + extrapolated_cf = np.zeros(projection_steps) + if last_observed_soh > 0.0: + extrapolated_cf = ( + timestep_capacity_factors[-1] * np.maximum(projected_soh, 0.0) / last_observed_soh + ) + timestep_capacity_factors = np.concatenate((timestep_capacity_factors, extrapolated_cf)) + # Mark a predicted replacement only if a future step actually reaches EOL. + # A projection cut short by plant life can leave the cycle incomplete. + trailing_has_replacement = bool( + projection_steps > 0 and projected_soh[-1] <= eol_soh + 1e-12 + ) + + # Each observed reset closes a cycle; the final cycle needs a projected replacement. + cycle_has_replacement = [True] * reset_indices.size + [trailing_has_replacement] + cycle_boundaries = np.concatenate(([0], reset_indices, [timestep_capacity_factors.size])) + # Aggregate each cycle separately so its mean CF uses only that cycle's performance. + # Split at plant-year boundaries, retaining partial segments at either end of a cycle. + cycle_cf_segments = [] + cycle_segment_durations = [] + for start_index, end_index in pairwise(cycle_boundaries): + capacity_factors, durations = time_weighted_annual_means( + timestep_capacity_factors[start_index:end_index], + dt, + start_year=start_index * sample_duration, + ) + cycle_cf_segments.append(capacity_factors) + cycle_segment_durations.append(durations) + + return cycle_cf_segments, cycle_has_replacement, cycle_segment_durations + + +def _cycle_timeline(cycle_cf_segments, cycle_segment_durations, soh_cycle_repetition, plant_life): + """Yield observed and repeated cycle placements on one elapsed-year clock. + + Place every supplied cycle once in order, then repeat either the full set + or the final cycle until plant life is covered. Cycle durations must be + positive. The final placement can extend beyond retirement; callers clip + CF contributions or exclude replacement events outside the plant lifetime. + + Args: + cycle_cf_segments (list[np.ndarray]): CF segment arrays, one per cycle. + Their order determines the initial cycle sequence. + cycle_segment_durations (list[np.ndarray]): Segment durations in 365-day + years, matching each cycle's CF segments element by element. + soh_cycle_repetition (str): ``"all_soh_cycles"`` to repeat all cycles + or ``"final_soh_cycle"`` to repeat only the final cycle. + plant_life (int): Positive integer number of plant years to cover. + + Yields: + tuple[int, float, np.ndarray]: Cycle index, start offset in elapsed + plant years, and that cycle's segment-duration array. Only cycles + starting before retirement are yielded. + """ + start = 0.0 + indices = range(len(cycle_cf_segments)) + while start < plant_life: + for index in indices: + if start >= plant_life: + return + durations = cycle_segment_durations[index] + yield index, start, durations + start += float(np.sum(durations)) + indices = ( + range(len(cycle_cf_segments)) + if soh_cycle_repetition == "all_soh_cycles" + else [len(cycle_cf_segments) - 1] + ) + + +def _project_capacity_factors( + cycle_cf_segments, soh_cycle_repetition, plant_life, cycle_segment_durations=None +): + """Project cycle CF segments into annual means across plant life. + + Place all supplied cycles before repeating the selected cycle sequence. + Weight each segment's contribution by its overlap with each fixed 365-day + plant year, clipping the final contribution at retirement. This projects + segment means, not the original timestep-level CF pattern. + + Args: + cycle_cf_segments (list[np.ndarray]): Nonempty list of dimensionless CF + segment arrays, in initial cycle order. + soh_cycle_repetition (str): ``"all_soh_cycles"`` to repeat all cycles + or ``"final_soh_cycle"`` to repeat only the final cycle. + plant_life (int): Positive integer number of plant years to return. + cycle_segment_durations (list[np.ndarray] | None): Segment durations in + years, matching each cycle's CF array. Defaults to ``None``, which + treats each segment as one full year. Cycle durations must be positive. + + Returns: + np.ndarray: Dimensionless mean CF for each of the ``plant_life`` years. + """ + if cycle_segment_durations is None: + cycle_segment_durations = [np.ones(segment.size) for segment in cycle_cf_segments] + annual_cf = np.zeros(plant_life) + for index, start, durations in _cycle_timeline( + cycle_cf_segments, cycle_segment_durations, soh_cycle_repetition, plant_life + ): + for value, duration in zip(cycle_cf_segments[index], durations, strict=True): + end = min(start + duration, plant_life) + for year in range(math.floor(start), math.ceil(end)): + annual_cf[year] += value * (min(end, year + 1) - max(start, year)) + start += duration + if start >= plant_life: + break + return annual_cf + + +def _build_replacement_schedule( + cycle_cf_segments, + cycle_has_replacement, + soh_cycle_repetition, + plant_life, + cycle_segment_durations=None, +): + """Count observed and projected replacements in each plant year. + + Each flagged cycle contributes one event at its end, including when that + cycle is repeated. Bin events into intervals ``[year, year + 1)``; a year + boundary event belongs to the following year. Exclude initial installation + and events at or after retirement. Times within 1e-10 years of an integer + year are treated as exactly on that boundary to absorb floating-point error. + + Args: + cycle_cf_segments (list[np.ndarray]): Nonempty list of CF segment arrays. + Used to determine the number of cycles and, when durations are + omitted, the number of one-year segments in each cycle. + cycle_has_replacement (list[bool]): One flag per cycle indicating an + observed or projected replacement at its end. + soh_cycle_repetition (str): ``"all_soh_cycles"`` to repeat all cycles + or ``"final_soh_cycle"`` to repeat only the final cycle, after the + initial sequence has been placed. + plant_life (int): Positive integer number of plant years to return. + cycle_segment_durations (list[np.ndarray] | None): Segment durations in + years, matching each cycle's CF array. Defaults to ``None``, which + treats each segment as one full year. Cycle durations must be positive. + + Returns: + np.ndarray: Integer replacement counts for each plant year. Counts can + exceed one; they are neither annualized rates nor fractions of a replacement. + """ + if cycle_segment_durations is None: + cycle_segment_durations = [np.ones(segment.size) for segment in cycle_cf_segments] + replacement_schedule = np.zeros(plant_life, dtype=int) + for index, start, durations in _cycle_timeline( + cycle_cf_segments, cycle_segment_durations, soh_cycle_repetition, plant_life + ): + event_time = start + float(np.sum(durations)) + nearest_year = round(event_time) + if math.isclose(event_time, nearest_year, rel_tol=0.0, abs_tol=1e-10): + event_time = float(nearest_year) + if cycle_has_replacement[index] and 0.0 < event_time < plant_life: + replacement_schedule[math.floor(event_time)] += 1 + return replacement_schedule diff --git a/h2integrate/core/connection_utils.py b/h2integrate/core/connection_utils.py index e144b4dd4..407d6aaeb 100644 --- a/h2integrate/core/connection_utils.py +++ b/h2integrate/core/connection_utils.py @@ -110,6 +110,60 @@ def _to_indices(specification): return parameter_names, source_indices +def check_resource_models_are_connected(sites, site_to_tech_connections): + """Raise if a resource model is unconnected or a connected resource is undefined. + + Args: + sites (dict): Site definitions, such as ``plant_config["sites"]``. Resource models are + read from each site's ``resources`` entry. + site_to_tech_connections (list): Connections from site resources to technologies, such + as ``plant_config["site_to_tech_connections"]``. The first element of each + connection is the resource (or site) name. + + Raises: + ValueError: If a resource model is not included in ``site_to_tech_connections``, or if a + connected resource is not defined in ``sites``. + """ + resource_models = {} + for site_grp, site_grp_inputs in sites.items(): + for resource_key, resource_params in site_grp_inputs.get("resources", {}).items(): + resource_models[f"{site_grp}.{resource_key}"] = resource_params + + resource_source_connections = [c[0] for c in site_to_tech_connections] + # Check if there is a missing resource to tech connection or missing resource model + if len(resource_models) != len(resource_source_connections): + if len(resource_models) > len(resource_source_connections): + # more resource models than resources connected to technologies + non_connected_resource = [ + k for k in resource_models if k not in resource_source_connections + ] + # check if theres a resource model that isn't connected to a technology + if len(non_connected_resource) > 0: + msg = ( + "Some resources are not connected to a technology. Resource models " + f"{non_connected_resource} are not included in " + "`site_to_tech_connections`. Please connect these resources " + "to their technologies under `site_to_tech_connections` in " + "the plant config file." + ) + raise ValueError(msg) + if len(resource_source_connections) > len(resource_models): + # more resources connected than resource models + missing_resource = [k for k in resource_source_connections if k not in resource_models] + + missing_resource = list(set(missing_resource) - set(sites)) + # check if theres a resource model that isn't connected to a technology + if len(missing_resource) > 0: + msg = ( + "Missing resource(s) are not defined but are connected to a" + f" technology. Missing resource(s) are {missing_resource}. " + "Please check ``site_to_tech_connections`` in the plant" + " config file or add the missing resources" + " to plant_config['site']['resources']." + ) + raise ValueError(msg) + + def check_dispatch_connections( technology_config, dispatch_connections, supported_models, technology_graph ): diff --git a/h2integrate/core/dict_utils.py b/h2integrate/core/dict_utils.py index 8f343a646..63f00f213 100644 --- a/h2integrate/core/dict_utils.py +++ b/h2integrate/core/dict_utils.py @@ -406,3 +406,82 @@ def check_inputs(prob, tech: str, tech_info: dict, tech_config_path: str): f"{tech_location}" ) raise AttributeError(msg) + + +def percent_diff(v1, v2, tol=1e-8): + """ + Calculate the percent difference between two numbers or arrays. Return 0 + percent difference if either value is smaller than tol. + + Args: + v1 (number | array-like): First value to compare + v2 (number | array-like): Second value to compare + tol (float): Tolerance to for ignoring differences + + Returns: + (number | array-like): Percent differences + """ + + # Compute percent difference + pd = np.nan_to_num((v2 - v1) / (0.5 * (v1 + v2))) + + # Clamp to zero if either value is smaller than 1e-8 + pd = np.where(np.max(np.abs(np.stack([v1, v2])), axis=0) <= tol, 0, pd) + + return pd * 100 + + +def percent_diff_dicts(d1, d2, tol=1e-8): + """ + Take two input dicts with matching keys and return a third dict with the + percent difference for each value stored in matching keys. + + Note: This function assumes the dicts come from opemdao inputs and outputs. + + Args: + d1 (dict): First dict to compare + d2 (dict): Second dict to compare + + Returns: + (dict): Dict of percent differences between d1 and d2 + + """ + if bool( + set(d1) ^ set(d2) + ): # if this is True, there are elements that are not in both dictionaries + raise ValueError("dictionaries do not have the same set of keys") + + d_out = {} + for k1, v1 in d1.items(): + v2 = d2[k1] + + if isinstance(v1["val"], dict | bool | str): + # If the H2I input or output is more complicated than an array, skip it + continue + + pd = percent_diff(v1["val"], v2["val"], tol=tol) + + # Use the norm of any percent difference arrays so output is always a scalar + d_out.update({k1: np.linalg.norm(pd)}) + + return d_out + + +def find_nonzero_percent_diffs(pd_dict, ref_dict, tol=1e-8): + """ + Find the keys that correspond to percent difference values, that are sufficiently non-zero. + + Args: + pd_dict (dict): Percent difference dict + ref_dict (dict): H2I inputs or outputs dict + + Returns: + tuple[dict, dict]: First dict is only the keys and items that have percent difference larger + than 1e-8. Second dict is only the keys and items that have percent difference larger + than 1e-8 and a norm of the original value larger than 1e-8. + """ + + # Return only the dict items that are non-zero + abs_pd = {k: v for k, v in pd_dict.items() if np.abs(v) > tol} + rel_pd = {k: v for k, v in abs_pd.items() if np.linalg.norm(ref_dict[k]["val"]) > tol} + return abs_pd, rel_pd diff --git a/h2integrate/core/h2integrate_model.py b/h2integrate/core/h2integrate_model.py index 99c7c8602..d53d541fb 100644 --- a/h2integrate/core/h2integrate_model.py +++ b/h2integrate/core/h2integrate_model.py @@ -6,11 +6,18 @@ from h2integrate.core.dict_utils import check_inputs from h2integrate.core.file_utils import get_path, find_file, load_yaml, load_component_config -from h2integrate.core.model_checks import check_model_time_step, check_model_control_classifier +from h2integrate.core.model_checks import ( + check_model_time_step, + check_model_is_steppable, + check_time_marching_config, + check_model_control_classifier, + check_model_simulation_duration, +) from h2integrate.core.connection_utils import ( create_technology_graph, check_dispatch_connections, check_technology_connections, + check_resource_models_are_connected, validate_technology_interconnections, split_indices_from_connected_parameter_definition, ) @@ -23,6 +30,7 @@ create_xdsm as create_xdsm_utility, print_results as print_model_results, ) +from h2integrate.core.time_marching_solver import TimeMarchingPlantSolver from h2integrate.core.commodity_stream_definitions import multivariable_streams from h2integrate.control.control_strategies.passthrough_controller import PassthroughController from h2integrate.control.control_strategies.system_level.solver_options import ( @@ -104,6 +112,23 @@ def __init__(self, config_input): # technologies are connected within the `technology_interconnections` section of the # plant config self.connect_technologies() + # sites and resources are connected to technologies within the + # `site_to_tech_connections` section of the plant config + self.connect_sites_to_techs() + # connect technology costs and performance specifications to the system-level + # finance models based on info in the `finance_parameters['finance_subgroups']` + # section of the plant config + self.connect_techs_to_finance() + # connect dispatch rules for Pyomo-based storage dispatch models based on + # `tech_to_dispatch_connections` section of the plant config + self.connect_dispatch_rules() + # auto-order the connections and check for any feedback loops in the connections + self.finalize_connections() + + # Ensure the plant group has the correct openmdao nonlinear solver + # considering system level control config options, system graph cycles, + # and steppable simulation framework options. + self.set_plant_solver() # create driver model # might be an analysis or optimization @@ -626,8 +651,7 @@ def add_system_level_controller(self, slc_topology): """Add a system-level controller component and connect it within the plant. Instantiates the controller specified by ``control_strategy`` in the plant configuration, - adds it as an OpenMDAO subsystem named ``"system_level_controller"``, configures - solvers on the plant group to resolve the feedback loop, and creates all + adds it as an OpenMDAO subsystem named ``"system_level_controller"``, and creates all necessary OpenMDAO connections between the controller and the technology models it dispatches. @@ -639,14 +663,7 @@ def add_system_level_controller(self, slc_topology): if the strategy name is not found. The instantiated component is added to ``self.plant`` as ``"system_level_controller"``. - 2. **Configure the plant-level nonlinear solver** - Because the controller creates a - feedback loop (controller outputs become technology inputs, whose outputs feed back to - the controller), a nonlinear solver is required. Solver type and options are read from - ``plant_config["system_level_control"]["solver_options"]`` via - ``SLCSolverOptionsConfig``. A ``DirectSolver`` is set as the linear solver and - is largely inconsequential as we're not propagating derivatives at this time. - - 3. **Connect technology outputs to controller inputs** - For each ``(tech_name, + 2. **Connect technology outputs to controller inputs** - For each ``(tech_name, commodity)`` pair in ``slc_topology["tech_to_commodity"]``: - **Feedstock techs**: Only the commodity output @@ -664,7 +681,7 @@ def add_system_level_controller(self, slc_topology): ``control_strategy`` or via the auto-injected ``PassthroughController`` — which converts the set-point signal into the appropriate performance-model command value. - 4. **Connect marginal-cost inputs for cost-aware strategies** - Only executed when + 3. **Connect marginal-cost inputs for cost-aware strategies** - Only executed when ``control_strategy`` is ``"CostMinimizationControl"`` or ``"ProfitMaximizationControl"``. Additional cost-aware control strategies would need to be added here. For each dispatchable tech, the ``cost_per_tech`` @@ -684,7 +701,7 @@ def add_system_level_controller(self, slc_topology): - Numeric scalar: no connection needed; the value is used directly as a constant marginal cost. - 5. **Connect the demand profile** - Connects the demand technology's output + 4. **Connect the demand profile** - Connects the demand technology's output (``{demand_tech}.{demand_commodity}_demand_out``) to the controller's demand input (``system_level_controller.{demand_commodity}_demand``). This relies on the current SLC constraint that exactly one demand component is defined. @@ -710,7 +727,6 @@ def add_system_level_controller(self, slc_topology): Side Effects: - Adds ``"system_level_controller"`` subsystem to ``self.plant``. - - Sets ``self.plant.nonlinear_solver`` and ``self.plant.linear_solver``. - Creates OpenMDAO connections within ``self.plant``. """ plant_slc_config = self.plant_config["system_level_control"] @@ -724,6 +740,13 @@ def add_system_level_controller(self, slc_topology): f"Must be a valid model name in supported_models." ) + check_model_simulation_duration( + strategy_name, + slc_cls, + self.plant_config["plant"]["simulation"]["n_timesteps"], + self.plant_config["plant"]["simulation"]["dt"], + ) + slc_comp = slc_cls( driver_config=self.driver_config, plant_config=self.plant_config, @@ -732,19 +755,7 @@ def add_system_level_controller(self, slc_topology): ) self.plant.add_subsystem("system_level_controller", slc_comp) - # --- Step 2: Configure the nonlinear solver on the plant group ---- - # The feedback loop (controller <-> technologies) requires an - # iterative nonlinear solver to converge. - solver_config = SLCSolverOptionsConfig.from_dict(plant_slc_config.get("solver_options", {})) - solver_cls = solver_config.return_nonlinear_solver() - solver = solver_cls() - solver_options = solver_config.get_solver_options() - for k, v in solver_options.items(): - solver.options[k] = v - self.plant.nonlinear_solver = solver - self.plant.linear_solver = om.DirectSolver() - - # --- Step 3: Connect technology outputs/inputs to the controller -- + # --- Step 2: Connect technology outputs/inputs to the controller -- for tech_to_commodity in slc_topology["tech_to_commodity"]: tech_name, commodity = tech_to_commodity @@ -794,7 +805,7 @@ def add_system_level_controller(self, slc_topology): f"{tech_name}.{commodity}_set_point", ) - # --- Step 4: Connect marginal-cost inputs (cost-aware strategies) - + # --- Step 3: Connect marginal-cost inputs (cost-aware strategies) - if strategy_name in ("CostMinimizationControl", "ProfitMaximizationControl"): cost_per_tech = plant_slc_config.get("control_parameters", {}).get("cost_per_tech", {}) technology_graph = slc_topology["technology_graph"] @@ -842,7 +853,7 @@ def add_system_level_controller(self, slc_topology): ) # numeric scalar: used directly, no connection needed - # --- Step 5: Connect the demand profile to the controller --------- + # --- Step 4: Connect the demand profile to the controller --------- # Input-to-input connection (OpenMDAO 3.44+): as with the buy_price # connection above, this must be made on the top-level model rather # than a subgroup. Connecting via ``self.plant`` (a subgroup) leaves @@ -926,6 +937,12 @@ def create_technology_models(self): comp, self.plant_config["plant"]["simulation"]["dt"], ) + check_model_simulation_duration( + perf_model, + comp, + self.plant_config["plant"]["simulation"]["n_timesteps"], + self.plant_config["plant"]["simulation"]["dt"], + ) self.tech_control_classifiers.update({tech_name: "feedstock"}) self.plant.add_subsystem(f"{tech_name}_source", comp) else: @@ -970,6 +987,12 @@ def create_technology_models(self): comp, self.plant_config["plant"]["simulation"]["dt"], ) + check_model_simulation_duration( + perf_model, + comp, + self.plant_config["plant"]["simulation"]["n_timesteps"], + self.plant_config["plant"]["simulation"]["dt"], + ) om_model_object = tech_group.add_subsystem(perf_model, comp, promotes=["*"]) self.performance_models.append(om_model_object) self.cost_models.append(om_model_object) @@ -1025,6 +1048,12 @@ def create_technology_models(self): "model", "" ): finance_object = self.supported_models[finance_name] + check_model_simulation_duration( + finance_name, + finance_object, + self.plant_config["plant"]["simulation"]["n_timesteps"], + self.plant_config["plant"]["simulation"]["dt"], + ) tech_group.add_subsystem( f"{tech_name}_finance", finance_object( @@ -1050,6 +1079,12 @@ def create_technology_models(self): comp, self.plant_config["plant"]["simulation"]["dt"], ) + check_model_simulation_duration( + tech_name, + comp, + self.plant_config["plant"]["simulation"]["n_timesteps"], + self.plant_config["plant"]["simulation"]["dt"], + ) self.plant.add_subsystem(tech_name, comp) n_non_transport_techs = sum( 1 for v in self.tech_control_classifiers.values() if v != "transport" @@ -1076,6 +1111,12 @@ def _process_model(self, model_type, individual_tech_config, tech_group): model_object, self.plant_config["plant"]["simulation"]["dt"], ) + check_model_simulation_duration( + model_name, + model_object, + self.plant_config["plant"]["simulation"]["n_timesteps"], + self.plant_config["plant"]["simulation"]["dt"], + ) om_model_object = tech_group.add_subsystem( model_name, @@ -1375,6 +1416,9 @@ def create_finance_model(self): "commodity_stream_output": subgroup_params.get( "commodity_stream_output", None ), + "annual_extrapolation_method": subgroup_params.get( + "annual_extrapolation_method" + ), } } ) @@ -1506,6 +1550,9 @@ def create_finance_model(self): adj_cf_comp = AdjustedCapacityFactorComp( plant_config=filtered_plant_config, commodity_type=commodity, + annual_extrapolation_method=finance_subgroups[subgroup_name][ + "annual_extrapolation_method" + ], ) finance_subgroup.add_subsystem("adjusted_cf_comp", adj_cf_comp, promotes=["*"]) @@ -1533,6 +1580,12 @@ def create_finance_model(self): self.finance_subgroups = finance_subgroups + @staticmethod + def _next_connection_index(counts, tech_name): + """Increment and return the 1-based connection index tracked for `tech_name`.""" + counts[tech_name] = counts.get(tech_name, 0) + 1 + return counts[tech_name] + def _connect_multivariable_stream( self, source_tech, dest_tech, stream_name, combiner_counts, splitter_counts ): @@ -1549,22 +1602,14 @@ def _connect_multivariable_stream( splitter_counts (dict): Tracks the next output index per splitter technology. """ if self.tech_control_classifiers.get(dest_tech) == "combiner": - if dest_tech not in combiner_counts: - combiner_counts[dest_tech] = 1 - else: - combiner_counts[dest_tech] += 1 - stream_index = combiner_counts[dest_tech] + stream_index = self._next_connection_index(combiner_counts, dest_tech) for var_name in multivariable_streams[stream_name]: self.plant.connect( f"{source_tech}.{stream_name}:{var_name}_out", f"{dest_tech}.{stream_name}:{var_name}_in{stream_index}", ) elif self.tech_control_classifiers.get(source_tech) == "splitter": - if source_tech not in splitter_counts: - splitter_counts[source_tech] = 1 - else: - splitter_counts[source_tech] += 1 - stream_index = splitter_counts[source_tech] + stream_index = self._next_connection_index(splitter_counts, source_tech) for var_name in multivariable_streams[stream_name]: self.plant.connect( f"{source_tech}.{stream_name}:{var_name}_out{stream_index}", @@ -1578,6 +1623,12 @@ def _connect_multivariable_stream( ) def connect_technologies(self): + """Connect technologies to each other via `technology_interconnections`. + + Each connection is either 4 elements (source, dest, item, transport type), which creates + a transport component, or 3 elements (source, dest, parameter), which connects + variables directly. + """ technology_interconnections = self.plant_config.get("technology_interconnections", []) combiner_counts = {} @@ -1587,346 +1638,292 @@ def connect_technologies(self): # the connection type (e.g. cable, pipeline, etc) for connection in technology_interconnections: if len(connection) == 4: - source_tech, dest_tech, transport_item, transport_type = connection - - # Check if this is a multivariable stream connection - # Format: [source, dest, stream_name, transport_type] - if transport_item in multivariable_streams: - self._connect_multivariable_stream( - source_tech, - dest_tech, - transport_item, - combiner_counts, - splitter_counts, - ) - continue # Skip the rest of the 4-element handling + self._connect_technologies_with_transport( + connection, combiner_counts, splitter_counts + ) + elif len(connection) == 3: + self._connect_technologies_directly(connection, combiner_counts, splitter_counts) + else: + err_msg = f"Invalid connection: {connection}" + raise ValueError(err_msg) - if transport_type in self.tech_names: - # if the transport type is already a technology, skip creating a new component - connection_name = f"{transport_type}" - else: - # make the connection_name based on source, dest, item, type - connection_name = ( - f"{transport_item}_{source_tech}_to_{dest_tech}_{transport_type}" - ) + def _connect_technologies_with_transport(self, connection, combiner_counts, splitter_counts): + """Connect two technologies through a transport component. - # Get the performance model of the source_tech - source_tech_config = self.technology_config["technologies"].get(source_tech, {}) - perf_model_name = source_tech_config.get("performance_model", {}).get("model") - cost_model_name = source_tech_config.get("cost_model", {}).get("model", "") + Args: + connection (list): `[source_tech, dest_tech, transport_item, transport_type]`. + combiner_counts (dict): Tracks the next input index per combiner technology. + splitter_counts (dict): Tracks the next output index per splitter technology. + """ + source_tech, dest_tech, transport_item, transport_type = connection + + # Check if this is a multivariable stream connection + # Format: [source, dest, stream_name, transport_type] + if transport_item in multivariable_streams: + self._connect_multivariable_stream( + source_tech, + dest_tech, + transport_item, + combiner_counts, + splitter_counts, + ) + return - # If the source is a feedstock, make sure to connect the amount of - # feedstock consumed from the technology back to the feedstock cost model - if "FeedstockCostModel" in cost_model_name: - self.plant.connect( - f"{dest_tech}.{transport_item}_consumed", - f"{source_tech}.{transport_item}_consumed", - ) - # Connect the feedstock performance model output to the cost model input - self.plant.connect( - f"{source_tech}_source.{transport_item}_out", - f"{source_tech}.{transport_item}_out", - ) + if transport_type in self.tech_names: + # if the transport type is already a technology, skip creating a new component + connection_name = f"{transport_type}" + else: + # make the connection_name based on source, dest, item, type + connection_name = f"{transport_item}_{source_tech}_to_{dest_tech}_{transport_type}" - if perf_model_name == "FeedstockPerformanceModel": - source_tech = f"{source_tech}_source" + # Get the performance model of the source_tech + source_tech_config = self.technology_config["technologies"].get(source_tech, {}) + perf_model_name = source_tech_config.get("performance_model", {}).get("model") + cost_model_name = source_tech_config.get("cost_model", {}).get("model", "") - # Create the transport object - # allow transport_type to be from self.tech_name - if transport_type in self.tech_names: - # Connect the connection component to the destination technology - pass - else: - connection_component = self.supported_models[transport_type]( - transport_item=transport_item, plant_config=self.plant_config - ) - - # Add the connection component to the model - check_model_time_step( - transport_type, - connection_component, - self.plant_config["plant"]["simulation"]["dt"], - ) - self.plant.add_subsystem(connection_name, connection_component) - - # Reorder the subsystems so transporters comes after their source technology - # NOTE: the private method must be used because setup() has not been called - subsystem_names = list(self.plant._static_subsystems_allprocs) - subsystem_names.remove(connection_name) - insert_idx = subsystem_names.index(source_tech) + 1 - subsystem_names.insert(insert_idx, connection_name) - self.plant.set_order(subsystem_names) - - # Check if the source technology is a splitter - if self.tech_control_classifiers.get(source_tech) == "splitter": - # Connect the source technology to the connection component - # with specific output names - if source_tech not in splitter_counts: - splitter_counts[source_tech] = 1 - else: - splitter_counts[source_tech] += 1 + # If the source is a feedstock, make sure to connect the amount of + # feedstock consumed from the technology back to the feedstock cost model + if "FeedstockCostModel" in cost_model_name: + self.plant.connect( + f"{dest_tech}.{transport_item}_consumed", + f"{source_tech}.{transport_item}_consumed", + ) + # Connect the feedstock performance model output to the cost model input + self.plant.connect( + f"{source_tech}_source.{transport_item}_out", + f"{source_tech}.{transport_item}_out", + ) - # Connect the splitter output to the connection component - self.plant.connect( - f"{source_tech}.{transport_item}_out{splitter_counts[source_tech]}", - f"{connection_name}.{transport_item}_in", - ) + if perf_model_name == "FeedstockPerformanceModel": + source_tech = f"{source_tech}_source" - else: - # Connect the source technology to the connection component - self.plant.connect( - f"{source_tech}.{transport_item}_out", - f"{connection_name}.{transport_item}_in", - ) + # Create the transport object, unless the transport type is already a technology + if transport_type not in self.tech_names: + connection_component = self.supported_models[transport_type]( + transport_item=transport_item, plant_config=self.plant_config + ) - # Check if the transport type is a combiner - if self.tech_control_classifiers.get(dest_tech) == "combiner": - # Connect the source technology to the connection component - # with specific input names - if dest_tech not in combiner_counts: - combiner_counts[dest_tech] = 1 - else: - combiner_counts[dest_tech] += 1 + # Add the connection component to the model + check_model_time_step( + transport_type, + connection_component, + self.plant_config["plant"]["simulation"]["dt"], + ) + check_model_simulation_duration( + transport_type, + connection_component, + self.plant_config["plant"]["simulation"]["n_timesteps"], + self.plant_config["plant"]["simulation"]["dt"], + ) + self.plant.add_subsystem(connection_name, connection_component) + + # Reorder the subsystems so transporters comes after their source technology + # NOTE: the private method must be used because setup() has not been called + subsystem_names = list(self.plant._static_subsystems_allprocs) + subsystem_names.remove(connection_name) + insert_idx = subsystem_names.index(source_tech) + 1 + subsystem_names.insert(insert_idx, connection_name) + self.plant.set_order(subsystem_names) + + # Connect the source technology to the connection component, using the numbered + # output if the source is a splitter + if self.tech_control_classifiers.get(source_tech) == "splitter": + source_index = self._next_connection_index(splitter_counts, source_tech) + source_variable = f"{source_tech}.{transport_item}_out{source_index}" + else: + source_variable = f"{source_tech}.{transport_item}_out" + self.plant.connect(source_variable, f"{connection_name}.{transport_item}_in") - # Connect the connection component to the destination technology - self.plant.connect( - f"{connection_name}.{transport_item}_out", - f"{dest_tech}.{transport_item}_in{combiner_counts[dest_tech]}", - ) - # Connect the source tech design and performance info to the combiner - self.plant.connect( - f"{source_tech}.rated_{transport_item}_production", - f"{dest_tech}.rated_{transport_item}_production{combiner_counts[dest_tech]}", - ) - self.plant.connect( - f"{source_tech}.capacity_factor", - f"{dest_tech}.{transport_item}_capacity_factor{combiner_counts[dest_tech]}", - ) + # Connect the connection component to the destination technology, using the numbered + # input (and extra design info) if the destination is a combiner + if self.tech_control_classifiers.get(dest_tech) == "combiner": + dest_index = self._next_connection_index(combiner_counts, dest_tech) + self.plant.connect( + f"{connection_name}.{transport_item}_out", + f"{dest_tech}.{transport_item}_in{dest_index}", + ) + # Connect the source tech design and performance info to the combiner + self.plant.connect( + f"{source_tech}.rated_{transport_item}_production", + f"{dest_tech}.rated_{transport_item}_production{dest_index}", + ) + self.plant.connect( + f"{source_tech}.capacity_factor", + f"{dest_tech}.{transport_item}_capacity_factor{dest_index}", + ) + else: + self.plant.connect( + f"{connection_name}.{transport_item}_out", + f"{dest_tech}.{transport_item}_in", + ) - else: - # Connect the connection component to the destination technology - self.plant.connect( - f"{connection_name}.{transport_item}_out", - f"{dest_tech}.{transport_item}_in", - ) + def _connect_technologies_directly(self, connection, combiner_counts, splitter_counts): + """Connect variables directly between two technologies. - elif len(connection) == 3: - source_tech, dest_tech, connected_parameter = connection - src_indices = None + Args: + connection (list): `[source_tech, dest_tech, connected_parameter]`, where + `connected_parameter` is a shared name or a `[source_name, dest_name]` pair, + optionally with `src_indices` slicing. + combiner_counts (dict): Tracks the next input index per combiner technology. + splitter_counts (dict): Tracks the next output index per splitter technology. + """ + source_tech, dest_tech, connected_parameter = connection + src_indices = None - # initialize src_indices to allow connections between different shaped variables - if isinstance(connected_parameter, list): - connected_parameter, src_indices = ( - split_indices_from_connected_parameter_definition(connected_parameter) - ) + # initialize src_indices to allow connections between different shaped variables + if isinstance(connected_parameter, list): + connected_parameter, src_indices = split_indices_from_connected_parameter_definition( + connected_parameter + ) - # connect directly from source to dest - if isinstance(connected_parameter, tuple | list): - source_parameter, dest_parameter = connected_parameter - # Check if this is a multivariable stream connection - if source_parameter in multivariable_streams: - self._connect_multivariable_stream( - source_tech, - dest_tech, - source_parameter, - combiner_counts, - splitter_counts, - ) - else: - self.plant.connect( - f"{source_tech}.{source_parameter}", - f"{dest_tech}.{dest_parameter}", - src_indices=src_indices, - ) - else: - # Check if the connected_parameter is a multivariable stream - if connected_parameter in multivariable_streams: - self._connect_multivariable_stream( - source_tech, - dest_tech, - connected_parameter, - combiner_counts, - splitter_counts, - ) - else: - self.plant.connect( - f"{source_tech}.{connected_parameter}", - f"{dest_tech}.{connected_parameter}", - src_indices=src_indices, - ) + if isinstance(connected_parameter, tuple | list): + source_parameter, dest_parameter = connected_parameter + else: + source_parameter = dest_parameter = connected_parameter + + if source_parameter in multivariable_streams: + self._connect_multivariable_stream( + source_tech, + dest_tech, + source_parameter, + combiner_counts, + splitter_counts, + ) + else: + self.plant.connect( + f"{source_tech}.{source_parameter}", + f"{dest_tech}.{dest_parameter}", + src_indices=src_indices, + ) - else: - err_msg = f"Invalid connection: {connection}" - raise ValueError(err_msg) + def connect_sites_to_techs(self): + """Connect site resource models to technologies via `site_to_tech_connections`.""" + if "sites" not in self.plant_config: + return site_to_tech_connections = self.plant_config.get("site_to_tech_connections", []) - if "sites" in self.plant_config: - resource_models = {} - for site_grp, site_grp_inputs in self.plant_config["sites"].items(): - for resource_key, resource_params in site_grp_inputs.get("resources", {}).items(): - resource_models[f"{site_grp}.{resource_key}"] = resource_params - - resource_source_connections = [c[0] for c in site_to_tech_connections] - # Check if there is a missing resource to tech connection or missing resource model - if len(resource_models) != len(resource_source_connections): - if len(resource_models) > len(resource_source_connections): - # more resource models than resources connected to technologies - non_connected_resource = [ - k for k in resource_models if k not in resource_source_connections - ] - # check if theres a resource model that isn't connected to a technology - if len(non_connected_resource) > 0: - msg = ( - "Some resources are not connected to a technology. Resource models " - f"{non_connected_resource} are not included in " - "`site_to_tech_connections`. Please connect these resources " - "to their technologies under `site_to_tech_connections` in " - "the plant config file." - ) - raise ValueError(msg) - if len(resource_source_connections) > len(resource_models): - # more resources connected than resource models - missing_resource = [ - k for k in resource_source_connections if k not in resource_models - ] - - missing_resource = list(set(missing_resource) - set(self.plant_config["sites"])) - # check if theres a resource model that isn't connected to a technology - if len(missing_resource) > 0: - msg = ( - "Missing resource(s) are not defined but are connected to a" - f" technology. Missing resource(s) are {missing_resource}. " - "Please check ``site_to_tech_connections`` in the plant" - " config file or add the missing resources" - " to plant_config['site']['resources']." - ) - raise ValueError(msg) + check_resource_models_are_connected(self.plant_config["sites"], site_to_tech_connections) - for connection in site_to_tech_connections: - if len(connection) != 3: - err_msg = f"Invalid resource to tech connection: {connection}" - raise ValueError(err_msg) + for connection in site_to_tech_connections: + if len(connection) != 3: + err_msg = f"Invalid resource to tech connection: {connection}" + raise ValueError(err_msg) - resource_name, tech_name, variable = connection + resource_name, tech_name, variable = connection - # Normalize both forms (paired [site_param, tech_param] vs a single shared - # name) into a common site_parameter/tech_parameter pair so the connection - # and latitude/longitude checks below only need to be written once. - is_pair = isinstance(variable, list | tuple) - if is_pair: - site_parameter, tech_parameter = variable - else: - site_parameter = tech_parameter = variable + # Normalize both forms (paired [site_param, tech_param] vs a single shared + # name) into a common site_parameter/tech_parameter pair so the connection + # and latitude/longitude checks below only need to be written once. + is_pair = isinstance(variable, list | tuple) + if is_pair: + site_parameter, tech_parameter = variable + else: + site_parameter = tech_parameter = variable - self.model.connect( - f"{resource_name}.{site_parameter}", f"{tech_name}.{tech_parameter}" - ) + self.model.connect(f"{resource_name}.{site_parameter}", f"{tech_name}.{tech_parameter}") - if site_parameter in ["latitude", "longitude"]: - # If site_parameter is latitude, make sure destination is not longitude - # (and vice versa) - other_loc_var = "longitude" if site_parameter == "latitude" else "latitude" - if is_pair: - # NOTE: this assumes that technologies with location inputs use full - # names, rather than shorthand versions like 'lat' and 'lon' - if other_loc_var in tech_parameter: - # connecting site latitude to tech longitude or - # site longitude to tech latitude - msg = ( - f"Invalid connection of {site_parameter} to {other_loc_var} " - f"(from {resource_name} to {tech_name}). Please update the " - f"connection so that {site_parameter} is connected to " - f"{tech_parameter.replace(other_loc_var, site_parameter)}." - ) - raise ValueError(msg) - other_variable = [ - other_loc_var, - tech_parameter.replace(site_parameter, other_loc_var), - ] - else: - other_variable = other_loc_var + if site_parameter not in ["latitude", "longitude"]: + continue - # If latitude is connected, make sure longitude is also connected - other_connection = [resource_name, tech_name, other_variable] - if other_connection not in site_to_tech_connections: - msg = ( - f"{site_parameter} is connected between {resource_name} and " - f"{tech_name}, but {other_loc_var} is not. Please ensure that " - f"both latitude and longitude are connected from " - f"'{resource_name}' to technology '{tech_name}'" - ) - raise ValueError(msg) + # If site_parameter is latitude, make sure destination is not longitude + # (and vice versa) + other_loc_var = "longitude" if site_parameter == "latitude" else "latitude" + if is_pair: + # NOTE: this assumes that technologies with location inputs use full + # names, rather than shorthand versions like 'lat' and 'lon' + if other_loc_var in tech_parameter: + # connecting site latitude to tech longitude or + # site longitude to tech latitude + msg = ( + f"Invalid connection of {site_parameter} to {other_loc_var} " + f"(from {resource_name} to {tech_name}). Please update the " + f"connection so that {site_parameter} is connected to " + f"{tech_parameter.replace(other_loc_var, site_parameter)}." + ) + raise ValueError(msg) + other_variable = [ + other_loc_var, + tech_parameter.replace(site_parameter, other_loc_var), + ] + else: + other_variable = other_loc_var - # connect outputs of the technology models to the cost and finance models of the - # same name if the cost and finance models are not None - if "finance_parameters" in self.plant_config: - # Connect the outputs of the technology models to the appropriate finance groups - for group_id, group_configs in self.finance_subgroups.items(): - tech_configs = group_configs.get("tech_configs") - primary_commodity_type = group_configs.get("commodity") - commodity_stream = group_configs.get("commodity_stream") - is_system_finance_model = group_configs.get("is_system_finance_model") - - if is_system_finance_model: - if group_configs.get("use_commodity_stream_timeseries", False): - # TODO: finish this logic - self.plant.connect( - f"{commodity_stream}.{group_configs.get('commodity_stream_output')}", - f"finance_subgroup_{group_id}.{primary_commodity_type}_produced", - ) - else: - # Connect the rated commodity production and capacity factor - # for system-level finance models - self.plant.connect( - f"{commodity_stream}.rated_{primary_commodity_type}_production", - f"finance_subgroup_{group_id}.rated_{primary_commodity_type}_production", - ) + # If latitude is connected, make sure longitude is also connected + other_connection = [resource_name, tech_name, other_variable] + if other_connection not in site_to_tech_connections: + msg = ( + f"{site_parameter} is connected between {resource_name} and " + f"{tech_name}, but {other_loc_var} is not. Please ensure that " + f"both latitude and longitude are connected from " + f"'{resource_name}' to technology '{tech_name}'" + ) + raise ValueError(msg) - self.plant.connect( - f"{commodity_stream}.capacity_factor", - f"finance_subgroup_{group_id}.capacity_factor", - ) + def connect_techs_to_finance(self): + """Connect technology cost outputs to their finance subgroups.""" + if "finance_parameters" not in self.plant_config: + return - # Only connect technologies that are included in the finance stackup - for tech_name in tech_configs.keys(): - # Skip technologies whose models doesn't add costs - perf_model = tech_configs[tech_name].get("performance_model").get("model") - cost_model = tech_configs[tech_name].get("cost_model", {}).get("model") - if perf_model in no_cost_models and cost_model is None: - continue + # Connect the outputs of the technology models to the appropriate finance groups + for group_id, group_configs in self.finance_subgroups.items(): + tech_configs = group_configs.get("tech_configs") + primary_commodity_type = group_configs.get("commodity") + commodity_stream = group_configs.get("commodity_stream") + is_system_finance_model = group_configs.get("is_system_finance_model") + if is_system_finance_model: + if group_configs.get("use_commodity_stream_timeseries", False): + # TODO: finish this logic self.plant.connect( - f"{tech_name}.CapEx", - f"finance_subgroup_{group_id}.capex_{tech_name}", - ) - self.plant.connect( - f"{tech_name}.OpEx", f"finance_subgroup_{group_id}.opex_{tech_name}" + f"{commodity_stream}.{group_configs.get('commodity_stream_output')}", + f"finance_subgroup_{group_id}.{primary_commodity_type}_produced", ) + else: + # Connect the rated commodity production and capacity factor + # for system-level finance models self.plant.connect( - f"{tech_name}.VarOpEx", f"finance_subgroup_{group_id}.varopex_{tech_name}" + f"{commodity_stream}.rated_{primary_commodity_type}_production", + f"finance_subgroup_{group_id}.rated_{primary_commodity_type}_production", ) + self.plant.connect( - f"{tech_name}.cost_year", - f"finance_subgroup_{group_id}.cost_year_{tech_name}", + f"{commodity_stream}.capacity_factor", + f"finance_subgroup_{group_id}.capacity_factor", ) - if is_system_finance_model and perf_model not in no_replacement_schedule_models: - # connect replacement schedule to system-level finance models - self.plant.connect( - f"{tech_name}.replacement_schedule", - f"finance_subgroup_{group_id}.replacement_schedule_{tech_name}", - ) + # Only connect technologies that are included in the finance stackup + for tech_name in tech_configs.keys(): + # Skip technologies whose models doesn't add costs + perf_model = tech_configs[tech_name].get("performance_model").get("model") + cost_model = tech_configs[tech_name].get("cost_model", {}).get("model") + if perf_model in no_cost_models and cost_model is None: + continue - self.plant.options["auto_order"] = True + self.plant.connect( + f"{tech_name}.CapEx", + f"finance_subgroup_{group_id}.capex_{tech_name}", + ) + self.plant.connect( + f"{tech_name}.OpEx", f"finance_subgroup_{group_id}.opex_{tech_name}" + ) + self.plant.connect( + f"{tech_name}.VarOpEx", f"finance_subgroup_{group_id}.varopex_{tech_name}" + ) + self.plant.connect( + f"{tech_name}.cost_year", + f"finance_subgroup_{group_id}.cost_year_{tech_name}", + ) - # Check if there are any loops in the technology interconnections - # If loops are present, add solvers to resolve the coupling - # Check if there are any cycles (loops) in the technology graph - if list(nx.simple_cycles(self.technology_graph)): - # If cycles are found, set solvers for the plant to resolve the coupling - self.plant.nonlinear_solver = om.NonlinearBlockGS() - self.plant.linear_solver = om.DirectSolver() + if is_system_finance_model and perf_model not in no_replacement_schedule_models: + # connect replacement schedule to system-level finance models + self.plant.connect( + f"{tech_name}.replacement_schedule", + f"finance_subgroup_{group_id}.replacement_schedule_{tech_name}", + ) + + def connect_dispatch_rules(self): + """Connect dispatch rule functions via `tech_to_dispatch_connections`.""" # initialize dispatch rules connection list tech_to_dispatch_connections = self.plant_config.get("tech_to_dispatch_connections", []) @@ -1954,6 +1951,72 @@ def connect_technologies(self): f"{dispatching_tech_name}.dispatch_block_rule_function_{tech_name}", ) + def finalize_connections(self): + """Let OpenMDAO order the plant and add solvers if the technology graph has loops.""" + self.plant.options["auto_order"] = True + + # If cycles are found, set solvers for the plant to resolve the coupling + if list(nx.simple_cycles(self.technology_graph)): + self.plant.nonlinear_solver = om.NonlinearBlockGS() + self.plant.linear_solver = om.DirectSolver() + + def set_plant_solver(self): + """Configures solvers on the plant group according to the needs of the run case. + + The plant group nonlinear solver is explicitly defined and attributed to the plant group if + a system level controller is defined, the technology graph has a cycle, or the + time-marching simulation framework is utilized. + """ + + simulation_config = self.plant_config["plant"]["simulation"] + has_cycles = bool(list(nx.simple_cycles(self.technology_graph))) + + # Validate the time-marching options before choosing any solver so unsupported + # combinations fail loudly instead of silently simulating only the first window. + time_marching = False + n_timesteps_window = simulation_config.get("n_timesteps_window") + if n_timesteps_window is not None: + time_marching = check_time_marching_config( + simulation_config["n_timesteps"], + n_timesteps_window, + bool(self.slc), + has_cycles, + ) + + if self.slc: + # Because the controller creates a feedback loop (controller outputs become technology + # inputs, whose outputs feed back to the controller), a nonlinear solver is required. + # Solver type and options are read from + # ``plant_config["system_level_control"]["solver_options"]`` via + # ``SLCSolverOptionsConfig``. A ``DirectSolver`` is set as the linear solver and is + # largely inconsequential as we're not propagating derivatives at this time. + + plant_slc_config = self.plant_config["system_level_control"] + + # The feedback loop (controller <-> technologies) requires an + # iterative nonlinear solver to converge. + solver_options = plant_slc_config.get("solver_options", {}) + solver_config = SLCSolverOptionsConfig.from_dict(solver_options) + solver_cls = solver_config.return_nonlinear_solver() + solver = solver_cls() + solver_options = solver_config.get_solver_options() + for k, v in solver_options.items(): + solver.options[k] = v + self.plant.nonlinear_solver = solver + self.plant.linear_solver = om.DirectSolver() + return + + # If loops are present in the technology graph, add solvers to resolve the coupling + if has_cycles: + self.plant.nonlinear_solver = om.NonlinearBlockGS() + self.plant.linear_solver = om.DirectSolver() + + if time_marching: + # Assign custom nonlinear solver to plant group to manage time-marching simulation + self.plant.nonlinear_solver = TimeMarchingPlantSolver(plant_config=self.plant_config) + + check_model_is_steppable(self.control_strategies, self.performance_models) + def create_driver_model(self): """ Add the driver to the OpenMDAO model and add recorder. diff --git a/h2integrate/core/inputs/plant_schema.yaml b/h2integrate/core/inputs/plant_schema.yaml index c118afe88..66dfc7281 100644 --- a/h2integrate/core/inputs/plant_schema.yaml +++ b/h2integrate/core/inputs/plant_schema.yaml @@ -73,6 +73,14 @@ properties: description: number of timesteps to simulate default: 8760 minimum: 1 + n_timesteps_window: + type: integer + description: number of timesteps per window in a time-marching simulation + minimum: 1 + annual_extrapolation_method: + type: string + description: Extrapolation method for annualized profiles beyond the simulation horizon + enum: [tile, final_sim_value, time_averaged_sim_value] start_time: type: string description: simulation start time formatted as mm/dd/yyyy HH:MM:SS or mm/dd HH:MM:SS @@ -141,6 +149,11 @@ properties: type: string description: Name of timeseries profile output variable from commodity_stream to use in finance calculations. Only used if use_commodity_stream_timeseries is True + annual_extrapolation_method: + type: string + enum: [tile, final_sim_value, time_averaged_sim_value] + description: Override plant.simulation.annual_extrapolation_method for this subgroup. Should generally match + the simulation setting so capacity factors and variable costs are extended consistently. required: [commodity, technologies] additionalProperties: false cost_adjustment_parameters: diff --git a/h2integrate/core/inputs/validation.py b/h2integrate/core/inputs/validation.py index 60cbdbacd..6a3bdd49f 100644 --- a/h2integrate/core/inputs/validation.py +++ b/h2integrate/core/inputs/validation.py @@ -8,6 +8,7 @@ import jsonschema as json from h2integrate.core.file_utils import load_yaml, write_yaml +from h2integrate.core.annualization import SECONDS_PER_PLANT_YEAR fschema_tech = Path(__file__).parent / "tech_schema.yaml" @@ -146,21 +147,57 @@ def _validate(finput, fschema, defaults=True): # --------------------- def load_tech_yaml(finput): + """Validate and return a technology configuration. + + Args: + finput (dict | str | Path): Technology configuration or path to its YAML file. + + Returns: + dict: Validated technology configuration. + """ return _validate(finput, fschema_tech) def load_plant_yaml(finput): + """Validate a plant configuration and ensure its simulation horizon fits the plant life. + + Args: + finput (dict | str | Path): Plant configuration or path to its YAML file. + + Returns: + dict: Validated plant configuration. + + Raises: + ValueError: If the simulation horizon is non-positive or longer than the plant life. + """ plant_config = _validate(finput, fschema_plant) - n_timesteps = plant_config["plant"]["simulation"]["n_timesteps"] - dt = plant_config["plant"]["simulation"]["dt"] + n_timesteps = int(plant_config["plant"]["simulation"]["n_timesteps"]) + dt = int(plant_config["plant"]["simulation"]["dt"]) + plant_life = int(plant_config["plant"]["plant_life"]) - if int(n_timesteps) * int(dt) != 31_536_000: + seconds_simulated = n_timesteps * dt + years_simulated = seconds_simulated / SECONDS_PER_PLANT_YEAR + + # The simulation may cover any positive duration (a fraction of a year, a single + # year, or multiple years). Performance/cost/finance models annualize their results + # using ``fraction_of_year_simulated`` (see the model base classes), so an arbitrary + # horizon is supported as long as it is positive and does not exceed the plant life. + if seconds_simulated <= 0: msg = ( - "H2Integrate does not currently support simulations that are less than or " - "greater than 1-year. Please ensure that " + "The simulation horizon must be positive. Please ensure that " "plant_config['plant']['simulation']['n_timesteps'] times " - "plant_config['plant']['simulation']['dt'] equals 31536000 (s)." + "plant_config['plant']['simulation']['dt'] is greater than 0 (s)." + ) + raise ValueError(msg) + + if years_simulated > plant_life: + msg = ( + "H2Integrate does not support simulations that are longer than the plant " + f"life. The configured simulation covers {years_simulated:.4g} years " + f"(n_timesteps={n_timesteps} * dt={dt} s = {seconds_simulated} s), but " + f"plant_config['plant']['plant_life'] is {plant_life} years. Either shorten " + "the simulation horizon or increase plant_life." ) raise ValueError(msg) @@ -168,6 +205,14 @@ def load_plant_yaml(finput): def load_driver_yaml(finput): + """Validate and return a driver configuration. + + Args: + finput (dict | str | Path): Driver configuration or path to its YAML file. + + Returns: + dict: Validated driver configuration. + """ return _validate(finput, fschema_driver) diff --git a/h2integrate/core/model_baseclass.py b/h2integrate/core/model_baseclass.py index b66448431..a5fc42195 100644 --- a/h2integrate/core/model_baseclass.py +++ b/h2integrate/core/model_baseclass.py @@ -1,5 +1,6 @@ import copy import hashlib +import functools from pathlib import Path import dill @@ -8,22 +9,94 @@ from attrs import field, define from h2integrate.core.utilities import BaseConfig +from h2integrate.core.annualization import ( + SECONDS_PER_PLANT_YEAR, + VALID_ANNUAL_EXTRAPOLATION_METHODS, + _project_capacity_factors, + extrapolate_annual_values, + time_weighted_annual_means, + _build_replacement_schedule, + _degraded_replacement_cycles, + _validate_annualization_inputs, +) + + +class SkippableComputeMixin: + """Mixin that lets a component's ``compute()`` be skipped via an option. + + Any subclass's ``compute()`` is automatically wrapped so it is skipped + whenever ``self.options["skip_compute"]`` is True. This lets the + time-marching simulation solver + (:class:`~h2integrate.core.time_marching_solver.TimeMarchingPlantSolver`) + avoid recomputing timestep-independent quantities (e.g. cost and finance + models) on every intermediate simulation step, without requiring each + model to check the flag itself. + """ + + def initialize(self): + super().initialize() + + self.options.declare( + "skip_compute", + types=bool, + default=False, + desc=( + "When True, compute() is skipped entirely. Set by the time-marching " + "simulation solver to avoid recomputing timestep-independent " + "quantities during intermediate simulation steps." + ), + ) + + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + + # Skip the rest of this setup if subclass is flagged as steppable + if getattr(cls, "_is_steppable", False): + return + + # Collect compute method from child class. + user_compute = cls.__dict__.get("compute") + if user_compute is None or getattr(user_compute, "_skips_when_flagged", False): + return + + # Create a new compute method that wraps the child's compute method. + @functools.wraps(user_compute) + def compute(self, *args): + # Steppable simulation is managed by a custom openmdao nonlinear solver, which sets + # the skip_compute option to False for all but the last simulation slice so the child + # class' compute method is skipped until the very end to avoid extra computation. + if self.options["skip_compute"]: + return None + return user_compute(self, *args) + + compute._skips_when_flagged = True + + # Replace the child class's compute method with the wrapped compute method. Now, the + # skip_compute behavior is enabled without modifying the child class's compute method nor + # the way its compute method is called. + cls.compute = compute class PerformanceModelBaseClass(om.ExplicitComponent): + # (min, max) permitted simulation duration in years; default is annual-only. + _simulation_duration_bounds = (1.0, 1.0) + # Flag to indicate steppability. This flag should be overwritten by a subclass when that + # subclass performance model is capable of steppable simulation. + _is_steppable = False + def initialize(self): self.options.declare("driver_config", types=dict) self.options.declare("plant_config", types=dict) self.options.declare("tech_config", types=dict) def setup(self): - # Below should be done in subclass that produces hydrogen + # Below should be done in a subclass that produces hydrogen # self.commodity = "hydrogen" # self.commodity_rate_units = "kg/h" # self.commodity_amount_units = "kg" # super().setup() - # Below should be done in subclass that produces electricity + # Below should be done in a subclass that produces electricity # self.commodity = "electricity" # self.commodity_rate_units = "kW" # self.commodity_amount_units = "kW*h" @@ -32,8 +105,18 @@ def setup(self): # n_timesteps is number of timesteps in a simulation self.n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] + # n_timesteps_window is the number of timesteps simulated per compute call + self.n_timesteps_window = int( + self.options["plant_config"]["plant"]["simulation"].get( + "n_timesteps_window", self.n_timesteps + ) + ) + # dt is seconds per timestep self.dt = int(self.options["plant_config"]["plant"]["simulation"]["dt"]) + self.annual_extrapolation_method = self.options["plant_config"]["plant"]["simulation"].get( + "annual_extrapolation_method" + ) # plant_life is number of years the plant is expected to operate for self.plant_life = int(self.options["plant_config"]["plant"]["plant_life"]) @@ -63,6 +146,9 @@ def setup(self): ) raise NotImplementedError(msg) + # The index to start the simulation slice when compute is called. + self.add_input("timestep_index", val=0, desc="Time step index") + # timeseries profiles self.add_output( f"{self.commodity}_out", @@ -116,6 +202,20 @@ def setup(self): desc=f"Full (uncurtailed) {self.commodity} output", ) + def _get_compute_time_range(self, time_index): + """ + This method gets the range of timestep indices that are simulated in a + single call to compute. + + Args: + time_index (numpy array): Starting time index of the simulation range. + + Returns: + range: range of time indices + """ + ti = int(time_index[0]) + return range(ti, ti + self.n_timesteps_window) + def apply_curtailment(self, outputs): """Apply curtailment to ``{commodity}_out`` based on ``{commodity}_command_value``. @@ -140,6 +240,132 @@ def apply_curtailment(self, outputs): command_value = self._inputs[command_value_key] outputs[commodity_out_key] = np.minimum(uncurtailed, command_value) + def calculate_annual_cf_and_replacement_schedule( + self, + performance_timeseries, + rated_performance, + state_of_health_timeseries, + eol_soh, + no_degradation_extrapolation=None, + soh_cycle_repetition="all_soh_cycles", + ): + """Project annual capacity factors and replacement counts across plant life. + + The simulated horizon can be any positive length up to plant life. Behavior: + + - Without ``state_of_health_timeseries``, capacity factors are time-weighted means of + each simulated plant year (the last may be partial) and are extended to plant life + with the selected extrapolation method. No replacements occur. + - With SOH, the simulation is split into replacement cycles. A cycle ends at an upward + SOH jump greater than 1e-9 (a reset). The final cycle is extended using the SOH loss + between its last two samples until SOH reaches ``eol_soh`` or plant life is covered. + Projected CF is the last timestep CF scaled by projected SOH over last observed SOH. + EOL is checked at timestep boundaries without interpolation, and nothing is inferred + from fewer than two post-reset samples or from stalled degradation. + - Completed cycles are then repeated per ``soh_cycle_repetition`` and averaged into + fixed 365-day plant years. Replacements are integer counts binned into + ``[year, year + 1)``; initial installation and retirement are not counted. + + Args: + performance_timeseries (array-like): timestep-level performance values used + to compute annual capacity factors over the simulated horizon. + rated_performance (float): rated performance value used in capacity-factor + calculation. + state_of_health_timeseries (array-like | None): timestep-level state-of-health + values. If None, no degradation/replacement projection is applied. + When provided, SOH is used to determine replacement timing. + eol_soh (float | None): State-of-health threshold at or below which the + technology is considered replaced. Only required when + ``state_of_health_timeseries`` is provided. + no_degradation_extrapolation (str | None): Extrapolation strategy when + ``state_of_health_timeseries`` is None. If None, use the plant simulation's + ``annual_extrapolation_method``; an override should generally match it. Required + only when multiple simulated annual values + must be extended to cover the remaining plant life. Options: + + - ``"tile"``: repeat simulated annual values cyclically. + - ``"final_sim_value"``: hold the last simulated annual value constant for + the rest of the plant life beyond simulated years. + - ``"time_averaged_sim_value"``: hold simulated average annual value constant for + the rest of the plant life beyond simulated years. + soh_cycle_repetition (str): When ``state_of_health_timeseries`` is provided, + controls what repeats after the simulated (and completed) cycle history. + All simulated data is always preserved at the front of the timeline. + Options: + + - ``"all_soh_cycles"``: repeat the full ordered set of simulated cycles. + - ``"final_soh_cycle"``: repeat only the final completed cycle. + + Returns: + tuple[np.ndarray, np.ndarray]: projected annual capacity factors and + replacement schedule. + """ + if no_degradation_extrapolation is None: + no_degradation_extrapolation = getattr(self, "annual_extrapolation_method", None) + + ( + performance_timeseries, + rated_performance, + state_of_health_timeseries, + use_degradation, + ) = _validate_annualization_inputs( + performance_timeseries, + rated_performance, + state_of_health_timeseries, + eol_soh, + no_degradation_extrapolation, + soh_cycle_repetition, + ) + if not np.isfinite(self.dt) or self.dt <= 0.0: + raise ValueError("dt must be finite and greater than zero.") + if ( + not np.isfinite(self.plant_life) + or self.plant_life < 1 + or int(self.plant_life) != self.plant_life + ): + raise ValueError("plant_life must be a positive integer number of years.") + plant_life = int(self.plant_life) + + # Without degradation, no replacements occur and capacity factors are simply + # extrapolated across the plant life. + if not use_degradation: + ( + simulated_annual_capacity_factors, + simulated_durations_years, + ) = time_weighted_annual_means(performance_timeseries / rated_performance, self.dt) + annual_capacity_factors = extrapolate_annual_values( + simulated_annual_capacity_factors, + plant_life, + no_degradation_extrapolation, + simulated_durations_years=simulated_durations_years, + ) + return annual_capacity_factors, np.zeros(plant_life, dtype=int) + + # With degradation, both outputs derive from the same decomposition of the simulated + # years into replacement cycles. + ( + cycle_cf_segments, + cycle_has_replacement, + cycle_segment_durations, + ) = _degraded_replacement_cycles( + performance_timeseries / rated_performance, + state_of_health_timeseries, + eol_soh, + self.dt, + plant_life, + ) + annual_capacity_factors = _project_capacity_factors( + cycle_cf_segments, soh_cycle_repetition, plant_life, cycle_segment_durations + ) + replacement_schedule = _build_replacement_schedule( + cycle_cf_segments, + cycle_has_replacement, + soh_cycle_repetition, + plant_life, + cycle_segment_durations=cycle_segment_durations, + ) + return annual_capacity_factors, replacement_schedule + def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): """ Computation for the OM component. @@ -155,7 +381,7 @@ class CostModelBaseConfig(BaseConfig): cost_year: int = field(converter=int) -class CostModelBaseClass(om.ExplicitComponent): +class CostModelBaseClass(SkippableComputeMixin, om.ExplicitComponent): """Baseclass to be used for all cost models. The built-in outputs are used by the finance model and must be outputted by all cost models. @@ -169,9 +395,19 @@ class CostModelBaseClass(om.ExplicitComponent): Discrete Outputs: - cost_year (int): dollar-year corresponding to CapEx and OpEx values. This may be inherent to the cost model, or may depend on user provided input values. + + Options: + - skip_compute (bool): see :class:`SkippableComputeMixin`. """ + # (min, max) permitted simulation duration in years; default is annual-only. + _simulation_duration_bounds = (1.0, 1.0) + # Flag to indicate steppability. This flag should be overwritten by a subclass when that + # subclass cost model is capable of steppable simulation. + _is_steppable = False + def initialize(self): + super().initialize() self.options.declare("driver_config", types=dict) self.options.declare("plant_config", types=dict) self.options.declare("tech_config", types=dict) @@ -181,6 +417,9 @@ def setup(self): self.n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) # dt is seconds per timestep self.dt = int(self.options["plant_config"]["plant"]["simulation"]["dt"]) + self.annual_extrapolation_method = self.options["plant_config"]["plant"]["simulation"].get( + "annual_extrapolation_method" + ) # plant_life is number of years the plant is expected to operate for self.plant_life = int(self.options["plant_config"]["plant"]["plant_life"]) @@ -216,6 +455,56 @@ def setup(self): desc="Marginal cost of production for dispatch decisions", ) + def calculate_annual_varopex( + self, + varopex_timeseries, + annual_extrapolation_method=None, + **kwargs, + ): + """Project annual variable OpEx across plant life from timestep-level costs. + + Args: + varopex_timeseries (array-like): timestep-level variable cost values in + annual cost numerator units (for example USD per timestep after any + flow * price multiplication). + annual_extrapolation_method (str | None): Extrapolation strategy after the simulated + horizon. If None, use the plant simulation's ``annual_extrapolation_method``; + an override should generally match it. + Required only when multiple simulated annual values must be extended to cover + the remaining plant life. Options are ``"tile"``, ``"final_sim_value"``, and + ``"time_averaged_sim_value"``. + + Returns: + np.ndarray: annualized variable OpEx for each year of plant life. + """ + varopex_timeseries = np.asarray(varopex_timeseries, dtype=float) + if varopex_timeseries.size == 0: + raise ValueError("varopex_timeseries must contain at least one value.") + + if annual_extrapolation_method is None: + annual_extrapolation_method = getattr(self, "annual_extrapolation_method", None) + + if ( + annual_extrapolation_method is not None + and annual_extrapolation_method not in VALID_ANNUAL_EXTRAPOLATION_METHODS + ): + raise ValueError( + "annual_extrapolation_method must be one of " + f"{VALID_ANNUAL_EXTRAPOLATION_METHODS}; got {annual_extrapolation_method!r}." + ) + + mean_timestep_costs, simulated_durations_years = time_weighted_annual_means( + varopex_timeseries, self.dt + ) + simulated_annual_varopex = mean_timestep_costs * (SECONDS_PER_PLANT_YEAR / self.dt) + + return extrapolate_annual_values( + simulated_annual_varopex, + self.plant_life, + annual_extrapolation_method, + simulated_durations_years=simulated_durations_years, + ) + def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): """ Computation for the OM component. diff --git a/h2integrate/core/model_checks.py b/h2integrate/core/model_checks.py index dc328933f..3b04cf96b 100644 --- a/h2integrate/core/model_checks.py +++ b/h2integrate/core/model_checks.py @@ -1,5 +1,7 @@ """Checks that validate individual model declarations.""" +from h2integrate.core.annualization import SECONDS_PER_PLANT_YEAR + def check_model_time_step(model_name, model_object, time_step): """Check that a model supports the configured simulation time step. @@ -28,6 +30,36 @@ def check_model_time_step(model_name, model_object, time_step): ) +def check_model_simulation_duration(model_name, model_object, n_timesteps, dt): + """Check that a model supports the configured simulation duration. + + Args: + model_name (str): Name used to identify the model in error messages. + model_object: Model class or instance defining ``_simulation_duration_bounds`` + as ``(min, max)`` permitted simulation durations in years. Models that do not + define the attribute default to supporting a single year only. + n_timesteps (int | float): Number of timesteps in the simulation. + dt (int | float): Simulation time step in seconds. + + Returns: + None: Returns when the configured simulation duration is supported. + + Raises: + ValueError: If the simulation duration falls outside the model's supported bounds. + """ + years_simulated = (int(n_timesteps) * int(dt)) / SECONDS_PER_PLANT_YEAR + minimum_years, maximum_years = getattr(model_object, "_simulation_duration_bounds", (1.0, 1.0)) + if minimum_years <= years_simulated <= maximum_years: + return + raise ValueError( + f"Model {model_name} is compatible with simulation durations between " + f"{minimum_years} and {maximum_years} years, but a simulation duration of " + f"{years_simulated:g} years (n_timesteps={int(n_timesteps)} * dt={int(dt)} s) was " + "specified. Please set plant_config['plant']['simulation'] so that n_timesteps * dt " + f"corresponds to a duration within the range [{minimum_years}, {maximum_years}] years." + ) + + def check_model_control_classifier(model_name, model_object, system_level_control_enabled): """Check that a model declares a classifier when system-level control is enabled. @@ -44,3 +76,98 @@ def check_model_control_classifier(model_name, model_object, system_level_contro """ if system_level_control_enabled and not hasattr(model_object, "_control_classifier"): raise ValueError(f"Model {model_name} is missing a control classifier") + + +def check_time_marching_config( + n_timesteps, n_timesteps_window, system_level_control_enabled, has_cycles +): + """Check that the time-marching simulation options are valid and supported. + + Args: + n_timesteps (int): Number of timesteps in the simulation. + n_timesteps_window (int): Number of timesteps simulated per window. + system_level_control_enabled (bool): Whether system-level control is enabled. + has_cycles (bool): Whether the technology graph contains cycles. + + Returns: + bool: True if the plant is to be simulated window by window, False if all timesteps are + simulated at once. + + Raises: + ValueError: If ``n_timesteps_window`` is not positive, is greater than ``n_timesteps``, + or does not evenly divide ``n_timesteps``. + NotImplementedError: If time-marching is combined with system-level control or a + technology graph with cycles. + """ + if n_timesteps_window < 1: + raise ValueError(f"n_timesteps_window ({n_timesteps_window}) must be positive.") + + if n_timesteps_window > n_timesteps: + raise ValueError( + f"n_timesteps_window ({n_timesteps_window}) " + f"cannot be greater than n_timesteps ({n_timesteps})." + ) + + if n_timesteps_window == n_timesteps: + return False + + if n_timesteps % n_timesteps_window != 0: + raise ValueError( + f"n_timesteps ({n_timesteps}) " + f"must be divisible by n_timesteps_window ({n_timesteps_window})." + ) + + if system_level_control_enabled: + raise NotImplementedError( + "Time-marching simulation (n_timesteps_window < n_timesteps) is not supported " + "with system-level control." + ) + + if has_cycles: + raise NotImplementedError( + "Time-marching simulation (n_timesteps_window < n_timesteps) is not supported " + "when the technology interconnections contain a cycle." + ) + + return True + + +def check_model_is_steppable(control_strategies, performance_models): + """Check that all technology control strategies and performance models have the attribute + _is_steppable == True. If _is_steppable attribute is missing or evaluates to False, then the + system is suitable for steppable simulation and an error is raised. + + Args: + control_strategies (list): + performance_models (list): + + Returns: + None: Returns if all models are steppable. + + Raises: + AttributeError: If control strategies or performance models are not flagged as steppable. + """ + + error_msg_list = [] + + non_steppable_control_strategies = [ + cs.__str__() for cs in control_strategies if not getattr(cs, "_is_steppable", False) + ] + if non_steppable_control_strategies: + control_msg = f"Control strategy(ies): {sorted(non_steppable_control_strategies)}" + error_msg_list.append(control_msg) + + non_steppable_performance_models = [ + pm.__str__() for pm in performance_models if not getattr(pm, "_is_steppable", False) + ] + if non_steppable_performance_models: + performance_msg = f"Performance model(s): {sorted(non_steppable_performance_models)}" + error_msg_list.append(performance_msg) + + if error_msg_list: + error_msg = ( + "Attempting to run a steppable simulation with unsupported models:\n\t" + + "\n\t".join(error_msg_list) + + "\n\t are not flagged as capable of steppable simulation." + ) + raise AttributeError(error_msg) diff --git a/h2integrate/core/test/test_annualization.py b/h2integrate/core/test/test_annualization.py new file mode 100644 index 000000000..9a0b5deb4 --- /dev/null +++ b/h2integrate/core/test/test_annualization.py @@ -0,0 +1,176 @@ +"""Tests for the pure annualization and replacement-cycle helpers.""" + +import numpy as np +import pytest + +from h2integrate.core.annualization import ( + _project_capacity_factors, + extrapolate_annual_values, + time_weighted_annual_means, + _build_replacement_schedule, + _degraded_replacement_cycles, +) + + +@pytest.mark.unit +@pytest.mark.parametrize( + "soh,expected_completed", + [ + (np.array([1.0, 0.9, 0.8, 0.8]), [False]), + (np.array([1.0, 0.9, 0.8, 0.7]), [False]), + (np.array([1.0, 0.8, 1.0, 0.8]), [True, False]), + (np.zeros(4), [False]), + ], +) +def test_observed_cycles_complete_only_at_soh_resets(subtests, soh, expected_completed) -> None: + _, completed, durations = _degraded_replacement_cycles( + timestep_capacity_factors=np.ones(soh.size), + state_of_health_timeseries=soh, + eol_soh=0.8, + dt=31_536_000 / 4, + plant_life=1, + ) + + with subtests.test("only observed upward SOH jumps complete cycles"): + assert completed == expected_completed + with subtests.test("no projection extends beyond the observed plant lifetime"): + assert sum(np.sum(segment) for segment in durations) == pytest.approx(1.0) + + +@pytest.mark.unit +@pytest.mark.parametrize( + "soh", + [np.array([1.0, 0.8]), np.array([0.8, 0.7]), np.full(2, 0.8), np.array([0.8]), np.zeros(2)], +) +def test_projected_eol_at_or_below_final_observed_soh(subtests, soh) -> None: + capacity_factors, completed, durations = _degraded_replacement_cycles( + timestep_capacity_factors=soh.copy(), + state_of_health_timeseries=soh, + eol_soh=0.8, + dt=31_536_000 / 4, + plant_life=3, + ) + + with subtests.test("EOL completes the cycle only after a projected timestep"): + assert completed == [True] + assert np.sum(durations[0]) == pytest.approx((soh.size + 1) / 4) + with subtests.test("zero SOH does not produce undefined capacity factors"): + assert np.all(np.isfinite(capacity_factors[0])) + + +@pytest.mark.unit +def test_time_weighted_annual_means() -> None: + """Return time-weighted means and durations for two complete plant years.""" + annual_means, durations = time_weighted_annual_means( + timestep_values=np.array([0.5, 0.25]), dt=31_536_000 + ) + np.testing.assert_allclose(annual_means, [0.5, 0.25]) + np.testing.assert_allclose(durations, [1.0, 1.0]) + + +@pytest.mark.unit +def test_extrapolate_annual_values_modes(): + simulated = np.array([0.6, 0.5, 0.4]) + np.testing.assert_allclose( + extrapolate_annual_values(simulated_values=simulated, plant_life=6, method="tile"), + [0.6, 0.5, 0.4, 0.6, 0.5, 0.4], + ) + np.testing.assert_allclose( + extrapolate_annual_values( + simulated_values=simulated, plant_life=6, method="final_sim_value" + ), + [0.6, 0.5, 0.4, 0.4, 0.4, 0.4], + ) + np.testing.assert_allclose( + extrapolate_annual_values( + simulated_values=simulated, plant_life=6, method="time_averaged_sim_value" + ), + [0.6, 0.5, 0.4, 0.5, 0.5, 0.5], + ) + with pytest.raises(ValueError): + extrapolate_annual_values(simulated_values=simulated, plant_life=6, method="not_a_mode") + + +@pytest.mark.unit +@pytest.mark.parametrize( + "method,expected_values", + [ + ("tile", np.array([1.0, 0.0, 1.0, 0.0])), + ("final_sim_value", np.array([1.0, 0.0, 0.0, 0.0])), + ("time_averaged_sim_value", np.array([1.0, 0.0, 0.8, 0.8])), + ], +) +def test_extrapolate_annual_values_with_partial_year( + method: str, expected_values: np.ndarray +) -> None: + """Use interval durations only when averaging beyond the simulated horizon.""" + projected_values = extrapolate_annual_values( + simulated_values=np.array([1.0, 0.0]), + plant_life=4, + method=method, + simulated_durations_years=np.array([1.0, 0.25]), + ) + np.testing.assert_allclose(projected_values, expected_values) + + +@pytest.mark.unit +def test_extrapolate_annual_values_requires_method_only_when_needed(): + with pytest.raises(ValueError, match="annual_extrapolation_method is required"): + extrapolate_annual_values( + simulated_values=np.array([1.0, 0.0]), + plant_life=3, + method=None, + ) + + np.testing.assert_array_equal( + extrapolate_annual_values( + simulated_values=np.array([1.0, 0.0]), + plant_life=2, + method=None, + ), + [1.0, 0.0], + ) + + +@pytest.mark.unit +def test_project_capacity_factors_repetition_modes(): + # Two completed cycles of lengths 2 and 1. + cycle_cf_segments = [np.array([1.0, 0.9]), np.array([0.8])] + np.testing.assert_allclose( + _project_capacity_factors( + cycle_cf_segments, soh_cycle_repetition="all_soh_cycles", plant_life=5 + ), + [1.0, 0.9, 0.8, 1.0, 0.9], + ) + np.testing.assert_allclose( + _project_capacity_factors( + cycle_cf_segments, soh_cycle_repetition="final_soh_cycle", plant_life=5 + ), + [1.0, 0.9, 0.8, 0.8, 0.8], + ) + + +@pytest.mark.unit +def test_build_replacement_schedule_marks_post_eol_cycle_starts(): + # A single 3-year cycle that reaches end of life, tiled across a 5-year plant life, + # triggers one replacement at the start of the second cycle (year 3). + cycle_cf_segments = [np.array([1.0, 0.9, 0.8])] + np.testing.assert_allclose( + _build_replacement_schedule( + cycle_cf_segments, + cycle_has_replacement=[True], + soh_cycle_repetition="all_soh_cycles", + plant_life=5, + ), + [0.0, 0.0, 0.0, 1.0, 0.0], + ) + # No replacements when the cycle never reaches end of life. + np.testing.assert_allclose( + _build_replacement_schedule( + cycle_cf_segments, + cycle_has_replacement=[False], + soh_cycle_repetition="all_soh_cycles", + plant_life=5, + ), + np.zeros(5), + ) diff --git a/h2integrate/core/test/test_connection_utils.py b/h2integrate/core/test/test_connection_utils.py new file mode 100644 index 000000000..57030e4b4 --- /dev/null +++ b/h2integrate/core/test/test_connection_utils.py @@ -0,0 +1,34 @@ +import pytest + +from h2integrate.core.connection_utils import check_resource_models_are_connected + + +@pytest.mark.unit +def test_check_resource_models_are_connected(subtests): + sites = {"site": {"resources": {"wind_resource": {}, "solar_resource": {}}}} + + with subtests.test("all resources connected"): + connections = [ + ["site.wind_resource", "wind", "wind_resource_data"], + ["site.solar_resource", "solar", "solar_resource_data"], + ] + check_resource_models_are_connected(sites, connections) + + with subtests.test("site-level connections without resources"): + check_resource_models_are_connected( + {"site": {}}, [["site", "wind", ["latitude", "latitude"]]] + ) + + with subtests.test("unconnected resource"): + connections = [["site.wind_resource", "wind", "wind_resource_data"]] + with pytest.raises(ValueError, match="not connected to a technology"): + check_resource_models_are_connected(sites, connections) + + with subtests.test("undefined resource"): + connections = [ + ["site.wind_resource", "wind", "wind_resource_data"], + ["site.solar_resource", "solar", "solar_resource_data"], + ["site.tidal_resource", "tidal", "tidal_resource_data"], + ] + with pytest.raises(ValueError, match=r"Missing resource\(s\) are not defined"): + check_resource_models_are_connected(sites, connections) diff --git a/h2integrate/core/test/test_framework.py b/h2integrate/core/test/test_framework.py index c568c9d7c..0f4f87f76 100644 --- a/h2integrate/core/test/test_framework.py +++ b/h2integrate/core/test/test_framework.py @@ -7,6 +7,7 @@ import yaml import numpy as np import pytest +import openmdao.api as om import h2integrate.postprocess.reporting as reporting_module import h2integrate.core.h2integrate_model as h2i_model_module @@ -18,7 +19,13 @@ load_plant_yaml, load_driver_yaml, ) -from h2integrate.core.model_checks import check_model_time_step, check_model_control_classifier +from h2integrate.core.model_checks import ( + check_model_time_step, + check_model_is_steppable, + check_time_marching_config, + check_model_control_classifier, + check_model_simulation_duration, +) from h2integrate.core.connection_utils import ( create_technology_graph, check_dispatch_connections, @@ -884,33 +891,33 @@ def test_custom_financial_model_grouping(temp_dir, subtests): # docs fencepost start: DO NOT REMOVE @pytest.mark.unit -def test_unsupported_simulation_parameters(temp_dir): +def test_unsupported_simulation_parameters(): orig_plant_config = EXAMPLE_DIR / "01_onshore_steel_mn" / "plant_config.yaml" - temp_plant_config_ntimesteps = temp_dir / "temp_plant_config_ntimesteps.yaml" - temp_plant_config_dt = temp_dir / "temp_plant_config_dt.yaml" + plant_config_data_ntimesteps = load_plant_yaml(orig_plant_config) + plant_config_data_dt = deepcopy(plant_config_data_ntimesteps) + # docs fencepost end: DO NOT REMOVE - shutil.copy(orig_plant_config, temp_plant_config_ntimesteps) - shutil.copy(orig_plant_config, temp_plant_config_dt) + plant_life = int(plant_config_data_ntimesteps["plant"]["plant_life"]) - # Load the plant_config YAML content - plant_config_data_ntimesteps = load_plant_yaml(temp_plant_config_ntimesteps) - plant_config_data_dt = load_plant_yaml(temp_plant_config_dt) - # docs fencepost end: DO NOT REMOVE + # Sub-year and multi-year horizons are supported when positive and within plant life. + plant_config_data_ntimesteps["plant"]["simulation"]["n_timesteps"] = 4380 # 0.5 year at 1 h + load_plant_yaml(plant_config_data_ntimesteps) - # Modify the n_timesteps entry for the temp_plant_config_ntimesteps - plant_config_data_ntimesteps["plant"]["simulation"]["n_timesteps"] = 8759 - # Modify the dt entry for the temp_plant_config_dt - plant_config_data_dt["plant"]["simulation"]["dt"] = 3601 + # A two-year hourly horizon should also load without error. + plant_config_data_dt["plant"]["simulation"]["n_timesteps"] = 2 * 8760 + load_plant_yaml(plant_config_data_dt) - # Save the modified plant_configs YAML back - with temp_plant_config_ntimesteps.open("w") as f: - yaml.safe_dump(plant_config_data_ntimesteps, f) - with temp_plant_config_dt.open("w") as f: - yaml.safe_dump(plant_config_data_dt, f) + # A horizon longer than the plant life is not supported and must raise. + over_life = deepcopy(plant_config_data_dt) + over_life["plant"]["simulation"]["n_timesteps"] = (plant_life + 1) * 8760 + with pytest.raises(ValueError, match="longer than the plant"): + load_plant_yaml(over_life) - # check that error is thrown when loading config with invalid number of timesteps - with pytest.raises(ValueError, match="greater than 1-year"): - load_plant_yaml(plant_config_data_ntimesteps) + # A non-positive horizon is invalid and must raise. + non_positive = deepcopy(plant_config_data_dt) + non_positive["plant"]["simulation"]["dt"] = 0 + with pytest.raises(ValueError, match="must be positive"): + load_plant_yaml(non_positive) @pytest.mark.unit @@ -958,6 +965,188 @@ class ClassifiedModel: check_model_control_classifier("ClassifiedModel", ClassifiedModel, True) +@pytest.mark.unit +def test_check_simulation_duration_defaults_to_annual_only(): + class DummyModel: + pass + + # Exactly one year (8760 h at 1 h steps) is allowed by default. + check_model_simulation_duration("DummyModel", DummyModel, 8760, 3600) + + with pytest.raises( + ValueError, + match=( + r"Model DummyModel is compatible with simulation durations between " + r"1.0 and 1.0 years, but a simulation duration of 2 years" + ), + ): + check_model_simulation_duration("DummyModel", DummyModel, 2 * 8760, 3600) + + +@pytest.mark.unit +def test_check_simulation_duration_respects_model_bounds(): + class MultiYearModel: + # (min, max) permitted simulation duration in years + _simulation_duration_bounds = (0.0, 5.0) + + # Half a year and two years are both within the model's supported range. + check_model_simulation_duration("MultiYearModel", MultiYearModel, 4380, 3600) + check_model_simulation_duration("MultiYearModel", MultiYearModel, 2 * 8760, 3600) + + with pytest.raises( + ValueError, + match=r"compatible with simulation durations between 0.0 and 5.0 years", + ): + check_model_simulation_duration("MultiYearModel", MultiYearModel, 6 * 8760, 3600) + + +@pytest.mark.unit +def test_system_level_controller_defaults_to_annual_only_duration(): + class AnnualOnlyController: + def __init__(self, **kwargs): + raise AssertionError("controller should not be constructed for an unsupported duration") + + model = object.__new__(H2IntegrateModel) + model.plant_config = { + "plant": { + "plant_life": 3, + "simulation": {"n_timesteps": 2 * 8760, "dt": 3600}, + }, + "system_level_control": {"control_strategy": "AnnualOnlyController"}, + } + model.supported_models = {"AnnualOnlyController": AnnualOnlyController} + + with pytest.raises(ValueError, match=r"Model AnnualOnlyController.*simulation durations"): + model.add_system_level_controller({}) + + +@pytest.mark.unit +@pytest.mark.parametrize( + "duration_bounds,n_timesteps,is_supported", + [ + (None, 4380, False), + ((1.0, 1.0), 4380, False), + ((0.0, 5.0), 4380, True), + ((1.0, 1.0), 8760, True), + ], +) +def test_technology_finance_checks_simulation_duration( + monkeypatch: pytest.MonkeyPatch, + duration_bounds: tuple[float, float] | None, + n_timesteps: int, + is_supported: bool, +) -> None: + """Enforce technology-finance duration bounds before creating its component.""" + + class FinanceModel(om.ExplicitComponent): + """Minimal finance component for checking framework duration validation.""" + + def initialize(self) -> None: + """Declare the configuration options passed by the framework.""" + self.options.declare("driver_config", types=dict) + self.options.declare("plant_config", types=dict) + self.options.declare("tech_config", types=dict) + + if duration_bounds is not None: + monkeypatch.setattr( + FinanceModel, "_simulation_duration_bounds", duration_bounds, raising=False + ) + + model = object.__new__(H2IntegrateModel) + model.plant = om.Group() + model.driver_config = {} + model.plant_config = { + "plant": { + "plant_life": 3, + "simulation": {"n_timesteps": n_timesteps, "dt": 3600}, + } + } + model.technology_config = { + "technologies": { + "test_technology": { + "finance_model": {"model": "FinanceModel"}, + "model_inputs": {}, + } + } + } + model.supported_models = {"FinanceModel": FinanceModel} + model.slc = False + + if is_supported: + model.create_technology_models() + assert model.finance_models == [FinanceModel] + else: + with pytest.raises(ValueError, match=r"Model FinanceModel.*simulation durations"): + model.create_technology_models() + assert model.finance_models == [] + + +@pytest.mark.unit +def test_check_model_is_steppable(subtests): + + class TechControllerSteppable: + _is_steppable = True + + def __str__(self): + return "TechControllerSteppable" + + class TechControllerNonSteppable: + def __str__(self): + return "TechControllerNonSteppable" + + class PerformanceModelSteppable: + _is_steppable = True + + def __str__(self): + return "PerformanceModelSteppable" + + class PerformanceModelNonSteppable: + def __str__(self): + return "PerformanceModelNonSteppable" + + with subtests.test("Steppable classes"): + check_model_is_steppable( + control_strategies=[TechControllerSteppable()], + performance_models=[PerformanceModelSteppable()], + ) + + with subtests.test("Nonsteppable classes"): + with pytest.raises(AttributeError): + check_model_is_steppable( + control_strategies=[TechControllerNonSteppable()], + performance_models=[PerformanceModelSteppable(), PerformanceModelNonSteppable()], + ) + + +@pytest.mark.unit +def test_check_time_marching_config(subtests): + with subtests.test("Valid window size enables time-marching"): + assert check_time_marching_config(8760, 24, False, False) is True + + with subtests.test("Window size equal to n_timesteps is a normal simulation"): + assert check_time_marching_config(8760, 8760, True, True) is False + + with subtests.test("Non-positive window size"): + with pytest.raises(ValueError, match=r"must be positive"): + check_time_marching_config(8760, 0, False, False) + + with subtests.test("Window size greater than n_timesteps"): + with pytest.raises(ValueError, match=r"cannot be greater than n_timesteps \(8760\)"): + check_time_marching_config(8760, 9000, False, False) + + with subtests.test("Window size does not divide n_timesteps"): + with pytest.raises(ValueError, match=r"divisible by n_timesteps_window \(7\)"): + check_time_marching_config(8760, 7, False, False) + + with subtests.test("System-level control is not supported"): + with pytest.raises(NotImplementedError, match="system-level control"): + check_time_marching_config(8760, 24, True, False) + + with subtests.test("Technology cycles are not supported"): + with pytest.raises(NotImplementedError, match="cycle"): + check_time_marching_config(8760, 24, False, True) + + @pytest.mark.unit def test_technology_connections(temp_dir): # Path to the original plant_config.yaml and high-level yaml in the example directory diff --git a/h2integrate/core/test/test_model_baseclasses.py b/h2integrate/core/test/test_model_baseclasses.py new file mode 100644 index 000000000..6869a306f --- /dev/null +++ b/h2integrate/core/test/test_model_baseclasses.py @@ -0,0 +1,700 @@ +"""Tests for ``PerformanceModelBaseClass.calculate_annual_cf_and_replacement_schedule``.""" + +import math + +import numpy as np +import pytest + +from h2integrate.core.model_baseclass import CostModelBaseClass, PerformanceModelBaseClass + + +class _DummyPerformanceModel: + """Minimal stand-in exposing the attributes the helper reads (``plant_life``, ``dt``).""" + + def __init__(self, plant_life=6, dt=31_536_000, annual_extrapolation_method="tile"): + self.plant_life = plant_life + self.dt = dt + self.annual_extrapolation_method = annual_extrapolation_method + + +def _run(dummy, performance_timeseries, rated, soh, eol, **kwargs): + return PerformanceModelBaseClass.calculate_annual_cf_and_replacement_schedule( + dummy, + performance_timeseries=np.asarray(performance_timeseries, dtype=float), + rated_performance=rated, + state_of_health_timeseries=None if soh is None else np.asarray(soh, dtype=float), + eol_soh=eol, + **kwargs, + ) + + +@pytest.mark.unit +def test_resets_on_replacement(): + cf_values, replacement_schedule = _run( + _DummyPerformanceModel(plant_life=6), + performance_timeseries=[1.0, 0.92, 0.84], + rated=1.0, + soh=[0.97, 0.89, 0.8], + eol=0.8, + ) + + projected_cf = 0.84 * 0.71 / 0.8 + np.testing.assert_allclose(cf_values, [1.0, 0.92, 0.84, projected_cf, 1.0, 0.92]) + np.testing.assert_allclose(replacement_schedule, [0, 0, 0, 0, 1, 0]) + + +@pytest.mark.unit +def test_without_degradation_tile(): + cf_values, replacement_schedule = _run( + _DummyPerformanceModel(plant_life=6), + performance_timeseries=[1.2, 1.0, 0.8], + rated=2.0, + soh=None, + eol=None, + no_degradation_extrapolation="tile", + ) + + np.testing.assert_allclose(cf_values, [0.6, 0.5, 0.4, 0.6, 0.5, 0.4]) + np.testing.assert_allclose(replacement_schedule, np.zeros(6)) + + +@pytest.mark.unit +def test_without_degradation_final_sim_value(): + cf_values, replacement_schedule = _run( + _DummyPerformanceModel(plant_life=6), + performance_timeseries=[0.6, 0.5, 0.4], + rated=1.0, + soh=None, + eol=None, + no_degradation_extrapolation="final_sim_value", + ) + + np.testing.assert_allclose(cf_values, [0.6, 0.5, 0.4, 0.4, 0.4, 0.4]) + np.testing.assert_allclose(replacement_schedule, np.zeros(6)) + + +@pytest.mark.unit +def test_without_degradation_time_averaged_sim_value(): + cf_values, replacement_schedule = _run( + _DummyPerformanceModel(plant_life=6), + performance_timeseries=[0.6, 0.5, 0.4], + rated=1.0, + soh=None, + eol=None, + no_degradation_extrapolation="time_averaged_sim_value", + ) + + np.testing.assert_allclose(cf_values, [0.6, 0.5, 0.4, 0.5, 0.5, 0.5]) + np.testing.assert_allclose(replacement_schedule, np.zeros(6)) + + +@pytest.mark.unit +def test_average_capacity_factor_weights_partial_year() -> None: + """Weight a full year and a partial year by their elapsed durations.""" + capacity_factors, replacement_schedule = _run( + _DummyPerformanceModel(plant_life=4, dt=31_536_000 / 4), + performance_timeseries=np.array([1.0, 1.0, 1.0, 1.0, 0.0]), + rated=1.0, + soh=None, + eol=None, + no_degradation_extrapolation="time_averaged_sim_value", + ) + np.testing.assert_allclose(capacity_factors, [1.0, 0.0, 0.8, 0.8]) + np.testing.assert_array_equal(replacement_schedule, np.zeros(4, dtype=int)) + + +@pytest.mark.unit +def test_extrapolation_method_required_for_multiple_simulated_years(): + with pytest.raises(ValueError, match="annual_extrapolation_method is required"): + _run( + _DummyPerformanceModel(plant_life=3, annual_extrapolation_method=None), + performance_timeseries=[1.0, 0.0], + rated=1.0, + soh=None, + eol=None, + ) + + cost_model = CostModelBaseClass() + cost_model.dt = 31_536_000 + cost_model.plant_life = 3 + with pytest.raises(ValueError, match="annual_extrapolation_method is required"): + cost_model.calculate_annual_varopex(np.array([10.0, 0.0])) + + +@pytest.mark.unit +def test_extrapolation_method_not_required_for_single_simulated_year(subtests): + with subtests.test( + "single simulated year does not require extrapolation method for capacity factors" + ): + capacity_factors, _ = _run( + _DummyPerformanceModel(plant_life=3, annual_extrapolation_method=None), + performance_timeseries=[0.5], + rated=1.0, + soh=None, + eol=None, + ) + np.testing.assert_allclose(capacity_factors, [0.5, 0.5, 0.5]) + + with subtests.test( + "single simulated year does not require extrapolation method for variable OPEX" + ): + cost_model = CostModelBaseClass() + cost_model.dt = 31_536_000 + cost_model.plant_life = 3 + np.testing.assert_allclose( + cost_model.calculate_annual_varopex(np.array([10.0])), [10.0, 10.0, 10.0] + ) + + +@pytest.mark.unit +def test_average_varopex_weights_partial_year() -> None: + """Weight annualized variable costs by the duration of each observed interval.""" + cost_model = CostModelBaseClass() + cost_model.dt = 31_536_000 / 4 + cost_model.plant_life = 4 + annual_varopex = cost_model.calculate_annual_varopex( + np.array([10.0, 10.0, 10.0, 10.0, 0.0]), + annual_extrapolation_method="time_averaged_sim_value", + ) + np.testing.assert_allclose(annual_varopex, [40.0, 0.0, 32.0, 32.0]) + + +@pytest.mark.unit +@pytest.mark.parametrize( + "annual_extrapolation_method,expected_capacity_factors,expected_varopex", + [ + ("tile", [1.0, 0.0, 1.0, 0.0], [10.0, 0.0, 10.0, 0.0]), + ("final_sim_value", [1.0, 0.0, 0.0, 0.0], [10.0, 0.0, 0.0, 0.0]), + ("time_averaged_sim_value", [1.0, 0.0, 0.5, 0.5], [10.0, 0.0, 5.0, 5.0]), + ], +) +def test_plant_annual_extrapolation_method_is_base_class_default( + annual_extrapolation_method: str, + expected_capacity_factors: list[float], + expected_varopex: list[float], +) -> None: + performance_model = _DummyPerformanceModel( + plant_life=4, dt=31_536_000, annual_extrapolation_method=annual_extrapolation_method + ) + capacity_factors, _ = _run( + performance_model, + performance_timeseries=[1.0, 0.0], + rated=1.0, + soh=None, + eol=None, + ) + + cost_model = CostModelBaseClass() + cost_model.dt = 31_536_000 + cost_model.plant_life = 4 + cost_model.annual_extrapolation_method = annual_extrapolation_method + annual_varopex = cost_model.calculate_annual_varopex(np.array([10.0, 0.0])) + + np.testing.assert_allclose(capacity_factors, expected_capacity_factors) + np.testing.assert_allclose(annual_varopex, expected_varopex) + + +@pytest.mark.regression +@pytest.mark.parametrize("dt", [300, 600]) +def test_exact_three_year_horizon_tiles_capacity_factor_and_varopex(subtests, dt: int) -> None: + """Tile year-one values after exact three-year simulations without a spurious segment.""" + steps_per_year = 31_536_000 // dt + timeseries = np.zeros(3 * steps_per_year) + timeseries[:steps_per_year] = 1.0 + + capacity_factors, replacement_schedule = _run( + _DummyPerformanceModel(plant_life=4, dt=dt), + performance_timeseries=timeseries, + rated=1.0, + soh=None, + eol=None, + no_degradation_extrapolation="tile", + ) + + cost_model = CostModelBaseClass() + cost_model.dt = dt + cost_model.plant_life = 4 + annual_varopex = cost_model.calculate_annual_varopex( + timeseries, annual_extrapolation_method="tile" + ) + + with subtests.test("capacity factor tiles the first year after the horizon"): + np.testing.assert_allclose(capacity_factors, [1.0, 0.0, 0.0, 1.0]) + np.testing.assert_array_equal(replacement_schedule, np.zeros(4, dtype=int)) + with subtests.test("variable OpEx tiles the first year after the horizon"): + np.testing.assert_allclose(annual_varopex, [steps_per_year, 0.0, 0.0, steps_per_year]) + + +@pytest.mark.regression +@pytest.mark.parametrize("dt", [300, 600]) +def test_exact_three_year_horizon_final_sim_value_uses_last_complete_year( + subtests, dt: int +) -> None: + """Do not use a roundoff-sized segment as the final simulated annual value.""" + steps_per_year = 31_536_000 // dt + timeseries = np.zeros(3 * steps_per_year) + timeseries[:steps_per_year] = 1.0 + timeseries[-1] = 1.0 + + capacity_factors, _ = _run( + _DummyPerformanceModel(plant_life=4, dt=dt), + performance_timeseries=timeseries, + rated=1.0, + soh=None, + eol=None, + no_degradation_extrapolation="final_sim_value", + ) + + cost_model = CostModelBaseClass() + cost_model.dt = dt + cost_model.plant_life = 4 + annual_varopex = cost_model.calculate_annual_varopex( + timeseries, annual_extrapolation_method="final_sim_value" + ) + + with subtests.test("capacity factor repeats the last complete year"): + np.testing.assert_allclose( + capacity_factors, [1.0, 0.0, 1.0 / steps_per_year, 1.0 / steps_per_year] + ) + with subtests.test("variable OpEx repeats the last complete year"): + np.testing.assert_allclose(annual_varopex, [steps_per_year, 0.0, 1.0, 1.0]) + + +@pytest.mark.unit +@pytest.mark.parametrize("method", ["tile", "final_sim_value", "time_averaged_sim_value"]) +def test_varopex_splits_timestep_at_year_boundary(subtests, method: str) -> None: + """Allocate a crossing timestep's cost by elapsed time before annualizing.""" + cost_model = CostModelBaseClass() + cost_model.dt = 10_000 + cost_model.plant_life = 3 + timestep_costs = np.zeros(4000) + timestep_costs[3153] = 100.0 + annual_varopex = cost_model.calculate_annual_varopex( + timestep_costs, annual_extrapolation_method=method + ) + partial_year_duration = (timestep_costs.size * cost_model.dt - 31_536_000) / 31_536_000 + partial_year_annual_cost = 40.0 / partial_year_duration + expected_fill = { + "tile": 60.0, + "final_sim_value": partial_year_annual_cost, + "time_averaged_sim_value": 100.0 / (1.0 + partial_year_duration), + }[method] + + with subtests.test("the crossing timestep contributes 60 dollars and 40 dollars"): + np.testing.assert_allclose(annual_varopex[:2], [60.0, partial_year_annual_cost]) + with subtests.test("future costs follow the selected extrapolation mode"): + assert annual_varopex[2] == pytest.approx(expected_fill) + + +@pytest.mark.unit +def test_extrapolates_incomplete_trailing_cycle_then_tiles(): + # SOH never reaches EOL during simulation, so the trailing cycle is completed by + # extrapolation before the completed cycle is tiled across plant life. + cf_values, replacement_schedule = _run( + _DummyPerformanceModel(plant_life=8), + performance_timeseries=[1.0, 0.9, 0.8], + rated=1.0, + soh=[0.95, 0.90, 0.85], + eol=0.8, + ) + + extrapolated_cf = 0.8 * 0.80 / 0.85 + np.testing.assert_allclose( + cf_values, + [1.0, 0.9, 0.8, extrapolated_cf, 1.0, 0.9, 0.8, extrapolated_cf], + ) + np.testing.assert_allclose(replacement_schedule, [0, 0, 0, 0, 1, 0, 0, 0]) + + +@pytest.mark.unit +def test_final_soh_cycle_vs_all_soh_cycles(): + performance_timeseries = [1.0, 0.95, 0.9, 0.85, 0.8] + soh = [0.85, 0.80, 0.90, 0.85, 0.80] + + final_cf, final_repl = _run( + _DummyPerformanceModel(plant_life=8), + performance_timeseries=performance_timeseries, + rated=1.0, + soh=soh, + eol=0.8, + soh_cycle_repetition="final_soh_cycle", + ) + np.testing.assert_allclose(final_cf, [1.0, 0.95, 0.9, 0.85, 0.8, 0.75, 0.9, 0.85]) + np.testing.assert_allclose(final_repl, [0, 0, 1, 0, 0, 0, 1, 0]) + + all_cf, all_repl = _run( + _DummyPerformanceModel(plant_life=8), + performance_timeseries=performance_timeseries, + rated=1.0, + soh=soh, + eol=0.8, + soh_cycle_repetition="all_soh_cycles", + ) + np.testing.assert_allclose(all_cf, [1.0, 0.95, 0.9, 0.85, 0.8, 0.75, 1.0, 0.95]) + np.testing.assert_allclose(all_repl, [0, 0, 1, 0, 0, 0, 1, 0]) + + +@pytest.mark.unit +def test_detects_reset_via_upward_soh_jump(): + # SOH never falls to EOL, but resets upward between years 1 and 2, marking a + # completed cycle. The trailing cycle is then completed via extrapolation. + cf_values, replacement_schedule = _run( + _DummyPerformanceModel(plant_life=6), + performance_timeseries=[1.0, 0.95, 0.9, 0.85, 0.8], + rated=1.0, + soh=[0.85, 0.82, 1.0, 0.9, 0.82], + eol=0.8, + ) + + extrapolated_cf = 0.8 * 0.74 / 0.82 + np.testing.assert_allclose(cf_values, [1.0, 0.95, 0.9, 0.85, 0.8, extrapolated_cf]) + np.testing.assert_allclose(replacement_schedule, [0, 0, 1, 0, 0, 0]) + + +@pytest.mark.unit +def test_replacement_schedule_counts_multiple_soh_resets_per_year(subtests): + steps_per_year = 8760 + n_sim_years = 2 + soh = np.linspace(0.99, 0.95, n_sim_years * steps_per_year) + soh[[100, 200, steps_per_year + 300]] = 1.0 + + _, replacement_schedule = _run( + _DummyPerformanceModel(plant_life=6, dt=3600), + performance_timeseries=np.ones(n_sim_years * steps_per_year), + rated=1.0, + soh=soh, + eol=0.8, + ) + + with subtests.test("two replacements are counted in simulated year one"): + assert replacement_schedule[0] == pytest.approx(2.0) + with subtests.test("one replacement is counted in simulated year two"): + assert replacement_schedule[1] == pytest.approx(1.0) + with subtests.test("schedule covers full plant life"): + assert replacement_schedule.shape == (6,) + + +@pytest.mark.unit +def test_invalid_soh_cycle_repetition_raises(): + with pytest.raises(ValueError): + _run( + _DummyPerformanceModel(plant_life=6), + performance_timeseries=[1.0, 0.9, 0.8], + rated=1.0, + soh=[0.95, 0.9, 0.85], + eol=0.8, + soh_cycle_repetition="not_a_mode", + ) + + +@pytest.mark.unit +@pytest.mark.parametrize( + "mode,expected_cf,expected_schedule", + [ + ("all_soh_cycles", [0.8, 0.65125, 0.767083333333, 0.684166666667], [1, 1, 1, 1]), + ("final_soh_cycle", [0.8, 0.67625, 0.67625, 0.734166666667], [1, 1, 0, 1]), + ], +) +def test_subyear_cycles_project_cf_and_replacements_together( + subtests, mode, expected_cf, expected_schedule +): + cf_values, schedule = _run( + _DummyPerformanceModel(plant_life=4, dt=31_536_000 / 4), + performance_timeseries=[1.0, 0.5, 0.9, 0.8, 0.7, 0.6], + rated=1.0, + soh=[0.9, 0.8, 0.97, 0.92, 0.86, 0.8], + eol=0.8, + soh_cycle_repetition=mode, + ) + with subtests.test("cycle-specific annual segments retain their half-year overlaps"): + np.testing.assert_allclose(cf_values, expected_cf) + with subtests.test("replacement counts follow the same cycle starts"): + np.testing.assert_array_equal(schedule, expected_schedule) + + +@pytest.mark.unit +def test_incomplete_and_observed_eol_cycles_project_future_replacements(subtests) -> None: + """Do not treat observed EOL as an observed reset when completing short cycles.""" + dummy = _DummyPerformanceModel(plant_life=3, dt=31_536_000 / 10) + incomplete_cf, incomplete_schedule = _run( + dummy, + performance_timeseries=np.array([1.0, 0.9]), + rated=1.0, + soh=np.array([1.0, 0.9]), + eol=0.8, + ) + observed_eol_cf, observed_eol_schedule = _run( + dummy, + performance_timeseries=np.array([1.0, 0.9, 0.8]), + rated=1.0, + soh=np.array([1.0, 0.9, 0.8]), + eol=0.8, + ) + with subtests.test("observed EOL requires its own future timestep before replacement"): + np.testing.assert_allclose(incomplete_cf, np.full(3, 0.9)) + np.testing.assert_allclose(observed_eol_cf, np.full(3, 0.85)) + with subtests.test("multiple replacements per year remain whole counts"): + np.testing.assert_array_equal(incomplete_schedule, np.full(3, 3)) + np.testing.assert_array_equal(observed_eol_schedule, [2, 2, 3]) + assert np.issubdtype(incomplete_schedule.dtype, np.integer) + + +@pytest.mark.unit +def test_final_subyear_cycle_retains_its_own_performance(subtests) -> None: + """Do not mix production from different cycles before selecting a repeat cycle.""" + cf_values, schedule = _run( + _DummyPerformanceModel(plant_life=3, dt=31_536_000 / 4), + performance_timeseries=np.array([0.0, 0.0, 1.0, 1.0]), + rated=1.0, + soh=np.array([1.0, 0.8, 1.0, 0.8]), + eol=0.8, + soh_cycle_repetition="final_soh_cycle", + ) + with subtests.test("only the final cycle supplies projected production"): + np.testing.assert_allclose(cf_values, [0.5, 0.875, 0.9375]) + with subtests.test("the final cycle includes a projected timestep before repeating"): + np.testing.assert_array_equal(schedule, [1, 1, 2]) + + +@pytest.mark.unit +@pytest.mark.parametrize( + "soh,eol,dt_years,plant_life,expected_schedule", + [ + (np.array([1.0, 0.9]), 0.85, 0.25, 3, np.array([1, 1, 1])), + (np.array([0.1, 0.05]), 0.0, 0.25, 3, np.array([1, 1, 1])), + (np.array([1.0, 0.99]), 0.1, 0.3, 2, np.array([0, 0])), + ], +) +def test_timestep_cycle_completion_boundaries( + subtests, + soh: np.ndarray, + eol: float, + dt_years: float, + plant_life: int, + expected_schedule: np.ndarray, +) -> None: + """Keep projected EOL on timestep boundaries and exclude retirement events.""" + cf_values, schedule = _run( + _DummyPerformanceModel(plant_life=plant_life, dt=dt_years * 31_536_000), + performance_timeseries=soh.copy(), + rated=1.0, + soh=soh, + eol=eol, + ) + with subtests.test("only completed cycles before retirement cause replacements"): + np.testing.assert_array_equal(schedule, expected_schedule) + assert np.issubdtype(schedule.dtype, np.integer) + with subtests.test("projection remains finite through zero SOH or an incomplete cycle"): + assert cf_values.shape == (plant_life,) + assert np.all(np.isfinite(cf_values)) + assert np.all(cf_values >= 0.0) + + +@pytest.mark.unit +def test_partial_year_cycle_projects_future_replacements(subtests): + cf_values, schedule = _run( + _DummyPerformanceModel(plant_life=3, dt=31_536_000 / 4), + performance_timeseries=[1.0, 0.5], + rated=1.0, + soh=[0.9, 0.8], + eol=0.8, + ) + with subtests.test("partial-year cycle fills the entire projected lifetime"): + projected_cf = 0.5 * 0.7 / 0.8 + np.testing.assert_allclose(cf_values, np.full(3, (1.0 + 0.5 + projected_cf) / 3)) + with subtests.test("events at 0.75, 1.5, and 2.25 remain whole replacement counts"): + np.testing.assert_array_equal(schedule, [1, 1, 1]) + + +@pytest.mark.unit +@pytest.mark.parametrize( + "mode,expected_cf,expected_schedule", + [ + ("all_soh_cycles", [0.8, 0.54375, 0.8, 0.54375], [1, 1, 2, 1]), + ("final_soh_cycle", [0.8, 0.54375, 0.491666666667, 0.491666666667], [1, 1, 2, 1]), + ], +) +def test_midyear_resets_preserve_trailing_cycle_phase( + subtests, mode, expected_cf, expected_schedule +): + cf_values, schedule = _run( + _DummyPerformanceModel(plant_life=4, dt=31_536_000 / 4), + performance_timeseries=[1.0, 0.5, 0.9, 0.8, 0.7, 0.6, 0.5], + rated=1.0, + soh=[1.0, 0.9, 1.0, 0.9, 0.8, 1.0, 0.8], + eol=0.8, + soh_cycle_repetition=mode, + ) + with subtests.test("annual means retain observed and repeated partial-year durations"): + np.testing.assert_allclose(cf_values, expected_cf) + with subtests.test("observed event at 1.25 precedes the projected event at 2.0"): + np.testing.assert_array_equal(schedule, expected_schedule) + with subtests.test("all observed and projected replacements are whole units"): + assert np.issubdtype(schedule.dtype, np.integer) + + +@pytest.mark.unit +@pytest.mark.parametrize("mode", ["all_soh_cycles", "final_soh_cycle"]) +def test_multiple_observed_and_extrapolated_resets(subtests, mode): + _, schedule = _run( + _DummyPerformanceModel(plant_life=5, dt=31_536_000 / 4), + performance_timeseries=np.ones(7), + rated=1.0, + soh=[0.8, 1.0, 0.8, 1.0, 0.8, 1.0, 0.9], + eol=0.8, + soh_cycle_repetition=mode, + ) + expected = [2, 1, 3, 1, 3] if mode == "all_soh_cycles" else [2, 1, 2, 1, 1] + with subtests.test("events at 0.25, 0.75, 1.25 and extrapolated 2.0 precede repetition"): + np.testing.assert_array_equal(schedule, expected) + + +@pytest.mark.unit +@pytest.mark.parametrize( + "soh,mode,expected_schedule", + [ + (np.ones(6), "all_soh_cycles", np.zeros(4, dtype=int)), + (np.full(6, 0.9), "all_soh_cycles", np.zeros(4, dtype=int)), + (np.array([0.9, 1.0]), "all_soh_cycles", np.full(4, 2, dtype=int)), + (np.array([0.9, 1.0]), "final_soh_cycle", np.array([1, 0, 0, 0])), + ], +) +def test_stalled_or_unmeasured_post_reset_degradation( + subtests, soh: np.ndarray, mode: str, expected_schedule: np.ndarray +) -> None: + """Do not infer degradation from constant SOH or a single post-reset sample.""" + cf_values, schedule = _run( + _DummyPerformanceModel(plant_life=4, dt=31_536_000 / 4), + performance_timeseries=np.ones(len(soh)), + rated=1.0, + soh=soh, + eol=0.8, + soh_cycle_repetition=mode, + ) + with subtests.test("stalling does not invent an EOL replacement"): + np.testing.assert_array_equal(schedule, expected_schedule) + with subtests.test("annual CF remains available across the full plant life"): + np.testing.assert_allclose(cf_values, np.ones(4)) + + +@pytest.mark.unit +@pytest.mark.parametrize("sample_count", [8, 6]) +def test_partial_final_year_normalizes_soh_degradation(subtests, sample_count): + soh = np.linspace(1.0, 0.9, 8)[:sample_count] + cf_values, schedule = _run( + _DummyPerformanceModel(plant_life=10, dt=31_536_000 / 4), + performance_timeseries=np.ones(sample_count), + rated=1.0, + soh=soh, + eol=0.8, + ) + degradation_per_step = (1.0 - 0.9) / 7 + projected_steps_in_year = 4 - sample_count % 4 + first_projected_cf = ( + soh[-1] - degradation_per_step * np.arange(1, projected_steps_in_year + 1) + ) / soh[-1] + observed_duration = sample_count / 4 + observed_fraction = observed_duration - math.floor(observed_duration) + expected_first_cf = observed_fraction + (1.0 - observed_fraction) * first_projected_cf.mean() + with subtests.test("partial final year uses the same SOH loss per elapsed year"): + assert cf_values[math.floor(observed_duration)] == pytest.approx(expected_first_cf) + with subtests.test("both observation lengths complete the same 3.75-year cycle"): + expected = np.zeros(10, dtype=int) + for event_time in np.arange(3.75, 10, 3.75): + expected[math.floor(event_time)] += 1 + np.testing.assert_array_equal(schedule, expected) + + +@pytest.mark.unit +def test_reset_tolerance_does_not_infer_unobservable_events(subtests): + cf_values, schedule = _run( + _DummyPerformanceModel(plant_life=3, dt=31_536_000 / 4), + performance_timeseries=np.ones(4), + rated=1.0, + soh=[1.0 - 5e-10, 1.0, 1.0, 1.0], + eol=0.8, + ) + with subtests.test("SOH jumps no larger than tolerance are not counted"): + np.testing.assert_array_equal(schedule, [0, 0, 0]) + with subtests.test("fresh equal samples do not imply repeated replacements"): + np.testing.assert_allclose(cf_values, [1, 1, 1]) + + +@pytest.mark.unit +@pytest.mark.parametrize( + "overrides,match", + [ + ({"performance_timeseries": []}, "at least one"), + ({"performance_timeseries": [[1.0] * 8]}, "one-dimensional"), + ({"performance_timeseries": [np.nan] * 8}, "finite"), + ({"performance_timeseries": [np.inf] * 8}, "finite"), + ({"soh": np.ones(5)}, "match"), + ({"soh": []}, "at least one"), + ({"soh": [[1.0] * 8]}, "one-dimensional"), + ({"soh": [np.nan] * 8}, "finite"), + ({"soh": [np.inf] * 8}, "finite"), + ({"soh": [-0.1] * 8}, "between zero and one"), + ({"soh": [1.1] * 8}, "between zero and one"), + ({"rated": 0}, "rated_performance"), + ({"rated": -1}, "rated_performance"), + ({"rated": np.inf}, "rated_performance"), + ({"rated": np.nan}, "rated_performance"), + ({"eol": None}, "eol_soh is required"), + ({"eol": np.nan}, "eol_soh"), + ({"eol": np.inf}, "eol_soh"), + ({"eol": -0.1}, "eol_soh"), + ({"eol": 1.1}, "eol_soh"), + ], +) +def test_annualization_rejects_invalid_arrays_and_quantities(overrides, match): + inputs = {"performance_timeseries": np.ones(8), "rated": 1.0, "soh": np.ones(8), "eol": 0.8} + inputs.update(overrides) + with pytest.raises(ValueError, match=match): + _run(_DummyPerformanceModel(dt=31_536_000 / 4), **inputs) + + +@pytest.mark.unit +@pytest.mark.parametrize( + "attribute,value", + [ + ("dt", 0), + ("dt", np.inf), + ("dt", -1), + ("plant_life", 0), + ("plant_life", 1.5), + ("plant_life", np.nan), + ], +) +def test_annualization_rejects_invalid_clock(attribute, value): + dummy = _DummyPerformanceModel() + setattr(dummy, attribute, value) + with pytest.raises(ValueError, match=attribute): + _run(dummy, performance_timeseries=[1.0], rated=1.0, soh=None, eol=None) + + +@pytest.mark.unit +def test_annual_means_weight_timesteps_crossing_fixed_year_boundaries(subtests): + cf_values, schedule = _run( + _DummyPerformanceModel(plant_life=3, dt=0.3 * 31_536_000), + performance_timeseries=[0.2, 0.4, 0.6, 0.8], + rated=1.0, + soh=[0.9, 0.85, 0.8, 0.75], + eol=0.8, + ) + with subtests.test("projected 1.5-year cycles share the same projection clock"): + np.testing.assert_allclose(cf_values, [0.44, 0.604, 0.604]) + with subtests.test("replacement at 1.5 is counted and retirement at 3.0 is excluded"): + np.testing.assert_array_equal(schedule, [0, 1, 0]) + + +@pytest.mark.unit +def test_invalid_no_degradation_extrapolation_raises(): + with pytest.raises(ValueError): + _run( + _DummyPerformanceModel(plant_life=6), + performance_timeseries=[0.6, 0.5, 0.4], + rated=1.0, + soh=None, + eol=None, + no_degradation_extrapolation="not_a_mode", + ) diff --git a/h2integrate/core/test/test_utilities.py b/h2integrate/core/test/test_utilities.py index 082193ec9..0cc254491 100644 --- a/h2integrate/core/test/test_utilities.py +++ b/h2integrate/core/test/test_utilities.py @@ -17,7 +17,12 @@ load_driver_yaml, ) from h2integrate.core.utilities import BaseConfig, build_time_series_from_plant_config -from h2integrate.core.dict_utils import check_inputs, dict_to_yaml_formatting +from h2integrate.core.dict_utils import ( + check_inputs, + percent_diff_dicts, + dict_to_yaml_formatting, + find_nonzero_percent_diffs, +) from h2integrate.core.file_utils import ( get_path, find_file, @@ -881,3 +886,53 @@ def test_check_inputs(subtests): assert expected_error_05 == str(excinfo.value) else: check_inputs(prob, tech, tech_info, tech_config_fpath) + + +@pytest.mark.unit +def test_percent_diff_dicts(subtests): + d1 = { + "v1": 10.5, + "v2": np.array([1.0, 2.0, 3.5]), + "v3": {"results": ["complicated_string", 12345]}, + "v4": "A good result", + "v5": True, + } + d2 = { + "v1": 10.5, + "v2": np.array([1.0, 2.0, 3.5]), + "v3": {"results": ["complicated_string", 54321]}, + "v4": "A bad result", + "v5": False, + } + + # Match openmdao formatting for input and output dicts. + d1 = {k: {"val": v} for k, v in d1.items()} + d2 = {k: {"val": v} for k, v in d2.items()} + + pd_same = percent_diff_dicts(d1, d2) + abs_same, rel_same = find_nonzero_percent_diffs(pd_same, d1) + + with subtests.test("Same dicts case"): + # Only the float and the array should be compared. The other values are skipped. + assert len(pd_same) == 2 + + assert pd_same["v1"] == 0 + assert pd_same["v2"] == 0 + + assert len(abs_same) == 0 + assert len(rel_same) == 0 + + # Change some values so there are differences between the two dicts. + d2["v1"]["val"] = 10 + d2["v2"]["val"] = np.array([1.1, 2.0, 3.6]) + + pd_different = percent_diff_dicts(d1, d2) + abs_different, rel_different = find_nonzero_percent_diffs(pd_different, d1) + + with subtests.test("Different dicts case"): + assert len(pd_different) == 2 + assert np.isclose(4.878048780487805, pd_different["v1"]) + assert np.isclose(9.931660555553472, pd_different["v2"]) + + assert len(abs_different) == 2 + assert len(rel_different) == 2 diff --git a/h2integrate/core/time_marching_solver.py b/h2integrate/core/time_marching_solver.py new file mode 100644 index 000000000..ad4c79328 --- /dev/null +++ b/h2integrate/core/time_marching_solver.py @@ -0,0 +1,67 @@ +import numpy as np +from openmdao.recorders.recording_iteration_stack import Recording +from openmdao.solvers.nonlinear.nonlinear_runonce import NonlinearRunOnce + + +class TimeMarchingPlantSolver(NonlinearRunOnce): + """ + Custom nonlinear solver to manage running the plant group in a loop. + + """ + + def __init__(self, plant_config): + super().__init__() + self.plant_config = plant_config + + def solve(self): + # Should only be used when system is the plant group + system = self._system() + + # Find subsystems that take timestep_index as an input + # Should only be performance models + timestep_keys = [k for k in system._inputs.keys() if k.endswith("timestep_index")] + + n_timesteps = self.plant_config["plant"]["simulation"]["n_timesteps"] + n_timesteps_window = self.plant_config["plant"]["simulation"]["n_timesteps_window"] + + # Make time stepping loop + sim_starts = np.arange(0, n_timesteps, n_timesteps_window) + + final_timestep_index = sim_starts[-1] + + # Subsystems whose compute() can be skipped on intermediate timesteps + # (cost/finance models; see SkippableComputeMixin). Found by option + # rather than by type to avoid coupling this solver to specific + # baseclasses. + skippable_subsystems = [ + s + for s in system.system_iter(include_self=False, recurse=True) + if "skip_compute" in getattr(s, "options", {}) + ] + + # Skip those subsystems' calculations in most of the simulation periods. + for s in skippable_subsystems: + s.options["skip_compute"] = True + + try: + with Recording("NLRunOnce", 0, self) as rec: + for ss in sim_starts: + # Update timestep_index in all subsystems + for tk in timestep_keys: + system._inputs[tk] = ss + + if ss == final_timestep_index: + # Allow skippable subsystems to compute once, on the final + # simulation period. + for s in skippable_subsystems: + s.options["skip_compute"] = False + + # Run one GS iteration on the plant group + self._gs_iter() + + rec.abs = 0.0 + rec.rel = 0.0 + finally: + # A failed step must not leave cost and finance models skipped on the next run. + for s in skippable_subsystems: + s.options["skip_compute"] = False diff --git a/h2integrate/demand/demand_baseclass.py b/h2integrate/demand/demand_baseclass.py index 9f3f0ddaa..0b3aec2f8 100644 --- a/h2integrate/demand/demand_baseclass.py +++ b/h2integrate/demand/demand_baseclass.py @@ -118,7 +118,7 @@ def compute(self): """ raise NotImplementedError("This method should be implemented in a subclass.") - def calculate_outputs(self, commodity_in, commodity_demand, outputs): + def calculate_outputs(self, commodity_in, commodity_demand, inputs, outputs): """Compute unmet demand, unused commodity, and converter output. This method compares the demand profile to the supplied commodity for @@ -129,6 +129,7 @@ def calculate_outputs(self, commodity_in, commodity_demand, outputs): Args: commodity_in (np.array): supplied commodity profile commodity_demand (np.array): entire commodity demand profile + inputs (dict-like): OM inputs to `compute()` method. outputs (dict-like): Mapping of output variable names where results will be written, including: @@ -145,20 +146,37 @@ def calculate_outputs(self, commodity_in, commodity_demand, outputs): ``curtailment_percent``, which are scalar summary outputs. """ - remaining_demand = commodity_demand - commodity_in + # Range object for the slice of the total simulation to run in this + # compute call + simulation_range = self._get_compute_time_range(inputs["timestep_index"]) + + # Arrays relevant to the current simulation range + commodity_in_sim = commodity_in[simulation_range.start : simulation_range.stop] + commodity_demand_sim = commodity_demand[simulation_range.start : simulation_range.stop] + + remaining_demand = commodity_demand_sim - commodity_in_sim # Calculate missed load and curtailed production - outputs[f"unmet_{self.commodity}_demand_out"] = np.where( - remaining_demand > 0, remaining_demand, 0 - ) - outputs[f"unused_{self.commodity}_out"] = np.where( - remaining_demand < 0, -1 * remaining_demand, 0 + outputs[f"unmet_{self.commodity}_demand_out"][ + simulation_range.start : simulation_range.stop + ] = np.where(remaining_demand > 0, remaining_demand, 0) + outputs[f"unused_{self.commodity}_out"][simulation_range.start : simulation_range.stop] = ( + np.where(remaining_demand < 0, -1 * remaining_demand, 0) ) # Calculate actual output based on demand met and curtailment - outputs[f"{self.commodity}_out"] = commodity_in - outputs[f"unused_{self.commodity}_out"] + outputs[f"{self.commodity}_out"][simulation_range.start : simulation_range.stop] = ( + commodity_in_sim + - outputs[f"unused_{self.commodity}_out"][ + simulation_range.start : simulation_range.stop + ] + ) + + # Simulation-length commodity_demand vector for bulk calculations + commodity_demand_full = commodity_demand + commodity_in_full = commodity_in - outputs[f"rated_{self.commodity}_production"] = commodity_demand.mean() + outputs[f"rated_{self.commodity}_production"] = commodity_demand_full.mean() outputs[f"total_{self.commodity}_produced"] = np.sum(outputs[f"{self.commodity}_out"]) * ( self.dt / 3600 @@ -168,10 +186,12 @@ def calculate_outputs(self, commodity_in, commodity_demand, outputs): outputs[f"total_{self.commodity}_produced"] / self.fraction_of_year_simulated ) - outputs["capacity_factor"] = outputs[f"{self.commodity}_out"].sum() / commodity_demand.sum() + outputs["capacity_factor"] = ( + outputs[f"{self.commodity}_out"].sum() / commodity_demand_full.sum() + ) - total_demand = commodity_demand.sum() - total_gen = commodity_in.sum() + total_demand = commodity_demand_full.sum() + total_gen = commodity_in_full.sum() if total_demand > 0: outputs["percent_load_missed"] = ( 100.0 * outputs[f"unmet_{self.commodity}_demand_out"].sum() / total_demand diff --git a/h2integrate/demand/flexible_demand.py b/h2integrate/demand/flexible_demand.py index c458dc73a..9c772feab 100644 --- a/h2integrate/demand/flexible_demand.py +++ b/h2integrate/demand/flexible_demand.py @@ -270,7 +270,7 @@ def compute(self, inputs, outputs): # Calculate missed load and curtailed production outputs = self.calculate_outputs( - inputs[f"{self.commodity}_in"], inputs[f"{self.commodity}_demand"], outputs + inputs[f"{self.commodity}_in"], inputs[f"{self.commodity}_demand"], inputs, outputs ) else: @@ -290,5 +290,5 @@ def compute(self, inputs, outputs): outputs[f"{self.commodity}_flexible_demand_profile"] = flexible_demand_profile outputs = self.calculate_outputs( - inputs[f"{self.commodity}_in"], flexible_demand_profile, outputs + inputs[f"{self.commodity}_in"], flexible_demand_profile, inputs, outputs ) diff --git a/h2integrate/demand/generic_demand.py b/h2integrate/demand/generic_demand.py index d69fcb436..b12d3315a 100644 --- a/h2integrate/demand/generic_demand.py +++ b/h2integrate/demand/generic_demand.py @@ -20,6 +20,8 @@ class GenericDemandComponent(DemandComponentBase): ``performance_parameters``. """ + _is_steppable = True + def setup(self): self.config = DemandComponentBaseConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), @@ -53,6 +55,6 @@ def compute(self, inputs, outputs): array shape ``(n_timesteps,)``. """ - outputs = self.calculate_outputs( - inputs[f"{self.commodity}_in"], inputs[f"{self.commodity}_demand"], outputs - ) + commodity_in = inputs[f"{self.commodity}_in"] + commodity_demand = inputs[f"{self.commodity}_demand"] + outputs = self.calculate_outputs(commodity_in, commodity_demand, inputs, outputs) diff --git a/h2integrate/demand/test/test_demand_components.py b/h2integrate/demand/test/test_demand_components.py index 45cbca3b9..3c6b8b2bd 100644 --- a/h2integrate/demand/test/test_demand_components.py +++ b/h2integrate/demand/test/test_demand_components.py @@ -83,6 +83,12 @@ def test_demand_converter_controller(subtests): [5.0, 4.0, 3.0, 2.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0] ) + with subtests.test("Check percent load missed"): + assert prob.get_val("percent_load_missed") == pytest.approx(100.0 * 15.0 / 50.0) + + with subtests.test("Check curtailment percent"): + assert prob.get_val("curtailment_percent") == pytest.approx(100.0 * 10.0 / 45.0) + @pytest.mark.unit def test_flexible_demand_converter_controller(subtests, variable_h2_production_profile): @@ -254,3 +260,54 @@ def test_flexible_demand_converter_controller_min_utilization( with subtests.test("Check min utilization value"): assert flexible_demand_utilization == pytest.approx(0.8010612244, rel=1e-6) + + +@pytest.mark.unit +def test_flexible_demand_converter_full_utilization(subtests, variable_h2_production_profile): + end_use_rated_demand = 10.0 # kg/h + tech_config = { + "performance_model": {"model": "FlexibleDemandComponent"}, + "model_inputs": { + "performance_parameters": { + "commodity": "hydrogen", + "commodity_rate_units": "kg", + "rated_demand": end_use_rated_demand, + "demand_profile": end_use_rated_demand, + "turndown_ratio": 0.25, + "ramp_down_rate_fraction": 0.2, + "ramp_up_rate_fraction": 0.4, + "min_utilization": 1.0, + }, + }, + } + plant_config = { + "plant": { + "plant_life": 30, + "simulation": {"n_timesteps": len(variable_h2_production_profile), "dt": 3600}, + } + } + + prob = om.Problem() + prob.model.add_subsystem( + name="IVC", + subsys=om.IndepVarComp(name="hydrogen_in", val=variable_h2_production_profile), + promotes=["*"], + ) + prob.model.add_subsystem( + "flexible_demand", + FlexibleDemandComponent(plant_config=plant_config, tech_config=tech_config), + promotes=["*"], + ) + prob.setup() + prob.run_model() + + with subtests.test("Flexible demand profile equals the rated demand"): + assert prob.get_val("hydrogen_flexible_demand_profile", units="kg") == pytest.approx( + end_use_rated_demand + ) + + with subtests.test("Unmet demand is the demand not covered by the input"): + expected_unmet = np.maximum(end_use_rated_demand - variable_h2_production_profile, 0.0) + assert prob.get_val("unmet_hydrogen_demand_out", units="kg") == pytest.approx( + expected_unmet + ) diff --git a/h2integrate/feedstocks/feedstocks.py b/h2integrate/feedstocks/feedstocks.py index 810e1a615..5dd3f914d 100644 --- a/h2integrate/feedstocks/feedstocks.py +++ b/h2integrate/feedstocks/feedstocks.py @@ -30,6 +30,8 @@ class FeedstockPerformanceModel(om.ExplicitComponent): 3600, ) # (min, max) time step lengths (in seconds) compatible with this model + _is_steppable = False + def initialize(self): self.options.declare("driver_config", types=dict) self.options.declare("plant_config", types=dict) diff --git a/h2integrate/finances/finances.py b/h2integrate/finances/finances.py index 3db49dbd2..3fd9fb404 100644 --- a/h2integrate/finances/finances.py +++ b/h2integrate/finances/finances.py @@ -2,6 +2,8 @@ import openmdao.api as om import numpy_financial as npf +from h2integrate.core.annualization import extrapolate_annual_values, time_weighted_annual_means + class AdjustedCapexOpexComp(om.ExplicitComponent): """ @@ -90,17 +92,27 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): class AdjustedCapacityFactorComp(om.ExplicitComponent): """OpenMDAO component to compute an adjusted capacity factor for a given commodity. - This component takes in a timeseries of commodity production values and computes - the rated (mean) production and a capacity factor for each year of the plant's - lifetime. Currently, the capacity factor is set to 1.0 by default. + This component takes a timeseries of commodity production values and splits it into + 365-day plant-year segments (the last may be partial). The rated production is the largest + segment mean, and each simulated year's capacity factor is its mean production divided by + the rated production. Capacity factors for plant years beyond the simulated horizon follow + ``annual_extrapolation_method``, which is only required when more than one simulated year + segment must be extended. + + Options: + annual_extrapolation_method (str | None): ``"tile"``, ``"final_sim_value"``, or + ``"time_averaged_sim_value"``. Defaults to the plant-level + ``plant.simulation.annual_extrapolation_method``. An override should generally match + the simulation setting: the finance model divides variable costs by production, so + mixing methods between capacity factor and variable costs distorts unit costs. Inputs: {commodity}_produced (array): Timeseries of commodity production values with shape ``(n_timesteps,)``. Units are determined by connection. Outputs: - rated_{commodity}_production (float): Mean commodity production across all - timesteps. Units are copied from the input. + rated_{commodity}_production (float): Largest annual mean commodity production + across the simulated year segments. Units are copied from the input. capacity_factor (array): Capacity factor for each year of plant life, with shape ``(plant_life,)``. Unitless. """ @@ -108,11 +120,19 @@ class AdjustedCapacityFactorComp(om.ExplicitComponent): def initialize(self): self.options.declare("plant_config", types=dict) self.options.declare("commodity_type", types=str) + self.options.declare("annual_extrapolation_method", default=None, allow_none=True) def setup(self): self.commodity = self.options["commodity_type"] plant_life = int(self.options["plant_config"]["plant"]["plant_life"]) n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) + # Integer dt matches the base classes and plant validation, so the horizon agrees. + self.dt = int(self.options["plant_config"]["plant"]["simulation"]["dt"]) + self.annual_extrapolation_method = self.options["annual_extrapolation_method"] + if self.annual_extrapolation_method is None: + self.annual_extrapolation_method = self.options["plant_config"]["plant"][ + "simulation" + ].get("annual_extrapolation_method") self.add_input( f"{self.commodity}_produced", @@ -136,10 +156,22 @@ def setup(self): ) def compute(self, inputs, outputs): - # Take the average of the timeseries profile - outputs[f"rated_{self.commodity}_production"] = np.mean( - inputs[f"{self.commodity}_produced"] + production_timeseries = inputs[f"{self.commodity}_produced"] + annual_mean_production, annual_durations = time_weighted_annual_means( + production_timeseries, self.dt ) + rated_production = float(np.max(annual_mean_production)) + outputs[f"rated_{self.commodity}_production"] = rated_production - # Set capacity factor equal to one - outputs["capacity_factor"] = 1.0 + plant_life = int(self.options["plant_config"]["plant"]["plant_life"]) + if rated_production == 0.0: + annual_capacity_factors = np.ones(annual_mean_production.size) + else: + annual_capacity_factors = annual_mean_production / rated_production + + outputs["capacity_factor"] = extrapolate_annual_values( + annual_capacity_factors, + plant_life, + self.annual_extrapolation_method, + simulated_durations_years=annual_durations, + ) diff --git a/h2integrate/finances/profast_baseclass.py b/h2integrate/finances/profast_baseclass.py index 9e144f3db..2c6f80226 100644 --- a/h2integrate/finances/profast_baseclass.py +++ b/h2integrate/finances/profast_baseclass.py @@ -7,6 +7,7 @@ from h2integrate.finances.tools import check_plant_config_and_profast_params from h2integrate.core.dict_utils import update_defaults from h2integrate.tools.profast_tools import create_years_of_operation, create_and_populate_profast +from h2integrate.core.model_baseclass import SkippableComputeMixin # Mapping between user-facing finance parameters and ProFAST internal parameter names @@ -454,7 +455,7 @@ def create_dict(self): return self.as_dict() -class ProFastBase(om.ExplicitComponent): +class ProFastBase(SkippableComputeMixin, om.ExplicitComponent): """ Base component for using the ProFAST financial model within OpenMDAO. @@ -495,8 +496,11 @@ class ProFastBase(om.ExplicitComponent): is replaced in each year of the plant life. """ + _is_steppable = False + def initialize(self): """Declare OpenMDAO component options.""" + super().initialize() self.options.declare("driver_config", types=dict) self.options.declare("plant_config", types=dict) self.options.declare("tech_config", types=dict) diff --git a/h2integrate/finances/test/test_finance_components.py b/h2integrate/finances/test/test_finance_components.py index 6c4f888db..f40a2cb62 100644 --- a/h2integrate/finances/test/test_finance_components.py +++ b/h2integrate/finances/test/test_finance_components.py @@ -50,3 +50,127 @@ def test_adjusted_cf_comp(plant_config, subtests): with subtests.test("Capacity factor length"): assert len(prob.model.get_val("comp.capacity_factor", units="unitless")) == 30 + + +@pytest.mark.unit +@pytest.mark.parametrize( + "n_timesteps,annual_extrapolation_method,expected_capacity_factor", + [ + (2 * 8760, "tile", [0.5, 1.0, 0.5, 1.0]), + (2 * 8760, "final_sim_value", [0.5, 1.0, 1.0, 1.0]), + (2 * 8760, "time_averaged_sim_value", [0.5, 1.0, 0.75, 0.75]), + (8760 + 4380, "tile", [0.5, 1.0, 0.5, 1.0]), + (8760 + 4380, "final_sim_value", [0.5, 1.0, 1.0, 1.0]), + (8760 + 4380, "time_averaged_sim_value", [0.5, 1.0, 2.0 / 3.0, 2.0 / 3.0]), + ], +) +def test_adjusted_cf_comp_preserves_annual_profile( + plant_config, + n_timesteps, + annual_extrapolation_method, + expected_capacity_factor, + subtests, +): + plant_config["plant"]["plant_life"] = 4 + plant_config["plant"]["simulation"]["annual_extrapolation_method"] = annual_extrapolation_method + rated_production = 300.0 # kW + production_timeseries = np.concatenate( + (np.full(8760, rated_production / 2.0), np.full(n_timesteps - 8760, rated_production)) + ) + + problem = om.Problem() + component = AdjustedCapacityFactorComp( + plant_config=plant_config, + commodity_type="electricity", + ) + problem.model.add_subsystem("component", component, promotes=["*"]) + + independent = om.IndepVarComp() + independent.add_output("electricity_produced", val=np.zeros(n_timesteps), units="kW") + problem.model.add_subsystem("independent", independent, promotes=["*"]) + + problem.setup() + problem.set_val("independent.electricity_produced", production_timeseries, units="kW") + problem.run_model() + + with subtests.test("Check rated electricity production"): + assert problem.get_val("component.rated_electricity_production", units="kW")[ + 0 + ] == pytest.approx(rated_production) + + with subtests.test("Check capacity factor"): + np.testing.assert_allclose( + problem.get_val("component.capacity_factor", units="unitless"), + expected_capacity_factor, + ) + + +@pytest.mark.unit +@pytest.mark.parametrize("n_timesteps", [2 * 8760]) +def test_adjusted_cf_comp_requires_method_for_multiyear_extrapolation(plant_config, n_timesteps): + plant_config["plant"]["plant_life"] = 4 + production_timeseries = np.concatenate((np.full(8760, 100.0), np.full(8760, 300.0))) + problem = om.Problem() + component = AdjustedCapacityFactorComp(plant_config=plant_config, commodity_type="electricity") + problem.model.add_subsystem("component", component, promotes=["*"]) + + independent = om.IndepVarComp() + independent.add_output("electricity_produced", val=production_timeseries, units="kW") + problem.model.add_subsystem("independent", independent, promotes=["*"]) + + problem.setup() + with pytest.raises(ValueError, match="annual_extrapolation_method is required"): + problem.run_model() + + +@pytest.mark.unit +@pytest.mark.parametrize("n_timesteps", [2 * 8760]) +def test_adjusted_cf_comp_subgroup_method_overrides_plant_method(plant_config, n_timesteps): + plant_config["plant"]["plant_life"] = 4 + plant_config["plant"]["simulation"]["annual_extrapolation_method"] = "tile" + production_timeseries = np.concatenate((np.full(8760, 100.0), np.full(8760, 300.0))) + + problem = om.Problem() + component = AdjustedCapacityFactorComp( + plant_config=plant_config, + commodity_type="electricity", + annual_extrapolation_method="final_sim_value", + ) + problem.model.add_subsystem("component", component, promotes=["*"]) + + independent = om.IndepVarComp() + independent.add_output("electricity_produced", val=np.zeros(n_timesteps), units="kW") + problem.model.add_subsystem("independent", independent, promotes=["*"]) + + problem.setup() + problem.set_val("independent.electricity_produced", production_timeseries, units="kW") + problem.run_model() + + np.testing.assert_allclose( + problem.get_val("component.capacity_factor", units="unitless"), + [1.0 / 3.0, 1.0, 1.0, 1.0], + ) + + +@pytest.mark.unit +@pytest.mark.parametrize("n_timesteps", [8760]) +def test_adjusted_cf_comp_uses_integer_dt_like_plant_validation(plant_config, n_timesteps): + # A fractional dt is truncated by plant validation and the base classes, so 8760 steps are + # exactly one year; using the raw dt would split them into two segments and require a method. + plant_config["plant"]["simulation"]["dt"] = 3600.5 + production_timeseries = np.full(n_timesteps, 100.0) + + problem = om.Problem() + component = AdjustedCapacityFactorComp(plant_config=plant_config, commodity_type="electricity") + problem.model.add_subsystem("component", component, promotes=["*"]) + + independent = om.IndepVarComp() + independent.add_output("electricity_produced", val=production_timeseries, units="kW") + problem.model.add_subsystem("independent", independent, promotes=["*"]) + + problem.setup() + problem.run_model() + + np.testing.assert_allclose( + problem.get_val("component.capacity_factor", units="unitless"), np.ones(30) + ) diff --git a/h2integrate/finances/test/test_finances.py b/h2integrate/finances/test/test_finances.py index f0b28c549..c8aa0377f 100644 --- a/h2integrate/finances/test/test_finances.py +++ b/h2integrate/finances/test/test_finances.py @@ -50,6 +50,10 @@ def model_configs(): }, "plant": { "plant_life": 30, + "simulation": { + "dt": 3600, + "n_timesteps": 8760, + }, "grid_connection": True, "ppa_price": 0.05, }, diff --git a/h2integrate/finances/test/test_profast_lco.py b/h2integrate/finances/test/test_profast_lco.py index e2152940b..5313ba78e 100644 --- a/h2integrate/finances/test/test_profast_lco.py +++ b/h2integrate/finances/test/test_profast_lco.py @@ -88,6 +88,37 @@ def test_profast_params_config(profast_inputs_no1, subtests): assert pf_params["maintenance"]["escalation"] == 0.02 +@pytest.mark.unit +def test_profast_setup_without_simulation( + profast_inputs_no1: dict, fake_filtered_tech_config: dict +) -> None: + """Allow standalone annual finance inputs without timestep configuration.""" + problem = om.Problem(reports=False) + plant_config = { + "plant": {"plant_life": 30}, + "finance_parameters": {"model_inputs": profast_inputs_no1}, + } + finance_model = ProFastLCO( + driver_config={}, + plant_config=plant_config, + tech_config=fake_filtered_tech_config, + commodity_type="electricity", + ) + independent = om.IndepVarComp() + independent.add_output("rated_electricity_production", 500000.0, units="kW") + independent.add_output("capacity_factor", val=1.0, shape=30, units="unitless") + problem.model.add_subsystem("ivc", independent, promotes=["*"]) + problem.model.add_subsystem( + "finance", + finance_model, + promotes_inputs=["rated_electricity_production", "capacity_factor"], + ) + + problem.setup() + + assert finance_model.params.plant_life == 30 + + @pytest.mark.regression def test_profast_comp(profast_inputs_no1, fake_filtered_tech_config, fake_cost_dict, subtests): mean_hourly_production = 500000.0 diff --git a/h2integrate/postprocess/mapping.py b/h2integrate/postprocess/mapping.py index 8cfc6bf1f..e204cff17 100644 --- a/h2integrate/postprocess/mapping.py +++ b/h2integrate/postprocess/mapping.py @@ -791,10 +791,11 @@ def auto_colorbar_limits(values: gpd.GeoSeries | pd.Series | np.ndarray): Automatically compute colorbar limits. This function calculates lower (`vmin`) and upper (`vmax`) limits for a colorbar - based on the range of the input data. It ensures that the limits: - - Extend slightly beyond the data's minimum and maximum. - - Are rounded to "nice" numbers for readability and colorbar tick placement. - - Handles very small or nearly constant data ranges gracefully. + based on the range of the input data. It ensures that the limits: + + - Extend slightly beyond the data's minimum and maximum. + - Are rounded to "nice" numbers for readability and colorbar tick placement. + - Handle very small or nearly constant data ranges gracefully. Args: values (gpd.GeoSeries | pd.Series | np.ndarray): Numeric data to compute colorbar limits. diff --git a/h2integrate/postprocess/test/test_sql_to_csv.py b/h2integrate/postprocess/test/test_sql_to_csv.py index 8bdf0f41b..d9a575406 100644 --- a/h2integrate/postprocess/test/test_sql_to_csv.py +++ b/h2integrate/postprocess/test/test_sql_to_csv.py @@ -60,9 +60,9 @@ def run_example_19_sql_fpath(example_folder): @pytest.mark.unit @pytest.mark.parametrize("example_folder,resource_example_folder", [("19_simple_dispatch", None)]) -def test_summarize_case_returns_dict(temp_copy_of_example): +def test_summarize_case_returns_dict(temp_copy_of_example_module_scope): """summarize_case should return a dictionary of scalar results.""" - sql_path = run_example_19_sql_fpath(temp_copy_of_example) + sql_path = run_example_19_sql_fpath(temp_copy_of_example_module_scope) cr = om.CaseReader(str(sql_path)) case = list(cr.get_cases())[0] @@ -74,9 +74,9 @@ def test_summarize_case_returns_dict(temp_copy_of_example): @pytest.mark.unit @pytest.mark.parametrize("example_folder,resource_example_folder", [("19_simple_dispatch", None)]) -def test_summarize_case_return_units(temp_copy_of_example): +def test_summarize_case_return_units(temp_copy_of_example_module_scope): """When return_units=True, summarize_case should return (values, units).""" - sql_path = run_example_19_sql_fpath(temp_copy_of_example) + sql_path = run_example_19_sql_fpath(temp_copy_of_example_module_scope) cr = om.CaseReader(str(sql_path)) case = list(cr.get_cases())[0] @@ -94,9 +94,9 @@ def test_summarize_case_return_units(temp_copy_of_example): @pytest.mark.unit @pytest.mark.parametrize("example_folder,resource_example_folder", [("19_simple_dispatch", None)]) -def test_summarize_case_scalar_only(temp_copy_of_example): +def test_summarize_case_scalar_only(temp_copy_of_example_module_scope): """summarize_case should only contain scalar (single-element) values, not timeseries.""" - sql_path = run_example_19_sql_fpath(temp_copy_of_example) + sql_path = run_example_19_sql_fpath(temp_copy_of_example_module_scope) cr = om.CaseReader(str(sql_path)) case = list(cr.get_cases())[0] @@ -114,9 +114,9 @@ def test_summarize_case_scalar_only(temp_copy_of_example): @pytest.mark.unit @pytest.mark.parametrize("example_folder,resource_example_folder", [("19_simple_dispatch", None)]) -def test_summarize_case_known_variables(temp_copy_of_example): +def test_summarize_case_known_variables(temp_copy_of_example_module_scope): """Expected variables from example 19 should appear in the summary.""" - sql_path = run_example_19_sql_fpath(temp_copy_of_example) + sql_path = run_example_19_sql_fpath(temp_copy_of_example_module_scope) cr = om.CaseReader(str(sql_path)) case = list(cr.get_cases())[0] @@ -143,9 +143,9 @@ def test_file_not_found(tmp_path): @pytest.mark.unit @pytest.mark.parametrize("example_folder,resource_example_folder", [("19_simple_dispatch", None)]) -def test_returns_dataframe(temp_copy_of_example): +def test_returns_dataframe(temp_copy_of_example_module_scope): """convert_sql_to_csv_summary should return a pandas DataFrame.""" - sql_path = run_example_19_sql_fpath(temp_copy_of_example) + sql_path = run_example_19_sql_fpath(temp_copy_of_example_module_scope) result = convert_sql_to_csv_summary(sql_path, save_to_file=False) assert isinstance(result, pd.DataFrame) @@ -154,9 +154,9 @@ def test_returns_dataframe(temp_copy_of_example): @pytest.mark.unit @pytest.mark.parametrize("example_folder,resource_example_folder", [("19_simple_dispatch", None)]) -def test_column_names_include_units(temp_copy_of_example): +def test_column_names_include_units(temp_copy_of_example_module_scope): """Columns for continuous variables should include units in parentheses.""" - sql_path = run_example_19_sql_fpath(temp_copy_of_example) + sql_path = run_example_19_sql_fpath(temp_copy_of_example_module_scope) result = convert_sql_to_csv_summary(sql_path, save_to_file=False) cols = result.columns.to_list() @@ -166,9 +166,9 @@ def test_column_names_include_units(temp_copy_of_example): @pytest.mark.unit @pytest.mark.parametrize("example_folder,resource_example_folder", [("19_simple_dispatch", None)]) -def test_save_to_file_creates_csv(subtests, temp_copy_of_example): +def test_save_to_file_creates_csv(subtests, temp_copy_of_example_module_scope): """When save_to_file=True, a CSV file should be written next to the SQL file.""" - sql_path = run_example_19_sql_fpath(temp_copy_of_example) + sql_path = run_example_19_sql_fpath(temp_copy_of_example_module_scope) result = convert_sql_to_csv_summary(sql_path, save_to_file=True) expected_csv = sql_path.parent / f"{sql_path.stem}.csv" @@ -184,9 +184,9 @@ def test_save_to_file_creates_csv(subtests, temp_copy_of_example): @pytest.mark.unit @pytest.mark.parametrize("example_folder,resource_example_folder", [("19_simple_dispatch", None)]) -def test_save_to_file_false_no_csv(temp_copy_of_example): +def test_save_to_file_false_no_csv(temp_copy_of_example_module_scope): """When save_to_file=False, no CSV file should be written.""" - sql_path = run_example_19_sql_fpath(temp_copy_of_example) + sql_path = run_example_19_sql_fpath(temp_copy_of_example_module_scope) csv_path = sql_path.parent / f"{sql_path.stem}.csv" # Remove any pre-existing CSV from earlier tests if csv_path.exists(): @@ -199,9 +199,9 @@ def test_save_to_file_false_no_csv(temp_copy_of_example): @pytest.mark.unit @pytest.mark.parametrize("example_folder,resource_example_folder", [("19_simple_dispatch", None)]) -def test_accepts_string_path(temp_copy_of_example): +def test_accepts_string_path(temp_copy_of_example_module_scope): """The function should accept a plain string path.""" - sql_path = run_example_19_sql_fpath(temp_copy_of_example) + sql_path = run_example_19_sql_fpath(temp_copy_of_example_module_scope) result = convert_sql_to_csv_summary(str(sql_path), save_to_file=False) assert isinstance(result, pd.DataFrame) @@ -210,9 +210,9 @@ def test_accepts_string_path(temp_copy_of_example): @pytest.mark.unit @pytest.mark.parametrize("example_folder,resource_example_folder", [("19_simple_dispatch", None)]) -def test_result_contains_expected_columns(temp_copy_of_example): +def test_result_contains_expected_columns(temp_copy_of_example_module_scope): """The summary should contain known scalar outputs from example 19.""" - sql_path = run_example_19_sql_fpath(temp_copy_of_example) + sql_path = run_example_19_sql_fpath(temp_copy_of_example_module_scope) result = convert_sql_to_csv_summary(sql_path, save_to_file=False) cols = result.columns.to_list() @@ -228,9 +228,9 @@ def test_result_contains_expected_columns(temp_copy_of_example): @pytest.mark.unit @pytest.mark.parametrize("example_folder,resource_example_folder", [("19_simple_dispatch", None)]) -def test_result_has_single_row(temp_copy_of_example): +def test_result_has_single_row(temp_copy_of_example_module_scope): """A single run should produce exactly one row in the summary.""" - sql_path = run_example_19_sql_fpath(temp_copy_of_example) + sql_path = run_example_19_sql_fpath(temp_copy_of_example_module_scope) result = convert_sql_to_csv_summary(sql_path, save_to_file=False) assert len(result) == 1 diff --git a/h2integrate/preprocess/populate_tech_yaml.py b/h2integrate/preprocess/populate_tech_yaml.py index 3636ccaa6..71c97b9ce 100644 --- a/h2integrate/preprocess/populate_tech_yaml.py +++ b/h2integrate/preprocess/populate_tech_yaml.py @@ -13,16 +13,19 @@ storage models where parameters must be carefully organized across multiple configuration sections. -Usage (CLI): +Usage (CLI):: + python -m h2integrate.preprocess.populate_tech_yaml \\ \\ [--output-path ] - Or use the command-line entry point: + Or use the command-line entry point:: + populate_tech_yaml \\ [--output-path ] -Usage (Python): +Usage (Python):: + from h2integrate.preprocess.populate_tech_yaml import populate_tech_yaml_from_file populated_config = populate_tech_yaml_from_file( "path/to/skeleton_tech_config.yaml", diff --git a/h2integrate/resource/resource_baseclass.py b/h2integrate/resource/resource_baseclass.py index 93bfc2f75..5edcf7ab5 100644 --- a/h2integrate/resource/resource_baseclass.py +++ b/h2integrate/resource/resource_baseclass.py @@ -21,10 +21,10 @@ check_data_length, contains_leap_day, add_resource_start_end_times, - resample_resource_data_to_dt, get_number_of_resource_years_needed, ) from h2integrate.resource.utilities.download_tools import download_from_api +from h2integrate.resource.utilities.resample_tools import resample_resource_data_to_dt @define(kw_only=True) diff --git a/h2integrate/resource/utilities/resample_tools.py b/h2integrate/resource/utilities/resample_tools.py new file mode 100644 index 000000000..6d1f601b8 --- /dev/null +++ b/h2integrate/resource/utilities/resample_tools.py @@ -0,0 +1,336 @@ +import warnings +from itertools import pairwise + +import numpy as np +import pandas as pd + +from h2integrate.resource.utilities.data_tools import separate_timeseries_and_meta_data +from h2integrate.resource.utilities.time_tools import TIME_DATA_KEYS, is_leap_year + + +# A step longer than one day is a calendar gap (e.g. a removed leap day), not a real native +# step, when the data is typically sub-daily. +SUBDAILY_GAP_THRESHOLD_S = 24 * 3600 +# TMY files stitch together months from different years, so the step between two months can +# span weeks to years. Any step longer than the shortest month (28 days) is a month jump when +# the data is typically daily or coarser. +MONTH_JUMP_THRESHOLD_S = 28 * 86400 + + +def _regenerate_time_columns_at_dt( + start_timestamp, dt_seconds, n_timesteps, present_time_keys, include_leap_day=True +): + """Generate time columns as ``n_timesteps`` steps of ``dt_seconds`` starting at + ``start_timestamp``. + + When ``include_leap_day`` is False, February 29 is skipped so the regenerated calendar + keeps the leap day excluded even at the new timestep. + + Args: + start_timestamp (pandas.Timestamp | datetime.datetime): timestamp of the first sample. + dt_seconds (int | float): new timestep duration in seconds. + n_timesteps (int): number of timesteps to generate. + present_time_keys (list[str]): list of time column keys (e.g. "year", "month", + "day", "hour", "minute", "second") present in the data to regenerate. + include_leap_day (bool, optional): whether to include February 29 when generating the + calendar index. Defaults to True. + + Returns: + dict[str, numpy.ndarray]: generated time columns keyed by ``present_time_keys``. + """ + freq = pd.Timedelta(seconds=dt_seconds) + n_periods = n_timesteps + while True: + new_index = pd.date_range(start=start_timestamp, periods=n_periods, freq=freq) + if include_leap_day: + break + new_index = new_index[~((new_index.month == 2) & (new_index.day == 29))] + if len(new_index) >= n_timesteps: + break + # Each skipped February 29 pushes the end later, so extend by the shortfall and retry. + n_periods += n_timesteps - len(new_index) + new_index = new_index[:n_timesteps] + field_map = { + "year": new_index.year, + "month": new_index.month, + "day": new_index.day, + "hour": new_index.hour, + "minute": new_index.minute, + "second": new_index.second, + } + return {k: np.asarray(field_map[k], dtype=float) for k in present_time_keys} + + +def _find_block_starts(calendar_index: pd.DatetimeIndex): + """Find the start index of each chronological block of a timestamp index. + + A new block starts wherever the month or year decreases from one sample to the next, which + happens at annual boundaries, TMY month jumps, and out-of-order resource years. + + Args: + calendar_index (pandas.DatetimeIndex): native timestamps of the resource data. + + Returns: + list[int]: block start indices, beginning with 0 and ending with ``len(calendar_index)``. + """ + months = calendar_index.month.to_numpy() + years = calendar_index.year.to_numpy() + resets = np.flatnonzero((months[1:] < months[:-1]) | (years[1:] < years[:-1])) + 1 + return [0, *resets.tolist(), len(calendar_index)] + + +def _get_effective_deltas(deltas: np.ndarray, block_starts: list[int], median_dt: float): + """Replace non-physical steps with the duration of the preceding step. + + The steps between blocks (e.g. Dec 31 to Jan 1 of an out-of-order year) and calendar gaps + (e.g. a removed leap day or a TMY month jump) do not describe the sampling interval. Each + is replaced by the preceding step so the last sample of a block still covers one interval. + + Args: + deltas (numpy.ndarray): step from each sample to the next, in seconds. + block_starts (list[int]): block start indices from ``_find_block_starts()``. + median_dt (float): median positive step in seconds, used if the first step is replaced. + + Returns: + numpy.ndarray: step durations in seconds with non-physical steps replaced. + """ + gap_threshold = SUBDAILY_GAP_THRESHOLD_S if median_dt <= 86400 else MONTH_JUMP_THRESHOLD_S + is_non_physical = deltas > gap_threshold + # The step into block b is the delta that ends at index block_starts[b] - 1 + is_non_physical[np.asarray(block_starts[1:-1], dtype=int) - 1] = True + + effective_deltas = deltas.copy() + # Processed in order so consecutive replaced steps all inherit the same preceding step + for i in np.flatnonzero(is_non_physical): + effective_deltas[i] = effective_deltas[i - 1] if i > 0 else median_dt + return effective_deltas + + +def _count_target_timesteps_per_block( + step_durations: np.ndarray, block_starts: list[int], target_dt: float +): + """Count the target timesteps needed to cover each block, rounding partial steps up. + + Args: + step_durations (numpy.ndarray): duration in seconds covered by each native sample. + block_starts (list[int]): block start indices from ``_find_block_starts()``. + target_dt (float): target timestep in seconds. + + Returns: + list[int]: number of target timesteps in each block. + """ + # Integer nanoseconds allow exact ceiling division without a float tolerance. + target_dt_ns = pd.Timedelta(seconds=target_dt).value + counts = [] + for start, end in pairwise(block_starts): + block_duration_ns = pd.to_timedelta(step_durations[start:end], unit="s").sum().value + counts.append((block_duration_ns + target_dt_ns - 1) // target_dt_ns) + return counts + + +def resample_resource_data_to_dt( + data: dict, + target_dt, + upsample_method: str | None = None, + downsample_method: str | None = None, +): + """Resample resource timeseries from its native timestep to ``target_dt``. + + Resampling is driven by the actual time span of the data (its native timestep(s), inferred + from the time columns), not by the number of timesteps. When the simulation timestep is + smaller than the native timestep the data is upsampled (finer resolution) using + `pandas.DataFrame.interpolate `_; + when it is larger the data is downsampled (coarser resolution) using + `pandas.DataFrame.agg `_. + Time columns are regenerated at ``target_dt`` and scalar metadata is left unchanged. + + Resampling operates on an elapsed timeline constructed from the input timestamps, + so non-uniform sample intervals (for example 1-hour and 2-hour intervals) receive target + timestamps proportional to their physical duration. For multi-year or discontinuous data, + the transition between segments completes each period using the preceding interval, + interpolating across the boundary between the final value of the preceding segment and the + first value of the subsequent segment. Time columns are regenerated at ``target_dt``, + preserving the original sequence of years and leap-day settings. + + Args: + data (dict): resource data dictionary with timeseries arrays and time columns. + target_dt (int | float): desired simulation timestep in seconds. + upsample_method (str | None): interpolation method passed to + `pandas.DataFrame.interpolate `_ + when upsampling. Must be explicitly set when upsampling is needed; otherwise a + ``ValueError`` is raised. + downsample_method (str | None): aggregation passed to + `pandas.DataFrame.agg `_ + when downsampling. Must be explicitly set when downsampling is needed; otherwise a + ``ValueError`` is raised. + + Returns: + dict: resource data resampled to ``target_dt``. + """ + if not isinstance(data, dict) or not target_dt: + return data + target_dt = float(target_dt) + + present_time_keys = [k for k in TIME_DATA_KEYS if k in data] + if not present_time_keys: + msg = ( + "Cannot resample resource data to the simulation timestep because the data has " + "no time columns (year/month/day/hour/minute) to determine its native timestep(s). " + "Provide resource data that includes time information." + ) + raise ValueError(msg) + + time_dict = {k: np.asarray(data[k]).astype(int) for k in present_time_keys} + calendar_index = pd.DatetimeIndex(pd.to_datetime(time_dict)) + if len(calendar_index) < 2: + return data + + native_len = len(calendar_index) + deltas = (calendar_index[1:] - calendar_index[:-1]).total_seconds().to_numpy() + pos_deltas = deltas[deltas > 0] + if pos_deltas.size == 0: + msg = ( + "Cannot resample resource data: no positive time steps could be derived from the " + "data's time columns. Ensure the resource data has increasing timestamps." + ) + raise ValueError(msg) + + # Detect non-chronological transitions (e.g. out-of-order years or TMY month jumps) + if (deltas < 0).any(): + warnings.warn( + "Resource data contains negative time steps (e.g., timestamps out of chronological " + "order or non-chronological resource years). Resampling will process segments in " + "their given order, preserving block transitions.", + UserWarning, + stacklevel=2, + ) + + # Blocks are chronological runs of data; a new block starts where the year or month resets + block_starts = _find_block_starts(calendar_index) + + # Positive steps exclude backward jumps; their median resists isolated large forward gaps. + median_dt = float(np.median(pos_deltas)) + effective_deltas = _get_effective_deltas(deltas, block_starts, median_dt) + + # Check if native data already matches target timestep uniformly + if abs(median_dt - target_dt) < 1e-6 and np.allclose(effective_deltas, target_dt, atol=1e-6): + return data + + # Each sample covers the duration of its step; the last sample reuses the final step + step_durations = np.append(effective_deltas, effective_deltas[-1]) + elapsed = np.insert(np.cumsum(effective_deltas), 0, 0.0) + + # Target step count per block to ensure correct time coverage + block_target_n_timesteps = _count_target_timesteps_per_block( + step_durations, block_starts, target_dt + ) + + target_n = sum(block_target_n_timesteps) + total_duration = float(np.sum(step_durations)) + if target_dt > total_duration: + msg = ( + f"Cannot resample resource data to a timestep of {target_dt:g} s: it is larger than " + f"the total time span of the data ({native_len} samples, total span " + f"{total_duration} s), so resampling would produce no timesteps. Use a smaller " + "timestep or provide more resource data." + ) + raise ValueError(msg) + + target_offsets = np.arange(target_n) * target_dt + # Pandas 3 may infer second resolution, which cannot represent fractional resampling bins. + native_index = pd.to_timedelta(elapsed, unit="s").as_unit("ns") + target_index = pd.to_timedelta(target_offsets, unit="s").as_unit("ns") + + # Only numeric timeseries columns are resampled; time columns are regenerated afterward. + _, ts_data = separate_timeseries_and_meta_data(data) + data_keys = [ + k + for k in ts_data + if k not in present_time_keys and len(np.atleast_1d(ts_data[k])) == native_len + ] + frame = pd.DataFrame( + {k: np.asarray(data[k], dtype=float) for k in data_keys}, index=native_index + ) + + # Native steps coarser than the target need upsampling; finer steps need downsampling + upsampling = np.max(effective_deltas) > target_dt + downsampling = np.min(effective_deltas) < target_dt + + if upsampling and upsample_method is None: + raise ValueError( + f"Resource data has time steps larger than {target_dt:g} s, which requires " + "upsampling. Resampling is not performed automatically: set the resource " + "`upsample_method` (for example 'time') to explicitly enable upsampling." + ) + if downsampling and downsample_method is None: + raise ValueError( + f"Resource data has time steps smaller than {target_dt:g} s, which requires " + "downsampling. Resampling is not performed automatically: set the resource " + "`downsample_method` (for example 'mean') to explicitly enable downsampling." + ) + + if upsampling and downsampling: + direction = "mixed resampling" + method_desc = f"upsample: '{upsample_method}', downsample: '{downsample_method}'" + elif upsampling: + direction = "upsampling" + method_desc = f"'{upsample_method}'" + else: + direction = "downsampling" + method_desc = f"'{downsample_method}'" + warnings.warn( + f"Resampling resource data to the simulation timestep of {target_dt:g} s " + f"({direction} with {method_desc}).", + UserWarning, + stacklevel=2, + ) + + target_freq = pd.Timedelta(seconds=target_dt) + if direction == "upsampling": + # Interpolate onto the (finer) target grid + union_index = frame.index.union(target_index) + resampled_frame = ( + frame.reindex(union_index).interpolate(method=upsample_method).reindex(target_index) + ) + elif direction == "downsampling": + # Aggregate native samples within each (coarser) target interval + resampled_frame = ( + frame.resample(target_freq, origin="start").agg(downsample_method).reindex(target_index) + ) + else: + # Mixed: first aggregate finer intervals, then interpolate coarser intervals + resampled_frame = ( + frame.resample(target_freq, origin="start") + .agg(downsample_method) + .reindex(target_index) + .interpolate(method=upsample_method) + ) + + # Fill any residual NaNs at the grid edges introduced by reindexing + resampled_frame = resampled_frame.ffill().bfill() + + resampled = dict(data) + for k in data_keys: + resampled[k] = resampled_frame[k].to_numpy() + + # Detect whether the source data has a leap day removed so the regenerated calendar keeps + # February 29 excluded at the new timestep. + has_feb29 = bool(((calendar_index.month == 2) & (calendar_index.day == 29)).any()) + spans_leap_year = any(is_leap_year(int(y)) for y in np.unique(calendar_index.year.to_numpy())) + include_leap_day = not spans_leap_year or has_feb29 + + # Regenerate calendar time columns per block at target_dt, preserving original block order + # (even when years are non-chronological) and leap-day settings. + block_time_cols = [ + _regenerate_time_columns_at_dt( + calendar_index[start], + target_dt, + n_block_timesteps, + present_time_keys, + include_leap_day=include_leap_day, + ) + for start, n_block_timesteps in zip(block_starts[:-1], block_target_n_timesteps) + ] + for k in present_time_keys: + resampled[k] = np.concatenate([cols[k] for cols in block_time_cols]) + return resampled diff --git a/h2integrate/resource/utilities/test/test_resample_tools.py b/h2integrate/resource/utilities/test/test_resample_tools.py new file mode 100644 index 000000000..12e3cbe3d --- /dev/null +++ b/h2integrate/resource/utilities/test/test_resample_tools.py @@ -0,0 +1,393 @@ +import numpy as np +import pandas as pd +import pytest + +from h2integrate.resource.utilities.resample_tools import resample_resource_data_to_dt + + +def _make_timeseries(n, freq_seconds, start="2012-01-01 00:00", values=None): + """Build a resource data dict with time columns spaced at ``freq_seconds``.""" + + idx = pd.date_range(start=start, periods=n, freq=pd.Timedelta(seconds=freq_seconds)) + if values is None: + values = np.arange(n, dtype=float) + return { + "wind_speed_100m": np.asarray(values, dtype=float), + "year": idx.year.to_numpy().astype(float), + "month": idx.month.to_numpy().astype(float), + "day": idx.day.to_numpy().astype(float), + "hour": idx.hour.to_numpy().astype(float), + "minute": idx.minute.to_numpy().astype(float), + "units": {"wind_speed_100m": "m/s"}, + "site_lat": 1.0, + } + + +@pytest.mark.unit +def test_resample_no_op_when_dt_matches(): + data = _make_timeseries(10, 3600) + result = resample_resource_data_to_dt(data, 3600) + assert result is data + + +@pytest.mark.unit +def test_resample_no_time_columns_raises(): + data = {"wind_speed_100m": np.arange(10, dtype=float), "site_lat": 1.0} + with pytest.raises(ValueError, match="no time columns"): + resample_resource_data_to_dt(data, 1800) + + +@pytest.mark.unit +def test_resample_target_dt_larger_than_span_raises(): + # 5 hourly samples span only a few hours; a 10-day timestep yields no full step + data = _make_timeseries(5, 3600) + with pytest.raises(ValueError, match="larger than the total time span"): + resample_resource_data_to_dt(data, 10 * 86400) + + +@pytest.mark.unit +def test_resample_non_positive_deltas_raises(): + # If all deltas are non-positive (e.g. all identical timestamps), raise ValueError + data = { + "wind_speed_100m": np.arange(3, dtype=float), + "year": np.array([2012, 2012, 2012], dtype=float), + "month": np.array([1, 1, 1], dtype=float), + "day": np.array([1, 1, 1], dtype=float), + "hour": np.array([0, 0, 0], dtype=float), + "minute": np.array([0, 0, 0], dtype=float), + } + with pytest.raises(ValueError, match="no positive time steps"): + resample_resource_data_to_dt(data, 1800) + + +@pytest.mark.unit +def test_resample_negative_deltas_warns(subtests): + # Out-of-order years (e.g. 2013 then 2011) produce a negative delta at the transition, + # which should emit a warning but successfully resample. + data1 = _make_timeseries(5, 3600, start="2013-01-01 00:00") + data2 = _make_timeseries(5, 3600, start="2011-01-01 00:00") + combined = { + k: np.concatenate([data1[k], data2[k]]) + for k in ["year", "month", "day", "hour", "minute", "wind_speed_100m"] + } + with pytest.warns(UserWarning, match="negative time steps"): + result = resample_resource_data_to_dt(combined, 1800, upsample_method="time") + with subtests.test("upsampled length is 2x"): + assert len(result["wind_speed_100m"]) == 20 + + +@pytest.mark.unit +def test_resample_infers_native_dt_across_initial_calendar_gap(subtests): + # Dataset starting adjacent to a removed leap day (Feb 28 23:00 to Mar 1 00:00). + # The first delta is 25 hours (90000 s), but subsequent hourly steps are 3600 s. + # Native dt must infer as 3600 s (not 90000 s). + idx = pd.DatetimeIndex( + [ + "2012-02-28 23:00", + "2012-03-01 00:00", + "2012-03-01 01:00", + "2012-03-01 02:00", + "2012-03-01 03:00", + ] + ) + data = { + "wind_speed_100m": np.arange(len(idx), dtype=float), + "year": idx.year.to_numpy().astype(float), + "month": idx.month.to_numpy().astype(float), + "day": idx.day.to_numpy().astype(float), + "hour": idx.hour.to_numpy().astype(float), + "minute": idx.minute.to_numpy().astype(float), + } + result = resample_resource_data_to_dt(data, 1800, upsample_method="time") + with subtests.test("upsampled length doubles based on 3600s native dt"): + assert len(result["wind_speed_100m"]) == 10 + with subtests.test("timestamps are at 30-minute intervals"): + assert np.isclose(result["minute"][1] - result["minute"][0], 30.0) + + +@pytest.mark.unit +def test_upsample_interpolation(subtests): + # hourly data upsampled to 30-minute resolution + data = _make_timeseries(5, 3600, values=[0, 1, 2, 3, 4]) + result = resample_resource_data_to_dt(data, 1800, upsample_method="time") + + with subtests.test("upsampled length doubles"): + assert len(result["wind_speed_100m"]) == 10 + expected = np.array([0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4], dtype=float) + with subtests.test("interpolated values match expectation"): + np.testing.assert_allclose(result["wind_speed_100m"], expected) + with subtests.test("minutes alternate on 30 minute grid"): + np.testing.assert_array_equal(result["minute"][:4], [0, 30, 0, 30]) + + +@pytest.mark.unit +def test_downsample_average(subtests): + # 30-minute data downsampled to hourly resolution via pandas mean aggregation + data = _make_timeseries(10, 1800, values=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + result = resample_resource_data_to_dt(data, 3600, downsample_method="mean") + + with subtests.test("downsampled length halves"): + assert len(result["wind_speed_100m"]) == 5 + expected = np.array([0.5, 2.5, 4.5, 6.5, 8.5], dtype=float) + with subtests.test("hourly means match expectation"): + np.testing.assert_allclose(result["wind_speed_100m"], expected) + with subtests.test("minutes stay on hour"): + np.testing.assert_array_equal(result["minute"], np.zeros(5)) + + +@pytest.mark.unit +def test_resample_keeps_leap_day_excluded_at_new_timestep(subtests): + # Hourly data for a leap year with the leap day removed, resampled to 2-hour. + # February 29 must stay excluded in the regenerated calendar at the new timestep. + + idx = pd.date_range("2012-01-01 00:30", "2012-12-31 23:30", freq="1h") + idx = idx[~((idx.month == 2) & (idx.day == 29))] # 8760 hourly, leap day removed + data = { + "wind_speed_100m": np.arange(len(idx), dtype=float), + "year": idx.year.to_numpy().astype(float), + "month": idx.month.to_numpy().astype(float), + "day": idx.day.to_numpy().astype(float), + "hour": idx.hour.to_numpy().astype(float), + "minute": idx.minute.to_numpy().astype(float), + } + + result = resample_resource_data_to_dt(data, 7200, downsample_method="mean") # 2-hour timestep + + with subtests.test("result length matches excluded leap year at 2 hour dt"): + assert len(result["wind_speed_100m"]) == 4380 + with subtests.test("february 29 remains excluded"): + assert not ((result["month"] == 2) & (result["day"] == 29)).any() + with subtests.test("starts on january 1"): + assert int(result["month"][0]) == 1 and int(result["day"][0]) == 1 + with subtests.test("ends on december 31"): + assert int(result["month"][-1]) == 12 and int(result["day"][-1]) == 31 + + +@pytest.mark.unit +def test_resample_keeps_leap_day_when_present_at_new_timestep(subtests): + # Leap year with the leap day retained, resampled to 2-hour: Feb 29 must remain. + + idx = pd.date_range("2012-01-01 00:30", "2012-12-31 23:30", freq="1h") # 8784, leap kept + data = { + "wind_speed_100m": np.arange(len(idx), dtype=float), + "year": idx.year.to_numpy().astype(float), + "month": idx.month.to_numpy().astype(float), + "day": idx.day.to_numpy().astype(float), + "hour": idx.hour.to_numpy().astype(float), + "minute": idx.minute.to_numpy().astype(float), + } + + result = resample_resource_data_to_dt(data, 7200, downsample_method="mean") # 2-hour timestep + + with subtests.test("result length matches leap year at 2 hour dt"): + assert len(result["wind_speed_100m"]) == 4392 + with subtests.test("leap day remains present"): + assert ((result["month"] == 2) & (result["day"] == 29)).any() + + +@pytest.mark.unit +def test_resample_keeps_leap_day_excluded_across_multiple_years(subtests): + # Four hourly years (2011-2014) with the 2012 leap day removed, resampled to 2-hour. + idx = pd.date_range("2011-01-01 00:30", "2014-12-31 23:30", freq="1h") + idx = idx[~((idx.month == 2) & (idx.day == 29))] # 4 * 8760 + data = { + "wind_speed_100m": np.arange(len(idx), dtype=float), + "year": idx.year.to_numpy().astype(float), + "month": idx.month.to_numpy().astype(float), + "day": idx.day.to_numpy().astype(float), + "hour": idx.hour.to_numpy().astype(float), + "minute": idx.minute.to_numpy().astype(float), + } + + result = resample_resource_data_to_dt(data, 7200, downsample_method="mean") + + with subtests.test("result length covers four 365-day years"): + assert len(result["wind_speed_100m"]) == 4 * 4380 + with subtests.test("february 29 remains excluded"): + assert not ((result["month"] == 2) & (result["day"] == 29)).any() + with subtests.test("each year ends on december 31"): + year_ends = np.flatnonzero(np.diff(result["year"]) != 0) + np.testing.assert_array_equal(result["month"][year_ends], 12) + np.testing.assert_array_equal(result["day"][year_ends], 31) + + +@pytest.mark.unit +def test_downsample_preserves_mean(subtests): + rng = np.random.default_rng(0) + values = rng.random(24) + data = _make_timeseries(24, 900, values=values) # 15-min data + result = resample_resource_data_to_dt(data, 3600, downsample_method="mean") # hourly + + with subtests.test("downsampling produces six hourly values"): + assert len(result["wind_speed_100m"]) == 6 + with subtests.test("downsampling preserves the overall mean"): + np.testing.assert_allclose(result["wind_speed_100m"].mean(), values.mean()) + + +@pytest.mark.unit +def test_downsample_with_calendar_gap_preserves_mean(subtests): + # Hourly data whose timestamps have a one-day gap in the middle (as when a leap + # day is removed). Resampling must treat the samples as an evenly spaced sequence, + # so the downsampled mean matches the exact pairwise mean. + + part1 = pd.date_range("2012-02-28 00:30", periods=4, freq="1h") + part2 = pd.date_range("2012-03-01 00:30", periods=4, freq="1h") + idx = part1.append(part2) + values = np.arange(8, dtype=float) + data = { + "wind_speed_100m": values.copy(), + "year": idx.year.to_numpy().astype(float), + "month": idx.month.to_numpy().astype(float), + "day": idx.day.to_numpy().astype(float), + "hour": idx.hour.to_numpy().astype(float), + "minute": idx.minute.to_numpy().astype(float), + } + result = resample_resource_data_to_dt(data, 7200, downsample_method="mean") # to 2-hour + + with subtests.test("downsampled length matches expected bins"): + assert len(result["wind_speed_100m"]) == 4 + with subtests.test("pairwise averages preserved across gap"): + np.testing.assert_allclose(result["wind_speed_100m"], [0.5, 2.5, 4.5, 6.5]) + with subtests.test("mean preserved despite gap"): + np.testing.assert_allclose(result["wind_speed_100m"].mean(), values.mean()) + + +@pytest.mark.unit +def test_resample_scalar_metadata_preserved(subtests): + data = _make_timeseries(5, 3600, values=[0, 1, 2, 3, 4]) + result = resample_resource_data_to_dt(data, 1800, upsample_method="time") + with subtests.test("units preserved"): + assert result["units"] == {"wind_speed_100m": "m/s"} + with subtests.test("site latitude preserved"): + assert result["site_lat"] == 1.0 + + +@pytest.mark.unit +def test_resample_unknown_upsample_method_raises(): + data = _make_timeseries(5, 3600) + # An invalid pandas interpolation method raises. The exact message differs across + # pandas versions, but the offending method name is always reported. + with pytest.raises(ValueError, match="not_a_method"): + resample_resource_data_to_dt(data, 1800, upsample_method="not_a_method") + + +@pytest.mark.unit +def test_resample_unknown_downsample_method_raises(): + data = _make_timeseries(10, 1800) + # An invalid pandas aggregation raises. Different pandas versions raise different + # exception types (AttributeError vs ValueError) with different messages, so match + # on the offending method name that is common to all versions. + with pytest.raises((AttributeError, ValueError), match="not_a_method"): + resample_resource_data_to_dt(data, 3600, downsample_method="not_a_method") + + +@pytest.mark.unit +def test_resample_upsampling_without_method_raises(): + data = _make_timeseries(5, 3600) + with pytest.raises(ValueError, match="requires upsampling"): + resample_resource_data_to_dt(data, 1800) + + +@pytest.mark.unit +def test_resample_downsampling_without_method_raises(): + data = _make_timeseries(10, 1800) + with pytest.raises(ValueError, match="requires downsampling"): + resample_resource_data_to_dt(data, 3600) + + +@pytest.mark.unit +def test_resample_warns_when_resampling(subtests): + with subtests.test("upsampling warns"): + with pytest.warns(UserWarning, match="upsampling"): + resample_resource_data_to_dt(_make_timeseries(5, 3600), 1800, upsample_method="time") + with subtests.test("downsampling warns"): + with pytest.warns(UserWarning, match="downsampling"): + resample_resource_data_to_dt(_make_timeseries(10, 1800), 3600, downsample_method="mean") + + +@pytest.mark.unit +def test_resample_non_integer_ratio_guarantees_coverage(subtests): + # Non-integer ratios must not round down and under-cover the original duration. + with subtests.test("downsampling ceil covers span"): + # 10 samples at 3600s = 36000s; 36000 / 7000 = 5.14 -> ceil must yield 6 steps, not 5 + data = _make_timeseries(10, 3600) + res = resample_resource_data_to_dt(data, 7000, downsample_method="mean") + assert len(res["wind_speed_100m"]) == 6 + + with subtests.test("upsampling ceil covers span"): + # 5 samples at 3600s = 18000s; 18000 / 2500 = 7.2 -> ceil must yield 8 steps, not 7 + data = _make_timeseries(5, 3600) + res = resample_resource_data_to_dt(data, 2500, upsample_method="time") + assert len(res["wind_speed_100m"]) == 8 + + +@pytest.mark.unit +@pytest.mark.parametrize( + "target_dt,expected_steps", + [(6000.0, 6), (5999.9999999, 7), (6000.1, 6)], + ids=["exact-division", "tiny-positive-remainder", "fractional-timestep"], +) +def test_resample_step_count_preserves_small_remainders(subtests, target_dt, expected_steps): + data = _make_timeseries(10, 3600) + result = resample_resource_data_to_dt(data, target_dt, downsample_method="mean") + n_steps = len(result["wind_speed_100m"]) + + with subtests.test("step count is the ceiling of the duration ratio"): + assert n_steps == expected_steps + with subtests.test("target intervals cover the complete source duration"): + assert n_steps * pd.Timedelta(seconds=target_dt) >= pd.Timedelta(hours=10) + + +@pytest.mark.unit +def test_resample_non_uniform_deltas(subtests): + # Non-uniform deltas within a series: 1h and 2h steps. + # Spacing is proportional to physical duration: 2 steps for 1h, 4 steps for 2h. + idx = pd.DatetimeIndex( + [ + "2020-01-01 00:00", + "2020-01-01 01:00", + "2020-01-01 03:00", + "2020-01-01 04:00", + "2020-01-01 06:00", + ] + ) + data = { + "year": idx.year.to_numpy().astype(float), + "month": idx.month.to_numpy().astype(float), + "day": idx.day.to_numpy().astype(float), + "hour": idx.hour.to_numpy().astype(float), + "minute": idx.minute.to_numpy().astype(float), + "val": np.array([0.0, 10.0, 30.0, 40.0, 60.0]), + } + res = resample_resource_data_to_dt(data, 1800, upsample_method="time") + with subtests.test("interpolated length covers 8 hours span at 30 min"): + assert len(res["val"]) == 16 + with subtests.test("1h interval contains 2 steps and 2h contains 4 steps"): + # At 30m: 00:00(0), 00:30(5), 01:00(10), 01:30(15), 02:00(20), 02:30(25), 03:00(30) + np.testing.assert_allclose(res["val"][:3], [0.0, 5.0, 10.0]) + np.testing.assert_allclose(res["val"][2:7], [10.0, 15.0, 20.0, 25.0, 30.0]) + + +@pytest.mark.unit +def test_resample_out_of_order_years_boundary_transition(subtests): + # Year 1 (2015) followed by Year 2 (2012). + # Transition across year boundary completes Year 1 using Year 2 first value. + idx1 = pd.DatetimeIndex(["2015-12-31 22:00", "2015-12-31 23:00"]) + idx2 = pd.DatetimeIndex(["2012-01-01 00:00", "2012-01-01 01:00"]) + idx = idx1.append(idx2) + data = { + "year": idx.year.to_numpy().astype(float), + "month": idx.month.to_numpy().astype(float), + "day": idx.day.to_numpy().astype(float), + "hour": idx.hour.to_numpy().astype(float), + "minute": idx.minute.to_numpy().astype(float), + "val": np.array([10.0, 20.0, 40.0, 50.0]), + } + with pytest.warns(UserWarning, match="negative time steps"): + res = resample_resource_data_to_dt(data, 1800, upsample_method="time") + with subtests.test("boundary step interpolates between Year 1 end and Year 2 start"): + # At t=23:30 (index 3), value must interpolate between 20.0 and 40.0 -> 30.0 + assert res["val"][3] == 30.0 + with subtests.test("regenerated years preserve 2015 then 2012 order"): + assert (res["year"][:4] == 2015).all() + assert (res["year"][4:] == 2012).all() diff --git a/h2integrate/resource/utilities/test/test_time_tools.py b/h2integrate/resource/utilities/test/test_time_tools.py index 21ceae5bf..c3cd8880f 100644 --- a/h2integrate/resource/utilities/test/test_time_tools.py +++ b/h2integrate/resource/utilities/test/test_time_tools.py @@ -8,7 +8,6 @@ check_data_length, contains_leap_day, add_resource_start_end_times, - resample_resource_data_to_dt, get_n_timesteps_from_year_list, get_number_of_resource_years_needed, ) @@ -77,7 +76,7 @@ def test_check_data_length(subtests): leap_day_data = _february_boundary_data(2012, include_february_29=True) with pytest.raises(ValueError) as excinfo: check_data_length(leap_day_data, n_timesteps=2) - assert "includes a leap day" in str(excinfo.value) + assert "includes a leap day. To remove data" in str(excinfo.value) assert "include_leap_day" in str(excinfo.value) @@ -127,7 +126,11 @@ def test_process_leap_day_without_february(): ) def test_add_resource_start_end_times_reports_dt_in_seconds(subtests, freq_seconds): # `timedelta.seconds` drops whole days, so steps of 24 h or more need `total_seconds()`. - data = _make_timeseries(4, freq_seconds) + index = pd.date_range("2012-01-01", periods=4, freq=pd.Timedelta(seconds=freq_seconds)) + data = { + key: getattr(index, key).to_numpy().astype(float) + for key in ["year", "month", "day", "hour", "minute"] + } result = add_resource_start_end_times(data) with subtests.test("timestep includes whole days in seconds"): @@ -136,368 +139,6 @@ def test_add_resource_start_end_times_reports_dt_in_seconds(subtests, freq_secon assert isinstance(result["dt"], int) -def _make_timeseries(n, freq_seconds, start="2012-01-01 00:00", values=None): - """Build a resource data dict with time columns spaced at ``freq_seconds``.""" - - idx = pd.date_range(start=start, periods=n, freq=pd.Timedelta(seconds=freq_seconds)) - if values is None: - values = np.arange(n, dtype=float) - return { - "wind_speed_100m": np.asarray(values, dtype=float), - "year": idx.year.to_numpy().astype(float), - "month": idx.month.to_numpy().astype(float), - "day": idx.day.to_numpy().astype(float), - "hour": idx.hour.to_numpy().astype(float), - "minute": idx.minute.to_numpy().astype(float), - "units": {"wind_speed_100m": "m/s"}, - "site_lat": 1.0, - } - - -@pytest.mark.unit -def test_resample_no_op_when_dt_matches(): - data = _make_timeseries(10, 3600) - result = resample_resource_data_to_dt(data, 3600) - assert result is data - - -@pytest.mark.unit -def test_resample_no_time_columns_raises(): - data = {"wind_speed_100m": np.arange(10, dtype=float), "site_lat": 1.0} - with pytest.raises(ValueError, match="no time columns"): - resample_resource_data_to_dt(data, 1800) - - -@pytest.mark.unit -def test_resample_target_dt_larger_than_span_raises(): - # 5 hourly samples span only a few hours; a 10-day timestep yields no full step - data = _make_timeseries(5, 3600) - with pytest.raises(ValueError, match="larger than the total time span"): - resample_resource_data_to_dt(data, 10 * 86400) - - -@pytest.mark.unit -def test_resample_non_positive_deltas_raises(): - # If all deltas are non-positive (e.g. all identical timestamps), raise ValueError - data = { - "wind_speed_100m": np.arange(3, dtype=float), - "year": np.array([2012, 2012, 2012], dtype=float), - "month": np.array([1, 1, 1], dtype=float), - "day": np.array([1, 1, 1], dtype=float), - "hour": np.array([0, 0, 0], dtype=float), - "minute": np.array([0, 0, 0], dtype=float), - } - with pytest.raises(ValueError, match="no positive time steps"): - resample_resource_data_to_dt(data, 1800) - - -@pytest.mark.unit -def test_resample_negative_deltas_warns(subtests): - # Out-of-order years (e.g. 2013 then 2011) produce a negative delta at the transition, - # which should emit a warning but successfully resample. - data1 = _make_timeseries(5, 3600, start="2013-01-01 00:00") - data2 = _make_timeseries(5, 3600, start="2011-01-01 00:00") - combined = { - k: np.concatenate([data1[k], data2[k]]) - for k in ["year", "month", "day", "hour", "minute", "wind_speed_100m"] - } - with pytest.warns(UserWarning, match="negative time steps"): - result = resample_resource_data_to_dt(combined, 1800, upsample_method="time") - with subtests.test("upsampled length is 2x"): - assert len(result["wind_speed_100m"]) == 20 - - -@pytest.mark.unit -def test_resample_infers_native_dt_across_initial_calendar_gap(subtests): - # Dataset starting adjacent to a removed leap day (Feb 28 23:00 to Mar 1 00:00). - # The first delta is 25 hours (90000 s), but subsequent hourly steps are 3600 s. - # Native dt must infer as 3600 s (not 90000 s). - idx = pd.DatetimeIndex( - [ - "2012-02-28 23:00", - "2012-03-01 00:00", - "2012-03-01 01:00", - "2012-03-01 02:00", - "2012-03-01 03:00", - ] - ) - data = { - "wind_speed_100m": np.arange(len(idx), dtype=float), - "year": idx.year.to_numpy().astype(float), - "month": idx.month.to_numpy().astype(float), - "day": idx.day.to_numpy().astype(float), - "hour": idx.hour.to_numpy().astype(float), - "minute": idx.minute.to_numpy().astype(float), - } - result = resample_resource_data_to_dt(data, 1800, upsample_method="time") - with subtests.test("upsampled length doubles based on 3600s native dt"): - assert len(result["wind_speed_100m"]) == 10 - with subtests.test("timestamps are at 30-minute intervals"): - assert np.isclose(result["minute"][1] - result["minute"][0], 30.0) - - -@pytest.mark.unit -def test_upsample_interpolation(subtests): - # hourly data upsampled to 30-minute resolution - data = _make_timeseries(5, 3600, values=[0, 1, 2, 3, 4]) - result = resample_resource_data_to_dt(data, 1800, upsample_method="time") - - with subtests.test("upsampled length doubles"): - assert len(result["wind_speed_100m"]) == 10 - expected = np.array([0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4], dtype=float) - with subtests.test("interpolated values match expectation"): - np.testing.assert_allclose(result["wind_speed_100m"], expected) - with subtests.test("minutes alternate on 30 minute grid"): - np.testing.assert_array_equal(result["minute"][:4], [0, 30, 0, 30]) - - -@pytest.mark.unit -def test_downsample_average(subtests): - # 30-minute data downsampled to hourly resolution via pandas mean aggregation - data = _make_timeseries(10, 1800, values=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - result = resample_resource_data_to_dt(data, 3600, downsample_method="mean") - - with subtests.test("downsampled length halves"): - assert len(result["wind_speed_100m"]) == 5 - expected = np.array([0.5, 2.5, 4.5, 6.5, 8.5], dtype=float) - with subtests.test("hourly means match expectation"): - np.testing.assert_allclose(result["wind_speed_100m"], expected) - with subtests.test("minutes stay on hour"): - np.testing.assert_array_equal(result["minute"], np.zeros(5)) - - -@pytest.mark.unit -def test_resample_keeps_leap_day_excluded_at_new_timestep(subtests): - # Hourly data for a leap year with the leap day removed, resampled to 2-hour. - # February 29 must stay excluded in the regenerated calendar at the new timestep. - - idx = pd.date_range("2012-01-01 00:30", "2012-12-31 23:30", freq="1h") - idx = idx[~((idx.month == 2) & (idx.day == 29))] # 8760 hourly, leap day removed - data = { - "wind_speed_100m": np.arange(len(idx), dtype=float), - "year": idx.year.to_numpy().astype(float), - "month": idx.month.to_numpy().astype(float), - "day": idx.day.to_numpy().astype(float), - "hour": idx.hour.to_numpy().astype(float), - "minute": idx.minute.to_numpy().astype(float), - } - - result = resample_resource_data_to_dt(data, 7200, downsample_method="mean") # 2-hour timestep - - with subtests.test("result length matches excluded leap year at 2 hour dt"): - assert len(result["wind_speed_100m"]) == 4380 - with subtests.test("february 29 remains excluded"): - assert not ((result["month"] == 2) & (result["day"] == 29)).any() - with subtests.test("starts on january 1"): - assert int(result["month"][0]) == 1 and int(result["day"][0]) == 1 - with subtests.test("ends on december 31"): - assert int(result["month"][-1]) == 12 and int(result["day"][-1]) == 31 - - -@pytest.mark.unit -def test_resample_keeps_leap_day_when_present_at_new_timestep(subtests): - # Leap year with the leap day retained, resampled to 2-hour: Feb 29 must remain. - - idx = pd.date_range("2012-01-01 00:30", "2012-12-31 23:30", freq="1h") # 8784, leap kept - data = { - "wind_speed_100m": np.arange(len(idx), dtype=float), - "year": idx.year.to_numpy().astype(float), - "month": idx.month.to_numpy().astype(float), - "day": idx.day.to_numpy().astype(float), - "hour": idx.hour.to_numpy().astype(float), - "minute": idx.minute.to_numpy().astype(float), - } - - result = resample_resource_data_to_dt(data, 7200, downsample_method="mean") # 2-hour timestep - - with subtests.test("result length matches leap year at 2 hour dt"): - assert len(result["wind_speed_100m"]) == 4392 - with subtests.test("leap day remains present"): - assert ((result["month"] == 2) & (result["day"] == 29)).any() - - -@pytest.mark.unit -def test_downsample_preserves_mean(subtests): - rng = np.random.default_rng(0) - values = rng.random(24) - data = _make_timeseries(24, 900, values=values) # 15-min data - result = resample_resource_data_to_dt(data, 3600, downsample_method="mean") # hourly - - with subtests.test("downsampling produces six hourly values"): - assert len(result["wind_speed_100m"]) == 6 - with subtests.test("downsampling preserves the overall mean"): - np.testing.assert_allclose(result["wind_speed_100m"].mean(), values.mean()) - - -@pytest.mark.unit -def test_downsample_with_calendar_gap_preserves_mean(subtests): - # Hourly data whose timestamps have a one-day gap in the middle (as when a leap - # day is removed). Resampling must treat the samples as an evenly spaced sequence, - # so the downsampled mean matches the exact pairwise mean. - - part1 = pd.date_range("2012-02-28 00:30", periods=4, freq="1h") - part2 = pd.date_range("2012-03-01 00:30", periods=4, freq="1h") - idx = part1.append(part2) - values = np.arange(8, dtype=float) - data = { - "wind_speed_100m": values.copy(), - "year": idx.year.to_numpy().astype(float), - "month": idx.month.to_numpy().astype(float), - "day": idx.day.to_numpy().astype(float), - "hour": idx.hour.to_numpy().astype(float), - "minute": idx.minute.to_numpy().astype(float), - } - result = resample_resource_data_to_dt(data, 7200, downsample_method="mean") # to 2-hour - - with subtests.test("downsampled length matches expected bins"): - assert len(result["wind_speed_100m"]) == 4 - with subtests.test("pairwise averages preserved across gap"): - np.testing.assert_allclose(result["wind_speed_100m"], [0.5, 2.5, 4.5, 6.5]) - with subtests.test("mean preserved despite gap"): - np.testing.assert_allclose(result["wind_speed_100m"].mean(), values.mean()) - - -@pytest.mark.unit -def test_resample_scalar_metadata_preserved(subtests): - data = _make_timeseries(5, 3600, values=[0, 1, 2, 3, 4]) - result = resample_resource_data_to_dt(data, 1800, upsample_method="time") - with subtests.test("units preserved"): - assert result["units"] == {"wind_speed_100m": "m/s"} - with subtests.test("site latitude preserved"): - assert result["site_lat"] == 1.0 - - -@pytest.mark.unit -def test_resample_unknown_upsample_method_raises(): - data = _make_timeseries(5, 3600) - # An invalid pandas interpolation method raises. The exact message differs across - # pandas versions, but the offending method name is always reported. - with pytest.raises(ValueError, match="not_a_method"): - resample_resource_data_to_dt(data, 1800, upsample_method="not_a_method") - - -@pytest.mark.unit -def test_resample_unknown_downsample_method_raises(): - data = _make_timeseries(10, 1800) - # An invalid pandas aggregation raises. Different pandas versions raise different - # exception types (AttributeError vs ValueError) with different messages, so match - # on the offending method name that is common to all versions. - with pytest.raises((AttributeError, ValueError), match="not_a_method"): - resample_resource_data_to_dt(data, 3600, downsample_method="not_a_method") - - -@pytest.mark.unit -def test_resample_upsampling_without_method_raises(): - data = _make_timeseries(5, 3600) - with pytest.raises(ValueError, match="requires upsampling"): - resample_resource_data_to_dt(data, 1800) - - -@pytest.mark.unit -def test_resample_downsampling_without_method_raises(): - data = _make_timeseries(10, 1800) - with pytest.raises(ValueError, match="requires downsampling"): - resample_resource_data_to_dt(data, 3600) - - -@pytest.mark.unit -def test_resample_warns_when_resampling(subtests): - with subtests.test("upsampling warns"): - with pytest.warns(UserWarning, match="upsampling"): - resample_resource_data_to_dt(_make_timeseries(5, 3600), 1800, upsample_method="time") - with subtests.test("downsampling warns"): - with pytest.warns(UserWarning, match="downsampling"): - resample_resource_data_to_dt(_make_timeseries(10, 1800), 3600, downsample_method="mean") - - -@pytest.mark.unit -def test_resample_non_integer_ratio_guarantees_coverage(subtests): - # Non-integer ratios must not round down and under-cover the original duration. - with subtests.test("downsampling ceil covers span"): - # 10 samples at 3600s = 36000s; 36000 / 7000 = 5.14 -> ceil must yield 6 steps, not 5 - data = _make_timeseries(10, 3600) - res = resample_resource_data_to_dt(data, 7000, downsample_method="mean") - assert len(res["wind_speed_100m"]) == 6 - - with subtests.test("upsampling ceil covers span"): - # 5 samples at 3600s = 18000s; 18000 / 2500 = 7.2 -> ceil must yield 8 steps, not 7 - data = _make_timeseries(5, 3600) - res = resample_resource_data_to_dt(data, 2500, upsample_method="time") - assert len(res["wind_speed_100m"]) == 8 - - -@pytest.mark.unit -@pytest.mark.parametrize( - "target_dt,expected_steps", - [(6000.0, 6), (5999.9999999, 7), (6000.1, 6)], - ids=["exact-division", "tiny-positive-remainder", "fractional-timestep"], -) -def test_resample_step_count_preserves_small_remainders(subtests, target_dt, expected_steps): - data = _make_timeseries(10, 3600) - result = resample_resource_data_to_dt(data, target_dt, downsample_method="mean") - n_steps = len(result["wind_speed_100m"]) - - with subtests.test("step count is the ceiling of the duration ratio"): - assert n_steps == expected_steps - with subtests.test("target intervals cover the complete source duration"): - assert n_steps * pd.Timedelta(seconds=target_dt) >= pd.Timedelta(hours=10) - - -@pytest.mark.unit -def test_resample_non_uniform_deltas(subtests): - # Non-uniform deltas within a series: 1h and 2h steps. - # Spacing is proportional to physical duration: 2 steps for 1h, 4 steps for 2h. - idx = pd.DatetimeIndex( - [ - "2020-01-01 00:00", - "2020-01-01 01:00", - "2020-01-01 03:00", - "2020-01-01 04:00", - "2020-01-01 06:00", - ] - ) - data = { - "year": idx.year.to_numpy().astype(float), - "month": idx.month.to_numpy().astype(float), - "day": idx.day.to_numpy().astype(float), - "hour": idx.hour.to_numpy().astype(float), - "minute": idx.minute.to_numpy().astype(float), - "val": np.array([0.0, 10.0, 30.0, 40.0, 60.0]), - } - res = resample_resource_data_to_dt(data, 1800, upsample_method="time") - with subtests.test("interpolated length covers 8 hours span at 30 min"): - assert len(res["val"]) == 16 - with subtests.test("1h interval contains 2 steps and 2h contains 4 steps"): - # At 30m: 00:00(0), 00:30(5), 01:00(10), 01:30(15), 02:00(20), 02:30(25), 03:00(30) - np.testing.assert_allclose(res["val"][:3], [0.0, 5.0, 10.0]) - np.testing.assert_allclose(res["val"][2:7], [10.0, 15.0, 20.0, 25.0, 30.0]) - - -@pytest.mark.unit -def test_resample_out_of_order_years_boundary_transition(subtests): - # Year 1 (2015) followed by Year 2 (2012). - # Transition across year boundary completes Year 1 using Year 2 first value. - idx1 = pd.DatetimeIndex(["2015-12-31 22:00", "2015-12-31 23:00"]) - idx2 = pd.DatetimeIndex(["2012-01-01 00:00", "2012-01-01 01:00"]) - idx = idx1.append(idx2) - data = { - "year": idx.year.to_numpy().astype(float), - "month": idx.month.to_numpy().astype(float), - "day": idx.day.to_numpy().astype(float), - "hour": idx.hour.to_numpy().astype(float), - "minute": idx.minute.to_numpy().astype(float), - "val": np.array([10.0, 20.0, 40.0, 50.0]), - } - with pytest.warns(UserWarning, match="negative time steps"): - res = resample_resource_data_to_dt(data, 1800, upsample_method="time") - with subtests.test("boundary step interpolates between Year 1 end and Year 2 start"): - # At t=23:30 (index 3), value must interpolate between 20.0 and 40.0 -> 30.0 - assert res["val"][3] == 30.0 - with subtests.test("regenerated years preserve 2015 then 2012 order"): - assert (res["year"][:4] == 2015).all() - assert (res["year"][4:] == 2012).all() - - @pytest.mark.unit @pytest.mark.parametrize( "n_timesteps,include_leap,expected_years", diff --git a/h2integrate/resource/utilities/time_tools.py b/h2integrate/resource/utilities/time_tools.py index 23fc94f8a..d5254f34b 100644 --- a/h2integrate/resource/utilities/time_tools.py +++ b/h2integrate/resource/utilities/time_tools.py @@ -1,7 +1,4 @@ -import math -import warnings from datetime import timezone, timedelta -from itertools import pairwise import numpy as np import pandas as pd @@ -74,20 +71,15 @@ def check_data_length(data, n_timesteps: int): _, ts_data = separate_timeseries_and_meta_data(data) data = pd.DataFrame(ts_data) - data = data.rename(columns={"month": "Month", "day": "Day"}) - - february_days = data[data["Month"] == 2]["Day"] - data_has_leap_day = not february_days.empty and february_days.max() == 29 - # Check if data is the same length as the number of timesteps if len(data) != n_timesteps: leap_day_msg = "" - if data_has_leap_day and len(data) > n_timesteps: + if len(data) > n_timesteps and contains_leap_day(data): # Add extra detail to error message if error may be due to leap day leap_day_msg = ( - "This may be because the resource data includes a leap day. ", + "This may be because the resource data includes a leap day. " "To remove data from a leap day from resource data, please set " - "`include_leap_day` to False.", + "`include_leap_day` to False." ) msg = ( @@ -125,8 +117,7 @@ def process_leap_day(data: dict, include_leap_day: bool): data = data.rename(columns={"month": "Month", "day": "Day"}) # Check if data includes leap day - february_days = data[data["Month"] == 2]["Day"] - data_has_leap_day = not february_days.empty and february_days.max() == 29 + data_has_leap_day = contains_leap_day(data) # Remove leap day if needed if not include_leap_day and data_has_leap_day: @@ -279,306 +270,3 @@ def get_number_of_resource_years_needed(dt: int, n_timesteps: int, include_leap: # conservative estimate of number of years needed return int((hours_simulated // 8760) + 1) - - -def _regenerate_time_columns_at_dt( - data, start_timestamp, dt_seconds, n_timesteps, present_time_keys, include_leap_day=True -): - """Regenerate time columns as ``n_timesteps`` steps of ``dt_seconds`` starting at - ``start_timestamp``. - - When ``include_leap_day`` is False, February 29 is skipped so the regenerated calendar - keeps the leap day excluded even at the new timestep. - - Args: - data (dict): resource data dictionary whose time column arrays are to be updated. - start_timestamp (pandas.Timestamp | datetime.datetime): timestamp of the first sample. - dt_seconds (int | float): new timestep duration in seconds. - n_timesteps (int): number of timesteps to generate. - present_time_keys (list[str]): list of time column keys (e.g. "year", "month", - "day", "hour", "minute", "second") present in the data to regenerate. - include_leap_day (bool, optional): whether to include February 29 when generating the - calendar index. Defaults to True. - - Returns: - dict: resource data dictionary with updated time columns. - """ - freq = pd.Timedelta(seconds=dt_seconds) - if not include_leap_day: - # Upper bound on skipped timestamps: every leap year up to one year past the nominal end - start_year = pd.Timestamp(start_timestamp).year - n_years = get_number_of_resource_years_needed(dt_seconds, n_timesteps, include_leap=False) - last_year = start_year + n_years + 1 # +1 needed for upper-bound calculation - n_leap_years = sum(is_leap_year(y) for y in range(start_year, last_year + 1)) - extra = n_leap_years * math.ceil(86400 / dt_seconds) - new_index = pd.date_range(start=start_timestamp, periods=n_timesteps + extra, freq=freq) - new_index = new_index[~((new_index.month == 2) & (new_index.day == 29))][:n_timesteps] - else: - new_index = pd.date_range(start=start_timestamp, periods=n_timesteps, freq=freq) - field_map = { - "year": new_index.year, - "month": new_index.month, - "day": new_index.day, - "hour": new_index.hour, - "minute": new_index.minute, - "second": new_index.second, - } - for k in present_time_keys: - data[k] = np.asarray(field_map[k], dtype=float) - return data - - -def resample_resource_data_to_dt( - data: dict, - target_dt, - upsample_method: str | None = None, - downsample_method: str | None = None, -): - """Resample resource timeseries from its native timestep to ``target_dt``. - - Resampling is driven by the actual time span of the data (its native timestep(s), inferred - from the time columns), not by the number of timesteps. When the simulation timestep is - smaller than the native timestep the data is upsampled (finer resolution) using - `pandas.DataFrame.interpolate `_; - when it is larger the data is downsampled (coarser resolution) using - `pandas.DataFrame.agg `_. - Time columns are regenerated at ``target_dt`` and scalar metadata is left unchanged. - - Resampling operates on an elapsed timeline constructed from the input timestamps, - so non-uniform sample intervals (for example 1-hour and 2-hour intervals) receive target - timestamps proportional to their physical duration. For multi-year or discontinuous data, - the transition between segments completes each period using the preceding interval, - interpolating across the boundary between the final value of the preceding segment and the - first value of the subsequent segment. Time columns are regenerated at ``target_dt``, - preserving the original sequence of years and leap-day settings. - - Args: - data (dict): resource data dictionary with timeseries arrays and time columns. - target_dt (int | float): desired simulation timestep in seconds. - upsample_method (str | None): interpolation method passed to - `pandas.DataFrame.interpolate `_ - when upsampling. Must be explicitly set when upsampling is needed; otherwise a - ``ValueError`` is raised. - downsample_method (str | None): aggregation passed to - `pandas.DataFrame.agg `_ - when downsampling. Must be explicitly set when downsampling is needed; otherwise a - ``ValueError`` is raised. - - Returns: - dict: resource data resampled to ``target_dt``. - """ - if not isinstance(data, dict) or not target_dt: - return data - - present_time_keys = [k for k in TIME_DATA_KEYS if k in data] - if not present_time_keys: - msg = ( - "Cannot resample resource data to the simulation timestep because the data has " - "no time columns (year/month/day/hour/minute) to determine its native timestep(s). " - "Provide resource data that includes time information." - ) - raise ValueError(msg) - - time_dict = {k: np.asarray(data[k]).astype(int) for k in present_time_keys} - calendar_index = pd.DatetimeIndex(pd.to_datetime(time_dict)) - if len(calendar_index) < 2: - return data - - deltas = pd.Series((calendar_index[1:] - calendar_index[:-1]).total_seconds()) - pos_deltas = deltas[deltas > 0] - if pos_deltas.empty: - msg = ( - "Cannot resample resource data: no positive time steps could be derived from the " - "data's time columns. Ensure the resource data has increasing timestamps." - ) - raise ValueError(msg) - - # Detect non-chronological transitions (e.g. out-of-order years or TMY month jumps) - has_negative_deltas = bool((deltas < 0).any()) - if has_negative_deltas: - warnings.warn( - "Resource data contains negative time steps (e.g., timestamps out of chronological " - "order or non-chronological resource years). Resampling will process segments in " - "their given order, preserving block transitions.", - UserWarning, - stacklevel=2, - ) - - # Detect indices of annual or segment boundaries where chronological year/month resets - native_len = len(calendar_index) - calendar_months = calendar_index.month.to_numpy() - calendar_years = calendar_index.year.to_numpy() - block_starts = [0] - for i in range(native_len - 1): - if calendar_months[i + 1] < calendar_months[i]: - block_starts.append(i + 1) - elif calendar_years[i + 1] < calendar_years[i]: - block_starts.append(i + 1) - block_starts.append(native_len) - - # Compute effective step durations across samples - # For transitions across boundaries, large TMY gaps, or calendar gaps (e.g. removed leap day), - # complete the period using the local step duration of the preceding sample. - # Positive steps exclude backward jumps; their median resists isolated large forward gaps. - median_dt = float(pos_deltas.median()) - effective_deltas = deltas.to_numpy().copy() - for b_idx in range(1, len(block_starts) - 1): - trans_i = block_starts[b_idx] - 1 - effective_deltas[trans_i] = effective_deltas[trans_i - 1] if trans_i > 0 else median_dt - for i in range(len(effective_deltas)): - # Any delta exceeding 24 hours when typical step is sub-daily represents a calendar gap - if deltas.iloc[i] > 24 * 3600 and median_dt <= 24 * 3600: - effective_deltas[i] = effective_deltas[i - 1] if i > 0 else median_dt - elif deltas.iloc[i] > 28 * 86400: - effective_deltas[i] = effective_deltas[i - 1] if i > 0 else median_dt - - # Check if native data already matches target timestep uniformly - if abs(median_dt - float(target_dt)) < 1e-6 and np.allclose( - effective_deltas, float(target_dt), atol=1e-6 - ): - return data - - step_durations = np.append(effective_deltas, effective_deltas[-1]) - elapsed = np.insert(np.cumsum(effective_deltas), 0, 0.0) - - # Calculate target step count per block to ensure correct time coverage - # Inputs are seconds; Timedelta.value returns integer nanoseconds. - target_dt_ns = pd.Timedelta(seconds=float(target_dt)).value - block_target_n_timesteps = [] - for s_i, e_i in pairwise(block_starts): - block_duration_ns = pd.to_timedelta(step_durations[s_i:e_i], unit="s").sum().value - # Integer ceiling division retains any positive remainder without a float tolerance. - block_target_n_timesteps.append((block_duration_ns + target_dt_ns - 1) // target_dt_ns) - - target_n = sum(block_target_n_timesteps) - total_duration = float(np.sum(step_durations)) - if float(target_dt) > total_duration: - msg = ( - f"Cannot resample resource data to a timestep of {target_dt} s: it is larger than " - f"the total time span of the data ({native_len} samples, total span " - f"{total_duration} s), so resampling would produce no timesteps. Use a smaller " - "timestep or provide more resource data." - ) - raise ValueError(msg) - - target_offsets = np.arange(target_n) * float(target_dt) - # Pandas 3 may infer second resolution, which cannot represent fractional resampling bins. - native_index = pd.to_timedelta(elapsed, unit="s").as_unit("ns") - target_index = pd.to_timedelta(target_offsets, unit="s").as_unit("ns") - - # Only numeric timeseries columns are resampled; time columns are regenerated afterward. - _, ts_data = separate_timeseries_and_meta_data(data) - data_keys = [ - k - for k in ts_data - if k not in present_time_keys and len(np.atleast_1d(ts_data[k])) == native_len - ] - frame = pd.DataFrame( - {k: np.asarray(data[k], dtype=float) for k in data_keys}, index=native_index - ) - - min_step = np.min(effective_deltas) - max_step = np.max(effective_deltas) - upsampling = min_step > float(target_dt) or max_step > float(target_dt) - downsampling = min_step < float(target_dt) or max_step < float(target_dt) - - if upsampling and upsample_method is None: - raise ValueError( - f"Resource data has time steps larger than {float(target_dt):g} s, which requires " - "upsampling. Resampling is not performed automatically: set the resource " - "`upsample_method` (for example 'time') to explicitly enable upsampling." - ) - if downsampling and downsample_method is None: - raise ValueError( - f"Resource data has time steps smaller than {float(target_dt):g} s, which requires " - "downsampling. Resampling is not performed automatically: set the resource " - "`downsample_method` (for example 'mean') to explicitly enable downsampling." - ) - - direction = ( - "upsampling" - if (upsampling and not downsampling) - else ("downsampling" if (downsampling and not upsampling) else "mixed resampling") - ) - method_desc = ( - f"'{upsample_method}'" - if (upsampling and not downsampling) - else ( - f"'{downsample_method}'" - if (downsampling and not upsampling) - else f"upsample: '{upsample_method}', downsample: '{downsample_method}'" - ) - ) - warnings.warn( - f"Resampling resource data to the simulation timestep of {float(target_dt):g} s " - f"({direction} with {method_desc}).", - UserWarning, - stacklevel=2, - ) - - if upsampling and not downsampling: - # Upsample: interpolate onto the (finer) target grid - union_index = frame.index.union(target_index) - resampled_frame = ( - frame.reindex(union_index).interpolate(method=upsample_method).reindex(target_index) - ) - elif downsampling and not upsampling: - # Downsample: aggregate native samples within each (coarser) target interval - target_freq = pd.Timedelta(seconds=float(target_dt)) - resampled_frame = ( - frame.resample(target_freq, origin="start").agg(downsample_method).reindex(target_index) - ) - else: - # Mixed: first aggregate finer intervals, then interpolate coarser intervals - target_freq = pd.Timedelta(seconds=float(target_dt)) - resampled_frame = ( - frame.resample(target_freq, origin="start") - .agg(downsample_method) - .reindex(target_index) - .interpolate(method=upsample_method) - ) - - # Fill any residual NaNs at the grid edges introduced by reindexing - resampled_frame = resampled_frame.ffill().bfill() - - resampled = dict(data) - for k in data_keys: - resampled[k] = resampled_frame[k].to_numpy() - - # Detect whether the source data has a leap day removed so the regenerated calendar keeps - # February 29 excluded at the new timestep. - has_feb29 = bool(((calendar_index.month == 2) & (calendar_index.day == 29)).any()) - spans_leap_year = any(is_leap_year(int(y)) for y in np.unique(calendar_index.year.to_numpy())) - include_leap_day = not spans_leap_year or has_feb29 - - # Regenerate calendar time columns per block at target_dt, preserving original block order - # (even when years are non-chronological) and leap-day settings. - if len(block_starts) > 2: - reg_time_cols = {k: [] for k in present_time_keys} - for b in range(len(block_starts) - 1): - b_start_idx = block_starts[b] - b_start_ts = calendar_index[b_start_idx] - b_target_n = block_target_n_timesteps[b] - b_dict = {} - _regenerate_time_columns_at_dt( - b_dict, - b_start_ts, - float(target_dt), - b_target_n, - present_time_keys, - include_leap_day=include_leap_day, - ) - for k in present_time_keys: - reg_time_cols[k].append(b_dict[k]) - for k in present_time_keys: - resampled[k] = np.concatenate(reg_time_cols[k]) - else: - resampled = _regenerate_time_columns_at_dt( - resampled, - calendar_index[0], - float(target_dt), - target_n, - present_time_keys, - include_leap_day=include_leap_day, - ) - return resampled diff --git a/h2integrate/resource/wind/test/test_openmeteo_wind_api.py b/h2integrate/resource/wind/test/test_openmeteo_wind_api.py index bad22956c..4aaf5cd4c 100644 --- a/h2integrate/resource/wind/test/test_openmeteo_wind_api.py +++ b/h2integrate/resource/wind/test/test_openmeteo_wind_api.py @@ -7,8 +7,8 @@ import openmdao.api as om from h2integrate.core.supported_models import supported_models -from h2integrate.resource.utilities.time_tools import resample_resource_data_to_dt from h2integrate.resource.utilities.download_tools import make_time_index_openmeteo +from h2integrate.resource.utilities.resample_tools import resample_resource_data_to_dt @pytest.mark.unit diff --git a/h2integrate/storage/battery/pysam_battery.py b/h2integrate/storage/battery/pysam_battery.py index 46d43f9b5..502617fd5 100644 --- a/h2integrate/storage/battery/pysam_battery.py +++ b/h2integrate/storage/battery/pysam_battery.py @@ -211,6 +211,7 @@ def simulate( storage_capacity: float, commodity_available=list | np.ndarray, sim_start_index: int = 0, + sim_end_index: int = 8760, ): """Run the PySAM BatteryStateful model over a control window. @@ -234,6 +235,9 @@ def simulate( PySAM control input to set each step ("input_power" or "input_current"). sim_start_index : int, optional Starting index for writing into persistent output arrays (default 0). + sim_end_index (int, optional): + Ending index for writing into persistent output arrays. + Defaults to 8760 (1 year) Returns: tuple[np.ndarray, np.ndarray]: Battery power (kW) and SOC (%) per timestep. diff --git a/h2integrate/storage/simple_storage_auto_sizing.py b/h2integrate/storage/simple_storage_auto_sizing.py index bd57100de..f9a1abb62 100644 --- a/h2integrate/storage/simple_storage_auto_sizing.py +++ b/h2integrate/storage/simple_storage_auto_sizing.py @@ -259,7 +259,7 @@ def compute(self, inputs, outputs, discrete_inputs=[], discrete_outputs=[]): # Estimate the initial SOC # 1. Set the starting SOC (as a fraction) at the start of the simulation. - self.current_soc = np.max( + self.soc_init = np.max( [self.config.min_soc_fraction, commodity_storage_soc[0] / rated_storage_capacity] ) diff --git a/h2integrate/storage/storage_baseclass.py b/h2integrate/storage/storage_baseclass.py index 2b7743ce9..e3043c509 100644 --- a/h2integrate/storage/storage_baseclass.py +++ b/h2integrate/storage/storage_baseclass.py @@ -1,3 +1,5 @@ +from copy import deepcopy + import numpy as np from attrs import field, define, validators from openmdao.utils import units as om_units @@ -72,6 +74,16 @@ def setup(self): commodity_amount_units = self.commodity_amount_units n_timesteps = self.n_timesteps + # Ensure _soc_timeseries is consistent length with simulation + self._soc_timeseries = np.zeros(self.n_timesteps) + + # Initialize soc to the value in the config, if given or halfway between + # the minimum and maximum limits otherwise. + if hasattr(self.config, "init_soc_fraction"): + self.soc_init = self.config.init_soc_fraction + else: + self.soc_init = self.config.min_soc_fraction + # Input timeseries self.add_input( f"{commodity}_in", @@ -215,7 +227,7 @@ def compute(self, inputs, outputs, discrete_inputs=[], discrete_outputs=[]): # Below is an example of what the compute method would look like in the # StoragePerformanceModel # Do whatever pre-calculations are necessary, then run storage - # self.current_soc = self.config.init_soc_fraction + # self.soc_init = self.config.init_soc_fraction # charge_rate = inputs["max_charge_rate"][0] # if "max_discharge_rate" in inputs: @@ -236,7 +248,7 @@ def run_storage( Example: >>> # In the `compute()` method: - >>> self.current_soc = self.config.init_soc_fraction + >>> self.soc_init = self.config.init_soc_fraction >>> charge_rate = inputs["max_charge_rate"][0] >>> discharge_rate = inputs["max_discharge_rate"][0] >>> storage_capacity = inputs["storage_capacity"] @@ -261,6 +273,11 @@ def run_storage( Returns: om.vectors.default_vector.DefaultVector: calculated OpenMDAO outputs. """ + + # Range object for the slice of the total simulation to run in this + # compute call + simulation_range = self._get_compute_time_range(inputs["timestep_index"]) + if "pyomo_dispatch_solver" in discrete_inputs: dispatch = discrete_inputs["pyomo_dispatch_solver"] # kwargs are tech-specific inputs to the simulate() method @@ -279,6 +296,8 @@ def run_storage( discharge_rate=discharge_rate, storage_capacity=storage_capacity, commodity_available=inputs[f"{self.commodity}_in"], + sim_start_index=simulation_range.start, + sim_end_index=simulation_range.stop, ) # determine storage charge and discharge @@ -293,19 +312,30 @@ def run_storage( outputs["storage_duration"] = 0 # Storage specific timeseries outputs - outputs[f"storage_{self.commodity}_charge"] = np.where( - storage_commodity_out < 0, storage_commodity_out, 0 + outputs[f"storage_{self.commodity}_charge"][ + simulation_range.start : simulation_range.stop + ] = np.where(storage_commodity_out < 0, storage_commodity_out, 0) + outputs[f"storage_{self.commodity}_discharge"][ + simulation_range.start : simulation_range.stop + ] = np.where(storage_commodity_out > 0, storage_commodity_out, 0) + outputs["SOC"][simulation_range.start : simulation_range.stop] = soc + outputs[f"{self.commodity}_out"][simulation_range.start : simulation_range.stop] = ( + storage_commodity_out ) - outputs[f"storage_{self.commodity}_discharge"] = np.where( - storage_commodity_out > 0, storage_commodity_out, 0 - ) - outputs["SOC"] = soc - outputs[f"{self.commodity}_out"] = storage_commodity_out # Performance model outputs + + # Simulation-length storage_commodity_out vector for bulk calculations + storage_commodity_out_full = ( + outputs[f"storage_{self.commodity}_discharge"] + + outputs[f"storage_{self.commodity}_charge"] + ) + outputs[f"rated_{self.commodity}_production"] = discharge_rate # rate * dt_amount = commodity_amount_units (works for any commodity_rate_units) - outputs[f"total_{self.commodity}_produced"] = np.sum(storage_commodity_out) * self.dt_amount + outputs[f"total_{self.commodity}_produced"] = ( + np.sum(storage_commodity_out_full) * self.dt_amount + ) outputs[f"annual_{self.commodity}_produced"] = outputs[ f"total_{self.commodity}_produced" ] * (1 / self.fraction_of_year_simulated) @@ -334,6 +364,7 @@ def simulate( storage_capacity: float, commodity_available: list | np.ndarray, sim_start_index: int = 0, + sim_end_index: int = 8760, ): """Run the storage model over a control window of ``n_control_window_hours`` length of time. @@ -376,6 +407,9 @@ def simulate( sim_start_index (int, optional): Starting index for writing into persistent output arrays. Defaults to 0. + sim_end_index (int, optional): + Ending index for writing into persistent output arrays. + Defaults to 8760 (1 year) Returns: tuple[np.ndarray, np.ndarray] @@ -388,14 +422,14 @@ def simulate( (0-100). """ - n = len(storage_dispatch_commands) + n = sim_end_index - sim_start_index storage_commodity_out_timesteps = np.zeros(n) soc_timesteps = np.zeros(n) # Early return when storage cannot operate: zero capacity or both # charge and discharge rates are zero. if storage_capacity <= 0 or (charge_rate <= 0 and discharge_rate <= 0): - soc_timesteps[:] = self.current_soc * 100.0 + soc_timesteps[:] = self.soc_init * 100.0 return storage_commodity_out_timesteps, soc_timesteps # Pre-compute scalar constants to avoid repeated attribute lookups @@ -405,8 +439,17 @@ def simulate( soc_max = self.config.max_soc_fraction soc_min = self.config.min_soc_fraction - commands = np.asarray(storage_dispatch_commands, dtype=float) - soc = float(self.current_soc) + if len(storage_dispatch_commands) > n: + commands = np.asarray(storage_dispatch_commands, dtype=float)[ + sim_start_index:sim_end_index + ] + else: + commands = np.asarray(storage_dispatch_commands, dtype=float) + + if sim_start_index == 0: + soc = deepcopy(self.soc_init) + else: + soc = self._soc_timeseries[sim_start_index - 1] for t, cmd in enumerate(commands): if cmd < 0.0: @@ -458,5 +501,6 @@ def simulate( # Persist the final SOC so subsequent simulate() calls (e.g. from the # Pyomo controller across rolling windows) start where we left off. - self.current_soc = soc + self._soc_timeseries[sim_start_index:sim_end_index] = soc_timesteps / 100 + return storage_commodity_out_timesteps, soc_timesteps diff --git a/h2integrate/storage/storage_performance_model.py b/h2integrate/storage/storage_performance_model.py index e80689ee0..cd429f97c 100644 --- a/h2integrate/storage/storage_performance_model.py +++ b/h2integrate/storage/storage_performance_model.py @@ -131,6 +131,8 @@ class StoragePerformanceModel(StoragePerformanceBase): 3600, ) # (min, max) time step lengths (in seconds) compatible with this model + _is_steppable = True + def setup(self): self.config = StoragePerformanceModelConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), @@ -160,14 +162,16 @@ def setup(self): def compute(self, inputs, outputs, discrete_inputs=[], discrete_outputs=[]): """Run the storage performance model.""" - self.current_soc = self.config.init_soc_fraction + # Unpack scalar inputs charge_rate = inputs["max_charge_rate"][0] + if "max_discharge_rate" in inputs: discharge_rate = inputs["max_discharge_rate"][0] else: discharge_rate = inputs["max_charge_rate"][0] storage_capacity = inputs["storage_capacity"][0] + outputs = self.run_storage( charge_rate, discharge_rate, storage_capacity, inputs, outputs, discrete_inputs ) diff --git a/h2integrate/transporters/generic_combiner.py b/h2integrate/transporters/generic_combiner.py index f3efad2a0..250966b6c 100644 --- a/h2integrate/transporters/generic_combiner.py +++ b/h2integrate/transporters/generic_combiner.py @@ -47,6 +47,8 @@ class GenericCombinerPerformanceModel(om.ExplicitComponent): _control_classifier = "combiner" + _is_steppable = True + def initialize(self): self.options.declare("driver_config", types=dict) self.options.declare("plant_config", types=dict) diff --git a/h2integrate/transporters/generic_splitter.py b/h2integrate/transporters/generic_splitter.py index fba950ff1..7a57d92d8 100644 --- a/h2integrate/transporters/generic_splitter.py +++ b/h2integrate/transporters/generic_splitter.py @@ -74,6 +74,8 @@ class GenericSplitterPerformanceModel(om.ExplicitComponent): _control_classifier = "splitter" + _is_steppable = False + def initialize(self): self.options.declare("driver_config", types=dict, default={}) self.options.declare("plant_config", types=dict, default={}) diff --git a/h2integrate/transporters/generic_transporter.py b/h2integrate/transporters/generic_transporter.py index 2d94e6867..146969789 100644 --- a/h2integrate/transporters/generic_transporter.py +++ b/h2integrate/transporters/generic_transporter.py @@ -31,6 +31,8 @@ class GenericTransporterPerformanceModel(om.ExplicitComponent): ) # (min, max) time step lengths (in seconds) compatible with this model _control_classifier = "transport" + _is_steppable = False + def initialize(self): self.options.declare("driver_config", types=dict) self.options.declare("plant_config", types=dict) diff --git a/pyproject.toml b/pyproject.toml index 4e6e47384..9da7e8302 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -161,6 +161,7 @@ markers = [ "regression: marks regression tests (deselect with '-m \"not regression\"')", "integration: marks unit tests (deselect with '-m \"not integration\"')", "hpc: marks unit tests (deselect with '-m \"not hpc\"')", + "examples: marks tests that run full example cases nightly (deselect with '-m \"not examples\"')", ] [tool.isort] diff --git a/test/conftest.py b/test/conftest.py index ad0ac6144..cc1f6bb4c 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -62,10 +62,10 @@ def pytest_sessionfinish(session, exitstatus): def pytest_collection_modifyitems(config, items): - """Enforce the usage marking tests as either unit, regression, or integration tests. - This method will need to be imported into all subsequent ``contest.py`` files. + """Enforce the usage marking tests as either unit, regression, integration, hpc, or examples + tests. This method will need to be imported into all subsequent ``contest.py`` files. """ - test_types = {"unit", "regression", "integration", "hpc"} + test_types = {"unit", "regression", "integration", "hpc", "examples"} missing_type_mark = [ f"{item.path}::{item.name}" for item in items @@ -75,7 +75,7 @@ def pytest_collection_modifyitems(config, items): errors = "\n".join(missing_type_mark) msg = ( "The following tests must be marked as either 'unit', 'regression', " - f"'integration', or 'hpc' tests using `@pytest.mark.`:\n{errors}" + f"'integration', 'hpc', or 'examples' tests using `@pytest.mark.`:\n{errors}" ) raise pytest.UsageError(msg) diff --git a/test/test_docs_consistency.py b/test/test_docs_consistency.py index 9c579bab4..4274805fc 100644 --- a/test/test_docs_consistency.py +++ b/test/test_docs_consistency.py @@ -49,7 +49,7 @@ "SimpleGasConsumerPerformance", "SimpleGasConsumerCost", } -UNREFERENCED_EXAMPLES = {"04_geo_h2", "31_tidal", "36_nuclear_reactor_htse"} +UNREFERENCED_EXAMPLES = set() CUSTOM_MODEL_NAMES = {"CustomRiverResource", "SimpleLCOFinance"} KNOWN_MISSING_PATHS = set()