diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..c3472894b6c1fcf60f397ec9ccac4bd288bbcd27 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,27 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/images/ac_dc_meshed.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/images/carbon_management-energy_balance-area_plot-AC-bus_carrier.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/images/carbon_management-energy_balance-area_plot.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/images/elec_s_256_lv1.09_Co2L-3H.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/images/lmp_and_line-loading.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/images/load-distribution.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/images/pypsa-animation.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/images/reactive-power.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/images/scigrid-curtailment.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/images/stacked-gen_and_storage-scigrid.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/images/stacked-gen.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/images/storage-scigrid.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/logo/logo-primary-dark.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/logo/logo-primary-light-white-bg.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/logo/logo-primary-light.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/logo/logo-secondary-dark.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/logo/logo-secondary-light.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/logo/logo-white-bg.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/docs/assets/logo/logo.png filter=lfs diff=lfs merge=lfs -text +PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed.nc filter=lfs diff=lfs merge=lfs -text +PyPSA/source/examples/networks/model-energy/model-energy.nc filter=lfs diff=lfs merge=lfs -text +PyPSA/source/examples/networks/scigrid-de/scigrid-de.nc filter=lfs diff=lfs merge=lfs -text +PyPSA/source/examples/networks/stochastic-network/stochastic-network.nc filter=lfs diff=lfs merge=lfs -text +PyPSA/source/examples/networks/storage-hvdc/storage-hvdc.nc filter=lfs diff=lfs merge=lfs -text diff --git a/PyPSA/mcp_output/README_MCP.md b/PyPSA/mcp_output/README_MCP.md new file mode 100644 index 0000000000000000000000000000000000000000..2f19f2998a87f3283317521c2c5f22cffd136f0a --- /dev/null +++ b/PyPSA/mcp_output/README_MCP.md @@ -0,0 +1,77 @@ +# MCP Plugin README + +## Project Overview + +The MCP Plugin is a component of the PyPSA (Python for Power System Analysis) ecosystem, designed to enhance the capabilities of power system modeling and analysis. PyPSA is an open-source toolbox for simulating and optimizing modern power systems over multiple periods. The MCP Plugin extends these functionalities by providing additional tools and features for more detailed and specific analyses. + +### Key Features + +- **Advanced Power System Modeling**: Leverage the robust PyPSA framework for detailed power system simulations. +- **Optimization Capabilities**: Perform complex optimization tasks to improve system efficiency and reliability. +- **Extensible and Customizable**: Easily integrate with existing PyPSA setups and customize according to specific project needs. + +## Installation Instructions + +To install the MCP Plugin, ensure you have Python installed on your system. The plugin requires Python 3.7 or later. Follow these steps to install: + +1. **Clone the Repository**: + ```bash + git clone https://github.com/PyPSA/PyPSA.git + cd PyPSA + ``` + +2. **Install Dependencies**: + The MCP Plugin requires several Python packages. You can install them using `pip`: + ```bash + pip install numpy pandas matplotlib + ``` + + Optional dependencies for enhanced functionalities include: + ```bash + pip install geopandas plotly + ``` + +3. **Install the Plugin**: + Navigate to the plugin directory and install it: + ```bash + pip install . + ``` + +## Usage Methods + +Once installed, the MCP Plugin can be used within the PyPSA framework to perform various analyses. Here’s a basic usage example: + +1. **Import the Plugin**: + ```python + import pypsa + ``` + +2. **Create a Network**: + ```python + network = pypsa.Network() + ``` + +3. **Add Components and Perform Analysis**: + Use the plugin’s extended functionalities to add components and perform analyses on the network. + +## Available Tool Endpoints + +The MCP Plugin provides several endpoints for interacting with the power system models: + +- **Network Optimization**: Optimize network configurations for efficiency. +- **Power Flow Analysis**: Conduct detailed power flow studies. +- **Statistical Analysis**: Perform statistical evaluations of network performance. + +## Notes and Troubleshooting + +- **Compatibility**: Ensure that your Python environment is compatible with the required dependencies. +- **Performance**: For large-scale simulations, consider optimizing your system resources. +- **Common Issues**: + - **Installation Errors**: Ensure all dependencies are correctly installed. + - **Runtime Errors**: Check for compatibility issues with other installed packages. + +For further assistance, refer to the [PyPSA documentation](https://pypsa.org/doc/) or reach out to the community via the [PyPSA GitHub repository](https://github.com/PyPSA/PyPSA). + +## Conclusion + +The MCP Plugin is a powerful extension to the PyPSA framework, offering advanced tools for power system analysis and optimization. By following the installation and usage instructions, users can effectively integrate and utilize the plugin for their specific needs. For any issues or contributions, please refer to the official PyPSA repository. \ No newline at end of file diff --git a/PyPSA/mcp_output/analysis.json b/PyPSA/mcp_output/analysis.json new file mode 100644 index 0000000000000000000000000000000000000000..e3e6e085368f82ea7e2491a83e731ed92f2a1ecb --- /dev/null +++ b/PyPSA/mcp_output/analysis.json @@ -0,0 +1,982 @@ +{ + "summary": { + "repository_url": "https://github.com/PyPSA/PyPSA", + "summary": "Imported via zip fallback, file count: 280", + "file_tree": { + ".github/ISSUE_TEMPLATE/bug_report.yaml": { + "size": 1876 + }, + ".github/ISSUE_TEMPLATE/config.yml": { + "size": 248 + }, + ".github/ISSUE_TEMPLATE/documentation_improvement.yaml": { + "size": 747 + }, + ".github/ISSUE_TEMPLATE/feature_request.md": { + "size": 480 + }, + ".github/dependabot.yml": { + "size": 536 + }, + ".github/pull_request_template.md": { + "size": 449 + }, + ".github/workflows/codeql.yml": { + "size": 4362 + }, + ".github/workflows/release.yml": { + "size": 5532 + }, + ".github/workflows/test-models.yml": { + "size": 5584 + }, + ".github/workflows/test.yml": { + "size": 5866 + }, + ".pre-commit-config.yaml": { + "size": 1494 + }, + ".readthedocs.yml": { + "size": 699 + }, + "CONTRIBUTING.md": { + "size": 430 + }, + "LICENSES/CC-BY-4.0.txt": { + "size": 16983 + }, + "LICENSES/MIT.txt": { + "size": 1078 + }, + "README.md": { + "size": 9772 + }, + "REUSE.toml": { + "size": 1508 + }, + "codecov.yml": { + "size": 210 + }, + "docs/CODE_OF_CONDUCT.md": { + "size": 5811 + }, + "docs/api/components/common.md": { + "size": 117 + }, + "docs/api/components/components.md": { + "size": 507 + }, + "docs/api/components/types/buses.md": { + "size": 116 + }, + "docs/api/components/types/carriers.md": { + "size": 119 + }, + "docs/api/components/types/generators.md": { + "size": 121 + }, + "docs/api/components/types/global-constraints.md": { + "size": 128 + }, + "docs/api/components/types/lines.md": { + "size": 224 + }, + "docs/api/components/types/links.md": { + "size": 116 + }, + "docs/api/components/types/loads.md": { + "size": 116 + }, + "docs/api/components/types/shapes.md": { + "size": 117 + }, + "docs/api/components/types/shunt-impedances.md": { + "size": 126 + }, + "docs/api/components/types/storage-units.md": { + "size": 123 + }, + "docs/api/components/types/stores.md": { + "size": 117 + }, + "docs/api/components/types/sub-networks.md": { + "size": 122 + }, + "docs/api/components/types/transformers.md": { + "size": 238 + }, + "docs/api/networks/cluster.md": { + "size": 129 + }, + "docs/api/networks/collection.md": { + "size": 265 + }, + "docs/api/networks/components.md": { + "size": 210 + }, + "docs/api/networks/descriptors.md": { + "size": 212 + }, + "docs/api/networks/indexing.md": { + "size": 200 + }, + "docs/api/networks/io.md": { + "size": 164 + }, + "docs/api/networks/network.md": { + "size": 236 + }, + "docs/api/networks/optimize.md": { + "size": 424 + }, + "docs/api/networks/plot.md": { + "size": 445 + }, + "docs/api/networks/power-flow.md": { + "size": 179 + }, + "docs/api/networks/statistics.md": { + "size": 348 + }, + "docs/api/networks/subnetwork.md": { + "size": 164 + }, + "docs/api/networks/transform.md": { + "size": 178 + }, + "docs/api/other/api-examples.md": { + "size": 267 + }, + "docs/api/other/common.md": { + "size": 669 + }, + "docs/api/other/consistency.md": { + "size": 624 + }, + "docs/api/other/options.md": { + "size": 627 + }, + "docs/assets/javascripts/gurubase-widget.js": { + "size": 1197 + }, + "docs/assets/javascripts/matjax.js": { + "size": 502 + }, + "docs/assets/javascripts/readthedocs.js": { + "size": 809 + }, + "docs/contributing/contributing.md": { + "size": 6313 + }, + "docs/contributing/contributors.md": { + "size": 1441 + }, + "docs/examples/examples.md": { + "size": 8698 + }, + "docs/home/citing.md": { + "size": 9740 + }, + "docs/home/features.md": { + "size": 7780 + }, + "docs/home/installation.md": { + "size": 5455 + }, + "docs/home/license.md": { + "size": 1282 + }, + "docs/home/models.md": { + "size": 5138 + }, + "docs/home/users.md": { + "size": 23810 + }, + "docs/hooks/cleanup.py": { + "size": 968 + }, + "docs/hooks/dynamic_inspect.py": { + "size": 1082 + }, + "docs/hooks/path_aliases.py": { + "size": 4223 + }, + "docs/hooks/shortcodes.py": { + "size": 16035 + }, + "docs/index.md": { + "size": 4839 + }, + "docs/release-notes.md": { + "size": 128422 + }, + "docs/requirements.txt": { + "size": 17102 + }, + "docs/user-guide/collection.md": { + "size": 7632 + }, + "docs/user-guide/components.md": { + "size": 9304 + }, + "docs/user-guide/components/buses.md": { + "size": 654 + }, + "docs/user-guide/components/carriers.md": { + "size": 682 + }, + "docs/user-guide/components/generators.md": { + "size": 1012 + }, + "docs/user-guide/components/global-constraints.md": { + "size": 353 + }, + "docs/user-guide/components/line-types.md": { + "size": 936 + }, + "docs/user-guide/components/lines.md": { + "size": 951 + }, + "docs/user-guide/components/links.md": { + "size": 3686 + }, + "docs/user-guide/components/loads.md": { + "size": 867 + }, + "docs/user-guide/components/shapes.md": { + "size": 337 + }, + "docs/user-guide/components/shunt-impedances.md": { + "size": 943 + }, + "docs/user-guide/components/storage-units.md": { + "size": 1551 + }, + "docs/user-guide/components/stores.md": { + "size": 2536 + }, + "docs/user-guide/components/sub-networks.md": { + "size": 893 + }, + "docs/user-guide/components/transformer-types.md": { + "size": 855 + }, + "docs/user-guide/components/transformers.md": { + "size": 683 + }, + "docs/user-guide/design.md": { + "size": 17819 + }, + "docs/user-guide/faq.md": { + "size": 10223 + }, + "docs/user-guide/import-export.md": { + "size": 7634 + }, + "docs/user-guide/learning-materials.md": { + "size": 1010 + }, + "docs/user-guide/linear-power-flow.md": { + "size": 3856 + }, + "docs/user-guide/network-optimization.md": { + "size": 9547 + }, + "docs/user-guide/optimization/capacity-limits.md": { + "size": 7284 + }, + "docs/user-guide/optimization/contingencies.md": { + "size": 6579 + }, + "docs/user-guide/optimization/custom-constraints.md": { + "size": 7494 + }, + "docs/user-guide/optimization/dispatch-limits.md": { + "size": 7222 + }, + "docs/user-guide/optimization/energy-balance.md": { + "size": 3791 + }, + "docs/user-guide/optimization/global-constraints.md": { + "size": 12604 + }, + "docs/user-guide/optimization/modelling-to-generate-alternatives.md": { + "size": 5313 + }, + "docs/user-guide/optimization/objective.md": { + "size": 10001 + }, + "docs/user-guide/optimization/overview.md": { + "size": 3517 + }, + "docs/user-guide/optimization/pathway-planning.md": { + "size": 10983 + }, + "docs/user-guide/optimization/power-flow.md": { + "size": 7236 + }, + "docs/user-guide/optimization/stochastic.md": { + "size": 22635 + }, + "docs/user-guide/optimization/storage.md": { + "size": 11229 + }, + "docs/user-guide/optimization/unit-commitment.md": { + "size": 16807 + }, + "docs/user-guide/options.md": { + "size": 5386 + }, + "docs/user-guide/plotting/charts.md": { + "size": 15305 + }, + "docs/user-guide/power-flow.md": { + "size": 10049 + }, + "docs/user-guide/statistics.md": { + "size": 6948 + }, + "docs/user-guide/support.md": { + "size": 973 + }, + "docs/user-guide/troubleshooting.md": { + "size": 5941 + }, + "docs/user-guide/user-guide.md": { + "size": 1165 + }, + "docs/user-guide/v1-guide.md": { + "size": 9077 + }, + "examples/networks/ac-dc-meshed/ac-dc-meshed/crs.json": { + "size": 920 + }, + "examples/networks/ac-dc-meshed/ac-dc-meshed/meta.json": { + "size": 2 + }, + "examples/networks/model-energy/model-energy/crs.json": { + "size": 920 + }, + "examples/networks/model-energy/model-energy/meta.json": { + "size": 2 + }, + "examples/networks/scigrid-de/scigrid-de/crs.json": { + "size": 920 + }, + "examples/networks/scigrid-de/scigrid-de/meta.json": { + "size": 2 + }, + "examples/networks/storage-hvdc/storage-hvdc/crs.json": { + "size": 920 + }, + "examples/networks/storage-hvdc/storage-hvdc/meta.json": { + "size": 2 + }, + "mkdocs.yml": { + "size": 15022 + }, + "pyproject.toml": { + "size": 6831 + }, + "pypsa/__init__.py": { + "size": 2807 + }, + "pypsa/_options.py": { + "size": 13897 + }, + "pypsa/clustering/__init__.py": { + "size": 3376 + }, + "pypsa/clustering/spatial.py": { + "size": 35407 + }, + "pypsa/clustering/temporal.py": { + "size": 132 + }, + "pypsa/collection.py": { + "size": 28144 + }, + "pypsa/common.py": { + "size": 27902 + }, + "pypsa/components/__init__.py": { + "size": 1130 + }, + "pypsa/components/_types/__init__.py": { + "size": 1419 + }, + "pypsa/components/_types/_patch.py": { + "size": 3265 + }, + "pypsa/components/_types/buses.py": { + "size": 1352 + }, + "pypsa/components/_types/carriers.py": { + "size": 1379 + }, + "pypsa/components/_types/generators.py": { + "size": 2298 + }, + "pypsa/components/_types/global_constraints.py": { + "size": 1456 + }, + "pypsa/components/_types/line_types.py": { + "size": 1393 + }, + "pypsa/components/_types/lines.py": { + "size": 3882 + }, + "pypsa/components/_types/links.py": { + "size": 3090 + }, + "pypsa/components/_types/loads.py": { + "size": 1277 + }, + "pypsa/components/_types/shapes.py": { + "size": 1205 + }, + "pypsa/components/_types/shunt_impedances.py": { + "size": 1273 + }, + "pypsa/components/_types/storage_units.py": { + "size": 2518 + }, + "pypsa/components/_types/stores.py": { + "size": 2178 + }, + "pypsa/components/_types/sub_networks.py": { + "size": 1408 + }, + "pypsa/components/_types/transformer_types.py": { + "size": 1449 + }, + "pypsa/components/_types/transformers.py": { + "size": 2366 + }, + "pypsa/components/abstract.py": { + "size": 2676 + }, + "pypsa/components/array.py": { + "size": 12538 + }, + "pypsa/components/common.py": { + "size": 1848 + }, + "pypsa/components/components.py": { + "size": 26324 + }, + "pypsa/components/descriptors.py": { + "size": 9148 + }, + "pypsa/components/index.py": { + "size": 4049 + }, + "pypsa/components/legacy.py": { + "size": 2654 + }, + "pypsa/components/store.py": { + "size": 6550 + }, + "pypsa/components/transform.py": { + "size": 6882 + }, + "pypsa/components/types.py": { + "size": 7435 + }, + "pypsa/consistency.py": { + "size": 37582 + }, + "pypsa/constants.py": { + "size": 458 + }, + "pypsa/definitions/components.py": { + "size": 2020 + }, + "pypsa/definitions/structures.py": { + "size": 2240 + }, + "pypsa/deprecations.py": { + "size": 593 + }, + "pypsa/descriptors.py": { + "size": 8736 + }, + "pypsa/examples.py": { + "size": 6607 + }, + "pypsa/geo.py": { + "size": 6227 + }, + "pypsa/guards.py": { + "size": 7156 + }, + "pypsa/network/__init__.py": { + "size": 138 + }, + "pypsa/network/abstract.py": { + "size": 2775 + }, + "pypsa/network/components.py": { + "size": 30570 + }, + "pypsa/network/descriptors.py": { + "size": 8052 + }, + "pypsa/network/graph.py": { + "size": 10946 + }, + "pypsa/network/index.py": { + "size": 40162 + }, + "pypsa/network/io.py": { + "size": 86634 + }, + "pypsa/network/power_flow.py": { + "size": 67785 + }, + "pypsa/network/transform.py": { + "size": 21288 + }, + "pypsa/networks.py": { + "size": 62697 + }, + "pypsa/optimization/__init__.py": { + "size": 393 + }, + "pypsa/optimization/abstract.py": { + "size": 24469 + }, + "pypsa/optimization/common.py": { + "size": 2361 + }, + "pypsa/optimization/constraints.py": { + "size": 64639 + }, + "pypsa/optimization/expressions.py": { + "size": 26869 + }, + "pypsa/optimization/global_constraints.py": { + "size": 32963 + }, + "pypsa/optimization/mga.py": { + "size": 28507 + }, + "pypsa/optimization/optimize.py": { + "size": 40975 + }, + "pypsa/optimization/variables.py": { + "size": 7716 + }, + "pypsa/plot/__init__.py": { + "size": 641 + }, + "pypsa/plot/accessor.py": { + "size": 1401 + }, + "pypsa/plot/maps/__init__.py": { + "size": 460 + }, + "pypsa/plot/maps/common.py": { + "size": 28329 + }, + "pypsa/plot/maps/interactive.py": { + "size": 76737 + }, + "pypsa/plot/maps/static.py": { + "size": 66061 + }, + "pypsa/plot/statistics/__init__.py": { + "size": 420 + }, + "pypsa/plot/statistics/base.py": { + "size": 2845 + }, + "pypsa/plot/statistics/charts.py": { + "size": 22037 + }, + "pypsa/plot/statistics/maps.py": { + "size": 12641 + }, + "pypsa/plot/statistics/plotter.py": { + "size": 31725 + }, + "pypsa/plot/statistics/schema.py": { + "size": 9371 + }, + "pypsa/statistics/__init__.py": { + "size": 510 + }, + "pypsa/statistics/abstract.py": { + "size": 10351 + }, + "pypsa/statistics/expressions.py": { + "size": 113146 + }, + "pypsa/statistics/grouping.py": { + "size": 13732 + }, + "pypsa/type_utils.py": { + "size": 1135 + }, + "pypsa/version.py": { + "size": 1410 + }, + "test/conftest.py": { + "size": 8906 + }, + "test/data/ac-dc-meshed/results-lopf/crs.json": { + "size": 921 + }, + "test/data/ac-dc-meshed/results-lopf/meta.json": { + "size": 3 + }, + "test/data/benchmark-sp/crs.json": { + "size": 921 + }, + "test/data/benchmark-sp/meta.json": { + "size": 3 + }, + "test/test_bugs.py": { + "size": 7722 + }, + "test/test_collection.py": { + "size": 14965 + }, + "test/test_collection_statistics.py": { + "size": 18943 + }, + "test/test_common.py": { + "size": 14154 + }, + "test/test_common_deprecations.py": { + "size": 6725 + }, + "test/test_components.py": { + "size": 2717 + }, + "test/test_components_array.py": { + "size": 9287 + }, + "test/test_components_custom.py": { + "size": 1695 + }, + "test/test_components_store.py": { + "size": 398 + }, + "test/test_components_utils.py": { + "size": 460 + }, + "test/test_consistency_check.py": { + "size": 16082 + }, + "test/test_constants.py": { + "size": 5967 + }, + "test/test_deprecations.py": { + "size": 2726 + }, + "test/test_descriptors.py": { + "size": 5230 + }, + "test/test_docs.py": { + "size": 7727 + }, + "test/test_examples.py": { + "size": 1442 + }, + "test/test_graph.py": { + "size": 6678 + }, + "test/test_guards.py": { + "size": 835 + }, + "test/test_indices.py": { + "size": 2754 + }, + "test/test_io.py": { + "size": 24161 + }, + "test/test_io_cloudpathlib.py": { + "size": 3414 + }, + "test/test_link_post_discretization.py": { + "size": 4230 + }, + "test/test_lopf_ac_dc.py": { + "size": 820 + }, + "test/test_lopf_activity_mask.py": { + "size": 14953 + }, + "test/test_lopf_and_subsequent_power_flow.py": { + "size": 313 + }, + "test/test_lopf_basic_constraints.py": { + "size": 11940 + }, + "test/test_lopf_global_constraints.py": { + "size": 4076 + }, + "test/test_lopf_iteratively.py": { + "size": 1226 + }, + "test/test_lopf_losses.py": { + "size": 770 + }, + "test/test_lopf_mga.py": { + "size": 6265 + }, + "test/test_lopf_modularity.py": { + "size": 1157 + }, + "test/test_lopf_multiinvest.py": { + "size": 35983 + }, + "test/test_lopf_multilink.py": { + "size": 3960 + }, + "test/test_lopf_quadratic_costs.py": { + "size": 687 + }, + "test/test_lopf_rolling_horizon.py": { + "size": 2715 + }, + "test/test_lopf_storage.py": { + "size": 2821 + }, + "test/test_lopf_unit_commitment.py": { + "size": 16924 + }, + "test/test_lopf_varying_inputs.py": { + "size": 1628 + }, + "test/test_lpf_ac_dc.py": { + "size": 1556 + }, + "test/test_lpf_against_pypower.py": { + "size": 2670 + }, + "test/test_meshed_threshold.py": { + "size": 3880 + }, + "test/test_network.py": { + "size": 22314 + }, + "test/test_network_cycles.py": { + "size": 12678 + }, + "test/test_network_index.py": { + "size": 14289 + }, + "test/test_network_transform.py": { + "size": 8491 + }, + "test/test_optimization_expressions.py": { + "size": 5421 + }, + "test/test_optional_dependencies.py": { + "size": 2196 + }, + "test/test_options.py": { + "size": 9946 + }, + "test/test_pf_activity_mask.py": { + "size": 1039 + }, + "test/test_pf_against_pandapower.py": { + "size": 2358 + }, + "test/test_pf_against_pypower.py": { + "size": 3229 + }, + "test/test_pf_distributed_slack.py": { + "size": 2579 + }, + "test/test_plot_maps_common.py": { + "size": 20887 + }, + "test/test_plot_maps_interactive.py": { + "size": 5380 + }, + "test/test_plot_maps_static.py": { + "size": 9255 + }, + "test/test_riskaversion.py": { + "size": 11021 + }, + "test/test_sclopf_scigrid.py": { + "size": 3089 + }, + "test/test_snapshot_weightings.py": { + "size": 1375 + }, + "test/test_spatial_clustering.py": { + "size": 7552 + }, + "test/test_stand_by_cost.py": { + "size": 1397 + }, + "test/test_statistics.py": { + "size": 9835 + }, + "test/test_statistics_plot.py": { + "size": 8051 + }, + "test/test_statistics_plot_interactive.py": { + "size": 10662 + }, + "test/test_statistics_plot_schema.py": { + "size": 1152 + }, + "test/test_stochastic.py": { + "size": 56214 + }, + "test/test_stochastic_not_implemented.py": { + "size": 1835 + }, + "test/test_subnetwork.py": { + "size": 2908 + }, + "test/test_version.py": { + "size": 1433 + }, + "test/test_warnings_cyclic_storage.py": { + "size": 7694 + } + }, + "processed_by": "zip_fallback", + "success": true + }, + "structure": { + "packages": [ + "source.pypsa", + "source.pypsa.clustering", + "source.pypsa.components", + "source.pypsa.network", + "source.pypsa.optimization", + "source.pypsa.plot", + "source.pypsa.statistics" + ] + }, + "dependencies": { + "has_environment_yml": false, + "has_requirements_txt": false, + "pyproject": true, + "setup_cfg": false, + "setup_py": false + }, + "entry_points": { + "imports": [], + "cli": [], + "modules": [] + }, + "llm_analysis": { + "core_modules": [ + { + "package": "source.pypsa", + "module": "__init__", + "functions": [], + "classes": [], + "description": "Initialization module for the PyPSA package." + }, + { + "package": "source.pypsa.clustering", + "module": "spatial", + "functions": [ + "cluster_spatial" + ], + "classes": [], + "description": "Functions for spatial clustering of network components." + }, + { + "package": "source.pypsa.components", + "module": "components", + "functions": [ + "add_component", + "remove_component" + ], + "classes": [ + "Component" + ], + "description": "Handles the addition and removal of components in the network." + }, + { + "package": "source.pypsa.network", + "module": "io", + "functions": [ + "import_network", + "export_network" + ], + "classes": [], + "description": "Functions for importing and exporting network data." + }, + { + "package": "source.pypsa.optimization", + "module": "optimize", + "functions": [ + "optimize_network" + ], + "classes": [], + "description": "Optimization routines for network operation and planning." + }, + { + "package": "source.pypsa.plot", + "module": "maps", + "functions": [ + "plot_static_map", + "plot_interactive_map" + ], + "classes": [], + "description": "Plotting functions for static and interactive maps." + }, + { + "package": "source.pypsa.statistics", + "module": "expressions", + "functions": [ + "calculate_statistics" + ], + "classes": [], + "description": "Functions for calculating various network statistics." + } + ], + "cli_commands": [], + "import_strategy": { + "primary": "import", + "fallback": "blackbox", + "confidence": 0.85 + }, + "dependencies": { + "required": [ + "numpy", + "pandas", + "matplotlib" + ], + "optional": [ + "geopandas", + "plotly" + ] + }, + "risk_assessment": { + "import_feasibility": 0.8, + "intrusiveness_risk": "medium", + "complexity": "medium" + } + }, + "deepwiki_analysis": { + "repo_url": "https://github.com/PyPSA/PyPSA", + "repo_name": "PyPSA", + "content": null, + "model": "gpt-4o", + "source": "selenium", + "success": true + }, + "deepwiki_options": { + "enabled": true, + "model": "gpt-4o" + }, + "risk": { + "import_feasibility": 0.8, + "intrusiveness_risk": "medium", + "complexity": "medium" + } +} \ No newline at end of file diff --git a/PyPSA/mcp_output/env_info.json b/PyPSA/mcp_output/env_info.json new file mode 100644 index 0000000000000000000000000000000000000000..c6cd9fe3150773d373e34c84e314ec20bec26880 --- /dev/null +++ b/PyPSA/mcp_output/env_info.json @@ -0,0 +1,15 @@ +{ + "environment": { + "type": "conda", + "name": "PyPSA_558543_env", + "files": {}, + "python": "3.10", + "exec_prefix": [] + }, + "original_tests": { + "passed": false, + "report_path": null + }, + "timestamp": 1763558620.0246599, + "conda_available": true +} \ No newline at end of file diff --git a/PyPSA/mcp_output/mcp_logs/llm_statistics.json b/PyPSA/mcp_output/mcp_logs/llm_statistics.json new file mode 100644 index 0000000000000000000000000000000000000000..5e352e0e03b0ad99fd22fe3ab87c2e5f4a299489 --- /dev/null +++ b/PyPSA/mcp_output/mcp_logs/llm_statistics.json @@ -0,0 +1,11 @@ +{ + "total_calls": 5, + "failed_calls": 0, + "retry_count": 0, + "total_prompt_tokens": 27816, + "total_completion_tokens": 4358, + "total_tokens": 32174, + "average_prompt_tokens": 5563.2, + "average_completion_tokens": 871.6, + "average_tokens": 6434.8 +} \ No newline at end of file diff --git a/PyPSA/mcp_output/mcp_logs/run_log.json b/PyPSA/mcp_output/mcp_logs/run_log.json new file mode 100644 index 0000000000000000000000000000000000000000..e1a4620035081281035020186753f06c21d02dec --- /dev/null +++ b/PyPSA/mcp_output/mcp_logs/run_log.json @@ -0,0 +1,66 @@ +{ + "timestamp": 1763558886.5027742, + "node": "RunNode", + "test_result": { + "passed": false, + "report_path": null, + "stdout": "", + "stderr": "onda.cli.main_run:execute(41): `conda run python mcp_output/start_mcp.py` failed. (See above for error)\nTraceback (most recent call last):\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/start_mcp.py\", line 17, in \n from mcp_service import create_app\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/mcp_plugin/mcp_service.py\", line 8, in \n from pypsa import Network, SubNetwork, clustering, common, components, descriptors, examples, geo, optimization, plot, statistics\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/source/pypsa/__init__.py\", line 21, in \n from pypsa import (\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/source/pypsa/clustering/__init__.py\", line 10, in \n import pandas as pd\nModuleNotFoundError: No module named 'pandas'\n\n" + }, + "run_result": { + "success": false, + "test_passed": false, + "exit_code": 1, + "stdout": "", + "stderr": "onda.cli.main_run:execute(41): `conda run python mcp_output/start_mcp.py` failed. (See above for error)\nTraceback (most recent call last):\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/start_mcp.py\", line 17, in \n from mcp_service import create_app\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/mcp_plugin/mcp_service.py\", line 8, in \n from pypsa import Network, SubNetwork, clustering, common, components, descriptors, examples, geo, optimization, plot, statistics\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/source/pypsa/__init__.py\", line 21, in \n from pypsa import (\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/source/pypsa/clustering/__init__.py\", line 10, in \n import pandas as pd\nModuleNotFoundError: No module named 'pandas'\n\n", + "timestamp": 1763558886.5027297, + "error_type": "ImportError", + "error": "Module import failed: ERROR conda.cli.main_run:execute(41): `conda run python mcp_output/start_mcp.py` failed. (See above for error)\nTraceback (most recent call last):\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/start_mcp.py\", line 17, in \n from mcp_service import create_app\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/mcp_plugin/mcp_service.py\", line 8, in \n from pypsa import Network, SubNetwork, clustering, common, components, descriptors, examples, geo, optimization, plot, statistics\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/source/pypsa/__init__.py\", line 21, in \n from pypsa import (\n File \"/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/source/pypsa/clustering/__init__.py\", line 10, in \n import pandas as pd\nModuleNotFoundError: No module named 'pandas'\n\n", + "details": { + "command": "/home/wshiah/code/miniconda3/bin/conda run -n PyPSA_558543_env --cwd /export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA python mcp_output/start_mcp.py", + "working_directory": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA", + "environment_type": "conda" + } + }, + "environment": { + "type": "conda", + "name": "PyPSA_558543_env", + "files": {}, + "python": "3.10", + "exec_prefix": [] + }, + "plugin_info": { + "files": { + "mcp_output/start_mcp.py": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/start_mcp.py", + "mcp_output/mcp_plugin/__init__.py": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/mcp_plugin/__init__.py", + "mcp_output/mcp_plugin/mcp_service.py": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/mcp_plugin/mcp_service.py", + "mcp_output/mcp_plugin/adapter.py": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/mcp_plugin/adapter.py", + "mcp_output/mcp_plugin/main.py": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/mcp_plugin/main.py", + "mcp_output/requirements.txt": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/requirements.txt", + "mcp_output/README_MCP.md": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/README_MCP.md", + "mcp_output/tests_mcp/test_mcp_basic.py": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/tests_mcp/test_mcp_basic.py" + }, + "adapter_mode": "import", + "endpoints": [ + "cluster_spatial", + "add_component", + "remove_component", + "component", + "import_network", + "export_network", + "optimize_network", + "plot_static_map", + "plot_interactive_map", + "calculate_statistics" + ], + "mcp_dir": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/mcp_plugin", + "tests_dir": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/tests_mcp", + "main_entry": "start_mcp.py", + "readme_path": "/export/project/shiweijie/ghh/LLM_MCP_RAG/MCP-agent-github-repo-output/workspace/PyPSA/mcp_output/README_MCP.md", + "requirements": [ + "fastmcp>=0.1.0", + "pydantic>=2.0.0" + ] + }, + "fastmcp_installed": false +} \ No newline at end of file diff --git a/PyPSA/mcp_output/mcp_plugin/__init__.py b/PyPSA/mcp_output/mcp_plugin/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/PyPSA/mcp_output/mcp_plugin/__pycache__/adapter.cpython-310.pyc b/PyPSA/mcp_output/mcp_plugin/__pycache__/adapter.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..49123eef3fc6cbc34798ad36dab8e3b2272e2291 Binary files /dev/null and b/PyPSA/mcp_output/mcp_plugin/__pycache__/adapter.cpython-310.pyc differ diff --git a/PyPSA/mcp_output/mcp_plugin/__pycache__/mcp_service.cpython-310.pyc b/PyPSA/mcp_output/mcp_plugin/__pycache__/mcp_service.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0801eae087280532942c3ccbd96077baa457ff8f Binary files /dev/null and b/PyPSA/mcp_output/mcp_plugin/__pycache__/mcp_service.cpython-310.pyc differ diff --git a/PyPSA/mcp_output/mcp_plugin/adapter.py b/PyPSA/mcp_output/mcp_plugin/adapter.py new file mode 100644 index 0000000000000000000000000000000000000000..c5e3bb936a8245550cad6c4ba0361e54cbba980f --- /dev/null +++ b/PyPSA/mcp_output/mcp_plugin/adapter.py @@ -0,0 +1,129 @@ +import os +import sys + +# Path settings +source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source") +sys.path.insert(0, source_path) + +# Import statements +try: + from pypsa import Network + from pypsa.clustering import spatial + from pypsa.optimization import optimize + from pypsa.plot import maps + from pypsa.statistics import expressions +except ImportError as e: + print(f"ImportError: {e}. Ensure the PyPSA package is correctly installed and accessible.") + # Fallback mode can be implemented here if necessary + +# Adapter class definition +class Adapter: + """ + Adapter class for the MCP plugin, utilizing the PyPSA library. + Provides methods to interact with various components and functionalities of PyPSA. + """ + + def __init__(self): + self.mode = "import" + + # Network Module + # ------------------------------------------------------------------------- + def create_network(self, *args, **kwargs): + """ + Create a new network instance. + + Parameters: + - args: Positional arguments for Network initialization. + - kwargs: Keyword arguments for Network initialization. + + Returns: + - dict: Status and network instance or error message. + """ + try: + network = Network(*args, **kwargs) + return {"status": "success", "network": network} + except Exception as e: + return {"status": "error", "message": f"Failed to create network: {e}"} + + # Clustering Module + # ------------------------------------------------------------------------- + def perform_spatial_clustering(self, network, *args, **kwargs): + """ + Perform spatial clustering on the given network. + + Parameters: + - network: The network instance to cluster. + - args: Additional positional arguments for clustering. + - kwargs: Additional keyword arguments for clustering. + + Returns: + - dict: Status and clustering result or error message. + """ + try: + result = spatial(network, *args, **kwargs) + return {"status": "success", "result": result} + except Exception as e: + return {"status": "error", "message": f"Failed to perform spatial clustering: {e}"} + + # Optimization Module + # ------------------------------------------------------------------------- + def optimize_network(self, network, *args, **kwargs): + """ + Optimize the given network. + + Parameters: + - network: The network instance to optimize. + - args: Additional positional arguments for optimization. + - kwargs: Additional keyword arguments for optimization. + + Returns: + - dict: Status and optimization result or error message. + """ + try: + result = optimize(network, *args, **kwargs) + return {"status": "success", "result": result} + except Exception as e: + return {"status": "error", "message": f"Failed to optimize network: {e}"} + + # Plotting Module + # ------------------------------------------------------------------------- + def plot_network_map(self, network, *args, **kwargs): + """ + Plot a map of the given network. + + Parameters: + - network: The network instance to plot. + - args: Additional positional arguments for plotting. + - kwargs: Additional keyword arguments for plotting. + + Returns: + - dict: Status and plot result or error message. + """ + try: + result = maps(network, *args, **kwargs) + return {"status": "success", "result": result} + except Exception as e: + return {"status": "error", "message": f"Failed to plot network map: {e}"} + + # Statistics Module + # ------------------------------------------------------------------------- + def calculate_expressions(self, network, *args, **kwargs): + """ + Calculate expressions for the given network. + + Parameters: + - network: The network instance for which to calculate expressions. + - args: Additional positional arguments for calculation. + - kwargs: Additional keyword arguments for calculation. + + Returns: + - dict: Status and calculation result or error message. + """ + try: + result = expressions(network, *args, **kwargs) + return {"status": "success", "result": result} + except Exception as e: + return {"status": "error", "message": f"Failed to calculate expressions: {e}"} + +# End of Adapter class definition +# ------------------------------------------------------------------------- \ No newline at end of file diff --git a/PyPSA/mcp_output/mcp_plugin/main.py b/PyPSA/mcp_output/mcp_plugin/main.py new file mode 100644 index 0000000000000000000000000000000000000000..fca6ec384e22f703b287550e94cc00baaaa4c4a7 --- /dev/null +++ b/PyPSA/mcp_output/mcp_plugin/main.py @@ -0,0 +1,13 @@ +""" +MCP Service Auto-Wrapper - Auto-generated +""" +from mcp_service import create_app + +def main(): + """Main entry point""" + app = create_app() + return app + +if __name__ == "__main__": + app = main() + app.run() \ No newline at end of file diff --git a/PyPSA/mcp_output/mcp_plugin/mcp_service.py b/PyPSA/mcp_output/mcp_plugin/mcp_service.py new file mode 100644 index 0000000000000000000000000000000000000000..0c5a10ec2510c82670617195b3c62774b1895fe2 --- /dev/null +++ b/PyPSA/mcp_output/mcp_plugin/mcp_service.py @@ -0,0 +1,524 @@ +import os +import sys +import json +from pathlib import Path + +source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source") +sys.path.insert(0, source_path) + +from fastmcp import FastMCP +from pypsa import Network +from pypsa.statistics import StatisticsAccessor, get_operation, get_transmission_branches +import logging + +# Set up logging +logging.basicConfig(level=logging.WARNING) +logger = logging.getLogger(__name__) + +mcp = FastMCP("pypsa_service") + +# ============================================================================ +# NETWORK I/O TOOLS +# ============================================================================ + +@mcp.tool( + name="load_network", + description="Load a PyPSA network from a file (netCDF, HDF5, CSV folder, or URL)" +) +def tool_load_network(file_path: str) -> dict: + """ + Load a PyPSA network from a specified file path. + + Supported formats: + - .nc (NetCDF) + - .h5 (HDF5) + - folder containing CSV files + - URL to remote network file + + Parameters: + - file_path (str): Path to the network file or folder + + Returns: + - dict: Network metadata and basic info + """ + try: + network = Network(file_path) + + # Extract network metadata + result = { + "success": True, + "result": { + "network_name": network.name, + "pypsa_version": network._pypsa_version, + "snapshots_count": len(network.snapshots), + "buses_count": len(network.buses), + "generators_count": len(network.generators), + "loads_count": len(network.loads), + "storage_units_count": len(network.storage_units), + "stores_count": len(network.stores), + "lines_count": len(network.lines), + "links_count": len(network.links), + "transformers_count": len(network.transformers), + "sub_networks_count": len(network.sub_networks), + "objective_value": float(network.objective) if hasattr(network, 'objective') else None, + "carriers": list(network.carriers.index) if len(network.carriers) > 0 else [], + "crs": str(network.crs), + "file_path": str(file_path), + "description": f"Network with {len(network.buses)} buses, {len(network.generators)} generators, {len(network.loads)} loads" + }, + "error": None + } + return result + except Exception as e: + return {"success": False, "result": None, "error": f"Failed to load network: {str(e)}"} + +@mcp.tool( + name="save_network", + description="Save a PyPSA network to a file" +) +def tool_save_network(network_path: str, output_path: str, format: str = "netcdf") -> dict: + """ + Save a PyPSA network to a file in specified format. + + Supported formats: + - netcdf (.nc) + - hdf5 (.h5) + - csv (folder) + + Parameters: + - network_path (str): Path to load the network from + - output_path (str): Path to save the network to + - format (str): Output format (netcdf, hdf5, or csv) + + Returns: + - dict: Save status and result information + """ + try: + network = Network(network_path) + + if format.lower() in ["netcdf", "nc"]: + network.export_to_netcdf(output_path) + format_used = "NetCDF (.nc)" + elif format.lower() in ["hdf5", "h5"]: + network.export_to_hdf5(output_path) + format_used = "HDF5 (.h5)" + elif format.lower() == "csv": + network.export_to_csv_folder(output_path) + format_used = "CSV (folder)" + else: + raise ValueError(f"Unsupported format: {format}") + + return { + "success": True, + "result": { + "output_path": str(output_path), + "format": format_used, + "description": f"Network saved successfully as {format_used}" + }, + "error": None + } + except Exception as e: + return {"success": False, "result": None, "error": f"Failed to save network: {str(e)}"} + +# ============================================================================ +# NETWORK ANALYSIS TOOLS +# ============================================================================ + +@mcp.tool( + name="get_network_statistics", + description="Calculate detailed statistics for the network" +) +def tool_get_network_statistics(file_path: str) -> dict: + """ + Calculate comprehensive statistics for the network. + + Includes: + - Component counts and attributes + - Capacity information + - Operating data + - Energy flows + - Network topology stats + + Parameters: + - file_path (str): Path to the network file + + Returns: + - dict: Detailed network statistics + """ + try: + network = Network(file_path) + + # Calculate basic statistics + stats = { + "success": True, + "result": { + "network_overview": { + "name": network.name, + "snapshots_count": len(network.snapshots), + "time_period": f"{network.snapshots[0]} to {network.snapshots[-1]}" if len(network.snapshots) > 0 else "N/A" + }, + "components": { + "buses": len(network.buses), + "generators": len(network.generators), + "storage_units": len(network.storage_units), + "stores": len(network.stores), + "loads": len(network.loads), + "lines": len(network.lines), + "links": len(network.links), + "transformers": len(network.transformers), + "sub_networks": len(network.sub_networks) + }, + "capacity_stats": { + "total_generator_capacity_mw": round(network.generators.p_nom.sum(), 2) if len(network.generators) > 0 else 0, + "total_storage_capacity_mwh": round(network.storage_units.e_nom.sum(), 2) if len(network.storage_units) > 0 else 0, + "total_store_capacity_mwh": round(network.stores.e_nom.sum(), 2) if len(network.stores) > 0 else 0, + "total_load_mw": round(network.loads.p_set.sum(), 2) if len(network.loads) > 0 else 0 + }, + "carrier_info": { + "unique_carriers": list(network.carriers.index) if len(network.carriers) > 0 else [], + "carrier_count": len(network.carriers) + }, + "transmission_info": { + "total_line_capacity_mva": round(network.lines.s_nom.sum(), 2) if len(network.lines) > 0 else 0, + "total_link_count": len(network.links), + "total_line_count": len(network.lines) + }, + "objective_value": float(network.objective) if hasattr(network, 'objective') else None + }, + "error": None + } + return stats + except Exception as e: + return {"success": False, "result": None, "error": f"Failed to get statistics: {str(e)}"} + +@mcp.tool( + name="optimize_network", + description="Run network optimization with linear optimal power flow (LOPF)" +) +def tool_optimize_network( + file_path: str, + snapshots: str = "all", + linearized_unit_commitment: bool = False, + multi_invest: bool = False, + solver: str = "glpk" +) -> dict: + """ + Run linear optimal power flow (LOPF) optimization on the network. + + Parameters: + - file_path (str): Path to the network file + - snapshots (str): "all" or comma-separated snapshot indices + - linearized_unit_commitment (bool): Enable linearized unit commitment + - multi_invest (bool): Enable multi-period investment optimization + - solver (str): Solver to use (glpk, cbc, cplex, gurobi, scip, etc.) + + Returns: + - dict: Optimization results with objective value and status + """ + try: + network = Network(file_path) + + # Set optimization options + if linearized_unit_commitment: + network._linearized_uc = 1 + if multi_invest: + network._multi_invest = 1 + + # Run optimization + status, termination_condition = network.optimize( + solver_name=solver, + multi_investment_periods=multi_invest + ) + + result = { + "success": True, + "result": { + "status": status, + "termination_condition": termination_condition, + "objective_value": round(float(network.objective), 2), + "optimization_info": { + "snapshots_optimized": len(network.snapshots), + "linearzed_unit_commitment": linearized_unit_commitment, + "multi_period_investment": multi_invest, + "solver_used": solver + }, + "description": f"Optimization completed with status: {status}, objective value: {network.objective:.2f}" + }, + "error": None + } + return result + except Exception as e: + return {"success": False, "result": None, "error": f"Optimization failed: {str(e)}"} + +# ============================================================================ +# NETWORK COMPONENTS ANALYSIS TOOLS +# ============================================================================ + +@mcp.tool( + name="get_buses_info", + description="Get information about all buses in the network" +) +def tool_get_buses_info(file_path: str) -> dict: + """ + Get detailed information about buses in the network. + + Returns: + - Bus counts by carrier + - Bus locations (if available) + - Control types + - Voltage levels + + Parameters: + - file_path (str): Path to the network file + + Returns: + - dict: Detailed bus information + """ + try: + network = Network(file_path) + + buses_info = { + "success": True, + "result": { + "total_buses": len(network.buses), + "buses_by_carrier": dict(network.buses.carrier.value_counts()) if len(network.buses) > 0 else {}, + "buses_with_generation": len(network.buses[network.buses.index.isin(network.generators.bus)]) if len(network.generators) > 0 else 0, + "buses_with_load": len(network.buses[network.buses.index.isin(network.loads.bus)]) if len(network.loads) > 0 else 0, + "buses_with_storage": len(network.buses[network.buses.index.isin(network.storage_units.bus)]) if len(network.storage_units) > 0 else 0, + "buses_list": list(network.buses.index[:20]), # First 20 buses + "description": f"Network contains {len(network.buses)} buses" + }, + "error": None + } + return buses_info + except Exception as e: + return {"success": False, "result": None, "error": f"Failed to get buses info: {str(e)}"} + +@mcp.tool( + name="get_generators_info", + description="Get information about all generators in the network" +) +def tool_get_generators_info(file_path: str) -> dict: + """ + Get detailed information about generators. + + Returns: + - Generator counts by carrier + - Total installed capacity + - Generator efficiency + - Marginal costs + + Parameters: + - file_path (str): Path to the network file + + Returns: + - dict: Detailed generator information + """ + try: + network = Network(file_path) + + if len(network.generators) == 0: + return { + "success": True, + "result": { + "total_generators": 0, + "description": "No generators in network" + }, + "error": None + } + + generators_info = { + "success": True, + "result": { + "total_generators": len(network.generators), + "generators_by_carrier": dict(network.generators.carrier.value_counts()), + "total_capacity_mw": round(network.generators.p_nom.sum(), 2), + "average_efficiency": round(network.generators.efficiency.mean(), 4), + "marginal_cost_range": { + "min": round(network.generators.marginal_cost.min(), 2), + "max": round(network.generators.marginal_cost.max(), 2), + "mean": round(network.generators.marginal_cost.mean(), 2) + }, + "generators_by_bus": dict(network.generators.bus.value_counts().head(10)), + "description": f"Network contains {len(network.generators)} generators with total capacity {network.generators.p_nom.sum():.2f} MW" + }, + "error": None + } + return generators_info + except Exception as e: + return {"success": False, "result": None, "error": f"Failed to get generators info: {str(e)}"} + +@mcp.tool( + name="get_transmission_info", + description="Get information about transmission branches (lines and links)" +) +def tool_get_transmission_info(file_path: str) -> dict: + """ + Get detailed information about transmission branches. + + Returns: + - Line and link counts + - Total transmission capacity + - Voltage levels + - Transmission losses + + Parameters: + - file_path (str): Path to the network file + + Returns: + - dict: Detailed transmission information + """ + try: + network = Network(file_path) + + transmission_info = { + "success": True, + "result": { + "transmission_components": { + "total_lines": len(network.lines), + "total_links": len(network.links), + "total_transformers": len(network.transformers) + }, + "transmission_capacity": { + "total_line_capacity_mva": round(network.lines.s_nom.sum(), 2) if len(network.lines) > 0 else 0, + "total_link_capacity_mw": round(network.links.p_nom.sum(), 2) if len(network.links) > 0 else 0, + "average_line_capacity_mva": round(network.lines.s_nom.mean(), 2) if len(network.lines) > 0 else 0 + }, + "transmission_losses": { + "ac_line_losses": round(network.lines.x.sum(), 4) if len(network.lines) > 0 else 0, + "transformer_losses": round(network.transformers.x.sum(), 4) if len(network.transformers) > 0 else 0 + }, + "description": f"Network has {len(network.lines)} transmission lines and {len(network.links)} links" + }, + "error": None + } + return transmission_info + except Exception as e: + return {"success": False, "result": None, "error": f"Failed to get transmission info: {str(e)}"} + +# ============================================================================ +# NETWORK CLUSTERING/AGGREGATION TOOLS +# ============================================================================ + +@mcp.tool( + name="cluster_network_by_kmeans", + description="Cluster network buses using k-means algorithm" +) +def tool_cluster_network_by_kmeans(file_path: str, clusters_count: int = 5) -> dict: + """ + Cluster network buses using k-means algorithm for network reduction. + + Parameters: + - file_path (str): Path to the network file + - clusters_count (int): Number of clusters to create + + Returns: + - dict: Clustering results and mapping + """ + try: + network = Network(file_path) + + # Use PyPSA's clustering accessor + busmap = network.clustering.busmap_by_kmeans(clusters_count) + + return { + "success": True, + "result": { + "original_buses_count": len(network.buses), + "target_clusters": clusters_count, + "created_clusters": len(busmap.unique()), + "clustering_method": "k-means", + "buses_per_cluster_avg": round(len(network.buses) / clusters_count, 2), + "description": f"Network reduced from {len(network.buses)} buses to {len(busmap.unique())} clusters using k-means" + }, + "error": None + } + except Exception as e: + return {"success": False, "result": None, "error": f"Clustering failed: {str(e)}"} + +# ============================================================================ +# NETWORK EXPORT TOOLS +# ============================================================================ + +@mcp.tool( + name="export_network_summary", + description="Export a detailed summary of the network to JSON" +) +def tool_export_network_summary(file_path: str, output_file: str = None) -> dict: + """ + Export a comprehensive network summary to JSON format. + + Parameters: + - file_path (str): Path to the network file + - output_file (str): Optional path to save JSON summary (if None, returns inline) + + Returns: + - dict: Network summary data + """ + try: + network = Network(file_path) + + summary = { + "network_name": network.name, + "pypsa_version": network._pypsa_version, + "statistics": { + "snapshots": len(network.snapshots), + "buses": len(network.buses), + "generators": len(network.generators), + "loads": len(network.loads), + "storage_units": len(network.storage_units), + "stores": len(network.stores), + "lines": len(network.lines), + "links": len(network.links), + "transformers": len(network.transformers) + }, + "capacity": { + "generator_capacity_mw": round(network.generators.p_nom.sum(), 2) if len(network.generators) > 0 else 0, + "storage_capacity_mwh": round(network.storage_units.e_nom.sum(), 2) if len(network.storage_units) > 0 else 0, + "transmission_capacity_mva": round(network.lines.s_nom.sum(), 2) if len(network.lines) > 0 else 0 + }, + "carriers": list(network.carriers.index) if len(network.carriers) > 0 else [], + "objective": float(network.objective) if hasattr(network, 'objective') else None + } + + if output_file: + with open(output_file, 'w') as f: + json.dump(summary, f, indent=2) + return { + "success": True, + "result": { + "summary_saved": True, + "output_file": str(output_file), + "description": f"Network summary exported to {output_file}" + }, + "error": None + } + else: + return { + "success": True, + "result": summary, + "error": None + } + except Exception as e: + return {"success": False, "result": None, "error": f"Export failed: {str(e)}"} + +# ============================================================================ +# APP CREATION +# ============================================================================ + +def create_app() -> FastMCP: + """ + Create and return the FastMCP application instance. + + Features: + - Network loading and saving (NetCDF, HDF5, CSV) + - Optimization with LOPF + - Network statistics and analysis + - Component information extraction + - Network clustering and reduction + - Comprehensive export capabilities + + Returns: + - FastMCP: The configured FastMCP instance with 10+ PyPSA tools. + """ + return mcp \ No newline at end of file diff --git a/PyPSA/mcp_output/requirements.txt b/PyPSA/mcp_output/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..069730c5c2176da8d4ba723f65369fe913465487 --- /dev/null +++ b/PyPSA/mcp_output/requirements.txt @@ -0,0 +1,9 @@ +fastmcp>=0.1.0 +pydantic>=2.0.0 +numpy +pandas +matplotlib + +# Optional Dependencies +# geopandas +# plotly diff --git a/PyPSA/mcp_output/simple_revise_error_analysis.json b/PyPSA/mcp_output/simple_revise_error_analysis.json new file mode 100644 index 0000000000000000000000000000000000000000..7e8249f9336f59678a6aad7f91677218d936edbc --- /dev/null +++ b/PyPSA/mcp_output/simple_revise_error_analysis.json @@ -0,0 +1,6 @@ +{ + "status": "FAIL", + "next_action": "fix_directly", + "confidence": 0.9, + "summary": "The error is due to a missing Python module 'pandas', which is required by the 'pypsa.clustering' module. This can be fixed directly by installing the 'pandas' package in the conda environment being used. Run 'conda install pandas' to resolve the issue. Additionally, ensure that the conda environment is activated correctly before running the script." +} \ No newline at end of file diff --git a/PyPSA/mcp_output/start_mcp.py b/PyPSA/mcp_output/start_mcp.py new file mode 100644 index 0000000000000000000000000000000000000000..004c319e0db21f5b93b1f20c0b3e019285e27262 --- /dev/null +++ b/PyPSA/mcp_output/start_mcp.py @@ -0,0 +1,33 @@ +""" +MCP Service Startup Entry +""" +import sys +import os + +project_root = os.path.dirname(os.path.abspath(__file__)) +mcp_plugin_dir = os.path.join(project_root, "mcp_plugin") +if mcp_plugin_dir not in sys.path: + sys.path.insert(0, mcp_plugin_dir) + +# Set path to source directory +source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source") +sys.path.insert(0, source_path) + +from mcp_plugin.mcp_service import create_app + +def main(): + """Start FastMCP service""" + app = create_app() + # Use environment variable to configure port, default 8000 + port = int(os.environ.get("MCP_PORT", "8000")) + + # Choose transport mode based on environment variable + transport = os.environ.get("MCP_TRANSPORT", "stdio") + if transport == "http": + app.run(transport="http", host="0.0.0.0", port=port) + else: + # Default to STDIO mode + app.run() + +if __name__ == "__main__": + main() diff --git a/PyPSA/mcp_output/tests_mcp/test_mcp_basic.py b/PyPSA/mcp_output/tests_mcp/test_mcp_basic.py new file mode 100644 index 0000000000000000000000000000000000000000..cfa9b36554276548850db7754ed047b24a344402 --- /dev/null +++ b/PyPSA/mcp_output/tests_mcp/test_mcp_basic.py @@ -0,0 +1,49 @@ +""" +MCP Service Basic Test +""" +import sys +import os + +project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +mcp_plugin_dir = os.path.join(project_root, "mcp_plugin") +if mcp_plugin_dir not in sys.path: + sys.path.insert(0, mcp_plugin_dir) + +source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source") +sys.path.insert(0, source_path) + +def test_import_mcp_service(): + """Test if MCP service can be imported normally""" + try: + from mcp_service import create_app + app = create_app() + assert app is not None + print("MCP service imported successfully") + return True + except Exception as e: + print("MCP service import failed: " + str(e)) + return False + +def test_adapter_init(): + """Test if adapter can be initialized normally""" + try: + from adapter import Adapter + adapter = Adapter() + assert adapter is not None + print("Adapter initialized successfully") + return True + except Exception as e: + print("Adapter initialization failed: " + str(e)) + return False + +if __name__ == "__main__": + print("Running MCP service basic test...") + test1 = test_import_mcp_service() + test2 = test_adapter_init() + + if test1 and test2: + print("All basic tests passed") + sys.exit(0) + else: + print("Some tests failed") + sys.exit(1) diff --git a/PyPSA/mcp_output/tests_smoke/test_smoke.py b/PyPSA/mcp_output/tests_smoke/test_smoke.py new file mode 100644 index 0000000000000000000000000000000000000000..445d2d83b4eb5ca82a21247f7c65351e481e3611 --- /dev/null +++ b/PyPSA/mcp_output/tests_smoke/test_smoke.py @@ -0,0 +1,29 @@ +import importlib, sys +import os + +# Add current directory to Python path +sys.path.insert(0, os.getcwd()) + +source_dir = os.path.join(os.getcwd(), "source") +if os.path.exists(source_dir): + sys.path.insert(0, source_dir) + + +try: + importlib.import_module("pypsa") + print("OK - Successfully imported pypsa") +except ImportError as e: + print(f"Failed to import pypsa: {e}") + fallback_packages = [] + + fallback_packages = ['pypsa'] + + for pkg in fallback_packages: + try: + importlib.import_module(pkg) + print(f"OK - Successfully imported {pkg}") + break + except ImportError: + continue + else: + print("All import attempts failed") diff --git a/PyPSA/source/.codespell.ignore b/PyPSA/source/.codespell.ignore new file mode 100644 index 0000000000000000000000000000000000000000..4fd2feaa8d62f4e0ddb57a648aea732f5e8f2212 --- /dev/null +++ b/PyPSA/source/.codespell.ignore @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +ines +psace +datas +fom +esy diff --git a/PyPSA/source/.git-blame-ignore-revs b/PyPSA/source/.git-blame-ignore-revs new file mode 100644 index 0000000000000000000000000000000000000000..26604c7b62ca8a90f630b5613bdd0593ee1d1ae5 --- /dev/null +++ b/PyPSA/source/.git-blame-ignore-revs @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +# apply pre-commit setup to all files (including black) +57099450d1df78f9d3571f4515721aa04613d456 diff --git a/PyPSA/source/.github/ISSUE_TEMPLATE/bug_report.yaml b/PyPSA/source/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 0000000000000000000000000000000000000000..96827c027fe23ae6ca3fd888b9962db59ca4e75f --- /dev/null +++ b/PyPSA/source/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +name: Bug Report +description: Create a report if something doesn't work quite right. +labels: ["needs triage"] +type: "Bug" + +body: +- type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + Please do not post usage questions here. Ask them on the [PyPSA Discord server](https://discord.gg/AnuJBk23FU). + +- type: checkboxes + id: checks + attributes: + label: Version Checks (indicate both or one) + options: + - label: > + I have confirmed this bug exists on the lastest + [release](https://github.com/pypsa/pypsa/releases) of PyPSA. + - label: > + I have confirmed this bug exists on the current + [`master`](https://github.com/pypsa/pypsa/tree/master) branch of PyPSA. + +- type: textarea + id: problem + attributes: + label: Issue Description + description: > + Please provide a description of the issue. + validations: + required: true + +- type: textarea + id: example + validations: + required: true + attributes: + label: Reproducible Example + description: > + Please provide a minimal reproduciable example. See how to [craft minimal bug reports](https://matthewrocklin.com/minimal-bug-reports). + placeholder: > + import pypsa + + n = pypsa.Network() + + render: python + +- type: textarea + id: expected-behavior + validations: + required: true + attributes: + label: Expected Behavior + description: > + Please describe or show a code example of the expected behavior. + +- type: textarea + id: version + attributes: + label: Installed Versions + description: > + Please share information on your environment. Paste the output below. For conda `conda env export` and for pip `pip freeze`. + value: > +
+ + Replace this line. + +
diff --git a/PyPSA/source/.github/ISSUE_TEMPLATE/config.yml b/PyPSA/source/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..bad4a5cb24058690241ffd4e26a2ab9ea66f144b --- /dev/null +++ b/PyPSA/source/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +blank_issues_enabled: true +contact_links: +- name: PyPSA Discord server + url: https://discord.gg/AnuJBk23FU + about: Please ask and answer general usage questions here. diff --git a/PyPSA/source/.github/ISSUE_TEMPLATE/documentation_improvement.yaml b/PyPSA/source/.github/ISSUE_TEMPLATE/documentation_improvement.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e16525ed7069735cefcd090bbfe19eb7e00368e7 --- /dev/null +++ b/PyPSA/source/.github/ISSUE_TEMPLATE/documentation_improvement.yaml @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +name: Documentation Improvement +description: Report wrong or missing documentation +labels: ["needs triage"] +type: 'Docs' + +body: +- type: textarea + id: location + attributes: + label: Location + description: > + Please provide the location of the documentation or docstring, e.g. `pypsa.Network.import_from_netcdf` or the link to the docs. + placeholder: https://docs.pypsa.org/latest/user-guide/linear-power-flow + required: true +- type: textarea + id: problem + attributes: + label: Documentation problem + description: > + Please provide a description of what you believe needs to be fixed/ improved/ explained. + validations: + required: true diff --git a/PyPSA/source/.github/ISSUE_TEMPLATE/feature_request.md b/PyPSA/source/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000000000000000000000000000000..e4429682a932607b7ab6985a353dcb9cc6dcd469 --- /dev/null +++ b/PyPSA/source/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,16 @@ +--- +name: Feature Request +about: Suggest an idea for this project +title: '' +labels: ["needs triage"] +type: 'Feature' +assignees: '' + +--- + + + +## Describe the feature you'd like to see + +*Please give a clear and concise description and provide context why the feature would be useful.* +*Also, we'd appreciate any implementation ideas and references you already have.* diff --git a/PyPSA/source/.github/dependabot.yml b/PyPSA/source/.github/dependabot.yml new file mode 100644 index 0000000000000000000000000000000000000000..e4b33f8b6b9aec03856996a076b1509516f6e7ef --- /dev/null +++ b/PyPSA/source/.github/dependabot.yml @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +# dependabot +# Ref: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +# ------------------------------------------------------------------------------ +version: 2 +updates: +- package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + groups: + # open a single pull-request for all GitHub actions updates + github-actions: + patterns: + - '*' diff --git a/PyPSA/source/.github/pull_request_template.md b/PyPSA/source/.github/pull_request_template.md new file mode 100644 index 0000000000000000000000000000000000000000..2b726f36822684d161a0da78b96983f0973dbdbf --- /dev/null +++ b/PyPSA/source/.github/pull_request_template.md @@ -0,0 +1,11 @@ +Closes # (if applicable). + +## Changes proposed in this Pull Request + + +## Checklist + +- [ ] Code changes are sufficiently documented; i.e. new functions contain docstrings and further explanations may be given in `docs`. +- [ ] Unit tests for new features were added (if applicable). +- [ ] A note for the release notes `docs/release-notes.md` of the upcoming release is included. +- [ ] I consent to the release of this PR's code under the MIT license. diff --git a/PyPSA/source/.github/workflows/codeql.yml b/PyPSA/source/.github/workflows/codeql.yml new file mode 100644 index 0000000000000000000000000000000000000000..f5943aa177323b0d041e8926bced9c5d0515599f --- /dev/null +++ b/PyPSA/source/.github/workflows/codeql.yml @@ -0,0 +1,96 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: ["master"] + pull_request: + branches: ["master"] + schedule: + - cron: '36 6 * * 4' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{matrix.language}}" diff --git a/PyPSA/source/.github/workflows/release.yml b/PyPSA/source/.github/workflows/release.yml new file mode 100644 index 0000000000000000000000000000000000000000..d649ef7d2b4bd51f8cb9be26b36c2ef8362e0aa8 --- /dev/null +++ b/PyPSA/source/.github/workflows/release.yml @@ -0,0 +1,174 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +name: Release + +on: + push: + tags: + - v*.*.* + +env: + PREPARATION_COMMIT: '[github-actions.ci] prepare release ${{ github.ref_name }}' + +jobs: + check-preparation: + name: Check if release is prepared + runs-on: ubuntu-latest + outputs: + prepared: ${{ steps.validate.outputs.prepared }} + steps: + - uses: actions/checkout@v5 + + - name: Validate commit message + id: validate + run: | + # Check if last commit is the expected commit message + COMMIT_MESSAGE=$(git log -1 --pretty=%B) + echo "Expected: '${{ env.PREPARATION_COMMIT }}'" + echo "Received: '$COMMIT_MESSAGE'" + + prepared="false" + if [[ "$COMMIT_MESSAGE" == "${{ env.PREPARATION_COMMIT }}" ]]; then + prepared="true" + fi + + echo "prepared=$prepared" >> $GITHUB_OUTPUT + + prepare-release: + name: Prepare release + needs: [check-preparation] + if: ${{ needs.check-preparation.outputs.prepared == 'false' }} + runs-on: ubuntu-latest + steps: + - name: Generate token for PyPSA Bot + id: generate-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.PYPSA_BOT_ID }} + private-key: ${{ secrets.PYPSA_BOT_PRIVATE_KEY }} + + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Find the branch for commit/ tag + run: | + branch=$(git branch -r --contains ${{ github.sha }} | grep -v 'HEAD' | head -n 1 | sed 's|origin/||' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + echo "Branch found: $branch" + echo "BRANCH_NAME=$branch" >> $GITHUB_ENV + + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + ref: ${{ env.BRANCH_NAME }} + token: ${{ steps.generate-token.outputs.token }} + + # Start of preparation script + + - uses: actions/setup-python@v6 + with: + python-version: 3.12 + + - name: Install packages + run: | + python -m pip install uv + uv pip install --system toml + uv pip install --system setuptools_scm + uv pip install --system . + + - name: Update CITATION.cff + run: | + import re + from setuptools_scm import get_version + + version = get_version() + + with open('CITATION.cff', 'r') as file: + cff_content = file.read() + cff_content = open('CITATION.cff', 'r').read() + + updated_cff_content = re.sub(r"(?<=version: ).+(?= #)",version,cff_content,flags=re.MULTILINE) + + with open('CITATION.cff', 'w') as file: + file.write(updated_cff_content) + shell: python + + - name: Update example notebooks + run: | + import pypsa + # ac-dc-meshed + n = pypsa.Network("examples/networks/ac-dc-meshed/ac-dc-meshed") + n.export_to_csv_folder("examples/networks/ac-dc-meshed/ac-dc-meshed") + n.export_to_netcdf("examples/networks/ac-dc-meshed/ac-dc-meshed.nc") + # storage-hvdc + n = pypsa.Network("examples/networks/storage-hvdc/storage-hvdc") + n.export_to_csv_folder("examples/networks/storage-hvdc/storage-hvdc") + n.export_to_netcdf("examples/networks/storage-hvdc/storage-hvdc.nc") + # scigrid-de + n = pypsa.Network("examples/networks/scigrid-de/scigrid-de") + n.export_to_csv_folder("examples/networks/scigrid-de/scigrid-de") + n.export_to_netcdf("examples/networks/scigrid-de/scigrid-de.nc") + # model-energy + n = pypsa.Network("examples/networks/model-energy/model-energy") + n.export_to_csv_folder("examples/networks/model-energy/model-energy") + n.export_to_netcdf("examples/networks/model-energy/model-energy.nc") + # stochastic-network + n = pypsa.Network("examples/networks/stochastic-network/stochastic-network.nc") + n.export_to_netcdf("examples/networks/stochastic-network/stochastic-network.nc") + shell: python + + # End of preparation script + + - name: Remove previous tag + run: | + git tag -d ${{ github.ref_name }} + git push origin --delete ${{ github.ref_name }} + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v7 + with: + branch: ${{ env.BRANCH_NAME }} + commit_message: '${{ env.PREPARATION_COMMIT }}' + tagging_message: '${{ github.ref_name }}' # add tag again + push_options: '${{ github.ref_name }}' + add_options: '-u' # Never add untracked files + + build: + name: Build and verify package + needs: [check-preparation] + if: ${{ needs.check-preparation.outputs.prepared == 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: hynek/build-and-inspect-python-package@v2 + + release: + name: Create GitHub release + needs: [build] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: softprops/action-gh-release@v2 + with: + body: | + Revised release notes are available in the [documentation](https://${{ github.event.repository.name }}.readthedocs.io/en/latest/release-notes.html). + append_body: true + generate_release_notes: true + + publish: + name: Publish to PyPI + needs: [build] + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/project/${{ github.event.repository.name }} + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v6 + with: + name: Packages + path: dist + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/PyPSA/source/.github/workflows/test-models.yml b/PyPSA/source/.github/workflows/test-models.yml new file mode 100644 index 0000000000000000000000000000000000000000..f624fe6118c86039ab8f28acd33ce2ff68cd3c9e --- /dev/null +++ b/PyPSA/source/.github/workflows/test-models.yml @@ -0,0 +1,206 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +name: Test models + +on: + push: + branches: + - master + - release-branch-v* + pull_request: + branches: ['*'] + schedule: + - cron: "0 5 * * *" + +env: + PACKAGE_NAME: 'pypsa' + +# Cancel any in-progress runs when a new run is triggered +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test-pypsa-eur: + name: PyPSA-Eur + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + version: + - master + # - latest + + defaults: + run: + shell: bash -l {0} + + steps: + - uses: actions/checkout@v5 + with: + repository: PyPSA/pypsa-eur + ref: master + + - name: Check out latest release + if: matrix.version == 'latest' + run: | + git fetch --tags + latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`) + git checkout $latest_tag + + - name: Setup Pixi + uses: prefix-dev/setup-pixi@v0.9.3 + with: + pixi-version: v0.59.0 + cache: true + # Do not cache in branches + cache-write: ${{ github.event_name == 'push' && github.ref_name == 'main' }} + + - name: Setup cache keys + run: | + echo "WEEK=$(date +'%Y%U')" >> $GITHUB_ENV # data and cutouts + + # Only run check if package is not pinned + - name: Check if inhouse package is pinned + run: | + grep_line=$(grep -- '- pypsa' envs/environment.yaml) + if [[ $grep_line == *"<"* || $grep_line == *"=="* ]]; then + echo "pinned=true" >> $GITHUB_ENV + else + echo "pinned=false" >> $GITHUB_ENV + fi + + - uses: actions/cache@v4 + if: env.pinned == 'false' + with: + path: | + data + cutouts + key: data-cutouts-pypsa-eur-${{ env.WEEK }} + + - name: Install package from ref + if: env.pinned == 'false' + run: | + pixi remove ${{ env.PACKAGE_NAME }} + pixi add --pypi --git https://github.com/${{ github.repository }}.git ${{ github.event.repository.name }} --rev ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + + - name: Run snakemake test workflows + if: env.pinned == 'false' + run: | + pixi run integration-tests + + - name: Run unit tests + if: env.pinned == 'false' + run: | + pixi run unit-tests + + - name: Upload artifacts + if: env.pinned == 'false' + uses: actions/upload-artifact@v5 + with: + name: results-pypsa-eur-${{ matrix.version }} + path: | + logs + .snakemake/log + results + retention-days: 3 + +# Temporarily disabled until PyPSA-DE issues are resolved. This is not a problem of PyPSA. + # test-pypsa-de: + # name: PyPSA-DE + # runs-on: ubuntu-latest + # strategy: + # fail-fast: false + # matrix: + # version: + # - master + # # - latest + + # defaults: + # run: + # shell: bash -l {0} + + # steps: + # - uses: actions/checkout@v5 + # with: + # repository: PyPSA/pypsa-de + # ref: main + + # - name: Check out latest release + # if: matrix.version == 'latest' + # run: | + # git fetch --tags + # latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`) + # git checkout $latest_tag + + # - name: Setup cache keys + # run: | + # echo "WEEK=$(date +'%Y%U')" >> $GITHUB_ENV # data and cutouts + + # # Only run check if package is not pinned + # - name: Check if inhouse package is pinned + # run: | + # grep_line=$(grep -- '- pypsa' envs/environment.yaml) + # if [[ $grep_line == *"<"* || $grep_line == *"=="* ]]; then + # echo "pinned=true" >> $GITHUB_ENV + # else + # echo "pinned=false" >> $GITHUB_ENV + # fi + + # - uses: actions/cache@v4 + # if: env.pinned == 'false' + # with: + # path: | + # data + # cutouts + # resources/ariadne_database.csv + # key: data-cutouts-pypsa-de-${{ env.WEEK }} + + # - uses: conda-incubator/setup-miniconda@v3 + # if: env.pinned == 'false' + # with: + # miniforge-version: latest + # activate-environment: pypsa-de + # channel-priority: strict + + # - name: Cache Conda env + # if: env.pinned == 'false' + # uses: actions/cache@v4 + # with: + # path: ${{ env.CONDA }}/envs + # key: conda-pypsa-de-${{ env.WEEK }}-${{ hashFiles('envs/linux-64.lock.yaml') }} + # id: cache-env + + # - name: Update environment + # if: env.pinned == 'false' && steps.cache-env.outputs.cache-hit != 'true' + # run: | + # conda env update -n pypsa-de -f envs/linux-64.lock.yaml + # echo "Run conda list" && conda list + + # - name: Install package from ref + # if: env.pinned == 'false' + # run: | + # python -m pip install git+https://github.com/${{ github.repository }}@${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + + # - name: Run snakemake test workflows + # if: env.pinned == 'false' + # run: | + # make test + + # - name: Run unit tests + # if: env.pinned == 'false' + # run: | + # make unit-test + + # - name: Upload artifacts + # if: env.pinned == 'false' + # uses: actions/upload-artifact@v5 + # with: + # name: results-pypsa-de-${{ matrix.version }} + # path: | + # logs + # .snakemake/log + # results + # retention-days: 3 diff --git a/PyPSA/source/.github/workflows/test.yml b/PyPSA/source/.github/workflows/test.yml new file mode 100644 index 0000000000000000000000000000000000000000..02b31d7bb3b9fb3372b68028884df35113306b5f --- /dev/null +++ b/PyPSA/source/.github/workflows/test.yml @@ -0,0 +1,223 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +name: Tests + +on: + push: + branches: + - master + - release-branch-v* + pull_request: + branches: ["*"] + schedule: + - cron: "0 5 * * *" + +# Cancel any in-progress runs when a new run is triggered +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + # Build the Python SDist and wheel, performs metadata and readme linting + name: Build and verify package + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 # Needed for setuptools_scm + - uses: hynek/build-and-inspect-python-package@v2 + id: baipp + + outputs: + python-versions: ${{ steps.baipp.outputs.supported_python_classifiers_json_array }} + + test: + # Test package build in matrix of OS and Python versions + name: Test package + needs: [build] + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + python-version: ${{ fromJSON(needs.build.outputs.python-versions) }} + os: + - ubuntu-latest + - macos-latest + - windows-latest + api: [default, new_api] + exclude: + - os: macos-latest + api: new_api + - os: windows-latest + api: new_api + + env: + MPLBACKEND: Agg # https://github.com/orgs/community/discussions/26434 + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 # Needed for setuptools_scm + + - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Install macos dependencies + if: matrix.os == 'macos-latest' + run: | + brew update + # pkg-config is deprecated but still installed in runner + # Can be removed once it is removed from the runner + brew unlink pkg-config@0.29.2 || true + brew install hdf5 + + - name: Download package + uses: actions/download-artifact@v6 + with: + name: Packages + path: dist + + - name: Install package and dependencies + run: | + python -m pip install uv + uv sync --all-extras --no-install-project + uv pip install "$(ls dist/*.whl)" + + - name: Run unit tests (old API) + if: matrix.api == 'default' + run: > + uv run pytest --mpl --mpl-generate-summary=html --mpl-results-path=mpl-results + --cov=pypsa --junitxml=junit.xml -o junit_family=legacy + && uv run coverage xml + + - name: Run unit tests (new API) + if: matrix.api == 'new_api' + run: > + uv run pytest + --new-components-api + --mpl --mpl-generate-summary=html --mpl-results-path=mpl-results + --cov=pypsa --junitxml=junit.xml -o junit_family=legacy + && uv run coverage xml + + - name: Upload artifacts + if: matrix.api == 'default' && matrix.os == 'macos-latest' && matrix.python-version == '3.13' + uses: actions/upload-artifact@v5 + with: + name: results-plotting + path: | + mpl-results + retention-days: 14 + if-no-files-found: ignore + + - name: Upload code coverage report + if: ${{ !cancelled() }} + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + flags: > + unit-tests, + os-${{ matrix.os }}, + python${{ matrix.python-version }}, + api-${{ matrix.api }} + + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + flags: > + unit-tests, + os-${{ matrix.os }}, + python${{ matrix.python-version }}, + api-${{ matrix.api }} + + check-docs: + name: Check docs + needs: [build] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 # Needed for setuptools_scm + + - name: Set up Python 3.13 + uses: actions/setup-python@v6 + with: + python-version: 3.13 + + - name: Download package + uses: actions/download-artifact@v6 + with: + name: Packages + path: dist + + - name: Install package and dependencies + run: | + python -m pip install uv + uv sync --all-extras --no-install-project + uv pip install "$(ls dist/*.whl)" + + - name: Run doc tests + run: > + uv run pytest + test/test_docs.py --test-docs + --cov=pypsa --junitxml=junit.xml -o junit_family=legacy + && uv run coverage xml + + - name: Upload code coverage report + if: ${{ !cancelled() }} + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + flags: > + doc-tests, + os-ubuntu-latest, + python3.13, + api-old-api + + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + flags: > + doc-tests, + os-ubuntu-latest, + python3.13, + api-old-api + + check-types: + name: Check types + needs: [build] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 # Needed for setuptools_scm + + - name: Set up Python 3.13 + uses: actions/setup-python@v6 + with: + python-version: 3.13 + + - name: Download package + uses: actions/download-artifact@v6 + with: + name: Packages + path: dist + + - name: Install package and dependencies + run: | + python -m pip install uv + uv sync --all-extras --no-install-project + uv pip install "$(ls dist/*.whl)" + + - name: Run type checker (mypy) + run: | + uv run mypy . diff --git a/PyPSA/source/.gitignore b/PyPSA/source/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..8d5e84e3bc06c7352e233e502575a7de980d4d3b --- /dev/null +++ b/PyPSA/source/.gitignore @@ -0,0 +1,54 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +*~ +.~* +*.py[cod] +.ipynb_checkpoints/ +*.log +old/ + +# Ignore all jupyter notebooks, except those for the docs +*.ipynb +!/docs/**/*.ipynb + + +test/.cache/ +.cache/ + +test/results/* + +pypsa.egg-info/ + +files.txt + +tom_notes.org + +*.pdf + +*.aux + +website/examples/*.py + +*.zip + +.vscode +.devcontainer +.github/copilot-instructions.md + +.idea + +.coverage + +dev-scripts +dev + +.venv +uv.lock +.aider* +CLAUDE.md +.claude +.github/copilot-instructions.md +site/ +doc/_build/ diff --git a/PyPSA/source/.pre-commit-config.yaml b/PyPSA/source/.pre-commit-config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..cda496ee9ed01ee5807e22e12752b196e3346efd --- /dev/null +++ b/PyPSA/source/.pre-commit-config.yaml @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +exclude: ^(docs/hooks|examples) + +ci: + autoupdate_schedule: quarterly + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-added-large-files + args: ['--maxkb=2000'] + - id: trailing-whitespace + - id: end-of-file-fixer + +# Run ruff to lint and format +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.13.3 + hooks: + # Run the linter. + - id: ruff + args: [--fix] + # Run the formatter. + - id: ruff-format + +# Find common spelling mistakes in comments and docstrings +- repo: https://github.com/codespell-project/codespell + rev: v2.4.1 + hooks: + - id: codespell + # Ignore capital case words, e.g. country codes + args: ['--ignore-regex="(\b[A-Z]+\b)"', '--ignore-words=.codespell.ignore'] + types_or: [python, rst, markdown] + files: ^(scripts|doc)/ + +# Remove output from Jupyter notebooks +- repo: https://github.com/aflc/pre-commit-jupyter + rev: v1.2.1 + hooks: + - id: jupyter-notebook-cleanup + args: ['--remove-kernel-metadata'] + +# Do YAML formatting (before the linter checks it for misses) +- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: v2.15.0 + hooks: + - id: pretty-format-yaml + args: [--autofix, --indent, '2', --preserve-quotes] + +# Check for FSFE REUSE compliance (licensing) +- repo: https://github.com/fsfe/reuse-tool + rev: v6.0.0 + hooks: + - id: reuse-lint-file diff --git a/PyPSA/source/.readthedocs.yml b/PyPSA/source/.readthedocs.yml new file mode 100644 index 0000000000000000000000000000000000000000..b8c8064ff4abad962bf463e4ae757cd0c32b0425 --- /dev/null +++ b/PyPSA/source/.readthedocs.yml @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +version: 2 + +build: + os: ubuntu-24.04 + tools: + python: "3.13" + jobs: + pre_system_dependencies: + - git fetch --unshallow # Needed to get version tags + pre_create_environment: + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + create_environment: + - uv venv "${READTHEDOCS_VIRTUALENV_PATH}" + install: + - UV_PROJECT_ENVIRONMENT="${READTHEDOCS_VIRTUALENV_PATH}" uv pip install -r docs/requirements.txt + pre_build: + # To create all doctest files + - uv run pytest test/test_docs.py --test-docs + +mkdocs: + configuration: mkdocs.yml + fail_on_warning: true diff --git a/PyPSA/source/CITATION.cff b/PyPSA/source/CITATION.cff new file mode 100644 index 0000000000000000000000000000000000000000..90d936a6e2a7dc9e6c6438c32e3f2978bbb4b2da --- /dev/null +++ b/PyPSA/source/CITATION.cff @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +cff-version: 1.2.0 +message: "If you use this package, please cite the corresponding manuscript in Journal of Open Research Software." +title: "PyPSA: Python for Power System Analysis" +repository: https://github.com/pypsa/pypsa +version: 1.0.3 # Don't touch, will be updated by the release script +license: MIT +journal: Journal of Open Research Software +doi: 10.5334/jors.188 +authors: + - family-names: Brown + given-names: Tom + orcid: https://orcid.org/0000-0001-5898-1911 + - family-names: Hörsch + given-names: Jonas + orcid: https://orcid.org/0000-0001-9438-767X + - family-names: Schlachtberger + given-names: David + orcid: https://orcid.org/0000-0002-8167-8213 diff --git a/PyPSA/source/CONTRIBUTING.md b/PyPSA/source/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..3938f37e26618772a000a622083974644572d553 --- /dev/null +++ b/PyPSA/source/CONTRIBUTING.md @@ -0,0 +1,11 @@ + + +🎉 Thank you for your interest in contributing to PyPSA! 🎉 + +We strongly welcome anyone interested in contributing to PyPSA and it's related projects. All types of contributions are encouraged and valued! + +Detailed guidelines can be found in the [Contributing](https://docs.pypsa.org/latest/contributing/contributing/) section of our documentation. diff --git a/PyPSA/source/LICENSE b/PyPSA/source/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d817195dad53ec992418c28ffca5fbd1cd86502a --- /dev/null +++ b/PyPSA/source/LICENSE @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/PyPSA/source/LICENSES/CC-BY-4.0.txt b/PyPSA/source/LICENSES/CC-BY-4.0.txt new file mode 100644 index 0000000000000000000000000000000000000000..13ca539f377dc705af32b8d2ce89262298ea2f06 --- /dev/null +++ b/PyPSA/source/LICENSES/CC-BY-4.0.txt @@ -0,0 +1,156 @@ +Creative Commons Attribution 4.0 International + + Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. + +Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors. + +Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public. + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. + +Section 1 – Definitions. + + a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. + + d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. + + g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. + + i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. + +Section 2 – Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: + + A. reproduce and Share the Licensed Material, in whole or in part; and + + B. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. + + 3. Term. The term of this Public License is specified in Section 6(a). + + 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. + + 5. Downstream recipients. + + A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. + + B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. + + 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). + +b. Other rights. + + 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this Public License. + + 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. + +Section 3 – License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified form), You must: + + A. retain the following if it is supplied by the Licensor with the Licensed Material: + + i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of warranties; + + v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; + + B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and + + C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. + + 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. + +Section 4 – Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; + + b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. +For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. + +Section 5 – Disclaimer of Warranties and Limitation of Liability. + + a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. + + b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. + + c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. + +Section 6 – Term and Termination. + + a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or + + 2. upon express reinstatement by the Licensor. + + c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. + + d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. + + e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. + +Section 7 – Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. + +Section 8 – Interpretation. + + a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. + + c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. + + d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. + +Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/PyPSA/source/LICENSES/MIT.txt b/PyPSA/source/LICENSES/MIT.txt new file mode 100644 index 0000000000000000000000000000000000000000..d817195dad53ec992418c28ffca5fbd1cd86502a --- /dev/null +++ b/PyPSA/source/LICENSES/MIT.txt @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/PyPSA/source/MANIFEST.in b/PyPSA/source/MANIFEST.in new file mode 100644 index 0000000000000000000000000000000000000000..c56dbb63a1c7269273d083c3de4ebc6536cf65e1 --- /dev/null +++ b/PyPSA/source/MANIFEST.in @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +include pypsa/data/component_attrs/*.csv +include pypsa/data/standard_types/*.csv +include pypsa/data/components.csv +include pypsa/data/variables.csv +include README.rst LICENSE.txt +include requirements.yml diff --git a/PyPSA/source/README.md b/PyPSA/source/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6fa9de07f990a72932d39716f63c5fc66d769ab2 --- /dev/null +++ b/PyPSA/source/README.md @@ -0,0 +1,210 @@ + + + + + PyPSA Banner + + +# PyPSA - Python for Power System Analysis + + +[![PyPI version](https://img.shields.io/pypi/v/pypsa.svg)](https://pypi.python.org/pypi/pypsa) +[![Conda version](https://img.shields.io/conda/vn/conda-forge/pypsa.svg)](https://anaconda.org/conda-forge/pypsa) +![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2FPyPSA%2FPyPSA%2Fmaster%2Fpyproject.toml) +[![Tests](https://github.com/PyPSA/PyPSA/actions/workflows/test.yml/badge.svg)](https://github.com/PyPSA/PyPSA/actions/workflows/test.yml) +[![Documentation Status](https://readthedocs.org/projects/pypsa/badge/?version=latest)](https://docs.pypsa.org/latest/?badge=latest) +[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/PyPSA/PyPSA/master.svg)](https://results.pre-commit.ci/latest/github/PyPSA/PyPSA/master) +[![Code coverage](https://codecov.io/gh/PyPSA/PyPSA/branch/master/graph/badge.svg?token=kCpwJiV6Jr)](https://codecov.io/gh/PyPSA/PyPSA) +[![REUSE status](https://api.reuse.software/badge/github.com/pypsa/pypsa)](https://api.reuse.software/info/github.com/pypsa/pypsa) +[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) +[![License](https://img.shields.io/pypi/l/pypsa.svg)](LICENSE) +[![Zenodo](https://zenodo.org/badge/DOI/10.5281/zenodo.3946412.svg)](https://doi.org/10.5281/zenodo.3946412) +[![Discord](https://img.shields.io/discord/911692131440148490?logo=discord)](https://discord.gg/AnuJBk23FU) +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) + +PyPSA stands for **Python for Power System Analysis**. It is pronounced +**pipes-ah**. + +PyPSA is an open-source Python framework for optimising and simulating modern +power and energy systems that include features such as conventional generators +with unit commitment, variable wind and solar generation, hydro-electricity, +inter-temporal storage, coupling to other energy sectors, elastic demands, and +linearised power flow with loss approximations in DC and AC networks. PyPSA is +designed to scale well with large networks and long time series. It is made for +researchers, planners and utilities with basic coding aptitude who need a fast, +easy-to-use and transparent tool for power and energy system analysis. + +PyPSA is maintained by the [Department of Digital Transformation in Energy +Systems](https://tu.berlin/en/ensys) at the [Technical University of +Berlin](https://www.tu.berlin). Previous versions were developed at the +[Karlsruhe Institute of Technology](http://www.kit.edu/english/index.php) funded +by the [Helmholtz Association](https://www.helmholtz.de/en/), and at +[FIAS](https://fias.uni-frankfurt.de/) funded by the [German Federal Ministry +for Education and Research (BMBF)](https://www.bmbf.de/bmbf/en/). + +## Features + +- **Economic Dispatch (ED):** Models short-term market-based dispatch including +unit commitment, renewable availability, short-duration and seasonal storage +including hydro reservoirs with inflow and spillage dynamics, elastic demands, +load shedding and conversion between energy carriers, using either perfect +operational foresight or rolling horizon time resolution. + +- **Linear Optimal Power Flow (LOPF):** Extends economic dispatch to determine +the least-cost dispatch while respecting network constraints in meshed AC-DC +networks, using a linearised representation of power flow (KVL, KCL) with +optional loss approximations. + +- **Security-Constrained LOPF (SCLOPF):** Extends LOPF by accounting for line +outage contingencies to ensure system reliability under $N-1$ conditions. + +- **Capacity Expansion Planning (CEP):** Supports least-cost +long-term system planning with investment decisions for generation, storage, +conversion, and transmission infrastructure. Handles both single and multiple +investment periods. Continuous and discrete investments are supported. + +- **Pathway Planning:** Supports co-optimisation of multiple investment periods to +plan energy system transitions over time with perfect planning foresight. + +- **Stochastic Optimisation:** Implements two-stage stochastic programming +framework with scenario-weighted uncertain inputs, with investments as +first-stage decisions and dispatch as recourse decisions. + +- **Modelling-to-Generate-Alternatives (MGA):** Explores near-optimal decision +spaces to provide insight into the range of feasible system configurations with +similar costs. + +- **Sector-Coupling:** Modelling integrated energy systems with multiple energy + carriers (electricity, heat, hydrogen, etc.) and conversion between them. + Flexible representation of technologies such as heat pumps, electrolysers, + battery electric vehicles (BEVs), direct air capture (DAC), and synthetic + fuels production. + +- **Static Power Flow Analysis:** Computes both full non-linear and linearised + load flows for meshed AC and DC grids using Newton-Raphson method. + +## Documentation + +PyPSA has extensive [documentation](https://docs.pypsa.org) with tutorials, user guides, examples and an API reference. + +## Installation + +pip: + +``` bash +pip install pypsa +``` + +conda/mamba: + +``` bash +conda install -c conda-forge pypsa +``` + +uv: + +``` bash +uv add pypsa +``` + +## Usage + +``` py +import pypsa + +# create a new network +n = pypsa.Network() +n.add("Bus", "mybus") +n.add("Load", "myload", bus="mybus", p_set=100) +n.add("Generator", "mygen", bus="mybus", p_nom=100, marginal_cost=20) + +# load an example network +n = pypsa.examples.ac_dc_meshed() + +# run the optimisation +n.optimize() + +# plot results +n.generators_t.p.plot() +n.plot() + +# get statistics +n.statistics() +n.statistics.energy_balance() +``` + +## Dependencies + +PyPSA relies heavily on other open-source Python packages. Some of them are: + +* [pandas](http://pandas.pydata.org/) for storing data about components and time series +* [numpy](http://www.numpy.org/) and [scipy](http://scipy.org/) for linear algebra and matrix calculations +* [linopy](https://github.com/PyPSA/linopy) for preparing optimisation problems +* [matplotlib](https://matplotlib.org/), [seaborn](https://seaborn.pydata.org/) and [plotly](https://plotly.com/python/) for static and interactive plotting +* [networkx](https://networkx.github.io/) for network calculations +* [pytest](http://pytest.org/) for unit testing + +Find the full list of dependencies in the [`pyproject.toml`](https://github.com/PyPSA/PyPSA/blob/master/pyproject.toml) file. + +PyPSA can be used with different solvers. For instance, the free solvers +[HiGHS](https://highs.dev/) (installed by default), [GLPK](https://www.gnu.org/software/glpk/) and +[CBC](https://github.com/coin-or/Cbc/) or commercial solvers like +[Gurobi](http://www.gurobi.com/) or [FICO Xpress](https://www.fico.com/en/products/fico-xpress-optimization) for which free academic licenses are available. + +## Contributing and Support + +We strongly welcome anyone interested in contributing to this project. If you have any ideas, suggestions or encounter problems, feel invited to file issues or make pull requests on GitHub. + +- To **discuss** with other PyPSA users, organise projects, share news, and get in touch with the community you can use the [Discord server](https://discord.gg/AnuJBk23FU). +- For **bugs and feature requests**, please [open an issue](https://github.com/PyPSA/PyPSA/issues). +- For **troubleshooting and support**, please check the [troubleshooting](https://docs.pypsa.org/latest/user-guide/support/) and [support](https://docs.pypsa.org/latest/user-guide/support/) sectionsin the documentation. + +Detailed guidelines can be found in the [Contributing](https://docs.pypsa.org/latest/contributing/contributing/) guidelines of our documentation. + +## Code of Conduct + +Please respect our [Code of Conduct](https://docs.pypsa.org/latest/contributing/code-of-conduct/). + +## Citing PyPSA + +If you use PyPSA for your research, we would appreciate it if you would +cite the following paper: + +- T. Brown, J. Hörsch, D. Schlachtberger, [PyPSA: Python for Power + System Analysis](https://arxiv.org/abs/1707.09913), 2018, [Journal + of Open Research + Software](https://openresearchsoftware.metajnl.com/), 6(1), + [arXiv:1707.09913](https://arxiv.org/abs/1707.09913), + [DOI:10.5334/jors.188](https://doi.org/10.5334/jors.188) + +Please use the following BibTeX: + + @article{PyPSA, + author = {T. Brown and J. H\"orsch and D. Schlachtberger}, + title = {{PyPSA: Python for Power System Analysis}}, + journal = {Journal of Open Research Software}, + volume = {6}, + issue = {1}, + number = {4}, + year = {2018}, + eprint = {1707.09913}, + url = {https://doi.org/10.5334/jors.188}, + doi = {10.5334/jors.188} + } + +If you want to cite a specific PyPSA version, each release of PyPSA is archived +on [Zenodo](https://zenodo.org/) with a release-specific DOI: +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3946412.svg)](https://doi.org/10.5281/zenodo.3946412) + +## Licence + +Copyright [PyPSA Contributors](https://docs.pypsa.org/latest/developers.html) + +PyPSA is licensed under the open source [MIT License](LICENSES/MIT.txt). +The documentation is licensed under [CC-BY-4.0](LICENSES/CC-BY-4.0.txt). + +The repository uses [REUSE](https://reuse.software/) to expose the licenses of its files. diff --git a/PyPSA/source/REUSE.toml b/PyPSA/source/REUSE.toml new file mode 100644 index 0000000000000000000000000000000000000000..481a498aa684d717c9644fc82551743e31e9119d --- /dev/null +++ b/PyPSA/source/REUSE.toml @@ -0,0 +1,51 @@ +version = 1 +SPDX-PackageName = "PyPSA" +SPDX-PackageSupplier = "Tom Brown " +SPDX-PackageDownloadLocation = "https://github.com/pypsa/pypsa" + +[[annotations]] +# As an essential part of PyPSA package, these parts are licensed under the same MIT license as the code itself +path = [ + ".github/**/*.md", + "pypsa/data/component_attrs/*.csv", + "pypsa/data/standard_types/*.csv", + "pypsa/data/*.csv", + "test/baseline/*.png", + "test/data/**/*.csv", + "test/data/**/*.json", + "docs/requirements.txt", +] +SPDX-FileCopyrightText = "PyPSA Contributors" +SPDX-License-Identifier = "MIT" + +[[annotations]] +# Data for examples, CC-BY-4.0 license for reuse by others +path = [ + "examples/**.csv", + "examples/**.json", + "examples/**.nc", +] +SPDX-FileCopyrightText = "PyPSA Contributors" +SPDX-License-Identifier = "CC-BY-4.0" + +[[annotations]] +# All documentation figures and notebooks (including their code) are for simplicity licensed under CC-BY-4.0 +path = [ + "docs/**/*.ipynb", + "docs/**/*.png", + "docs/**/*.html", + "docs/**/*.svg", + "docs/**/*.jpg", +] +SPDX-FileCopyrightText = "PyPSA Contributors" +SPDX-License-Identifier = "CC-BY-4.0" + + +[[annotations]] +# TODO find correct information for this external image +path = [ + "docs/assets/images/reserve-power-funding.gif", +] +# We think this is the correct attribution (copyright holder and license) +SPDX-FileCopyrightText = "Ministry for Economic Affairs and Climate Action" +SPDX-License-Identifier = "CC-BY-4.0" diff --git a/PyPSA/source/__init__.py b/PyPSA/source/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9abd9369d6b9cdcf0e96fafcc36714ab14bebc4c --- /dev/null +++ b/PyPSA/source/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +""" +PyPSA Project Package Initialization File +""" diff --git a/PyPSA/source/codecov.yml b/PyPSA/source/codecov.yml new file mode 100644 index 0000000000000000000000000000000000000000..483bbaa5f8ac4299eae030136fd65291d3e77ee4 --- /dev/null +++ b/PyPSA/source/codecov.yml @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +comment: false + +coverage: + status: + project: + default: + threshold: 0.1% + +codecov: + notify: + after_n_builds: 13 diff --git a/PyPSA/source/docs/CODE_OF_CONDUCT.md b/PyPSA/source/docs/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000000000000000000000000000000..699b12621018152e90844f13c85d75950fa315d8 --- /dev/null +++ b/PyPSA/source/docs/CODE_OF_CONDUCT.md @@ -0,0 +1,143 @@ + + +# Code of Conduct + +!!! info + + This Code of Conduct follows the [Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/). + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +f.neumann@tu-berlin.de / m.hofmann@tu-berlin.de / t.brown@tu-berlin.de. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/PyPSA/source/docs/api/components/common.md b/PyPSA/source/docs/api/components/common.md new file mode 100644 index 0000000000000000000000000000000000000000..939b534acfc9635a1a2fc30a272d8486d373fb3b --- /dev/null +++ b/PyPSA/source/docs/api/components/common.md @@ -0,0 +1,7 @@ + + +::: pypsa.components.common diff --git a/PyPSA/source/docs/api/components/components.md b/PyPSA/source/docs/api/components/components.md new file mode 100644 index 0000000000000000000000000000000000000000..d8f6db1299980162283038b8b84bfaf221c91667 --- /dev/null +++ b/PyPSA/source/docs/api/components/components.md @@ -0,0 +1,24 @@ + + +::: pypsa.components.Components + options: + heading_level: 2 + members_order: source + inherited_members: true + filters: + - "!^_[^_]" + - "!logger" + - "!iteration" + - "!__deepcopy__" + - "!__getitem__" + - "!__setitem__" + - "!add" + - "!get_bounds_pu" + +::: pypsa.components.components.ComponentsData + options: + heading_level: 2 diff --git a/PyPSA/source/docs/api/components/types/buses.md b/PyPSA/source/docs/api/components/types/buses.md new file mode 100644 index 0000000000000000000000000000000000000000..5771bbba08c60c8ca67bfbecef7372bdd346065d --- /dev/null +++ b/PyPSA/source/docs/api/components/types/buses.md @@ -0,0 +1,7 @@ + + +::: pypsa.components.Buses diff --git a/PyPSA/source/docs/api/components/types/carriers.md b/PyPSA/source/docs/api/components/types/carriers.md new file mode 100644 index 0000000000000000000000000000000000000000..a04a9ffdb1195729b14feb75b86622fdbaea5c07 --- /dev/null +++ b/PyPSA/source/docs/api/components/types/carriers.md @@ -0,0 +1,7 @@ + + +::: pypsa.components.Carriers diff --git a/PyPSA/source/docs/api/components/types/generators.md b/PyPSA/source/docs/api/components/types/generators.md new file mode 100644 index 0000000000000000000000000000000000000000..e78ce7309ac398cc4b8ec57530bfcad94f1285b3 --- /dev/null +++ b/PyPSA/source/docs/api/components/types/generators.md @@ -0,0 +1,7 @@ + + +::: pypsa.components.Generators diff --git a/PyPSA/source/docs/api/components/types/global-constraints.md b/PyPSA/source/docs/api/components/types/global-constraints.md new file mode 100644 index 0000000000000000000000000000000000000000..9fae690b22fe9ec57388898bba6fc86260d155dd --- /dev/null +++ b/PyPSA/source/docs/api/components/types/global-constraints.md @@ -0,0 +1,7 @@ + + +::: pypsa.components.GlobalConstraints diff --git a/PyPSA/source/docs/api/components/types/lines.md b/PyPSA/source/docs/api/components/types/lines.md new file mode 100644 index 0000000000000000000000000000000000000000..e41f751a5597b11b0bb4a74db49d2a2d7aca9b22 --- /dev/null +++ b/PyPSA/source/docs/api/components/types/lines.md @@ -0,0 +1,13 @@ + + +::: pypsa.components.Lines + options: + heading_level: 2 + +::: pypsa.components.LineTypes + options: + heading_level: 2 diff --git a/PyPSA/source/docs/api/components/types/links.md b/PyPSA/source/docs/api/components/types/links.md new file mode 100644 index 0000000000000000000000000000000000000000..522f0fc0030e220f6da3d8458def623a9d576369 --- /dev/null +++ b/PyPSA/source/docs/api/components/types/links.md @@ -0,0 +1,7 @@ + + +::: pypsa.components.Links diff --git a/PyPSA/source/docs/api/components/types/loads.md b/PyPSA/source/docs/api/components/types/loads.md new file mode 100644 index 0000000000000000000000000000000000000000..6fba6dbc2776cb7dae39033e1a7e63060bfb207a --- /dev/null +++ b/PyPSA/source/docs/api/components/types/loads.md @@ -0,0 +1,7 @@ + + +::: pypsa.components.Loads diff --git a/PyPSA/source/docs/api/components/types/shapes.md b/PyPSA/source/docs/api/components/types/shapes.md new file mode 100644 index 0000000000000000000000000000000000000000..6ed47cf5fc095920d022200242882d547563ea91 --- /dev/null +++ b/PyPSA/source/docs/api/components/types/shapes.md @@ -0,0 +1,7 @@ + + +::: pypsa.components.Shapes diff --git a/PyPSA/source/docs/api/components/types/shunt-impedances.md b/PyPSA/source/docs/api/components/types/shunt-impedances.md new file mode 100644 index 0000000000000000000000000000000000000000..0441dcad0f371f3f6cfa8ee06dada7495eb391cf --- /dev/null +++ b/PyPSA/source/docs/api/components/types/shunt-impedances.md @@ -0,0 +1,7 @@ + + +::: pypsa.components.ShuntImpedances diff --git a/PyPSA/source/docs/api/components/types/storage-units.md b/PyPSA/source/docs/api/components/types/storage-units.md new file mode 100644 index 0000000000000000000000000000000000000000..67c8e5b12085092e60e15a4968074f992377ac2c --- /dev/null +++ b/PyPSA/source/docs/api/components/types/storage-units.md @@ -0,0 +1,7 @@ + + +::: pypsa.components.StorageUnits diff --git a/PyPSA/source/docs/api/components/types/stores.md b/PyPSA/source/docs/api/components/types/stores.md new file mode 100644 index 0000000000000000000000000000000000000000..c35265aba05fb0105a2597e3db072878d756b50f --- /dev/null +++ b/PyPSA/source/docs/api/components/types/stores.md @@ -0,0 +1,7 @@ + + +::: pypsa.components.Stores diff --git a/PyPSA/source/docs/api/components/types/sub-networks.md b/PyPSA/source/docs/api/components/types/sub-networks.md new file mode 100644 index 0000000000000000000000000000000000000000..63a692adaecd3f925c9caa11a5309457c35cbe1e --- /dev/null +++ b/PyPSA/source/docs/api/components/types/sub-networks.md @@ -0,0 +1,7 @@ + + +::: pypsa.components.SubNetworks diff --git a/PyPSA/source/docs/api/components/types/transformers.md b/PyPSA/source/docs/api/components/types/transformers.md new file mode 100644 index 0000000000000000000000000000000000000000..9e5f67284a821423d1f3147a195bf040a46b4adf --- /dev/null +++ b/PyPSA/source/docs/api/components/types/transformers.md @@ -0,0 +1,13 @@ + + +::: pypsa.components.Transformers + options: + heading_level: 2 + +::: pypsa.components.TransformerTypes + options: + heading_level: 2 diff --git a/PyPSA/source/docs/api/networks/cluster.md b/PyPSA/source/docs/api/networks/cluster.md new file mode 100644 index 0000000000000000000000000000000000000000..ec17eaa603375f7d3af723c80393646ad9d4ab97 --- /dev/null +++ b/PyPSA/source/docs/api/networks/cluster.md @@ -0,0 +1,7 @@ + + +::: pypsa.clustering.ClusteringAccessor diff --git a/PyPSA/source/docs/api/networks/collection.md b/PyPSA/source/docs/api/networks/collection.md new file mode 100644 index 0000000000000000000000000000000000000000..f9e295ab0e7f7db36d65557d465b0de5c150d464 --- /dev/null +++ b/PyPSA/source/docs/api/networks/collection.md @@ -0,0 +1,14 @@ + + +::: pypsa.NetworkCollection + options: + filters: + - "!^_[^_]" + - "!__getattr__" + - "!logger" + - "!statistics" + - "!index" diff --git a/PyPSA/source/docs/api/networks/components.md b/PyPSA/source/docs/api/networks/components.md new file mode 100644 index 0000000000000000000000000000000000000000..b12f42a3dfd1f79f635d042e75b91bc8e9ceff49 --- /dev/null +++ b/PyPSA/source/docs/api/networks/components.md @@ -0,0 +1,10 @@ + + +::: pypsa.network.components.NetworkComponentsMixin + options: + show_bases: False + members_order: source diff --git a/PyPSA/source/docs/api/networks/descriptors.md b/PyPSA/source/docs/api/networks/descriptors.md new file mode 100644 index 0000000000000000000000000000000000000000..84f3ef6498f4b95cb72de7cec0c3f727c2f05753 --- /dev/null +++ b/PyPSA/source/docs/api/networks/descriptors.md @@ -0,0 +1,10 @@ + + +::: pypsa.network.descriptors.NetworkDescriptorsMixin + options: + show_bases: False + members_order: source diff --git a/PyPSA/source/docs/api/networks/indexing.md b/PyPSA/source/docs/api/networks/indexing.md new file mode 100644 index 0000000000000000000000000000000000000000..c61b7fb42f27b144ec3fbff3e85ee6efc8bce100 --- /dev/null +++ b/PyPSA/source/docs/api/networks/indexing.md @@ -0,0 +1,10 @@ + + +::: pypsa.network.index.NetworkIndexMixin + options: + show_bases: False + members_order: source diff --git a/PyPSA/source/docs/api/networks/io.md b/PyPSA/source/docs/api/networks/io.md new file mode 100644 index 0000000000000000000000000000000000000000..93d1189111730368552385b7fb2ba15265df7f9f --- /dev/null +++ b/PyPSA/source/docs/api/networks/io.md @@ -0,0 +1,9 @@ + + +::: pypsa.network.io.NetworkIOMixin + options: + show_bases: False diff --git a/PyPSA/source/docs/api/networks/network.md b/PyPSA/source/docs/api/networks/network.md new file mode 100644 index 0000000000000000000000000000000000000000..76da3dd1b05a71d0c5287d4af653b53d910a46ba --- /dev/null +++ b/PyPSA/source/docs/api/networks/network.md @@ -0,0 +1,13 @@ + + +::: pypsa.Network + options: + members_order: source + filters: + - "!^_[^_]" + - "!logger" + - "!iteration" diff --git a/PyPSA/source/docs/api/networks/optimize.md b/PyPSA/source/docs/api/networks/optimize.md new file mode 100644 index 0000000000000000000000000000000000000000..d40a101504302aabfa72bf0d50aacb1106615c00 --- /dev/null +++ b/PyPSA/source/docs/api/networks/optimize.md @@ -0,0 +1,21 @@ + + +# Optimization Methods + +::: pypsa.optimization.OptimizationAccessor + options: + heading_level: 2 + show_bases: False + inherited_members: true + +::: pypsa.optimization.mga + options: + heading_level: 2 + filters: + - "!^_[^_]" + - "!logger" + - "!OptimizationAbstractMGAMixin" diff --git a/PyPSA/source/docs/api/networks/plot.md b/PyPSA/source/docs/api/networks/plot.md new file mode 100644 index 0000000000000000000000000000000000000000..abcfda3e57205fab34778b3eef50569348ed63f1 --- /dev/null +++ b/PyPSA/source/docs/api/networks/plot.md @@ -0,0 +1,20 @@ + + +::: pypsa.plot.accessor.PlotAccessor + options: + heading_level: 2 + members_order: source + +::: pypsa.plot.statistics.plotter.StatisticPlotter + options: + heading_level: 2 + members_order: source + +::: pypsa.plot.statistics.plotter.StatisticInteractivePlotter + options: + heading_level: 2 + members_order: source diff --git a/PyPSA/source/docs/api/networks/power-flow.md b/PyPSA/source/docs/api/networks/power-flow.md new file mode 100644 index 0000000000000000000000000000000000000000..c96b07799c38982b53d5d5f97e18313fc508cd31 --- /dev/null +++ b/PyPSA/source/docs/api/networks/power-flow.md @@ -0,0 +1,9 @@ + + +::: pypsa.network.power_flow.NetworkPowerFlowMixin + options: + show_bases: False diff --git a/PyPSA/source/docs/api/networks/statistics.md b/PyPSA/source/docs/api/networks/statistics.md new file mode 100644 index 0000000000000000000000000000000000000000..eafcd596c8aca83cb3a8213fbd139e19e4bb69a2 --- /dev/null +++ b/PyPSA/source/docs/api/networks/statistics.md @@ -0,0 +1,17 @@ + + +::: pypsa.statistics.StatisticsAccessor + options: + heading_level: 2 + +::: pypsa.statistics.grouping.Groupers + options: + heading_level: 2 + +::: pypsa.optimization.expressions.StatisticExpressionsAccessor + options: + heading_level: 2 diff --git a/PyPSA/source/docs/api/networks/subnetwork.md b/PyPSA/source/docs/api/networks/subnetwork.md new file mode 100644 index 0000000000000000000000000000000000000000..4157c7b347e6db35171e940ad5d0ae6863410716 --- /dev/null +++ b/PyPSA/source/docs/api/networks/subnetwork.md @@ -0,0 +1,9 @@ + + +::: pypsa.networks.SubNetwork + options: + inherited_members: true diff --git a/PyPSA/source/docs/api/networks/transform.md b/PyPSA/source/docs/api/networks/transform.md new file mode 100644 index 0000000000000000000000000000000000000000..ea15e8fdf7a587bf77b138681ff66ae522ce4fa3 --- /dev/null +++ b/PyPSA/source/docs/api/networks/transform.md @@ -0,0 +1,9 @@ + + +::: pypsa.network.transform.NetworkTransformMixin + options: + show_bases: False diff --git a/PyPSA/source/docs/api/other/api-examples.md b/PyPSA/source/docs/api/other/api-examples.md new file mode 100644 index 0000000000000000000000000000000000000000..18e5a8f03551d598ba12e1551e2052e1bd899775 --- /dev/null +++ b/PyPSA/source/docs/api/other/api-examples.md @@ -0,0 +1,13 @@ + + +::: pypsa.examples + options: + members_order: source + heading_level: 2 + show_root_heading: False + show_root_toc_entry: False + summary: false diff --git a/PyPSA/source/docs/api/other/common.md b/PyPSA/source/docs/api/other/common.md new file mode 100644 index 0000000000000000000000000000000000000000..9ffc8c6dc14eb53354ae5d704c68c69d6c34fdda --- /dev/null +++ b/PyPSA/source/docs/api/other/common.md @@ -0,0 +1,26 @@ + + +::: pypsa.common + options: + heading_level: 2 + members_order: source + filters: + - "!^_[^_]" + - "!logger" + - "!MethodHandlerWrapper" + - "!URL_CREATE_ISSUE" + - "!check_optional_dependency" + - "!deprecated_common_kwargs" + - "!deprecated_in_next_major" + - "!deprecated_kwargs" + - "!deprecated_namespace" + - "!pass_empty_series_if_keyerror" + - "!pass_none_if_keyerror ¶" + - "!rename_deprecated_kwargs" +::: pypsa.geo + options: + heading_level: 2 diff --git a/PyPSA/source/docs/api/other/consistency.md b/PyPSA/source/docs/api/other/consistency.md new file mode 100644 index 0000000000000000000000000000000000000000..2576a3d39d788d71c888cd703fd233ef47aeae16 --- /dev/null +++ b/PyPSA/source/docs/api/other/consistency.md @@ -0,0 +1,27 @@ + + +# Consistency Checks + +::: pypsa.consistency.NetworkConsistencyMixin + options: + show_bases: False + heading_level: 2 + + +::: pypsa.consistency + options: + heading: "Specific checks" + toc_label: "Specific checks" + heading_level: 2 + filters: + - "!^_[^_]" + - "!^logger" + - "!^ConsistencyError" + - "!^NetworkConsistencyMixin" + - "!^consistency_check" + - "!^consistency_check_plotting" + - "!^plotting_consistency_check" diff --git a/PyPSA/source/docs/api/other/options.md b/PyPSA/source/docs/api/other/options.md new file mode 100644 index 0000000000000000000000000000000000000000..f4ab9519cf67e085604339072d277b3654864aa1 --- /dev/null +++ b/PyPSA/source/docs/api/other/options.md @@ -0,0 +1,28 @@ + + +::: pypsa._options.OptionsNode + options: + heading_level: 2 + show_root_heading: False + show_root_toc_entry: False + summary: false + members: + - "__getattr__" + - "__setattr__" + - "describe" + - "get_option" + - "set_option" + - "reset_option" + +::: pypsa._options + options: + heading_level: 2 + show_root_heading: False + show_root_toc_entry: False + summary: false + members: + - "option_context" diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-area_plot.jpg b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-area_plot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7ae62177fd53130e4661d5dd535cfa32124db18d Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-area_plot.jpg differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-area_plot.png b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-area_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..b340ea57ddb3b06fb4b762f5c5ec71eaed52bdf8 Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-area_plot.png differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-bar_plot.jpg b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-bar_plot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7bd0cafa39f00737f67b54e670236a68e23c6055 Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-bar_plot.jpg differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-bar_plot.png b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-bar_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..6b18251de1f1e17887fc800c62df82a8ae5cb767 Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-bar_plot.png differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-box_plot.png b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-box_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..e2dbe78cf2f2cfe9d8898161efed42165eb514c8 Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-box_plot.png differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-custom_area_plot.png b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-custom_area_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..8aeb4360854945c183b9c62d62c5842da6cbd6aa Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-custom_area_plot.png differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-histogram_plot.jpg b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-histogram_plot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f8f07e732ec550f211a24c2c2b338f2a27c7016b Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-histogram_plot.jpg differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-histogram_plot.png b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-histogram_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..0e1b6e3d3bfa522d5c7f28be0bd46079f85d8635 Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-histogram_plot.png differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-line_plot.jpg b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-line_plot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d4f379ad749627f8bb4c63610fe2f19acbd4c238 Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-line_plot.jpg differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-line_plot.png b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-line_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..aef3973b4e4b1b4b860c02b92edad12410639d73 Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-line_plot.png differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-map_plot.png b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-map_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..cc24fa42441b6c89d88c549b8e32ef154916b971 Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-map_plot.png differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-scatter_plot.jpg b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-scatter_plot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cb01f24d62a920102eea7e7f5ff6af19288f97ab Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-scatter_plot.jpg differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-scatter_plot.png b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-scatter_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..d3cbe987b2ba81cb3d503d7edf2d5d5795c5a7c1 Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-scatter_plot.png differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-violin_plot.png b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-violin_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..5a1c0cb1b163d2f3709faae7348028dc7299289e Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-energy_balance-violin_plot.png differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-installed_capacity-bar_plot.jpg b/PyPSA/source/docs/assets/images/ac_dc_meshed-installed_capacity-bar_plot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..87634f2128e7e45ce0f3b2fcbce36acce400f8f2 Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-installed_capacity-bar_plot.jpg differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed-installed_capacity-bar_plot.png b/PyPSA/source/docs/assets/images/ac_dc_meshed-installed_capacity-bar_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..52e591709559b7a27a425846eed7553ca65964b2 Binary files /dev/null and b/PyPSA/source/docs/assets/images/ac_dc_meshed-installed_capacity-bar_plot.png differ diff --git a/PyPSA/source/docs/assets/images/ac_dc_meshed.png b/PyPSA/source/docs/assets/images/ac_dc_meshed.png new file mode 100644 index 0000000000000000000000000000000000000000..13ef05438b68f6b026767054fb0707340657a180 --- /dev/null +++ b/PyPSA/source/docs/assets/images/ac_dc_meshed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2476977d9ad9d8859acb79f98b44fa7e4146fab75a04b344b72cddf014a69d35 +size 442770 diff --git a/PyPSA/source/docs/assets/images/buses.png b/PyPSA/source/docs/assets/images/buses.png new file mode 100644 index 0000000000000000000000000000000000000000..56cbc16244ef768732f81f435d408209e64a8bc1 Binary files /dev/null and b/PyPSA/source/docs/assets/images/buses.png differ diff --git a/PyPSA/source/docs/assets/images/carbon_management-energy_balance-area_plot-AC-bus_carrier.png b/PyPSA/source/docs/assets/images/carbon_management-energy_balance-area_plot-AC-bus_carrier.png new file mode 100644 index 0000000000000000000000000000000000000000..6b8b6bfd3a01ac2cb71cc88b1ffde5fed3ab2650 --- /dev/null +++ b/PyPSA/source/docs/assets/images/carbon_management-energy_balance-area_plot-AC-bus_carrier.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:287ca41722822b53ae1e4964b9169c7c27856697b7efcb5814fb2d05f4eef8b2 +size 234275 diff --git a/PyPSA/source/docs/assets/images/carbon_management-energy_balance-area_plot.png b/PyPSA/source/docs/assets/images/carbon_management-energy_balance-area_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..e79f18504dfb3c87e2e508e7504c99c5f4408505 --- /dev/null +++ b/PyPSA/source/docs/assets/images/carbon_management-energy_balance-area_plot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5bae0a63c9b6388cd15c73c090259e5ad7f0c124db84cab0cda01ec7435b190 +size 226128 diff --git a/PyPSA/source/docs/assets/images/elec_s_256_lv1.09_Co2L-3H.png b/PyPSA/source/docs/assets/images/elec_s_256_lv1.09_Co2L-3H.png new file mode 100644 index 0000000000000000000000000000000000000000..cd2ec729674482066181eb9fbe243eb663e41aa5 --- /dev/null +++ b/PyPSA/source/docs/assets/images/elec_s_256_lv1.09_Co2L-3H.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca279f9a4d63cb4294264804201a467449a7aaef0b4ed34af0566352a9d6155d +size 765735 diff --git a/PyPSA/source/docs/assets/images/line-equivalent.png b/PyPSA/source/docs/assets/images/line-equivalent.png new file mode 100644 index 0000000000000000000000000000000000000000..a07e3ab959582cd3613a24ec4bd7692fc67ef23a Binary files /dev/null and b/PyPSA/source/docs/assets/images/line-equivalent.png differ diff --git a/PyPSA/source/docs/assets/images/lmp_and_line-loading.png b/PyPSA/source/docs/assets/images/lmp_and_line-loading.png new file mode 100644 index 0000000000000000000000000000000000000000..4d24d0b6beb6dcf86812cd3b2756edffc517fe98 --- /dev/null +++ b/PyPSA/source/docs/assets/images/lmp_and_line-loading.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e555a1a71a1dabd7034890062ebcf2acde53fc050dcb8df025835a325019a5eb +size 685754 diff --git a/PyPSA/source/docs/assets/images/load-distribution.png b/PyPSA/source/docs/assets/images/load-distribution.png new file mode 100644 index 0000000000000000000000000000000000000000..dcf34ae2f10d52145a16aa09c243615be9435408 --- /dev/null +++ b/PyPSA/source/docs/assets/images/load-distribution.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e537a967704aaa884eb730ed0e3947ac618f968e78ce9a78cdc394a16c74dcc6 +size 372118 diff --git a/PyPSA/source/docs/assets/images/p_max_pu.png b/PyPSA/source/docs/assets/images/p_max_pu.png new file mode 100644 index 0000000000000000000000000000000000000000..e5e56210a36d47c2f82e2fd60c5bf633da6e32e5 Binary files /dev/null and b/PyPSA/source/docs/assets/images/p_max_pu.png differ diff --git a/PyPSA/source/docs/assets/images/pypsa-animation.png b/PyPSA/source/docs/assets/images/pypsa-animation.png new file mode 100644 index 0000000000000000000000000000000000000000..f24d4c1fd06c7151eec2a9296446db99779d8a00 --- /dev/null +++ b/PyPSA/source/docs/assets/images/pypsa-animation.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:390a9df545e9888bc90bbf9e469bacf49ace65eac395bfaeefd848b2e5b83f47 +size 174645 diff --git a/PyPSA/source/docs/assets/images/pypsa-logo.png b/PyPSA/source/docs/assets/images/pypsa-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fe663fe0b4403e57382da04271553b6d0c408032 Binary files /dev/null and b/PyPSA/source/docs/assets/images/pypsa-logo.png differ diff --git a/PyPSA/source/docs/assets/images/reactive-power.png b/PyPSA/source/docs/assets/images/reactive-power.png new file mode 100644 index 0000000000000000000000000000000000000000..11f96b58e5c2ada5cca737bd6058afc04eb31f20 --- /dev/null +++ b/PyPSA/source/docs/assets/images/reactive-power.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7efe42f1a343ab41ed5d2747cb25330f9e3b5c546af99b1f30a098a2916a697 +size 663114 diff --git a/PyPSA/source/docs/assets/images/reserve-power-funding.gif b/PyPSA/source/docs/assets/images/reserve-power-funding.gif new file mode 100644 index 0000000000000000000000000000000000000000..3f2c403eef782fa88dc56e7009c9c4e1910c52cb Binary files /dev/null and b/PyPSA/source/docs/assets/images/reserve-power-funding.gif differ diff --git a/PyPSA/source/docs/assets/images/reserve-power-graph.png b/PyPSA/source/docs/assets/images/reserve-power-graph.png new file mode 100644 index 0000000000000000000000000000000000000000..b09f41a7c118377885711e74c5a09fb0ff1961dd Binary files /dev/null and b/PyPSA/source/docs/assets/images/reserve-power-graph.png differ diff --git a/PyPSA/source/docs/assets/images/scigrid-curtailment.png b/PyPSA/source/docs/assets/images/scigrid-curtailment.png new file mode 100644 index 0000000000000000000000000000000000000000..3b33a1e9b49009d074361d9d261fd3efb23f1324 --- /dev/null +++ b/PyPSA/source/docs/assets/images/scigrid-curtailment.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8ebddb48debecc25e25a7e616e84509b51298a6fdb72a43f735e41936991c11 +size 145590 diff --git a/PyPSA/source/docs/assets/images/stacked-gen.png b/PyPSA/source/docs/assets/images/stacked-gen.png new file mode 100644 index 0000000000000000000000000000000000000000..f5cb2946c2295b89e21f9820ca2d09c3a859d9af --- /dev/null +++ b/PyPSA/source/docs/assets/images/stacked-gen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6a24ddac246395745dd3f28192c2dc5b42a0d544381f2727aab3cff6f469537 +size 260509 diff --git a/PyPSA/source/docs/assets/images/stacked-gen_and_storage-scigrid.png b/PyPSA/source/docs/assets/images/stacked-gen_and_storage-scigrid.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd4b97d82c2a8cd482d24946f16842e373821ec --- /dev/null +++ b/PyPSA/source/docs/assets/images/stacked-gen_and_storage-scigrid.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7214950ba15535398829178716be5319d618cbe90a04e2eed8e008c1e850c321 +size 255325 diff --git a/PyPSA/source/docs/assets/images/statistics_advanced_usage.png b/PyPSA/source/docs/assets/images/statistics_advanced_usage.png new file mode 100644 index 0000000000000000000000000000000000000000..da161060c6cc5b4cba9fe57d971534a051437fd5 Binary files /dev/null and b/PyPSA/source/docs/assets/images/statistics_advanced_usage.png differ diff --git a/PyPSA/source/docs/assets/images/storage-scigrid.png b/PyPSA/source/docs/assets/images/storage-scigrid.png new file mode 100644 index 0000000000000000000000000000000000000000..68543d98a941a1980c9a8da952517785b3cadee4 --- /dev/null +++ b/PyPSA/source/docs/assets/images/storage-scigrid.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8588a9fec7259f348158aa51f0278595d45444a486723d7542aa38827c8459ab +size 181422 diff --git a/PyPSA/source/docs/assets/images/transformer-pi-equivalent-tap-hv.png b/PyPSA/source/docs/assets/images/transformer-pi-equivalent-tap-hv.png new file mode 100644 index 0000000000000000000000000000000000000000..c4f048688658d3c78150db49713ff2298178653b Binary files /dev/null and b/PyPSA/source/docs/assets/images/transformer-pi-equivalent-tap-hv.png differ diff --git a/PyPSA/source/docs/assets/images/transformer-pi-equivalent-tap-lv.png b/PyPSA/source/docs/assets/images/transformer-pi-equivalent-tap-lv.png new file mode 100644 index 0000000000000000000000000000000000000000..46a4618f43c1b1da64ec770693c16216cfae1438 Binary files /dev/null and b/PyPSA/source/docs/assets/images/transformer-pi-equivalent-tap-lv.png differ diff --git a/PyPSA/source/docs/assets/images/transformer-t-equivalent-tap-hv.png b/PyPSA/source/docs/assets/images/transformer-t-equivalent-tap-hv.png new file mode 100644 index 0000000000000000000000000000000000000000..50891bfbcaf864a11f35d4b0d2ab32774b049405 Binary files /dev/null and b/PyPSA/source/docs/assets/images/transformer-t-equivalent-tap-hv.png differ diff --git a/PyPSA/source/docs/assets/images/transformer-t-equivalent-tap-lv.png b/PyPSA/source/docs/assets/images/transformer-t-equivalent-tap-lv.png new file mode 100644 index 0000000000000000000000000000000000000000..86dab2909ff94a57b6619c33ed4d9de283d11593 Binary files /dev/null and b/PyPSA/source/docs/assets/images/transformer-t-equivalent-tap-lv.png differ diff --git a/PyPSA/source/docs/assets/interactive/ac_dc_meshed-energy_balance-area_iplot.html b/PyPSA/source/docs/assets/interactive/ac_dc_meshed-energy_balance-area_iplot.html new file mode 100644 index 0000000000000000000000000000000000000000..627fbe8e024956ef4fe7930d8d7d4e596bc86685 --- /dev/null +++ b/PyPSA/source/docs/assets/interactive/ac_dc_meshed-energy_balance-area_iplot.html @@ -0,0 +1,7 @@ + + + +
+
+ + diff --git a/PyPSA/source/docs/assets/interactive/carbon_management-energy_balance-area_iplot-AC-bus_carrier.html b/PyPSA/source/docs/assets/interactive/carbon_management-energy_balance-area_iplot-AC-bus_carrier.html new file mode 100644 index 0000000000000000000000000000000000000000..734cfa9e3e8a89e03989b945289e1f5466417f1a --- /dev/null +++ b/PyPSA/source/docs/assets/interactive/carbon_management-energy_balance-area_iplot-AC-bus_carrier.html @@ -0,0 +1,7 @@ + + + +
+
+ + diff --git a/PyPSA/source/docs/assets/interactive/carbon_management-energy_balance-area_iplot.html b/PyPSA/source/docs/assets/interactive/carbon_management-energy_balance-area_iplot.html new file mode 100644 index 0000000000000000000000000000000000000000..f8f78d2f52cc9d2fd813fd8c9acddbda28803a36 --- /dev/null +++ b/PyPSA/source/docs/assets/interactive/carbon_management-energy_balance-area_iplot.html @@ -0,0 +1,7 @@ + + + +
+
+ + diff --git a/PyPSA/source/docs/assets/javascripts/gurubase-widget.js b/PyPSA/source/docs/assets/javascripts/gurubase-widget.js new file mode 100644 index 0000000000000000000000000000000000000000..0a8b688e0df77508c68442184020bc3016b41caf --- /dev/null +++ b/PyPSA/source/docs/assets/javascripts/gurubase-widget.js @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: PyPSA Contributors +// +// SPDX-License-Identifier: MIT + +document.addEventListener("DOMContentLoaded", () => { + // Load the GuruBase widget + const guruScript = document.createElement("script"); + guruScript.src = "https://widget.gurubase.io/widget.latest.min.js"; + guruScript.defer = true; + guruScript.id = "guru-widget-id"; + + // Configure widget settings + const widgetSettings = { + "data-widget-id": "3a8zez8jVEVRucKLlyrKs0hKb2-xxpTg4sZkfwY17JM", + "data-text": "Ask AI", + "data-margins": JSON.stringify({ bottom: "30px", right: "30px" }), + "data-light-mode": "auto", + "data-name": "PyPSA", + "data-icon-url": "https://raw.githubusercontent.com/PyPSA/PyPSA/master/docs/assets/logo/logo.svg", + "data-bg-color": "#D10A49", + "data-overlap-content": "true", + "data-tooltip": + "Ask questions about PyPSA. Please note that questions and answers are visible anonymously to the PyPSA team and GuruBase.", + "data-tooltip-side": "left", + + }; + + // Add widget settings as data attributes + Object.entries(widgetSettings).forEach(([key, value]) => { + guruScript.setAttribute(key, value); + }); + + document.body.appendChild(guruScript); +}); diff --git a/PyPSA/source/docs/assets/javascripts/matjax.js b/PyPSA/source/docs/assets/javascripts/matjax.js new file mode 100644 index 0000000000000000000000000000000000000000..eccb83102666739fc3f21697720fc0ef845607b2 --- /dev/null +++ b/PyPSA/source/docs/assets/javascripts/matjax.js @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: PyPSA Contributors +// +// SPDX-License-Identifier: MIT + +window.MathJax = { + tex: { + inlineMath: [["\\(", "\\)"]], + displayMath: [["\\[", "\\]"]], + processEscapes: true, + processEnvironments: true + }, + options: { + ignoreHtmlClass: ".*|", + processHtmlClass: "arithmatex" + } + }; + + document$.subscribe(() => { + + + MathJax.startup.output.clearCache() + MathJax.typesetClear() + MathJax.texReset() + MathJax.typesetPromise() + }) diff --git a/PyPSA/source/docs/assets/javascripts/readthedocs.js b/PyPSA/source/docs/assets/javascripts/readthedocs.js new file mode 100644 index 0000000000000000000000000000000000000000..bafd91548b527319182d106efedca711dfdd3af6 --- /dev/null +++ b/PyPSA/source/docs/assets/javascripts/readthedocs.js @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: PyPSA Contributors +// +// SPDX-License-Identifier: MIT + +// Use CustomEvent to generate the version selector +document.addEventListener( + "readthedocs-addons-data-ready", + function (event) { + const config = event.detail.data(); + const versioning = ` +
+ + + +
`; + + document.querySelector(".md-header__topic").insertAdjacentHTML("beforeend", versioning); + }); diff --git a/PyPSA/source/docs/assets/logo/logo-primary-dark.png b/PyPSA/source/docs/assets/logo/logo-primary-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..83e89a47740b46d69180e4a25737e444b54800c9 --- /dev/null +++ b/PyPSA/source/docs/assets/logo/logo-primary-dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:efa5ed1a118d02b3862b208d173f82dc424133620f946518f179687233bca9eb +size 267802 diff --git a/PyPSA/source/docs/assets/logo/logo-primary-dark.svg b/PyPSA/source/docs/assets/logo/logo-primary-dark.svg new file mode 100644 index 0000000000000000000000000000000000000000..6d40b9cc439f82c8eb581b7b57e7b450a33605da --- /dev/null +++ b/PyPSA/source/docs/assets/logo/logo-primary-dark.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PyPSA/source/docs/assets/logo/logo-primary-light-white-bg.png b/PyPSA/source/docs/assets/logo/logo-primary-light-white-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..1a1b9dda0f15e48c0b003cc5386c28fb9f774ac1 --- /dev/null +++ b/PyPSA/source/docs/assets/logo/logo-primary-light-white-bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95374169789c67d17f6f1b9a523f8df7d8526f3fa8c62970f48b8b5701154f29 +size 294106 diff --git a/PyPSA/source/docs/assets/logo/logo-primary-light.png b/PyPSA/source/docs/assets/logo/logo-primary-light.png new file mode 100644 index 0000000000000000000000000000000000000000..f9900918d2e2ef572e67275c49a6eea9a90fdd32 --- /dev/null +++ b/PyPSA/source/docs/assets/logo/logo-primary-light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c285d796ca15eb2e170b8517110cb2f29eb29b2aeda7c14a571554132dbc00b +size 282063 diff --git a/PyPSA/source/docs/assets/logo/logo-primary-light.svg b/PyPSA/source/docs/assets/logo/logo-primary-light.svg new file mode 100644 index 0000000000000000000000000000000000000000..ab89ab451f0d6099c633da7b487051973703d30e --- /dev/null +++ b/PyPSA/source/docs/assets/logo/logo-primary-light.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PyPSA/source/docs/assets/logo/logo-secondary-dark.png b/PyPSA/source/docs/assets/logo/logo-secondary-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..aad5cd2fb4574b773cc30a679282faf3357e56b3 --- /dev/null +++ b/PyPSA/source/docs/assets/logo/logo-secondary-dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:caada7143aefebab323487a2ea8999f558d6b8eeb1498d896695fe9e045869ff +size 161472 diff --git a/PyPSA/source/docs/assets/logo/logo-secondary-dark.svg b/PyPSA/source/docs/assets/logo/logo-secondary-dark.svg new file mode 100644 index 0000000000000000000000000000000000000000..ed2ed7a26063c08b51c9a9b5a351f6fedb915f82 --- /dev/null +++ b/PyPSA/source/docs/assets/logo/logo-secondary-dark.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PyPSA/source/docs/assets/logo/logo-secondary-light.png b/PyPSA/source/docs/assets/logo/logo-secondary-light.png new file mode 100644 index 0000000000000000000000000000000000000000..76d97e763cefa84b82d1de9fb6025515a2e28d1c --- /dev/null +++ b/PyPSA/source/docs/assets/logo/logo-secondary-light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0023f7ff56fcc2d7d0924d9a9c56ec7ee6738693b73ce0dc94e0c592dff9bab1 +size 165994 diff --git a/PyPSA/source/docs/assets/logo/logo-secondary-light.svg b/PyPSA/source/docs/assets/logo/logo-secondary-light.svg new file mode 100644 index 0000000000000000000000000000000000000000..c7db9debbe9d2fd6cc44e0a138ee8693e0f8e0ef --- /dev/null +++ b/PyPSA/source/docs/assets/logo/logo-secondary-light.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PyPSA/source/docs/assets/logo/logo-white-bg.png b/PyPSA/source/docs/assets/logo/logo-white-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..cb88f23e2d329a7330427bd595544c2d20c45f8c --- /dev/null +++ b/PyPSA/source/docs/assets/logo/logo-white-bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a0b85444e0e777c98e7821af8fd8dd7b4db55b9d4f494dfd475f733a54e104d +size 123887 diff --git a/PyPSA/source/docs/assets/logo/logo.png b/PyPSA/source/docs/assets/logo/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..04aede109bc2ef92a95d2a96c4f22835bde402c9 --- /dev/null +++ b/PyPSA/source/docs/assets/logo/logo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee2cdcfb1ed6f5519e15438ba8f908cd1df082817a34dfe372b3d809f2692677 +size 118023 diff --git a/PyPSA/source/docs/assets/logo/logo.svg b/PyPSA/source/docs/assets/logo/logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..efb2c8cf1a9f66b545f8556faf0d9de8fbbdc670 --- /dev/null +++ b/PyPSA/source/docs/assets/logo/logo.svg @@ -0,0 +1,52 @@ + + diff --git a/PyPSA/source/docs/assets/overrides/main.html b/PyPSA/source/docs/assets/overrides/main.html new file mode 100644 index 0000000000000000000000000000000000000000..7b80a42d5f90ba580e4cb5d7067252cbcfe238a0 --- /dev/null +++ b/PyPSA/source/docs/assets/overrides/main.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} + + +{% block announce %} +🎉 PyPSA 1.0 is here! Check out what's new and the new landing page. 🎉 +{% endblock %} + + +{% block outdated %} + You're not viewing the latest version. + + Click here to go to latest. + +{% endblock %} + + +{% block site_meta %} +{{ super() }} + +{% endblock %} + + +{% block content %} +{% if page.nb_url %} + + {% include ".icons/material/download.svg" %} + +{% endif %} + +{{ super() }} +{% endblock content %} diff --git a/PyPSA/source/docs/assets/stylesheets/extra.css b/PyPSA/source/docs/assets/stylesheets/extra.css new file mode 100644 index 0000000000000000000000000000000000000000..c361fd229b2dc34863577798ace3349c8f1b90c4 --- /dev/null +++ b/PyPSA/source/docs/assets/stylesheets/extra.css @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: PyPSA Contributors + * + * SPDX-License-Identifier: MIT + */ + +:root > * { + --md-primary-fg-color: #292F36; + --md-primary-fg-color--light: #ECB7B7; + --md-primary-fg-color--dark: #90030C; + --md-primary-bg-color: white; + --md-accent-fg-color: rgba(209, 10, 73, 0.5); + --md-typeset-a-color: #d10a49; +} + +.md-banner { + text-align: center; + background-color: #0A8754; + --md-typeset-a-color: #292F36; + --md-accent-fg-color: white; +} + +/* Custom admonition icons */ +:root { + --md-admonition-icon--deprecation: url('data:image/svg+xml;charset=utf-8,api-off') +} +.md-typeset .admonition.deprecation, +.md-typeset details.deprecation { + border-color: #ff9101; +} +.md-typeset .deprecation > .admonition-title, +.md-typeset .deprecation > summary { + background-color: #fff5eb; +} +.md-typeset .deprecation > .admonition-title::before, +.md-typeset .deprecation > summary::before { + background-color: #ff9101; + -webkit-mask-image: var(--md-admonition-icon--deprecation); + mask-image: var(--md-admonition-icon--deprecation); +} + +/* Hide In/Out prompts */ +.md-typeset .jp-InputPrompt, +.md-typeset .jp-OutputPrompt, +.md-typeset .prompt { display: none !important; } + +/* Clamp notebook width to Material content width */ +.md-content__inner :is(.jp-Notebook, .nbconvert-document) { + max-width: var(--md-typeset-width) !important; + margin-inline: auto !important; + width: 100% !important; +} + +/* Use Material’s code font in notebook code (inputs + outputs) */ +.md-typeset :is(.jp-Notebook, .nbconvert-document) pre { + font-family: var(--md-code-font) !important; + font-size: 15px !important; +} + +/* Remove the frame around input cells */ +.md-typeset :is(.jp-InputArea, .jp-InputArea-editor, .jp-Cell-inputWrapper, .cell .input_area, .CodeMirror, .cm-editor) { + border: 0 !important; + box-shadow: none !important; + outline: 0 !important; + background: transparent !important; +} + +/* Simple badge styles for shortcodes */ +.mdx-badge { + display: inline-flex; + align-items: center; + font-size: 0.85em; + vertical-align: middle; +} + +.mdx-badge__text { + padding: 0.125rem 0.375rem; + background: var(--md-code-bg-color, #f5f5f5); + border-radius: 0.125rem; + color: var(--md-typeset-a-color, #d10a49); +} + +.mdx-badge a { + color: inherit; + text-decoration: none; +} + +.mdx-badge a:hover { + opacity: 0.8; +} diff --git a/PyPSA/source/docs/contributing/contributing.md b/PyPSA/source/docs/contributing/contributing.md new file mode 100644 index 0000000000000000000000000000000000000000..a263fcc84fa7470aecf6b20af5a44477ad7cdb93 --- /dev/null +++ b/PyPSA/source/docs/contributing/contributing.md @@ -0,0 +1,172 @@ + + +# Instructions for Contributing + +First of all, thank you for thinking about contributing to PyPSA! + +We strongly welcome anyone interested in PyPSA and all its related projects, be it +with new ideas, suggestions, submitting bug reports or contributing code changes. + +**How to contribute** + +* [Code Contributions](#code): Implement new features, fix bugs, or improve the performance. +* [Documentation Contributions](#documentation): Improve the documentation by adding new sections, fixing typos, or clarifying existing content. +* [Example Contributions](#examples): Showcase your work, which could be useful for others. + +**Where to go for help** + +* To **discuss** with other PyPSA users, organise projects, share news, and get in touch with the community you can use the [Discord server](https://discord.gg/AnuJBk23FU). +* For **troubleshooting**, please check the [troubleshooting](troubleshooting.md) in the documentation. +* For **guidelines to contribute** to PyPSA, stay right here. + +## Code + +**Contribution workflow in a nutshell** + +1. Fork the repository on GitHub +2. Clone your fork: `git clone https://github.com//PyPSA.git` +3. Fetch the upstream tags `git fetch --tags https://github.com/PyPSA/PyPSA.git` +4. Install with dependencies in editable mode: `pip install -e .[dev]` +5. Setup linter and formatter, e.g `pre-commit install` (see [Code Style](#style)) +6. Write your code (preferably on a new branch) +7. Run tests: `pytest` (see [Testing](#testing)) +8. Push your changes to your fork and create a pull request on GitHub + + + +### Style + +**pre-commit** + +We run a couple of tools via [pre-commit](https://pre-commit.com) to ensure a +consistent code style and to catch common programming errors or bad practices before +they are committed. Don't worry, you can just start coding and the pre-commit will +tell you if something is not right. + +It is already installed with the development dependencies, but you can also install it +manually via `pip install pre-commit` or `conda install -c conda-forge pre-commit`. + +To use it automatically before every commit (recommended), just run once: + +``` bash +pre-commit install +``` + +This will automatically check the changes which are staged before you commit them. + +To manually run it, use: + +``` bash +pre-commit run --all +``` + +This will check all files in the repository. + +**Ruff** + +One of the tools that is run by pre-commit is [Ruff](https://docs.astral.sh/ruff), +which is our linter and formatter. It combines common tools like Flake8, Black, etc. +Besides pre-commit, you can also run it via your CLI (see [Ruff installation](https://docs.astral.sh/ruff/installation/)) +or IDE (e.g. VSCode [plugin](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff)). +This will help you to keep your code clean and consistent already during development. + +Ruff is also already installed with the development dependencies, but you can also install it +manually using `pip install ruff`. + +To use the linter in your CLI, run: + +``` bash +ruff check . --fix +``` + +This will check all files in the repository and gives you hints on what to improve. The +`--fix` flag will also automatically fix some of the issues, if possible. Some +issues need to be fixed manually. + +And to run the formatter, use: + +``` bash +ruff format . +``` + +This will format all the files in the repository and immediately apply the changes to +them. It is basically [the same](https://docs.astral.sh/ruff/faq/#how-does-ruffs-formatter-compare-to-black) +as Black. + +!!! note + + It is not mandatory to use either Ruff or pre-commit. We will also be running it in our CI/CD pipeline. But it's highly recommended, to make everyone's life easier. + +### Testing + +Unit testing is performed with pytest which is installed with the development dependencies. + +The tests can be found in the `test/` folder and can be run with: + +``` bash +pytest +``` + +Or to run individual tests: + +``` bash +pytest test_lpf_against_pypower.py +``` + +Power flow is tested against PYPOWER (the Python implementation of MATPOWER) +and pandapower. + +Unit testing is also performed in the CI/CD pipeline, similar to the linting and formatting. + +## Documentation + +The documentation is built with [MkDocs](https://www.mkdocs.org) and the +[Material for MkDocs](https://squidfunk.github.io/mkdocs-material) theme. + +We strive to keep documentation useful and up to date for all PyPSA users. If +you encounter an area where documentation is not available or insufficient, we +very much welcome your contribution. + +!!! note + + If you are not familiar with Markdown, consult the following [quick guide](https://www.markdownguide.org/basic-syntax/). + +For bigger changes, we recommend to draft them locally. Just follow the steps in +[Code Contributions](#code) to set up your local environment. In addition you can: + +1. Also install the documentation dependencies via `pip install -e .[docs]`. +2. Make your changes in the corresponding `.md` file in the `docs` directory. +3. Compile your changes by running the following command in your terminal in the `doc` folder: `mkdocs serve` + +!!! info + + If you are not editing example notebooks (`.ipynb` files), you may wanna comment out + the `mkdocs-jupyter` plugin in the `mkdocs.yml` file, to make the build faster. + In any case, you will end up with a link to a local server (e.g. `http://127.0.0.1:8000`). + +For simple changes, you can also edit the documentation directly on GitHub: + +1. If you are on the documentation page, click on the little paper icon with the pen on the top right. This will bring you straight to the source file. +2. Make your changes in the file. +3. Commit your changes and create a pull request. + +## Examples + +Nice examples are always welcome. + +You can even submit your Jupyter notebook (`.ipynb`) directly +as an example. Please run the linter (see [Code Style](#style)) to ensure +that the notebook is clean and metadata is removed. + +Then for every notebook: + +1. Write the notebook (let's call it `foo.ipynb`) and place it + in `docs/examples/foo.ipynb`. + +2. Reference it in the configuration file `docs/mkdocs.yml` where the other examples are listed. + +3. Commit your changes and create a pull request. diff --git a/PyPSA/source/docs/contributing/contributors.md b/PyPSA/source/docs/contributing/contributors.md new file mode 100644 index 0000000000000000000000000000000000000000..c3c1950f2cd1104c3a60f7d81a03beb11032fb2a --- /dev/null +++ b/PyPSA/source/docs/contributing/contributors.md @@ -0,0 +1,28 @@ + + +# Contributors + +Various people have contributed to PyPSA over the years. We would like to +thank all of them for their contributions, be it code, documentation, bug +reports, or any other kind of support! + +PyPSA is maintained by the [Department of Digital Transformation in Energy +Systems](https://tu.berlin/en/ensys) at the [Technical University of +Berlin](https://www.tu.berlin). The project is currently supported by the +[German Research Foundation](https://www.dfg.de/en/) (grant number [`528775426`](https://gepris.dfg.de/gepris/projekt/528775426)) +and has received contributions from a variety of other projects. Previous versions were developed at the [Karlsruhe Institute of Technology](http://www.kit.edu/english/index.php) funded by the [Helmholtz Association](https://www.helmholtz.de/en/), and at [FIAS](https://fias.uni-frankfurt.de/) funded by the [German Federal Ministry for Education and Research (BMBF)](https://www.bmbf.de/bmbf/en/). + +
+ +
+ +!!! info "How to contribute" + + If you would like to contribute to PyPSA, please check out the + [:material-bookshelf: Contributing Guide](../contributing/contributing.md). diff --git a/PyPSA/source/docs/examples/3-node-cem.ipynb b/PyPSA/source/docs/examples/3-node-cem.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..7a5e7bbb08f6556a017a86a6abb1757ff36278c6 --- /dev/null +++ b/PyPSA/source/docs/examples/3-node-cem.ipynb @@ -0,0 +1,812 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "087b95ab", + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "af1141d4", + "metadata": {}, + "source": [ + "# 3-Node Capacity Expansion\n", + "\n", + "This example builds on the [single-node capacity expansion example]() and extends it to three regions in Australia (New South Wales, Victoria, and South Australia), which can be connected via transmission links.\n", + "The purpose is to illustrate how to set up a capacity expansion model with multiple interconnected regions, where the model can optimize for trade-offs between generation capacity, storage, and transmission infrastructure.\n", + "\n", + "In this first block, we import the necessary libraries and set up some basic functions (e.g. annuity factors) and constants (e.g. temporal resolution and solver):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a6ec620", + "metadata": {}, + "outputs": [], + "source": [ + "import cartopy.crs as ccrs\n", + "import geopandas as gpd\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import pypsa\n", + "from pypsa.common import annuity\n", + "\n", + "RESOLUTION = 3 # 3-hourly\n", + "SOLVER = \"highs\" # or 'gurobi'" + ] + }, + { + "cell_type": "markdown", + "id": "adf4a097", + "metadata": {}, + "source": [ + "In a fresh PyPSA network instance, we set the coordinates of each state's capital (Sydney, Melbourne, and Adelaide) as the coordinates of the electricity buses." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f301c2fa", + "metadata": {}, + "outputs": [], + "source": [ + "n = pypsa.Network()\n", + "\n", + "REGIONS = pd.Index([\"VIC\", \"SA\", \"NSW\"])\n", + "LAT = [-37.81, -34.93, -33.87]\n", + "LON = [145.01, 138.63, 151.20]\n", + "n.add(\"Bus\", REGIONS, x=LON, y=LAT);" + ] + }, + { + "cell_type": "markdown", + "id": "993cae45", + "metadata": {}, + "source": [ + "We also define a range of carriers (i.e. energy carriers or technologies) that we will use in the model. This will be useful later when plotting optimisation results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1786cd1d", + "metadata": {}, + "outputs": [], + "source": [ + "CARRIERS = {\n", + " \"solar\": \"gold\",\n", + " \"wind\": \"steelblue\",\n", + " \"load shedding\": \"indianred\",\n", + " \"battery charger\": \"lightgreen\",\n", + " \"battery discharger\": \"lightgreen\",\n", + " \"battery storage\": \"grey\",\n", + " \"battery\": \"grey\",\n", + " \"electrolysis\": \"violet\",\n", + " \"turbine\": \"violet\",\n", + " \"hydrogen storage\": \"orchid\",\n", + " \"hydrogen\": \"orchid\",\n", + " \"AC\": \"black\",\n", + " \"HVDC\": \"lightseagreen\",\n", + " \"load\": \"slategrey\",\n", + "}\n", + "\n", + "n.add(\n", + " \"Carrier\",\n", + " CARRIERS.keys(),\n", + " color=CARRIERS.values(),\n", + ");" + ] + }, + { + "cell_type": "markdown", + "id": "9e8173b5", + "metadata": {}, + "source": [ + "As data, we have given:\n", + "\n", + "- `australia-example-p_set.csv` - a time series of electricity demand in each region in 2011 from the [Australian Energy Market Operator (AEMO)](https://aemo.com.au/energy-systems/electricity/national-electricity-market-nem/data-nem/aggregated-data) \n", + "- `australia-example-p_max_pu.csv` - a time series of capacity factors in 2011 for onshore wind and solar PV for each region from [model.energy](https://model.energy/)\n", + "- `australia-example-shapes.geojson` - a geographical representation of the three regions from the [GeoBoundaries](https://www.geoboundaries.org/) project\n", + "\n", + "The timestamps in the datasets are given in [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time).\n", + "\n", + "The datasets have been obtained from the following code:\n", + "\n", + "```python\n", + "import pandas as pd\n", + "import geopandas as gpd\n", + "\n", + "REGIONS = [\"VIC\", \"SA\", \"NSW\"]\n", + "\n", + "# 1. Capacity factors\n", + "capacity_factors = {\n", + " \"VIC\": \"https://model.energy/data/time-series-c3ec7795e4d52617defed46495899b50.csv\",\n", + " \"SA\": \"https://model.energy/data/time-series-efcb605660bd6f9db75fc7c5b3fd5379.csv\",\n", + " \"NSW\": \"https://model.energy/data/time-series-ef16f3da1c24442d7efac9d7c4a0ce05.csv\",\n", + "}\n", + "\n", + "p_max_pu = pd.concat({\n", + " c: pd.read_csv(\n", + " url,\n", + " index_col=0,\n", + " parse_dates=True,\n", + " )\n", + " for c, url in capacity_factors.items()\n", + "}, axis=1)\n", + "\n", + "p_max_pu.to_csv(\"australia-example-p_max_pu.csv\")\n", + "\n", + "# 2. Electricity demand\n", + "so = {'User-Agent': 'Mozilla/5.0'}\n", + "year_months = [f\"2011{m:02d}\" for m in range(1, 13)] + [\"201201\"] # All months in 2011 + Jan 2012\n", + "\n", + "p_set = pd.concat([\n", + " pd.concat([\n", + " pd.read_csv(\n", + " f\"https://aemo.com.au/aemo/data/nem/priceanddemand/PRICE_AND_DEMAND_{ym}_{region}1.csv\",\n", + " storage_options=so,\n", + " index_col=1,\n", + " parse_dates=True\n", + " )[\"TOTALDEMAND\"].rename(region)\n", + " for ym in year_months\n", + " ])\n", + " for region in REGIONS\n", + "], axis=1)\n", + "\n", + "p_set = p_set.resample(\"1h\").mean().ffill(inplace=True)\n", + "\n", + "p_set = p_set.tz_localize(\n", + " \"Australia/NSW\", ambiguous=\"NaT\", nonexistent=\"shift_backward\"\n", + ").tz_convert(\"UTC\").tz_localize(None).reindex(index=p_max_pu.index)\n", + "\n", + "p_set.to_csv(\"australia-example-p_set.csv\")\n", + "\n", + "# 3. Geographical shapes\n", + "url = \"https://media.githubusercontent.com/media/wmgeolab/geoBoundaries/bdfb316b1fdcac1473051979152cac0943c549fa/releaseData/gbOpen/AUS/ADM1/geoBoundaries-AUS-ADM1-all.zip\"\n", + "\n", + "gdf = gpd.read_file(url, layer=\"geoBoundaries-AUS-ADM1_simplified\")\n", + "gdf.index = gdf.shapeISO.str.split(\"-\").str[1]\n", + "gdf = gdf.loc[REGIONS]\n", + "gdf.to_file(\"australia-example-shapes.geojson\", driver=\"GeoJSON\")\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "58a23b1a", + "metadata": {}, + "source": [ + "We load this data and set the snapshots of the network to the timestamps of the time series data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1bbeb8f2", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://tubcloud.tu-berlin.de/s/oPQbAebrciFBZP2/download/australia-example-p_max_pu.csv\"\n", + "p_max_pu = pd.read_csv(url, index_col=0, parse_dates=True, header=[0, 1])\n", + "display(p_max_pu.head(3))\n", + "\n", + "url = \"https://tubcloud.tu-berlin.de/s/8C6d7z3HxE7yZi9/download/australia-example-p_set.csv\"\n", + "p_set = pd.read_csv(url, index_col=0, parse_dates=True)\n", + "display(p_set.head(3))\n", + "\n", + "n.set_snapshots(p_max_pu.index)" + ] + }, + { + "cell_type": "markdown", + "id": "88a0e313", + "metadata": {}, + "source": [ + "Then, we add the loads for each region:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "960146d4", + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Load\", REGIONS, suffix=\" load\", bus=REGIONS, p_set=p_set, carrier=\"load\");" + ] + }, + { + "cell_type": "markdown", + "id": "ad06c539", + "metadata": {}, + "source": [ + "For the wind and solar generators, we pass the capacity factors as `p_max_pu` and add the annuitized capital costs. Here, we assume a capital cost of 2000 AUD/kW for wind and 700 AUD/kW for solar, with a discount rate of 5% and a lifetime of 20 years. Note again that the capacity is given in MW, so we multiply the capital costs by 1000 to convert them to kW.\n", + "\n", + "As a trick for modelling curtailment, we add a small `marginal_cost` to the wind generators to nudge those to be curtailed first before solar." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14b888cf", + "metadata": {}, + "outputs": [], + "source": [ + "p_max_pu_wind = p_max_pu.xs(\"onwind\", level=1, axis=1).rename(\n", + " columns=lambda s: s + \" wind\"\n", + ")\n", + "\n", + "n.add(\n", + " \"Generator\",\n", + " p_max_pu_wind.columns,\n", + " bus=REGIONS,\n", + " p_max_pu=p_max_pu_wind,\n", + " p_nom_extendable=True,\n", + " capital_cost=annuity(0.05, 30) * 2_000_000,\n", + " marginal_cost=0.5,\n", + " carrier=\"wind\",\n", + ")\n", + "\n", + "p_max_pu_solar = p_max_pu.xs(\"solar\", level=1, axis=1).rename(\n", + " columns=lambda s: s + \" solar\"\n", + ")\n", + "\n", + "n.add(\n", + " \"Generator\",\n", + " p_max_pu_solar.columns,\n", + " bus=REGIONS,\n", + " p_max_pu=p_max_pu_solar,\n", + " p_nom_extendable=True,\n", + " capital_cost=annuity(0.05, 30) * 700_000,\n", + " carrier=\"solar\",\n", + ");" + ] + }, + { + "cell_type": "markdown", + "id": "3d1b4690", + "metadata": {}, + "source": [ + "We also add a generator with a very high marginal cost to represent load shedding with a value of lost load (VoLL) of 3000 AUD/MWh." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a0cccfa", + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Generator\",\n", + " REGIONS,\n", + " suffix=\" load shedding\",\n", + " bus=REGIONS,\n", + " p_nom=p_set.max(),\n", + " marginal_cost=3000,\n", + " carrier=\"load shedding\",\n", + ");" + ] + }, + { + "cell_type": "markdown", + "id": "ad0ef23c", + "metadata": {}, + "source": [ + "To save some computation time, we only sample every third snapshot, which corresponds to a temporal resolution of 3 hours. Note that the snapshot weightings have to be adjusted accordingly.\n", + "\n", + "Let's give this basic model a first spin:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ab41bdb", + "metadata": {}, + "outputs": [], + "source": [ + "n.set_snapshots(n.snapshots[::RESOLUTION])\n", + "n.snapshot_weightings.loc[:, :] = RESOLUTION\n", + "\n", + "n.optimize(solver_name=SOLVER, log_to_console=False)" + ] + }, + { + "cell_type": "markdown", + "id": "a92a09be", + "metadata": {}, + "source": [ + "In this first run, every region is isolated (no transmission) and has no storage (no batteries or hydrogen storage).\n", + "\n", + "Around 2.5% of the load is shed; roughly 50 TWh come from solar and 80 TWh from wind. The average cost of 226.57 AUD/MWh is quite high." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97319b87", + "metadata": {}, + "outputs": [], + "source": [ + "display(n.statistics.energy_balance().div(1e6).round(2).sort_values()) # TWh\n", + "\n", + "average_cost = (\n", + " (n.statistics.capex().sum() + n.statistics.opex().sum())\n", + " / n.loads_t.p_set.sum().sum()\n", + " / RESOLUTION\n", + ")\n", + "\n", + "display(f\"Average cost: {average_cost:.2f} AUD/MWh\")" + ] + }, + { + "cell_type": "markdown", + "id": "458d0c87", + "metadata": {}, + "source": [ + "The statistics and plotting functionality of PyPSA can be used to create a more detailed overview of the results on a map. For example, we can plot the energy balance and energy mix of each region as pie charts, where the upper half shows the generation and the lower half the consumption. We can also add other geographical layers from other objects to the map, such as the geographical shapes of the regions colored by the average locational marginal price (LMP)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ba51c09", + "metadata": {}, + "outputs": [], + "source": [ + "# Create an empty figure with a Cartopy projection\n", + "crs = ccrs.PlateCarree()\n", + "fig, ax = plt.subplots(figsize=(10, 6), subplot_kw={\"projection\": crs})\n", + "\n", + "# Use the energy balance statistics to prepare the bus sizes and plot the network\n", + "bus_size = n.statistics.energy_balance(groupby=[\"bus\", \"carrier\"]).droplevel(\n", + " \"component\"\n", + ")\n", + "n.plot(ax=ax, bus_size=bus_size / 4e7, margin=0.75, bus_split_circle=True)\n", + "\n", + "# Load the shapefiles and add them to the plot colored by average LMP\n", + "prices = n.buses_t.marginal_price.mean()\n", + "gdf = gpd.read_file(\n", + " \"https://tubcloud.tu-berlin.de/s/4n9PegitETESBGR/download/australia-example-shapes.geojson\"\n", + ").set_index(\"index\")\n", + "gdf.to_crs(crs).plot(ax=ax, column=prices, cmap=\"RdYlGn_r\", vmin=90, vmax=240)\n", + "\n", + "# Add a legend for the LMP color scale\n", + "norm = plt.Normalize(vmin=90, vmax=240)\n", + "sm = plt.cm.ScalarMappable(cmap=\"RdYlGn_r\", norm=norm)\n", + "fig.colorbar(sm, ax=ax, label=\"LMP [AUD/MWh]\", shrink=0.4)" + ] + }, + { + "cell_type": "markdown", + "id": "8278c0de", + "metadata": {}, + "source": [ + "Now, we add some storage options to each model region:\n", + "- For **battery storage**, we separately model the battery inverter (charger and discharger) and the battery storage itself. This has the advantage that the power-to-energy ratio can be optimised.\n", + "- For **hydrogen storage**, we model the electrolyser, the hydrogen storage, and the turbine for re-electrification separately, so that these components can be independently sized." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f9471d84", + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Bus\", REGIONS, suffix=\" hydrogen\", carrier=\"hydrogen\", x=LON, y=LAT)\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " REGIONS,\n", + " suffix=\" electrolysis\",\n", + " bus0=REGIONS,\n", + " bus1=pd.Index(REGIONS) + \" hydrogen\",\n", + " carrier=\"electrolysis\",\n", + " p_nom_extendable=True,\n", + " efficiency=0.7,\n", + " capital_cost=annuity(0.05, 25) * 2_500_000,\n", + ")\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " REGIONS,\n", + " suffix=\" turbine\",\n", + " bus0=pd.Index(REGIONS) + \" hydrogen\",\n", + " bus1=REGIONS,\n", + " carrier=\"turbine\",\n", + " p_nom_extendable=True,\n", + " efficiency=0.4,\n", + " capital_cost=annuity(0.05, 25) * 2_000_000,\n", + ")\n", + "\n", + "n.add(\n", + " \"Store\",\n", + " REGIONS,\n", + " suffix=\" hydrogen storage\",\n", + " bus=pd.Index(REGIONS) + \" hydrogen\",\n", + " carrier=\"hydrogen storage\",\n", + " capital_cost=annuity(0.05, 30) * 80,\n", + " e_nom_extendable=True,\n", + " e_cyclic=True,\n", + ")\n", + "\n", + "n.add(\"Bus\", REGIONS, suffix=\" battery\", carrier=\"battery\", x=LON, y=LAT)\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " REGIONS,\n", + " suffix=\" battery charger\",\n", + " bus0=REGIONS,\n", + " bus1=REGIONS + \" battery\",\n", + " carrier=\"battery charger\",\n", + " p_nom_extendable=True,\n", + " efficiency=0.95,\n", + " capital_cost=annuity(0.05, 10) * 300_000,\n", + ")\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " REGIONS,\n", + " suffix=\" battery discharger\",\n", + " bus0=REGIONS + \" battery\",\n", + " bus1=REGIONS,\n", + " carrier=\"battery discharger\",\n", + " p_nom_extendable=True,\n", + " efficiency=0.95,\n", + ")\n", + "\n", + "n.add(\n", + " \"Store\",\n", + " REGIONS,\n", + " suffix=\" battery storage\",\n", + " bus=REGIONS + \" battery\",\n", + " carrier=\"battery storage\",\n", + " capital_cost=annuity(0.05, 25) * 250_000,\n", + " e_nom_extendable=True,\n", + " e_cyclic=True,\n", + ");" + ] + }, + { + "cell_type": "markdown", + "id": "ca3cd48b", + "metadata": {}, + "source": [ + "For the battery storage, we need to take special care of the inverter. As the same component can be used for charging and discharging, the capacities of our battery charger and discharger component must be linked.\n", + "This is not done automatically by PyPSA, so we have to add an extra constraint, which we can pass as `extra_functionality` to the optimisation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "889ae8ab", + "metadata": {}, + "outputs": [], + "source": [ + "def battery_constraint(n: pypsa.Network, sns: pd.Index) -> None:\n", + " \"\"\"Constraint to ensure that the nominal capacity of battery chargers and dischargers are in a fixed ratio.\"\"\"\n", + " dischargers_i = n.links[n.links.index.str.contains(\" discharger\")].index\n", + " chargers_i = n.links[n.links.index.str.contains(\" charger\")].index\n", + "\n", + " eff = n.links.efficiency[dischargers_i].values\n", + " lhs = n.model[\"Link-p_nom\"].loc[chargers_i]\n", + " rhs = n.model[\"Link-p_nom\"].loc[dischargers_i] * eff\n", + "\n", + " n.model.add_constraints(lhs == rhs, name=\"Link-charger_ratio\")\n", + "\n", + "\n", + "n.optimize(\n", + " solver_name=SOLVER, log_to_console=False, extra_functionality=battery_constraint\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "a557465a", + "metadata": {}, + "source": [ + "A look at the new energy balance shows use of both battery and hydrogen storage, as well as a shift from wind to solar generation compared to the previous run.\n", + "In fact, overall, we see more generation from wind and solar than the load, in order to compansate for the losses in the storage systems.\n", + "There is a difference of around 5 TWh between power consumption and injection of the battery and a 15 TWh difference between electrolyser electricity consumption and turbine electricity production. Yet, overall, average costs are reduced drastically to 100.51 AUD/MWh." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "595fa46a", + "metadata": {}, + "outputs": [], + "source": [ + "display(\n", + " n.statistics.energy_balance(bus_carrier=\"AC\").div(1e6).round(2).sort_values()\n", + ") # TWh\n", + "\n", + "average_cost = (\n", + " (n.statistics.capex().sum() + n.statistics.opex().sum())\n", + " / n.loads_t.p_set.sum().sum()\n", + " / RESOLUTION\n", + ")\n", + "\n", + "print(f\"Average cost: {average_cost:.2f} AUD/MWh\")" + ] + }, + { + "cell_type": "markdown", + "id": "dd087a4e", + "metadata": {}, + "source": [ + "Again, we can show these results spatially disagreggated on a map. The upper and lower half circles are still equal because no imbalances are possible due to the lack of transmission links. However, we can now see green elements for battery and purple elements for hydrogen storage, in addition to wind and solar generation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd8553fc", + "metadata": {}, + "outputs": [], + "source": [ + "bus_size = (\n", + " n.statistics.energy_balance(groupby=[\"bus\", \"carrier\"])\n", + " .droplevel(\"component\")\n", + " .loc[REGIONS]\n", + ")\n", + "\n", + "n.plot(bus_size=bus_size / 4e7, margin=0.75, bus_split_circle=True);" + ] + }, + { + "cell_type": "markdown", + "id": "b7302075", + "metadata": {}, + "source": [ + "Of course, there are other plots we can create from the optimised model, for instance, a price duration curve for Victoria.\n", + "For this we sort the time series of bus marginal prices in descending order and reindex to a relative axis from 0% to 100%:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bb23494d", + "metadata": {}, + "outputs": [], + "source": [ + "pdc = (\n", + " n.buses_t.marginal_price[\"VIC\"].sort_values(ascending=False).reset_index(drop=True)\n", + ")\n", + "pdc.index = np.arange(0, 100, 100 / len(pdc.index))\n", + "pdc.plot(\n", + " ylim=[0, 1000], xlim=[0, 100], xlabel=\"Share of time [%]\", ylabel=\"Price [AUD/MWh]\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "62789312", + "metadata": {}, + "source": [ + "As a final modelling step, we will now add transmission links between the regions. By modelling the interconnectors as bidirectional links, we will assume that they are controllable point-to-point HVDC connections (rather than lines subject to Kirchhoff's Voltage Law). They are also assumed to be lossless, otherwise a similar workaround with two unidirectional links as for battery inverters has to be implemented. The cost of the transmission links is length-dependent and set to 1000 AUD/MW/km." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e26de531", + "metadata": {}, + "outputs": [], + "source": [ + "connections = [\n", + " (\"VIC\", \"NSW\", 700), # km\n", + " (\"VIC\", \"SA\", 650), # km\n", + " (\"NSW\", \"SA\", 1150), # km\n", + "]\n", + "\n", + "for bus0, bus1, length in connections:\n", + " n.add(\n", + " \"Link\",\n", + " f\"{bus0}-{bus1}\",\n", + " bus0=bus0,\n", + " bus1=bus1,\n", + " carrier=\"HVDC\",\n", + " p_nom_extendable=True,\n", + " capital_cost=annuity(0.05, 40) * 1_000 * length,\n", + " p_min_pu=-1, # bidirectional\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "395558a5", + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize(\n", + " solver_name=SOLVER, log_to_console=False, extra_functionality=battery_constraint\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "258306c4", + "metadata": {}, + "source": [ + "With transmission enabled, we can see that the average costs are reduced further to 93.00 AUD/MWh.\n", + "This is achieved as the interconnection facilitates the integration of wind variability across regions, \n", + "reducing curtailment and the some need for hydrogen storage. The energy mix also shifts slightly from solar towards wind." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7debc33a", + "metadata": {}, + "outputs": [], + "source": [ + "display(\n", + " n.statistics.energy_balance(bus_carrier=\"AC\").div(1e6).round(2).sort_values()\n", + ") # TWh\n", + "\n", + "average_cost = (\n", + " (n.statistics.capex().sum() + n.statistics.opex().sum())\n", + " / n.loads_t.p_set.sum().sum()\n", + " / RESOLUTION\n", + ")\n", + "\n", + "display(f\"Average cost: {average_cost:.2f} AUD/MWh\")" + ] + }, + { + "cell_type": "markdown", + "id": "0ac89890", + "metadata": {}, + "source": [ + "Now, we can add further elements to the map, such as the interconnector capacities, the net flow direction as well as the average loading of the interconnectors, which show how Victoria becomes a net importer of electricity from New South Wales and South Australia." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70d6b4bc", + "metadata": {}, + "outputs": [], + "source": [ + "bus_size = (\n", + " n.statistics.energy_balance(bus_carrier=\"AC\", groupby=[\"bus\", \"carrier\"])\n", + " .droplevel(\"component\")\n", + " .loc[REGIONS]\n", + " .drop(\"HVDC\", level=\"carrier\")\n", + ")\n", + "\n", + "link_flow = n.links_t.p0.mean()[n.links.carrier == \"HVDC\"]\n", + "\n", + "link_width = n.links.p_nom_opt[n.links.carrier == \"HVDC\"]\n", + "\n", + "link_loading = n.links_t.p0.abs().mean()[n.links.carrier == \"HVDC\"] / link_width\n", + "\n", + "n.plot(\n", + " bus_size=bus_size / 4e7,\n", + " margin=0.75,\n", + " bus_split_circle=True,\n", + " link_flow=link_flow / 50,\n", + " link_width=link_width / 1e3,\n", + " link_color=link_loading,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "ecd743a0", + "metadata": {}, + "source": [ + "Of course, we can also just retrieve the optimised interconnector capacities as `pandas.Series`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5dab6681", + "metadata": {}, + "outputs": [], + "source": [ + "n.links.query(\"carrier == 'HVDC'\").p_nom_opt.round(2)" + ] + }, + { + "cell_type": "markdown", + "id": "73a083b7", + "metadata": {}, + "source": [ + "A further interesting insight reveals itself when we plto the interconnector flows over the year (here as weekly rolling averages).\n", + "The seasonality of the link between Victoria and New South Wales is striking, with VIC exporting to NSW in winter and importing in summer (Southern Hemisphere!)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d6a4090", + "metadata": {}, + "outputs": [], + "source": [ + "n.links_t.p0.loc[:, n.links.carrier == \"HVDC\"].rolling(\"7d\").mean().plot(\n", + " ylim=[-2500, 2500], grid=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "61380c10", + "metadata": {}, + "source": [ + "Also the state of charge profile of the hydrogen storage shows a clear seasonal pattern, with the storage being full in summer and empty in winter (Southern Hemisphere!)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e970d2a", + "metadata": {}, + "outputs": [], + "source": [ + "n.stores_t.e.filter(like=\"hydrogen\").sum(axis=1).div(1e6).plot(\n", + " ylabel=\"Hydrogen storage [TWh]\", grid=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "a8ee1cdb", + "metadata": {}, + "source": [ + "Finally, there are some more functions to interactively plot hourly energy balance time series for different carriers, such as electricity:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d6e1f0fa", + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.energy_balance.iplot.area(bus_carrier=\"AC\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/examples/ac-dc-lopf.ipynb b/PyPSA/source/docs/examples/ac-dc-lopf.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..c333fafa7a3a438beeed45bdc83463da88b004c1 --- /dev/null +++ b/PyPSA/source/docs/examples/ac-dc-lopf.ipynb @@ -0,0 +1,318 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Meshed AC-DC Networks\n", + "\n", + "This example demonstrates how to optimise meshed AC-DC networks in PyPSA. The example has a 3-node AC network coupled via AC-DC converters to a 3-node DC network. There is also a single point-to-point DC connection using the `Link` component." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "import pypsa\n", + "\n", + "n = pypsa.examples.ac_dc_meshed()\n", + "n.links.loc[\"Norwich Converter\", \"p_nom_extendable\"] = False" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "line_color = n.lines.bus0.map(n.buses.carrier).map(\n", + " lambda ct: \"r\" if ct == \"DC\" else \"b\"\n", + ")\n", + "\n", + "n.plot(\n", + " line_color=line_color,\n", + " link_color=\"c\",\n", + " title=\"AC (blue) - DC (red) - P2P-DC (cyan)\",\n", + " jitter=0.4,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We inspect the topology of the network. Therefore, use `n.determine_network_topology()` and inspect the subnetworks in `n.sub_networks`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.determine_network_topology()\n", + "n.sub_networks[\"n_branches\"] = [len(sn.branches()) for sn in n.sub_networks.obj]\n", + "n.sub_networks[\"n_buses\"] = [\n", + " len(sn.components.buses.static) for sn in n.sub_networks.obj\n", + "]\n", + "\n", + "n.sub_networks" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The network covers 10 time steps. These are given by the `snapshots` attribute." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.snapshots" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are 6 generators in the network, 3 wind and 3 gas. All are attached to AC buses:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that the generators have different capital and marginal costs. All of them have a `p_nom_extendable` set to `True`, meaning that capacities can be extended in the optimisation. The wind generators have a per unit limit for each time step, given by the weather potentials at the site. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators_t.p_max_pu.plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alright now we know how the network looks like, where the generators and lines are. Now, let's perform a optimization of the operation and capacities." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "n.optimize(log_to_console=False);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The objective is given by:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.objective" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Why is this number negative? It considers the starting point of the optimisation, thus the existent capacities given by `n.generators.p_nom` are taken into account. \n", + "\n", + "The real system cost are given by" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.objective + n.objective_constant" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The optimal capacities are given by `p_nom_opt` for generators, links and storages and `s_nom_opt` for lines.\n", + "\n", + "Let's look how the optimal capacities for the generators look like." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators.p_nom_opt.div(1e3).round(2).sort_values()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Their production is again given as a time-series in `n.generators_t`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators_t.p.div(1e3).plot.area(stacked=True, lw=0, ylabel=\"GW\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What are the locational marginal prices in the network? From the optimisation these are given for each bus and snapshot." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.marginal_price.mean(axis=1).plot(figsize=(8, 3), ylabel=\"€/MWh\")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can inspect further quantities as the active power of AC-DC converters and HVDC link." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links_t.p0.round(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "n.lines_t.p0.round(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "...or the active power injection per bus." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + }, + "scrolled": true + }, + "outputs": [], + "source": [ + "n.buses_t.p.round(2)" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/PyPSA/source/docs/examples/battery-electric-vehicle-charging.ipynb b/PyPSA/source/docs/examples/battery-electric-vehicle-charging.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..2cb2b480244cfd34af87e722a8e3ddf990b53b64 --- /dev/null +++ b/PyPSA/source/docs/examples/battery-electric-vehicle-charging.ipynb @@ -0,0 +1,280 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Electric Vehicles\n", + "\n", + "In this example, a battery electric vehicle (BEV) is driven 100 km in the morning and 100 km in the evening, to simulate commuting, and charged during the day by a solar panel at the driver's place of work. The size of the panel is computed by the optimisation.\n", + "\n", + "The BEV has a battery of size 100 kWh and an electricity consumption of 0.18 kWh/km.\n", + "\n", + "This example will use units of kW and kWh, unlike the PyPSA defaults. This is unproblematic as long as no power flow simulations are performed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "\n", + "import pypsa" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As time index, we use a 24 hour period." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "index = pd.date_range(\"2016-01-01 00:00\", \"2016-01-01 23:00\", freq=\"h\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The consumption pattern in kW of the BEV is defined as follows" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "bev_usage = pd.Series([0] * 7 + [9] * 2 + [0] * 8 + [9] * 2 + [0] * 5, index)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The capacity factor profile of the solar panel in per-unit of capacity is given by:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pv_pu = pd.Series(\n", + " [0.0] * 7\n", + " + [0.2, 0.4, 0.6, 0.75, 0.85, 0.9, 0.85, 0.75, 0.6, 0.4, 0.2, 0.1]\n", + " + [0.0] * 5,\n", + " index,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The availability of charging - i.e. only when parked at office - is constrained as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "charger_p_max_pu = pd.Series(0, index=index)\n", + "charger_p_max_pu[\"2016-01-01 09:00\":\"2016-01-01 16:00\"] = 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Together, this gives:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.concat({\"BEV\": bev_usage, \"PV\": pv_pu, \"Charger\": charger_p_max_pu}, axis=1)\n", + "df.plot.area(subplots=True);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now initialise the network and add the relevant components. Then optimise:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n = pypsa.Network()\n", + "n.set_snapshots(index)\n", + "\n", + "n.add(\"Bus\", \"place of work\")\n", + "\n", + "n.add(\"Bus\", \"car battery\")\n", + "\n", + "n.add(\n", + " \"Generator\",\n", + " \"PV panel\",\n", + " bus=\"place of work\",\n", + " p_nom_extendable=True,\n", + " p_max_pu=pv_pu,\n", + " capital_cost=1000, # dummy cost value\n", + ")\n", + "\n", + "n.add(\"Load\", \"driving\", bus=\"car battery\", p_set=bev_usage)\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " \"charger\",\n", + " bus0=\"place of work\",\n", + " bus1=\"car battery\",\n", + " p_nom=120,\n", + " p_max_pu=charger_p_max_pu,\n", + " efficiency=0.9,\n", + ")\n", + "\n", + "n.add(\"Store\", \"battery\", bus=\"car battery\", e_cyclic=True, e_nom=100);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize()\n", + "print(\"Objective:\", n.objective)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The optimal panel size in kW is:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators.p_nom_opt[\"PV panel\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators_t.p.plot.area()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The battery operation is optimised to follow:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.DataFrame({attr: n.stores_t[attr][\"battery\"] for attr in [\"p\", \"e\"]})\n", + "df.plot(grid=True, ylim=(-10, 40))\n", + "plt.legend(labels=[\"Energy output\", \"State of charge\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The losses in kWh per pay are:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(n.generators_t.p.loc[:, \"PV panel\"].sum() - n.loads_t.p.loc[:, \"driving\"].sum())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links_t.p0.plot.area()" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/PyPSA/source/docs/examples/biomass-synthetic-fuels-carbon-management.ipynb b/PyPSA/source/docs/examples/biomass-synthetic-fuels-carbon-management.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..4ec86172e3d2d62867c10e225d242b191bafb9e4 --- /dev/null +++ b/PyPSA/source/docs/examples/biomass-synthetic-fuels-carbon-management.ipynb @@ -0,0 +1,401 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Carbon Management\n", + "\n", + "In this example, we show how to manage different biomass stocks with different potentials and costs, synthetic fuel production, direct air capture (DAC) and carbon capture and usage/sequestration/cycling.\n", + "\n", + "Demand for electricity and diesel transport have to be met from various biomass sources, natural gas with possibility for carbon capture, electrolysis for hydrogen production, direct air capture of CO2, and diesel synthesis via Fischer-Tropsch processes.\n", + "\n", + "The system has to reach a target of net negative emissions over the optimisation period.\n", + "\n", + "*All numbers/costs/efficiencies are fictitious to allow easy analysis.*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pypsa\n", + "\n", + "n = pypsa.Network()\n", + "n.set_snapshots(range(10))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add a constant electrical load, a constant demand for transport, a diesel storage, and a hydrogen storage (which are not limited in capacity)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Bus\", \"electricity\")\n", + "n.add(\"Load\", \"load\", bus=\"electricity\", p_set=1)\n", + "\n", + "n.add(\"Bus\", \"transport\")\n", + "n.add(\"Load\", \"transport\", bus=\"transport\", p_set=1)\n", + "\n", + "n.add(\"Bus\", \"diesel\")\n", + "n.add(\"Store\", \"diesel\", bus=\"diesel\", e_cyclic=True, e_nom=1000)\n", + "\n", + "n.add(\"Bus\", \"hydrogen\")\n", + "n.add(\"Store\", \"hydrogen\", bus=\"hydrogen\", e_cyclic=True, e_nom=1000);" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add a link to represent electrolysis for hydrogen production from electricity." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"electrolysis\",\n", + " p_nom=2.0,\n", + " efficiency=0.8,\n", + " bus0=\"electricity\",\n", + " bus1=\"hydrogen\",\n", + ");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we set up the accounting of CO2 in the system. We add a carrier for CO2 (with minus sign because it runs opposite to how fossil fuels are used; burning of gas reduces gas, but CO2 in the atmosphere up), a bus and store for the CO2 in the atmosphere (which can be negative for the optimisation period), and a bus with a store for the CO2 that is captured and stored (for sequestration or use)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Carrier\", \"co2\", co2_emissions=-1)\n", + "\n", + "n.add(\"Bus\", \"co2 atmosphere\", carrier=\"co2\")\n", + "n.add(\"Store\", \"co2 atmosphere\", e_nom=1000, e_min_pu=-1, bus=\"co2 atmosphere\")\n", + "\n", + "n.add(\"Bus\", \"co2 stored\")\n", + "n.add(\"Store\", \"co2 stored\", e_nom=1000, e_min_pu=-1, bus=\"co2 stored\");" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With that, add a multi-link for the production of diesel from H2 and CO2 using Fischer-Tropsch. Note that CO2 is another input, hence, the `efficiency2` has to be negative." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"Fischer-Tropsch\",\n", + " p_nom=4,\n", + " bus0=\"hydrogen\",\n", + " bus1=\"diesel\",\n", + " bus2=\"co2 stored\",\n", + " efficiency=1,\n", + " efficiency2=-1,\n", + ");" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Direct air capture consumes electricity to take CO2 from the air to the store." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"DAC\",\n", + " bus0=\"electricity\",\n", + " bus1=\"co2 stored\",\n", + " bus2=\"co2 atmosphere\",\n", + " efficiency=1,\n", + " efficiency2=-1,\n", + " p_nom=5,\n", + ");" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add a link to meet transport demands with diesel. This emits CO2 into the atmosphere." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"diesel car\",\n", + " bus0=\"diesel\",\n", + " bus1=\"transport\",\n", + " bus2=\"co2 atmosphere\",\n", + " efficiency=1,\n", + " efficiency2=1,\n", + " p_nom=2,\n", + ");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add two gas power plants, one with carbon capture and one without. The one without carbon capture emits CO2 into the atmosphere. The one with carbon capture captures CO2, but imperfectly, so that some CO2 is still emitted into the atmosphere." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Bus\", \"gas\")\n", + "\n", + "n.add(\"Store\", \"gas\", e_initial=50, e_nom=50, marginal_cost=20, bus=\"gas\")\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " \"OCGT\",\n", + " bus0=\"gas\",\n", + " bus1=\"electricity\",\n", + " bus2=\"co2 atmosphere\",\n", + " p_nom_extendable=True,\n", + " efficiency=0.5,\n", + " efficiency2=1,\n", + ")\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " \"OCGT+CCS\",\n", + " bus0=\"gas\",\n", + " bus1=\"electricity\",\n", + " bus2=\"co2 stored\",\n", + " bus3=\"co2 atmosphere\",\n", + " p_nom_extendable=True,\n", + " efficiency=0.4,\n", + " efficiency2=0.9,\n", + " efficiency3=0.1,\n", + ");" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add two biomass generators, one with carbon capture and one without. The one without carbon capture simultaneously withdraws and emits CO2 into the atmosphere (hence we can ignore it for the purposes of carbon accounting). The one with carbon capture captures the renewable CO2, which counts as negative emissions if it remains in the CO2 store." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Bus\", \"biomass\")\n", + "\n", + "n.add(\n", + " \"Store\",\n", + " \"biomass\",\n", + " bus=\"biomass\",\n", + " marginal_cost=30,\n", + " e_nom=55,\n", + " e_initial=55,\n", + ")\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " \"biomass\",\n", + " bus0=\"biomass\",\n", + " bus1=\"electricity\",\n", + " p_nom_extendable=True,\n", + " capital_cost=1,\n", + " efficiency=0.5,\n", + ")\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " \"biomass+CCS\",\n", + " bus0=\"biomass\",\n", + " bus1=\"electricity\",\n", + " bus2=\"co2 stored\",\n", + " bus3=\"co2 atmosphere\",\n", + " p_nom_extendable=True,\n", + " capital_cost=1,\n", + " efficiency=0.4,\n", + " efficiency2=1,\n", + " efficiency3=-1,\n", + ");" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we add a global constraint on the amount of CO2 that can be emitted into the atmosphere.\n", + "In fact, we can set this to a negative value, which means that the system has to reach a target of net negative emissions over the optimisation period.\n", + "In this example, we can go as far as -50, but at some point not enough electricity can be generated to supply the DAC." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"GlobalConstraint\",\n", + " \"co2_limit\",\n", + " sense=\"<=\",\n", + " constant=-50,\n", + ");" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize(log_to_console=False);" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "How do the different stores in the system behave?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.stores_t.e.plot()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's have a look at the production" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links_t.p0.plot()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "At all times, the amount of carbon in the system is constant, meaning that all carbon is tracked through the system." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.stores_t.e[[\"co2 stored\", \"co2 atmosphere\", \"gas\", \"diesel\"]].sum(axis=1)" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/PyPSA/source/docs/examples/capacity-expansion-planning-single-node.ipynb b/PyPSA/source/docs/examples/capacity-expansion-planning-single-node.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..8e9d9261fc04abd9d838e715ddb81112f05f3ef5 --- /dev/null +++ b/PyPSA/source/docs/examples/capacity-expansion-planning-single-node.ipynb @@ -0,0 +1,633 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 1-Node Capacity Expansion" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "In this example, we build a replica of [model.energy](https://model.energy). This tool calculates the cost of meeting a constant electricity demand from a combination of wind power, solar power and storage for different regions of the world. It includes capacity investments and dispatch optimisation. We deviate from model.energy by including an electricity demand profiles rather than a constant electricity demand." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T08:46:20.357375Z", + "start_time": "2024-05-24T08:46:17.846410Z" + }, + "jp-MarkdownHeadingCollapsed": true, + "tags": [] + }, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "import pypsa" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## Techno-economic assumptions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We take techno-economic assumptions from the [technology-data](https://github.com/PyPSA/technology-data) repository which collects assumptions on costs and efficiencies:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:30.201456Z", + "start_time": "2024-05-24T07:37:29.732485Z" + } + }, + "outputs": [], + "source": [ + "YEAR = 2030\n", + "url = f\"https://raw.githubusercontent.com/PyPSA/technology-data/master/outputs/costs_{YEAR}.csv\"\n", + "costs = pd.read_csv(url, index_col=[0, 1])\n", + "costs.loc[costs.unit.str.contains(\"/kW\"), \"value\"] *= 1e3\n", + "costs = costs.value.unstack().fillna({\"discount rate\": 0.07, \"lifetime\": 20, \"FOM\": 0})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Based on this, we calculate the marginal costs (€/MWh):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "costs[\"marginal_cost\"] = costs[\"VOM\"] + costs[\"fuel\"] / costs[\"efficiency\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We also calculate the capital costs (i.e. annualised investment costs, €/MW/a or €/MWh/a for storage), using a small utility function to calculate the **annuity factor** to annualise investment costs based on is the discount rate $r$ and lifetime $n$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:30.222911Z", + "start_time": "2024-05-24T07:37:30.220818Z" + } + }, + "outputs": [], + "source": [ + "from pypsa.common import annuity" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:30.237265Z", + "start_time": "2024-05-24T07:37:30.232914Z" + } + }, + "outputs": [], + "source": [ + "a = costs.apply(lambda x: annuity(x[\"discount rate\"], x[\"lifetime\"]), axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:30.240224Z", + "start_time": "2024-05-24T07:37:30.238032Z" + } + }, + "outputs": [], + "source": [ + "costs[\"capital_cost\"] = (a + costs[\"FOM\"] / 100) * costs[\"investment\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Wind, solar and load time series" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:33.705510Z", + "start_time": "2024-05-24T07:37:30.240941Z" + } + }, + "outputs": [], + "source": [ + "RESOLUTION = 3 # hours\n", + "url = \"https://tubcloud.tu-berlin.de/s/9toBssWEdaLgHzq/download/time-series.csv\"\n", + "ts = pd.read_csv(url, index_col=0, parse_dates=True)[::RESOLUTION]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:33.730588Z", + "start_time": "2024-05-24T07:37:33.709003Z" + } + }, + "outputs": [], + "source": [ + "ts.head(3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model initialisation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:33.819917Z", + "start_time": "2024-05-24T07:37:33.743147Z" + } + }, + "outputs": [], + "source": [ + "n = pypsa.Network()\n", + "n.add(\"Bus\", \"electricity\", carrier=\"electricity\")\n", + "n.set_snapshots(ts.index)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The weighting of the snapshots (e.g. how many hours they represent, see $w_t$ in problem formulation above) must be set in `n.snapshot_weightings`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:33.845382Z", + "start_time": "2024-05-24T07:37:33.840216Z" + } + }, + "outputs": [], + "source": [ + "n.snapshot_weightings.loc[:, :] = RESOLUTION" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Adding carriers (this is only necessary for convenience in plotting later):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:33.861842Z", + "start_time": "2024-05-24T07:37:33.856810Z" + } + }, + "outputs": [], + "source": [ + "carriers = [\n", + " \"wind\",\n", + " \"solar\",\n", + " \"hydrogen storage\",\n", + " \"battery storage\",\n", + " \"load shedding\",\n", + " \"electrolysis\",\n", + " \"turbine\",\n", + " \"electricity\",\n", + " \"hydrogen\",\n", + "]\n", + "colors = [\n", + " \"dodgerblue\",\n", + " \"gold\",\n", + " \"black\",\n", + " \"yellowgreen\",\n", + " \"darkorange\",\n", + " \"magenta\",\n", + " \"red\",\n", + " \"grey\",\n", + " \"grey\",\n", + "]\n", + "n.add(\"Carrier\", carriers, color=colors)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Adding load:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:33.866618Z", + "start_time": "2024-05-24T07:37:33.862680Z" + } + }, + "outputs": [], + "source": [ + "n.add(\n", + " \"Load\",\n", + " \"demand\",\n", + " bus=\"electricity\",\n", + " p_set=ts.load_mw,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add a load shedding generator with high marginal cost of 2000 €/MWh:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:34.553677Z", + "start_time": "2024-05-24T07:37:34.549041Z" + } + }, + "outputs": [], + "source": [ + "n.add(\n", + " \"Generator\",\n", + " \"load shedding\",\n", + " bus=\"electricity\",\n", + " carrier=\"load shedding\",\n", + " marginal_cost=2000,\n", + " p_nom=ts.load_mw.max(),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Adding the variable renewable generators works almost identically, but we also need to supply the capacity factors to the model via the attribute `p_max_pu`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:34.561820Z", + "start_time": "2024-05-24T07:37:34.554441Z" + } + }, + "outputs": [], + "source": [ + "n.add(\n", + " \"Generator\",\n", + " \"wind\",\n", + " bus=\"electricity\",\n", + " carrier=\"wind\",\n", + " p_max_pu=ts.wind_pu,\n", + " capital_cost=costs.at[\"onwind\", \"capital_cost\"],\n", + " marginal_cost=costs.at[\"onwind\", \"marginal_cost\"],\n", + " p_nom_extendable=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Generator\",\n", + " \"solar\",\n", + " bus=\"electricity\",\n", + " carrier=\"solar\",\n", + " p_max_pu=ts.pv_pu,\n", + " capital_cost=costs.at[\"solar\", \"capital_cost\"],\n", + " marginal_cost=costs.at[\"solar\", \"marginal_cost\"],\n", + " p_nom_extendable=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Adding a 3-hour battery:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"StorageUnit\",\n", + " \"battery storage\",\n", + " bus=\"electricity\",\n", + " carrier=\"battery storage\",\n", + " max_hours=3,\n", + " capital_cost=costs.at[\"battery inverter\", \"capital_cost\"]\n", + " + 3 * costs.at[\"battery storage\", \"capital_cost\"],\n", + " efficiency_store=costs.at[\"battery inverter\", \"efficiency\"],\n", + " efficiency_dispatch=costs.at[\"battery inverter\", \"efficiency\"],\n", + " p_nom_extendable=True,\n", + " cyclic_state_of_charge=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Adding a hydrogen storage system consisting of electrolyser, hydrogen turbine and underground storage, which can all be flexibly optimised:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Bus\", \"hydrogen\", carrier=\"hydrogen\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"electrolysis\",\n", + " bus0=\"electricity\",\n", + " bus1=\"hydrogen\",\n", + " carrier=\"electrolysis\",\n", + " p_nom_extendable=True,\n", + " efficiency=costs.at[\"electrolysis\", \"efficiency\"],\n", + " capital_cost=costs.at[\"electrolysis\", \"capital_cost\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"turbine\",\n", + " bus0=\"hydrogen\",\n", + " bus1=\"electricity\",\n", + " carrier=\"turbine\",\n", + " p_nom_extendable=True,\n", + " efficiency=costs.at[\"OCGT\", \"efficiency\"],\n", + " capital_cost=costs.at[\"OCGT\", \"capital_cost\"] / costs.at[\"OCGT\", \"efficiency\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Store\",\n", + " \"hydrogen storage\",\n", + " bus=\"hydrogen\",\n", + " carrier=\"hydrogen storage\",\n", + " capital_cost=costs.at[\"hydrogen storage underground\", \"capital_cost\"],\n", + " e_nom_extendable=True,\n", + " e_cyclic=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Model run" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:35.306567Z", + "start_time": "2024-05-24T07:37:34.744060Z" + } + }, + "outputs": [], + "source": [ + "n.optimize(solver_name=\"highs\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Model evaluation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Total system cost by technology:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:35.309989Z", + "start_time": "2024-05-24T07:37:35.307258Z" + } + }, + "outputs": [], + "source": [ + "tsc = (\n", + " pd.concat([n.statistics.capex(), n.statistics.opex()], axis=1).sum(axis=1).div(1e9)\n", + ")\n", + "tsc" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tsc.sum()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The optimised capacities in GW (GWh for `Store` component):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:35.321024Z", + "start_time": "2024-05-24T07:37:35.316333Z" + } + }, + "outputs": [], + "source": [ + "n.statistics.optimal_capacity().div(1e3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Energy balances on electricity side (in TWh):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:35.325340Z", + "start_time": "2024-05-24T07:37:35.321798Z" + } + }, + "outputs": [], + "source": [ + "n.statistics.energy_balance(bus_carrier=\"electricity\").sort_values().div(1e6)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Energy balances plot as time series (in MW):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.energy_balance.plot.area(linewidth=0, bus_carrier=\"electricity\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Price time series for electricity and hydrogen:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.marginal_price.plot(figsize=(7, 2))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/PyPSA/source/docs/examples/chained-hydro-reservoirs.ipynb b/PyPSA/source/docs/examples/chained-hydro-reservoirs.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..ad44292ecd20555d7825ab38a02c5860de4f8b6e --- /dev/null +++ b/PyPSA/source/docs/examples/chained-hydro-reservoirs.ipynb @@ -0,0 +1,253 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Chained Hydro-Reservoirs\n", + "\n", + "In this example, two disconnected electrical loads are fed from two reservoirs linked by a river; the first reservoir has inflow from rain onto a water basin.\n", + "\n", + "Note that the two reservoirs are tightly coupled, meaning there is **no time delay** between the first one emptying and the second one filling, as there would be if there were a long stretch of river between the reservoirs. The reservoirs are hence assumed to be close to each other. A time delay is currently not supported in PyPSA." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "import pypsa\n", + "\n", + "n = pypsa.Network()\n", + "n.set_snapshots(range(4))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add assets to the network." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Carrier\", \"reservoir\")\n", + "n.add(\"Carrier\", \"rain\")\n", + "\n", + "n.add(\"Bus\", \"0 electricity\")\n", + "n.add(\"Bus\", \"1 electricity\")\n", + "\n", + "n.add(\"Bus\", \"0 reservoir\", carrier=\"reservoir\")\n", + "n.add(\"Bus\", \"1 reservoir\", carrier=\"reservoir\")\n", + "\n", + "n.add(\n", + " \"Generator\",\n", + " \"rain\",\n", + " bus=\"0 reservoir\",\n", + " carrier=\"rain\",\n", + " p_nom=1000,\n", + " p_max_pu=[0.0, 0.2, 0.7, 0.4],\n", + ")\n", + "\n", + "n.add(\"Load\", \"0 load\", bus=\"0 electricity\", p_set=20)\n", + "n.add(\"Load\", \"1 load\", bus=\"1 electricity\", p_set=30);" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The efficiency of a river is the relation between the gravitational potential energy of 1 cubic metre of water in reservoir 0 relative to its turbine versus the potential energy of 1 cubic metre of water in reservoir 1 relative to its turbine. Water can either be spilled without generating electricity or be sent through the turbine to generate power. In both cases, the water goes into the next reservoir." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"spillage\",\n", + " bus0=\"0 reservoir\",\n", + " bus1=\"1 reservoir\",\n", + " efficiency=0.5,\n", + " p_nom_extendable=True,\n", + ")\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " \"0 turbine\",\n", + " bus0=\"0 reservoir\",\n", + " bus1=\"0 electricity\",\n", + " bus2=\"1 reservoir\",\n", + " efficiency=0.9,\n", + " efficiency2=0.5,\n", + " capital_cost=1000,\n", + " p_nom_extendable=True,\n", + ")\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " \"1 turbine\",\n", + " bus0=\"1 reservoir\",\n", + " bus1=\"1 electricity\",\n", + " efficiency=0.9,\n", + " capital_cost=1000,\n", + " p_nom_extendable=True,\n", + ")\n", + "\n", + "\n", + "n.add(\n", + " \"Store\",\n", + " \"0 reservoir\",\n", + " bus=\"0 reservoir\",\n", + " e_cyclic=True,\n", + " e_nom=10_000,\n", + ")\n", + "\n", + "n.add(\n", + " \"Store\",\n", + " \"1 reservoir\",\n", + " bus=\"1 reservoir\",\n", + " e_cyclic=True,\n", + " e_nom=10_000,\n", + ");" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize(n.snapshots)\n", + "print(\"Objective:\", n.objective)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators_t.p.plot.area()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's have look at the different outputs of the links." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "n.links_t.p0.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links_t.p1.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links_t.p2.plot()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What are the energy outputs and energy levels at the reservoirs?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pd.DataFrame({attr: n.stores_t[attr][\"0 reservoir\"] for attr in [\"p\", \"e\"]})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pd.DataFrame({attr: n.stores_t[attr][\"1 reservoir\"] for attr in [\"p\", \"e\"]})" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/PyPSA/source/docs/examples/chp-fixed-heat-power-ratio.ipynb b/PyPSA/source/docs/examples/chp-fixed-heat-power-ratio.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..2dbf1926842c1afdf6bbe1bf0fae67f657df48a4 --- /dev/null +++ b/PyPSA/source/docs/examples/chp-fixed-heat-power-ratio.ipynb @@ -0,0 +1,137 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Backpressure CHP\n", + "\n", + "This example demonstrates how to model a Combined Heat and Power (CHP) plant with a fixed heat-power ratio, assuming that the plant is operated in backpressure mode.\n", + "For an example of a CHP plant with a more complicated heat-power feasible operational area, see the [extraction-condensing CHP example](). In this example, the CHP is modelled as a `Link` component with two output buses: one for electricity and one for heat. The CHP unit must be heat-following since there is no other supply of heat to the \"Frankfurt heat\" bus." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pypsa\n", + "\n", + "n = pypsa.Network()\n", + "\n", + "n.add(\"Bus\", \"Frankfurt\", carrier=\"AC\")\n", + "n.add(\"Load\", \"Frankfurt\", bus=\"Frankfurt\", p_set=5)\n", + "\n", + "n.add(\"Bus\", \"Frankfurt heat\", carrier=\"heat\")\n", + "n.add(\"Load\", \"Frankfurt heat\", bus=\"Frankfurt heat\", p_set=3)\n", + "\n", + "n.add(\"Bus\", \"Frankfurt gas\", carrier=\"gas\")\n", + "n.add(\"Generator\", \"Frankfurt gas\", bus=\"Frankfurt gas\", marginal_cost=100, p_nom=100)\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " \"OCGT\",\n", + " bus0=\"Frankfurt gas\",\n", + " bus1=\"Frankfurt\",\n", + " p_nom_extendable=True,\n", + " capital_cost=600,\n", + " efficiency=0.4, # electricity per unit of gas\n", + ")\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " \"CHP\",\n", + " bus0=\"Frankfurt gas\",\n", + " bus1=\"Frankfurt\",\n", + " bus2=\"Frankfurt heat\",\n", + " p_nom_extendable=True,\n", + " capital_cost=1400,\n", + " efficiency=0.3, # electricity per unit of gas\n", + " efficiency2=0.3, # heat per unit of gas\n", + ")\n", + "\n", + "n.optimize(log_to_console=False);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.loads_t.p" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links_t.p0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links_t.p1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links_t.p2" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/PyPSA/source/docs/examples/demand-elasticity.ipynb b/PyPSA/source/docs/examples/demand-elasticity.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..e2487e5ecb4d97d36dcac2fcb5252a891a236d54 --- /dev/null +++ b/PyPSA/source/docs/examples/demand-elasticity.ipynb @@ -0,0 +1,429 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Demand Elasticity" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "This example demonstrates how demand elasticity can be modelled in PyPSA, using single node capacity expansion model in the style of [model.energy](https://model.energy).\n", + "\n", + "See [Brown, Neumann, Riepin (2025)](https://doi.org/10.1016/j.eneco.2025.108483) for more details." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T08:46:20.357375Z", + "start_time": "2024-05-24T08:46:17.846410Z" + }, + "jp-MarkdownHeadingCollapsed": true, + "tags": [] + }, + "source": [ + "## Preparations\n", + "\n", + "We start by loading packages, example networks and creating a utility function to retrieve price duration curve." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import pypsa\n", + "\n", + "plt.style.use(\"bmh\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_price_duration(n: pypsa.Network, bus: str = \"electricity\") -> pd.Series:\n", + " s = (\n", + " n.buses_t.marginal_price[bus]\n", + " .sort_values(ascending=False)\n", + " .reset_index(drop=True)\n", + " )\n", + " s.index = np.arange(0, 100, 100 / len(s.index))\n", + " return s" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:33.819917Z", + "start_time": "2024-05-24T07:37:33.743147Z" + } + }, + "outputs": [], + "source": [ + "n = pypsa.examples.model_energy()\n", + "n.remove(\"Load\", \"demand\")\n", + "n.remove(\"Generator\", \"load shedding\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To save some computation time, we will sample just every fifth day of the year. Each day is considered at 3-hourly resolution, so we will have 8 snapshots per representative day." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "selected = n.snapshots.normalize().unique()[::5]\n", + "snapshots = n.snapshots[n.snapshots.normalize().isin(selected)]\n", + "n.set_snapshots(snapshots)\n", + "n.snapshot_weightings[[\"objective\", \"generators\"]] *= 5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Perfectly inelastic demand\n", + "\n", + "Most commonly, capacity expansion models would prescribe a perfectly inelastic demand via the `p_set` attribute, e.g. 100 MW.\n", + "\n", + "The utility drawn from this consumption is effectively infinite. The model has to find a way to satisfy it. Otherwise, the model is infeasible." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Load\", \"demand\", bus=\"electricity\", p_set=100)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Market clearing prices can spike to extreme values in few hours of the year, while remaining close to zero for a majority of time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots()\n", + "get_price_duration(n).plot(\n", + " ax=ax,\n", + " ylabel=\"Clearing Price [€/MWh]\",\n", + " xlabel=\"Fraction of Time [%]\",\n", + " label=\"default\",\n", + " legend=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "capacities = n.statistics.optimal_capacity(round=2).to_frame(\"inelastic\")\n", + "capacities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Perfectly inelastic demand up to VOLL\n", + "\n", + "One way to avoid the price spikes is to model demand as perfectly inelastic up to a pre-defined value of lost load (VOLL).\n", + "\n", + "Effectively, this is defined by a utility function $U(d) = Vd$ with a constant value $V$ for consumption $d\\in[0,D]$, for instance 1000 €/MWh.\n", + "\n", + "The demand curve is a step function. It is perfectly inelastic up to a price of $V$ at which point it is perfectly elastic.\n", + "\n", + "When we make the substitution $d=D-g$, we see that we can model the VOLL case with a load shedding generator with marginal costs of $V=1000$ €/MWh, omitting the constant term $VD$.\n", + "\n", + "$U(d) = Vd$\n", + "\n", + "$U(d) = VD - Vg$\n", + "\n", + "Note that the objective sense of PyPSA is to minimise costs in order to maximise utility, so any costs have a postive sign and utility gains have a negative sign in the objective." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Generator\",\n", + " \"load-shedding\",\n", + " bus=\"electricity\",\n", + " carrier=\"load\",\n", + " marginal_cost=1000,\n", + " p_nom=100,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, the peak price is capped at 2000 €/MWh:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "get_price_duration(n).plot(ax=ax, label=\"VOLL\", legend=True)\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This results in some changes in the cost-optimal capacity mix, in particular in terms of backup capacities." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "capacities[\"VOLL\"] = n.statistics.optimal_capacity(round=2)\n", + "capacities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Linear demand curve\n", + "\n", + "In reality, electricity demand is at least partially elastic. Consumers would use less electricity if its more expensive, or would use more electricity if prices are low. \n", + "\n", + "For a linear demand curve $p = a - bd$, where $p$ is the price, the utility is quadratic: $U(d) = ad - 0.5 b d^2$\n", + "\n", + "For a choice of $a=2000$ and $b=20$, the demand curve looks like this:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x = np.linspace(0, 100, 200)\n", + "plt.figure(figsize=(8, 4))\n", + "plt.plot(x, 2000 - 20 * x)\n", + "plt.xlabel(\"Demand (MW)\")\n", + "plt.ylabel(\"Price (€/MWh)\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That means, for instance, at a price of 1000 €/MWh, the demand would be only 50 MW. At a price of 400 €/MWh, 80 MW. And so on.\n", + "\n", + "Applying the same substition $d = a/b - g$ ($a/b$ yields the maximum consumption at the zero point), turns \n", + "\n", + "$U(d) = ad - 0.5 b d^2$\n", + "\n", + "into \n", + "\n", + "$U(g) = \\frac{a^2}{2b} - 0.5 b g^2$\n", + "\n", + "which represents a load shedding generator with quadratic marginal cost $b/2$, again omitting the constant term from the objective.\n", + "\n", + "Due to the quadratic terms in the objective function, this addition turns the model into a quadratic problem (QP)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.remove(\"Generator\", \"load-shedding\")\n", + "\n", + "n.add(\n", + " \"Generator\",\n", + " \"load-shedding\",\n", + " bus=\"electricity\",\n", + " carrier=\"load\",\n", + " marginal_cost_quadratic=20 / 2,\n", + " p_nom=100,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize(solver_name=\"highs\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The price duration curve is considerably smoother with less extreme prices and fewer zero-price hours:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "get_price_duration(n).plot(ax=ax, label=\"linear-elastic\", legend=True)\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Also, the optimised capacity mix is drastically different. The model cuts down on balancing technologies and rather curtails a lot demand instead. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "capacities[\"linear-elastic\"] = n.statistics.optimal_capacity(round=2)\n", + "capacities" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The drawback and explanation here is that the linear demand curve becomes unrealistically elastic at higher prices." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Partial demand elasticity\n", + "\n", + "It is also possible to mix different demand modelling approaches. For instance, keeping 80% of demand perfectly inelastic, while modelling 20% with a linear demand curve.\n", + "\n", + "This just requires adjusting the capacity and cost terms of the load shedding generator (adjusts the slope of the linear demand curve)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators.loc[\"load-shedding\", \"p_nom_max\"] *= 0.2\n", + "n.generators.loc[\"load-shedding\", \"marginal_cost_quadratic\"] /= 0.2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Piecewise-linear demand curve\n", + "\n", + "It is also possible to model a set of piecewise linear demand curves, e.g. to approximate a log-log demand curve ($\\ln p = a - b \\ln d$), without much modification.\n", + "\n", + "For details see [Brown, Neumann, Riepin (2025)](https://doi.org/10.1016/j.eneco.2025.108483), Section 3.2 and Appendix A. " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/PyPSA/source/docs/examples/example-1.ipynb b/PyPSA/source/docs/examples/example-1.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..3b5ca565d472fecf54e39d6b19d1ee2d1c06f428 --- /dev/null +++ b/PyPSA/source/docs/examples/example-1.ipynb @@ -0,0 +1,295 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "8d26d3a9", + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "8b78da35", + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "source": [ + "# Quickstart 1 - Markets\n", + "\n", + "## Problem Description\n", + "\n", + "Consider the following simple electricity market with two zones, each with a generator and a load. \n", + "The demand in zone 1 is 500 MW and in zone 2 is 1500 MW.\n", + "The generators have a nominal capacity of 2000 MW each, with cost functions defined as follows:\n", + "\n", + "- Zone 1: C1(g1) = 10 g1 + 0.005 g1²\n", + "- Zone 2: C2(g2) = 13 g2 + 0.01 g2²\n", + "\n", + "Find the least-cost dispatch of the generators to meet the load while respecting the transmission capacity. Identify the marginal prices at each bus and the flow on the transmission line. Calculate the congestion rent.\n", + "\n", + "## PyPSA Solution\n", + "\n", + "For installation instructions, consult the [Installation](../home/installation.md) section first.\n", + "\n", + "The first step is always to import the `pypsa` module:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5853e887", + "metadata": {}, + "outputs": [], + "source": [ + "import pypsa" + ] + }, + { + "cell_type": "markdown", + "id": "8baa1a24", + "metadata": {}, + "source": [ + "\n", + "A new PyPSA network instance can be created with the `pypsa.Network` constructor." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "daa64f75", + "metadata": {}, + "outputs": [], + "source": [ + "n = pypsa.Network()" + ] + }, + { + "cell_type": "markdown", + "id": "617ff3a2", + "metadata": {}, + "source": [ + "Components like buses can be added with `n.add()` and registered under an arbitrary name, e.g. `\"zone_1\"`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8e031adb", + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Bus\", \"zone_1\")\n", + "n.add(\"Bus\", \"zone_2\")\n", + "n.buses" + ] + }, + { + "cell_type": "markdown", + "id": "3e5ae91a", + "metadata": {}, + "source": [ + "Next, add the loads to the network, where `p_set` specifies the power demand in MW at the corresponding bus:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e9f8a64f", + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Load\", \"load_1\", bus=\"zone_1\", p_set=500)\n", + "n.add(\"Load\", \"load_2\", bus=\"zone_2\", p_set=1500)\n", + "n.loads" + ] + }, + { + "cell_type": "markdown", + "id": "a576f828", + "metadata": {}, + "source": [ + "Generators are added in a similar way, where `p_nom` specifies the nominal capacity, and `marginal_cost` and `marginal_cost_quadratic` specify the linear and quadratic coefficients of the cost function:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1f59b5b", + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Generator\",\n", + " \"gen_1\",\n", + " bus=\"zone_1\",\n", + " p_nom=2000,\n", + " marginal_cost=10,\n", + " marginal_cost_quadratic=0.005,\n", + ")\n", + "n.add(\n", + " \"Generator\",\n", + " \"gen_2\",\n", + " bus=\"zone_2\",\n", + " p_nom=2000,\n", + " marginal_cost=13,\n", + " marginal_cost_quadratic=0.01,\n", + ")\n", + "n.generators" + ] + }, + { + "cell_type": "markdown", + "id": "9a1c138e", + "metadata": {}, + "source": [ + "Lines connecting two buses `bus0` and `bus1` are added with a nominal capacity `s_nom` in MW and a reactance `x` in Ohm (which is required for modelling power flow):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ecff4644", + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Line\", \"line_1\", bus0=\"zone_1\", bus1=\"zone_2\", x=0.01, s_nom=400)\n", + "n.lines" + ] + }, + { + "cell_type": "markdown", + "id": "b32efdce", + "metadata": {}, + "source": [ + "With all components added, the network can be optimised with the `n.optimize()` method.\n", + "This function constructs the optimisation problem with the [`linopy`](https://linopy.readthedocs.io) library, solves it with a selected solver (`\"highs\"` as default), and stores the results in the network instance `n`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8269c5a4", + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize(solver_name=\"highs\", log_to_console=False)" + ] + }, + { + "cell_type": "markdown", + "id": "4048af75", + "metadata": {}, + "source": [ + "The optimised generators dispatch can be accessed with:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec03b8d2", + "metadata": {}, + "outputs": [], + "source": [ + "n.generators_t.p" + ] + }, + { + "cell_type": "markdown", + "id": "1e93200c", + "metadata": {}, + "source": [ + "The market clearing prices per bus can be accessed with:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36db960a", + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.marginal_price" + ] + }, + { + "cell_type": "markdown", + "id": "cccae818", + "metadata": {}, + "source": [ + "The optimised flows on transmission lines can be accessed with:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b215da0c", + "metadata": {}, + "outputs": [], + "source": [ + "n.lines_t.p1" + ] + }, + { + "cell_type": "markdown", + "id": "ff110269", + "metadata": {}, + "source": [ + "Here, `n.lines_t.p0` denotes flow from `bus0` to `bus1` if values are positive. The related attribute `n.lines_t.p1` denotes flow from `bus1` to `bus0` if values are positive.\n", + "\n", + "The congestion rent can be calculated as the product of the flow on the line and the price difference between the two buses:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23817fab", + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.marginal_price.eval(\"zone_2 - zone_1\") * n.lines_t.p0[\"line_1\"]" + ] + }, + { + "cell_type": "markdown", + "id": "c4ee0769", + "metadata": {}, + "source": [ + "This example is based on Tom Brown's [Energy Systems](https://nworbmot.org/courses/es-25) course, taken from the lecture on [Complex Markets](https://nworbmot.org/courses/es-25/es-9-complex_markets.pdf), slides 37ff." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/examples/example-2.ipynb b/PyPSA/source/docs/examples/example-2.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..cbae6c17e856678603ab97420d2e0105e27af06e --- /dev/null +++ b/PyPSA/source/docs/examples/example-2.ipynb @@ -0,0 +1,275 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "f73abc7d", + "metadata": {}, + "source": [ + "# Quickstart 2 - Power Flow\n", + "\n", + "## Problem Description\n", + "\n", + "Consider a three-zone electricity market with the following loads and generators:\n", + "\n", + "| Zone | Load (MW) | Generator | Capacity (MW) | Marginal Cost (€/MWh) |\n", + "|------|-----------|-----------|---------------|------------------------|\n", + "| 1 | 50 | Gen A | 140 | 7.5 |\n", + "| | | Gen B | 285 | 6.0 |\n", + "| 2 | 60 | Gen C | 90 | 14.0 |\n", + "| 3 | 300 | Gen D | 85 | 10.0 |\n", + "\n", + "The zones are connected by transmission lines running on 11 kV with the following characteristics:\n", + "\n", + "| Line | From | To | Capacity (MW) | Reactance (Ohm) |\n", + "|------|------|------|--------------|----------------|\n", + "| 1 | Zone 1 | Zone 2 | 126 | 0.2 |\n", + "| 2 | Zone 1 | Zone 3 | 250 | 0.2 |\n", + "| 3 | Zone 2 | Zone 3 | 130 | 0.1 |\n", + "\n", + "Find the least-cost dispatch of the generators to meet the loads while respecting the transmission capacities. Identify the marginal prices at each bus and the flow on the transmission lines.\n", + "Calculate the non-linear power flow based on the optimised results, and determine the losses on each line as well as the voltage angles at each bus.\n", + "\n", + "## PyPSA Solution" + ] + }, + { + "cell_type": "markdown", + "id": "1eb2890e", + "metadata": {}, + "source": [ + "Start by importing PyPSA, creating a new network instance and adding the three buses with voltage level 11 kV:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cae76f4b", + "metadata": {}, + "outputs": [], + "source": [ + "from numpy import pi\n", + "\n", + "import pypsa\n", + "\n", + "n = pypsa.Network()\n", + "\n", + "n.add(\"Bus\", [\"zone_1\", \"zone_2\", \"zone_3\"], v_nom=11)" + ] + }, + { + "cell_type": "markdown", + "id": "005acaef", + "metadata": {}, + "source": [ + "Adding multiple components with `n.add()` is supported by passing lists for each argument. Any scalar values are broadcasted to all components.\n", + "\n", + "Thus, we can add the buses, loads, generators, and lines in one go and solve the network::" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "818d69d3", + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Load\",\n", + " [\"load_1\", \"load_2\", \"load_3\"],\n", + " bus=[\"zone_1\", \"zone_2\", \"zone_3\"],\n", + " p_set=[50, 60, 300],\n", + ")\n", + "\n", + "n.add(\n", + " \"Generator\",\n", + " [\"gen_A\", \"gen_B\", \"gen_C\", \"gen_D\"],\n", + " bus=[\"zone_1\", \"zone_1\", \"zone_2\", \"zone_3\"],\n", + " p_nom=[140, 285, 90, 85],\n", + " marginal_cost=[7.5, 6, 14, 10],\n", + ")\n", + "\n", + "n.add(\n", + " \"Line\",\n", + " [\"line_1\", \"line_2\", \"line_3\"],\n", + " bus0=[\"zone_1\", \"zone_1\", \"zone_2\"],\n", + " bus1=[\"zone_2\", \"zone_3\", \"zone_3\"],\n", + " s_nom=[126, 250, 130],\n", + " x=[0.02, 0.02, 0.01],\n", + " r=0.01,\n", + ")\n", + "\n", + "n.optimize(log_to_console=False)" + ] + }, + { + "cell_type": "markdown", + "id": "096acc81", + "metadata": {}, + "source": [ + "It is possible to inspect the underlying `linopy` model once either `n.optimize()` or `n.optimize.create_model()` have been called. This can be done by accessing the `n.model` attribute, which prints the variable and constraint names and their dimensions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b07bed8", + "metadata": {}, + "outputs": [], + "source": [ + "n.model" + ] + }, + { + "cell_type": "markdown", + "id": "a41a7fd8", + "metadata": {}, + "source": [ + "To look at the equations of a specific constraint, such as the nodal balances of the buses in each time step, execute:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "669b228f", + "metadata": {}, + "outputs": [], + "source": [ + "n.model.constraints[\"Bus-nodal_balance\"]" + ] + }, + { + "cell_type": "markdown", + "id": "1c9a1bd2", + "metadata": {}, + "source": [ + "As in the first example, the optimal solution can be accessed from the network object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cbd416ea", + "metadata": {}, + "outputs": [], + "source": [ + "display(n.buses_t.marginal_price)\n", + "display(n.generators_t.p)\n", + "display(n.lines_t.p0)" + ] + }, + { + "cell_type": "markdown", + "id": "9e5a9b9c", + "metadata": {}, + "source": [ + "For the next step, we want to calculate the non-linear AC power flow based on the optimised results (which uses the linearised DC power flow model) using the Newton-Raphson method.\n", + "This can be done by calling [`n.pf()`](), but first we need to provide the set points for the generator dispatch.\n", + "These will be kept in the power flow, except for the slack generator, the output of which will be adjusted to balance the network." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ca7c5cc4", + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize.fix_optimal_dispatch()\n", + "\n", + "display(n.generators_t.p_set)\n", + "display(n.generators.control)\n", + "\n", + "n.pf()" + ] + }, + { + "cell_type": "markdown", + "id": "d6d4f1a4", + "metadata": {}, + "source": [ + "Alright, it converged! Now we can see how the slack generator is used to balance\n", + "the line losses as well as the reactive power of the generators and lines. The\n", + "linear power flow necglects both.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8954ac52", + "metadata": {}, + "outputs": [], + "source": [ + "display(n.generators_t.p)\n", + "display(n.generators_t.q)\n", + "display(n.lines_t.q0)\n", + "display((n.lines_t.p0 + n.lines_t.p1).sum().sum()) # active power losses" + ] + }, + { + "cell_type": "markdown", + "id": "60e10e48", + "metadata": {}, + "source": [ + "You can see that the increase in the slack generator's output is equal to the sum of the active power losses in the lines.\n", + "\n", + "With non-linear power flow, the voltage angles at the buses are calculated as well, which are stored in radians but can easily be converted to degrees:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f22ecb44", + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.v_ang * 180 / pi" + ] + }, + { + "cell_type": "markdown", + "id": "8c64de27", + "metadata": {}, + "source": [ + "This example is based on Tom Brown's [Energy Systems](https://nworbmot.org/courses/es-25) course, taken from the lecture [Complex Markets](https://nworbmot.org/courses/es-25/es-9-complex_markets.pdf), slides 42ff." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/examples/example-3.ipynb b/PyPSA/source/docs/examples/example-3.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..bf2ec4f3620825f4b0dae2a58a7c6ebae65fd3d9 --- /dev/null +++ b/PyPSA/source/docs/examples/example-3.ipynb @@ -0,0 +1,367 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "7fa1d5b2", + "metadata": {}, + "source": [ + "\n", + "# Quickstart 3 - Investments & Storage\n", + "\n", + "\n", + "## Problem Description\n", + "\n", + "A data centre in Seville, Spain, has a constant demand of 100 MW. The operator\n", + "considers investing in on-site solar PV and battery storage to reduce reliance\n", + "on grid electricity, which is priced at 120 €/MWh. The investment costs and\n", + "characteristics of the components are as follows:\n", + "\n", + "\n", + "| Component | Overnight Cost | Lifetime | Discount Rate |\n", + "|-----------|----------------|----------|---------------|\n", + "| Solar PV | 400 €/kW | 25 years | 5% |\n", + "| Battery Storage | 150 €/kWh | 25 years | 5% |\n", + "| Battery Inverter | 170 €/kW | 10 years | 5% |\n", + "\n", + "- The battery storage system has a round-trip efficiency of 90% and an energy-to-power ratio of 4 hours.\n", + "- The solar PV plant has a capacity factor time series given [here](https://model.energy/data/time-series-f17c3736a2719ce7da58484180d89e2d.csv).\n", + "- Assume that feeding electricity into the grid is not allowed.\n", + "\n", + "Find the least-cost investment in solar PV and battery storage to cover the load. What is the average cost per unit of electricity consumed?\n", + "How much electricity is consumed from the grid and when? How is the battery operated?\n", + "\n", + "## PyPSA Solution\n", + "\n", + "We start by creating a new network with a single bus and the data centre load.\n", + "We also add a generator to model supply from the grid priced at 120 €/MWh.\n", + "These are the fixed components of the network." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cfe717e0", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import pypsa\n", + "from pypsa.common import annuity\n", + "\n", + "n = pypsa.Network()\n", + "\n", + "n.add(\"Bus\", \"seville\")\n", + "\n", + "n.add(\"Load\", \"demand\", bus=\"seville\", p_set=100)\n", + "\n", + "n.add(\"Generator\", \"grid\", bus=\"seville\", p_nom=100, marginal_cost=120, carrier=\"grid\");" + ] + }, + { + "cell_type": "markdown", + "id": "4133f74f", + "metadata": {}, + "source": [ + "Next, we read in the capacity factor time series of the network, which covers hourly data for the year 2011." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a78d0105", + "metadata": {}, + "outputs": [], + "source": [ + "p_max_pu = pd.read_csv(\n", + " \"https://model.energy/data/time-series-f17c3736a2719ce7da58484180d89e2d.csv\",\n", + " index_col=0,\n", + " parse_dates=True,\n", + ")[\"solar\"]\n", + "p_max_pu[7:15]" + ] + }, + { + "cell_type": "markdown", + "id": "b6cdee7c", + "metadata": {}, + "source": [ + "We need to tell PyPSA that these are the snapshots (time steps) we want to optimise over." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b08e0a1a", + "metadata": {}, + "outputs": [], + "source": [ + "n.set_snapshots(p_max_pu.index)\n", + "len(n.snapshots)" + ] + }, + { + "cell_type": "markdown", + "id": "38dfb874", + "metadata": {}, + "source": [ + "Then, we add the solar PV with the availability time series as `p_max_pu`, the annualised costs in €/MW/a as `capital_cost` and mark the component as extendable with `p_nom_extendable`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c4e74b6", + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Generator\",\n", + " \"solar\",\n", + " bus=\"seville\",\n", + " p_max_pu=p_max_pu,\n", + " capital_cost=annuity(0.05, 25) * 400_000,\n", + " p_nom_extendable=True,\n", + " carrier=\"solar\",\n", + ");" + ] + }, + { + "cell_type": "markdown", + "id": "417fbf37", + "metadata": {}, + "source": [ + "Similarly, we add the battery storage. Here, we need to take extra care with the multiple cost components of the battery system for the `capital_cost`, and the energy-to-power ratio (`max_hours`)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d29cd8ae", + "metadata": {}, + "outputs": [], + "source": [ + "cc_inverter = annuity(0.05, 25) * 170_000\n", + "cc_storage = annuity(0.05, 25) * 150_000\n", + "\n", + "n.add(\n", + " \"StorageUnit\",\n", + " \"battery\",\n", + " bus=\"seville\",\n", + " capital_cost=cc_inverter + 4 * cc_storage,\n", + " p_nom_extendable=True,\n", + " carrier=\"battery\",\n", + " efficiency_store=np.sqrt(0.9),\n", + " efficiency_dispatch=np.sqrt(0.9),\n", + " max_hours=4,\n", + ");" + ] + }, + { + "cell_type": "markdown", + "id": "d2ac023b", + "metadata": {}, + "source": [ + "Now, the model is ready to be solved:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2cb8256", + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize(log_to_console=False)" + ] + }, + { + "cell_type": "markdown", + "id": "2093f988", + "metadata": {}, + "source": [ + "To retrieve the optimised capacities, we can either directly access the `p_nom_opt` attribute of the components, or use the `n.statistics` module." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "85993680", + "metadata": {}, + "outputs": [], + "source": [ + "display(n.generators.p_nom_opt)\n", + "display(n.storage_units.p_nom_opt)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aca55198", + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.optimal_capacity()" + ] + }, + { + "cell_type": "markdown", + "id": "89e7238c", + "metadata": {}, + "source": [ + "The statistics module also provides a convenient way to calculate investment and operational costs, as well as the average cost per unit of electricity consumed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2aac689b", + "metadata": {}, + "outputs": [], + "source": [ + "totex = {\"opex\": n.statistics.opex(), \"capex\": n.statistics.capex()}\n", + "pd.concat(totex, axis=1).div(1e6).round(2) # M€/a" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1a1f113", + "metadata": {}, + "outputs": [], + "source": [ + "(n.statistics.capex().sum() + n.statistics.opex().sum()) / 100 / 8760 # €/MWh" + ] + }, + { + "cell_type": "markdown", + "id": "4e0469ac", + "metadata": {}, + "source": [ + "The statistics module can also give you the energy balances of the system, to see how much electricity is consumed from the grid and when, and what the battery storage losses are." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "902d26e0", + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.energy_balance().div(1e3) # GWh" + ] + }, + { + "cell_type": "markdown", + "id": "94e24dce", + "metadata": {}, + "source": [ + "To access and plot the state of charge profile of the battery for January, run" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45979457", + "metadata": {}, + "outputs": [], + "source": [ + "n.storage_units_t.state_of_charge.loc[\"2011-01\"].plot(backend=\"plotly\")" + ] + }, + { + "cell_type": "markdown", + "id": "80334eaa", + "metadata": {}, + "source": [ + "The statistics functions also have built-in plotting capabilities, e.g. to plot the dispatch profiles of the system as stacked area charts." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3eccce6e", + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Carrier\",\n", + " [\"grid\", \"solar\", \"battery\", \"AC\"],\n", + " color=[\"blue\", \"yellow\", \"green\", \"k\"],\n", + ")\n", + "\n", + "n.statistics.energy_balance.iplot()" + ] + }, + { + "cell_type": "markdown", + "id": "ed5e87b4", + "metadata": {}, + "source": [ + "Finally, any network object can be exported to files, for example to Excel or NetCDF, for further analysis or reporting. Importing from files is of course also possible." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ab792144", + "metadata": {}, + "outputs": [], + "source": [ + "n.export_to_excel(\"data-centre-investment.xlsx\")\n", + "n.export_to_netcdf(\"data-centre-investment.nc\")\n", + "\n", + "o = pypsa.Network(\"data-centre-investment.nc\")" + ] + }, + { + "cell_type": "markdown", + "id": "4b9cfaa6", + "metadata": {}, + "source": [ + "Find many more extensive examples in the [examples](examples.md) section.\n", + "\n", + "The [user guide](user-guide.md) section contains detailed information on architecture, components, problem formulation and utilities." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/examples/examples.md b/PyPSA/source/docs/examples/examples.md new file mode 100644 index 0000000000000000000000000000000000000000..3e798f386e87a05b013e1488ca162349940c51ac --- /dev/null +++ b/PyPSA/source/docs/examples/examples.md @@ -0,0 +1,255 @@ + + +# Examples + +The examples below demonstrate PyPSA's capabilities for energy system modeling. They cover a broad range of topics, including electricity markets, linear optimal power flow, unit commitment, capacity expansion, grid modelling, and more. + +## Operational Problems + +
+ + +- :material-notebook:{ .lg .middle } **Electricity Market** + + Demonstrates basic electricity market modeling with with multiple bidding zones, renewables and storage. + + [:octicons-arrow-right-24: Go to example](simple-electricity-market-examples.ipynb) + +- :material-notebook:{ .lg .middle } **Unit Commitment** + + Models generator unit commitment with start-up and shut-down costs, ramping limits, minimum part loads, up and down times using binary variables. + + [:octicons-arrow-right-24: Go to example](unit-commitment.ipynb) + +- :material-notebook:{ .lg .middle } **Meshed AC-DC Networks** + + Builds a stylized 3-node AC network coupled via AC-DC converters to a 3-node DC network. + + [:octicons-arrow-right-24: Go to example](ac-dc-lopf.ipynb) + +- :material-notebook:{ .lg .middle } **SciGRID Network** + + Performs linear optimal power flow on a high-resolution German grid model to analyze power flows and nodal prices. + + [:octicons-arrow-right-24: Go to example](scigrid-lopf-then-pf.ipynb) + +- :material-notebook:{ .lg .middle } **Security-Constrained LOPF** + + Implements N-1 security constraints in linear optimal power flow models to ensure grid reliability under line outage events. + + [:octicons-arrow-right-24: Go to example](scigrid-sclopf.ipynb) + +- :material-notebook:{ .lg .middle } **Newton-Raphson Power Flow** + + Solves non-linear AC power flow equations using the Newton-Raphson method to inspect voltage magnitudes and angles. + + [:octicons-arrow-right-24: Go to example](minimal-example-pf.ipynb) + +- :material-notebook:{ .lg .middle } **Negative LMPs from Line Congestion** + + Explores how Kirchhoff's Voltage Law can lead to negative locational marginal prices when lines are congested. + + [:octicons-arrow-right-24: Go to example](negative-prices-kvl-baker.ipynb) + +- :material-notebook:{ .lg .middle } **Rolling-Horizon Optimization** + + Explores how rolling-horizon optimization can be used to account for imperfect forecast horizons in reality. + + [:octicons-arrow-right-24: Go to example](rolling-horizon.ipynb) + +
+ +## Planning Problems + + +
+ +- :material-notebook:{ .lg .middle } **Single-Node Capacity Expansion** + + Models investment decisions for generation and storage in a single-node system in the style of [model.energy](https://model.energy). + + [:octicons-arrow-right-24: Go to example](capacity-expansion-planning-single-node.ipynb) + +- :material-notebook:{ .lg .middle } **Three-Node Capacity Expansion** + + Co-optimizes generation, storage and transmission investments in a stylized three-node network in Australia :flag_au:. + + [:octicons-arrow-right-24: Go to example](3-node-cem.ipynb) + +- :material-notebook:{ .lg .middle } **Pathway Planning** + + Optimizes investment decisions across multiple investment periods for a long-term transition pathway with perfect foresight. + + [:octicons-arrow-right-24: Go to example](multi-investment-optimisation.ipynb) + +- :material-notebook:{ .lg .middle } **Myopic Pathway Planning** + + Optimizes investment decisions across multiple investment periods for a + long-term transition pathway with myopic foresight. + + [:octicons-arrow-right-24: Go to example](myopic-pathway.ipynb) + + +- :material-notebook:{ .lg .middle } **Stochastic Optimization** + + Demonstrates investment planning under uncertainty with scenario-based + two-stage stochastic optimization. + + [:octicons-arrow-right-24: Go to example](stochastic-optimization.ipynb) + +- :material-notebook:{ .lg .middle } **Modelling-to-Generate Alternatives** + + Explores near-optimal solution diversity by generating alternative system + designs with similar costs. + + [:octicons-arrow-right-24: Go to example](mga.ipynb) + +- :material-notebook:{ .lg .middle } **Exploring Near-Optimal Spaces** + + Explores near-optimal space to understand flexibility in investment + decisions while maintaining cost-effectiveness. + + [:octicons-arrow-right-24: Go to example](near-opt-space.ipynb) + +- :material-notebook:{ .lg .middle } **Modular Capacity Expansion** + + Models discrete capacity additions with integer constraints on investment + decisions considering predefined unit sizes. + + [:octicons-arrow-right-24: Go to example](modular-expansion.ipynb) + +
+ + +## Special Problems + +
+ +- :material-notebook:{ .lg .middle } **Redispatch** + + Sketches how redispatch can be modelled by separating market clearing and + congestion management. + + [:octicons-arrow-right-24: Go to example](scigrid-redispatch.ipynb) + +- :material-notebook:{ .lg .middle } **Demand Elasticity** + + Demonstrates modelling of price-responsive electricity demands and how they + affect price formation. + + [:octicons-arrow-right-24: Go to example](demand-elasticity.ipynb) + +- :material-notebook:{ .lg .middle } **Screening Curves** + + Determines optimal generation capacity mix based on screening curves. + + [:octicons-arrow-right-24: Go to example](generation-investment-screening-curve.ipynb) + +- :material-notebook:{ .lg .middle } **Chained Hydro Reservoirs** + + Models cascaded hydropower systems with water flow constraints between + reservoirs. + + [:octicons-arrow-right-24: Go to example](chained-hydro-reservoirs.ipynb) + +- :material-notebook:{ .lg .middle } **Transformers** + + Shows how transformers can be considered with varying tap ratios and phase + shifts. + + [:octicons-arrow-right-24: Go to example](transformer-example.ipynb) + + +- :material-notebook:{ .lg .middle } **Reserve Constraints** + + Implements operating reserve requirements in power system optimization. + + [:octicons-arrow-right-24: Go to example](reserve-power.ipynb) + +
+ +## Sector Coupling + +
+ +- :material-notebook:{ .lg .middle } **Single-Node Sector-Coupling** + + Extends the 1-node capacity expansion example with hydrogen, heat and + transport demand. + + [:octicons-arrow-right-24: Go to example](sector-coupling-single-node.ipynb) + + +- :material-notebook:{ .lg .middle } **Islanded Methanol Production** + + Optimizes islanded renewable methanol production systems in Namibia + :flag_na: or Argentina :flag_ar:. + + [:octicons-arrow-right-24: Go to example](islanded-methanol-production.ipynb) + + +- :material-notebook:{ .lg .middle } **Electric Vehicles** + + Demonstrates how to model flexible electric vehicle charging and discharging. + + [:octicons-arrow-right-24: Go to example](battery-electric-vehicle-charging.ipynb) + + +- :material-notebook:{ .lg .middle } **Backpressure CHPs** + + Models combined heat and power plants with fixed heat-to-power ratios. + + [:octicons-arrow-right-24: Go to example](chp-fixed-heat-power-ratio.ipynb) + + +- :material-notebook:{ .lg .middle } **Extraction-Condensing CHPs** + + Models combined heat and power plants with variable heat-to-power ratios. + + [:octicons-arrow-right-24: Go to example](power-to-gas-boiler-chp.ipynb) + + +- :material-notebook:{ .lg .middle } **Heat Pumps and Thermal Storage** + + Models sector coupling with heat pumps and thermal energy storage. + + [:octicons-arrow-right-24: Go to example](power-to-heat-water-tank.ipynb) + + +- :material-notebook:{ .lg .middle } **Carbon Management** + + Models carbon flows between atmosphere, biomass, and synthetic fuels. + + [:octicons-arrow-right-24: Go to example](biomass-synthetic-fuels-carbon-management.ipynb) + +
+ + +## Complexity Management + +
+ +- :material-notebook:{ .lg .middle } **Time Series Aggregation** + + Shows how model complexity can be reduced by aggregating snapshots. + + [:octicons-arrow-right-24: Go to example](time-series-aggregation.ipynb) + +- :material-notebook:{ .lg .middle } **Storage Units as Links & Stores** + + Shows how storage units can be replaced by more fundamental links and stores. + + [:octicons-arrow-right-24: Go to example](replace-generator-storage-units-with-store.ipynb) + +- :material-notebook:{ .lg .middle } **Tracing Infeasibilities** + + Shows how to trace infeasibilities in the optimization problem using Irreducible Infeasible Subsets (IIS). + + [:octicons-arrow-right-24: Go to example](tracing-infeasibilities.ipynb) + + +
diff --git a/PyPSA/source/docs/examples/generation-investment-screening-curve.ipynb b/PyPSA/source/docs/examples/generation-investment-screening-curve.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..efae40bb22141673ebae29b63feccdc872ce550d --- /dev/null +++ b/PyPSA/source/docs/examples/generation-investment-screening-curve.ipynb @@ -0,0 +1,332 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Screening Curves\n", + "\n", + "Compute the long-term equilibrium power plant investment for a given load duration curve ($1000-1000z$ for $z \\in [0,1]$) and a given set of generator investment options.\n", + "\n", + "To read up on the theory behind screenin curves, see [this lecture](https://nworbmot.org/courses/es-25/es-10-investment.pdf)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import pypsa" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Generator marginal (`m`) and capital (`c`) costs are given in EUR/MWh - numbers chosen for simple answer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "generators = {\n", + " \"coal\": {\"m\": 2, \"c\": 15},\n", + " \"gas\": {\"m\": 12, \"c\": 10},\n", + " \"load-shedding\": {\"m\": 1012, \"c\": 0},\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The screening curve intersections are at 0.01 and 0.5." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x = np.linspace(0, 1, 101)\n", + "df = pd.DataFrame({k: v[\"c\"] + x * v[\"m\"] for k, v in generators.items()}, index=x)\n", + "df.plot(ylim=[0, 50], title=\"Screening Curve\");" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n = pypsa.Network()\n", + "\n", + "num_snapshots = 1001\n", + "n.snapshots = np.linspace(0, 1, num_snapshots)\n", + "n.snapshot_weightings = n.snapshot_weightings / num_snapshots\n", + "\n", + "n.add(\"Bus\", name=\"bus\")\n", + "\n", + "n.add(\"Load\", name=\"load\", bus=\"bus\", p_set=1000 - 1000 * n.snapshots.values)\n", + "\n", + "for gen in generators:\n", + " n.add(\n", + " \"Generator\",\n", + " name=gen,\n", + " bus=\"bus\",\n", + " p_nom_extendable=True,\n", + " marginal_cost=generators[gen][\"m\"],\n", + " capital_cost=generators[gen][\"c\"],\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.loads_t.p_set.plot.area(title=\"Load Duration Curve\", ylabel=\"MW\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize()\n", + "n.objective" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The capacity is set by total electricity required.\n", + "\n", + "**NB:** No load shedding since all prices are below 10 000." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators.p_nom_opt.round(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.marginal_price.plot(title=\"Price Duration Curve\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The prices correspond either to VOLL (1012) for first 0.01 or the marginal costs (12 for 0.49 and 2 for 0.5)\n", + "\n", + "**Except** for (infinitesimally small) points at the screening curve intersections, which correspond to changing the load duration near the intersection, so that capacity changes. This explains 7 = (12+10 - 15) (replacing coal with gas) and 22 = (12+10) (replacing load-shedding with gas). \n", + "\n", + "Note: What remains unclear is what is causing l = 0... it should be 2." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.marginal_price.round(2).sum(axis=1).value_counts()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators_t.p.plot(ylim=[0, 600], title=\"Generation Dispatch\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Demonstrate zero-profit condition.\n", + "\n", + "1. The total cost is given by" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "weights = n.snapshot_weightings.objective\n", + "(\n", + " n.generators.p_nom_opt * n.generators.capital_cost\n", + " + weights @ n.generators_t.p * n.generators.marginal_cost\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "2. The total revenue by" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "weights @ n.generators_t.p.mul(n.buses_t.marginal_price[\"bus\"], axis=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, take the capacities from the above long-term equilibrium, then disallow expansion.\n", + "\n", + "Show that the resulting market prices are identical.\n", + "\n", + "This holds in this example, but does **not** necessarily hold and breaks down in some circumstances (for example, when there is a lot of storage and inter-temporal shifting)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators.p_nom_extendable = False\n", + "n.generators.p_nom = n.generators.p_nom_opt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize();" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.marginal_price.plot(title=\"Price Duration Curve\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.marginal_price.sum(axis=1).value_counts()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Demonstrate zero-profit condition. Differences are due to singular times, see above, not a problem\n", + "\n", + "1. Total costs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(\n", + " n.generators.p_nom * n.generators.capital_cost\n", + " + weights @ n.generators_t.p * n.generators.marginal_cost\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "2. Total revenue" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "weights @ n.generators_t.p.mul(n.buses_t.marginal_price[\"bus\"], axis=0)" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/PyPSA/source/docs/examples/islanded-methanol-production.ipynb b/PyPSA/source/docs/examples/islanded-methanol-production.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..1bbef6e6429cacabd96cf83b084d13feecc1cc9e --- /dev/null +++ b/PyPSA/source/docs/examples/islanded-methanol-production.ipynb @@ -0,0 +1,788 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Islanded Fuel Production" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "In this example, we build a simple model to assess the economics of islanded methanol production in Namibia (or Argentina).\n", + "The model can optimise investment and operation of wind and solar, electrolysers, turbine, hydrogen and battery storage, direct air capture, CO$_2$ storage, methanolisation and methanol stores to supply a constant methanol demand of 1 TWh/a." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T08:46:20.357375Z", + "start_time": "2024-05-24T08:46:17.846410Z" + }, + "jp-MarkdownHeadingCollapsed": true, + "tags": [] + }, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "import pypsa\n", + "from pypsa.common import annuity" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## Techno-economic assumptions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We take techno-economic assumptions from the [technology-data](https://github.com/PyPSA/technology-data) repository which collects assumptions on costs and efficiencies:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:30.201456Z", + "start_time": "2024-05-24T07:37:29.732485Z" + } + }, + "outputs": [], + "source": [ + "YEAR = 2030\n", + "url = f\"https://raw.githubusercontent.com/PyPSA/technology-data/master/outputs/costs_{YEAR}.csv\"\n", + "costs = pd.read_csv(url, index_col=[0, 1])\n", + "costs.loc[costs.unit.str.contains(\"/kW\"), \"value\"] *= 1e3\n", + "costs = costs.value.unstack().fillna({\"discount rate\": 0.07, \"lifetime\": 20, \"FOM\": 0})\n", + "\n", + "# Let's also take a little more optimistic view on the costs of electrolysers\n", + "costs.loc[\"electrolysis\", \"investment\"] = 500 # €/kW" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We calculate the capital costs (i.e. annualised investment costs, €/MW/a or €/MWh/a for storage), using a small utility function to calculate the **annuity factor** to annualise investment costs based on is the discount rate $r$ and lifetime $n$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:30.237265Z", + "start_time": "2024-05-24T07:37:30.232914Z" + } + }, + "outputs": [], + "source": [ + "a = costs.apply(lambda x: annuity(x[\"discount rate\"], x[\"lifetime\"]), axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:30.240224Z", + "start_time": "2024-05-24T07:37:30.238032Z" + } + }, + "outputs": [], + "source": [ + "costs[\"capital_cost\"] = (a + costs[\"FOM\"] / 100) * costs[\"investment\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Wind and solar time series" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:33.705510Z", + "start_time": "2024-05-24T07:37:30.240941Z" + } + }, + "source": [ + "The wind and solar capacity factor time series have been retrieved from [model.energy](https://model.energy). Go there to find more time series for other countries and plug them in here." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "RESOLUTION = 4 # hours\n", + "url = \"https://model.energy/data/time-series-ca2bcb9e843aeb286cd6295854c885b6.csv\" # South of Argentina\n", + "# url = \"https://model.energy/data/time-series-57f7bbcb5c4821506de052e52d022b48.csv\" # Namibia\n", + "ts = pd.read_csv(url, index_col=0, parse_dates=True)[::RESOLUTION]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:33.730588Z", + "start_time": "2024-05-24T07:37:33.709003Z" + } + }, + "outputs": [], + "source": [ + "ts.head(3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Build model\n", + "\n", + "### Initialisation\n", + "\n", + "Add buses, carriers and set snapshots." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:33.819917Z", + "start_time": "2024-05-24T07:37:33.743147Z" + } + }, + "outputs": [], + "source": [ + "n = pypsa.Network()\n", + "for carrier in [\"electricity\", \"hydrogen\", \"co2\", \"methanol\"]:\n", + " n.add(\"Bus\", carrier, carrier=carrier, unit=\"t/h\" if carrier == \"co2\" else \"MW\")\n", + "n.set_snapshots(ts.index)\n", + "n.snapshot_weightings.loc[:, :] = RESOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:33.861842Z", + "start_time": "2024-05-24T07:37:33.856810Z" + } + }, + "outputs": [], + "source": [ + "carriers = {\n", + " \"wind\": \"dodgerblue\",\n", + " \"solar\": \"gold\",\n", + " \"hydrogen storage\": \"blueviolet\",\n", + " \"battery storage 3h\": \"yellowgreen\",\n", + " \"battery storage 6h\": \"yellowgreen\",\n", + " \"electrolysis\": \"magenta\",\n", + " \"turbine\": \"darkorange\",\n", + " \"methanolisation\": \"cyan\",\n", + " \"direct air capture\": \"coral\",\n", + " \"co2 storage\": \"black\",\n", + " \"methanol storage\": \"cadetblue\",\n", + " \"electricity\": \"grey\",\n", + " \"hydrogen\": \"grey\",\n", + " \"co2\": \"grey\",\n", + " \"methanol\": \"grey\",\n", + "}\n", + "n.add(\"Carrier\", carriers.keys(), color=carriers.values())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Demand\n", + "\n", + "Add a constant methanol demand adding up to an annual target production of 1 TWh of methanol:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:33.866618Z", + "start_time": "2024-05-24T07:37:33.862680Z" + } + }, + "outputs": [], + "source": [ + "n.add(\n", + " \"Load\",\n", + " \"demand\",\n", + " bus=\"methanol\",\n", + " p_set=1e6 / 8760,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Wind and solar" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add the wind and solar generators:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:34.561820Z", + "start_time": "2024-05-24T07:37:34.554441Z" + } + }, + "outputs": [], + "source": [ + "n.add(\n", + " \"Generator\",\n", + " \"wind\",\n", + " bus=\"electricity\",\n", + " carrier=\"wind\",\n", + " p_max_pu=ts.onwind,\n", + " capital_cost=costs.at[\"onwind\", \"capital_cost\"],\n", + " p_nom_extendable=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Generator\",\n", + " \"solar\",\n", + " bus=\"electricity\",\n", + " carrier=\"solar\",\n", + " p_max_pu=ts.solar,\n", + " capital_cost=costs.at[\"solar\", \"capital_cost\"],\n", + " p_nom_extendable=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Batteries\n", + "\n", + "Add a 3-hour and a 6-hour battery storage:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for max_hours in [3, 6]:\n", + " n.add(\n", + " \"StorageUnit\",\n", + " f\"battery storage {max_hours}h\",\n", + " bus=\"electricity\",\n", + " carrier=f\"battery storage {max_hours}h\",\n", + " max_hours=max_hours,\n", + " capital_cost=costs.at[\"battery inverter\", \"capital_cost\"]\n", + " + max_hours * costs.at[\"battery storage\", \"capital_cost\"],\n", + " efficiency_store=costs.at[\"battery inverter\", \"efficiency\"],\n", + " efficiency_dispatch=costs.at[\"battery inverter\", \"efficiency\"],\n", + " p_nom_extendable=True,\n", + " cyclic_state_of_charge=True,\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Hydrogen\n", + "\n", + "Add electrolysers, hydrogen storage (steel tank), hydrogen turbine:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"electrolysis\",\n", + " bus0=\"electricity\",\n", + " bus1=\"hydrogen\",\n", + " carrier=\"electrolysis\",\n", + " p_nom_extendable=True,\n", + " efficiency=costs.at[\"electrolysis\", \"efficiency\"],\n", + " capital_cost=costs.at[\"electrolysis\", \"capital_cost\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"turbine\",\n", + " bus0=\"hydrogen\",\n", + " bus1=\"electricity\",\n", + " carrier=\"turbine\",\n", + " p_nom_extendable=True,\n", + " efficiency=costs.at[\"OCGT\", \"efficiency\"],\n", + " capital_cost=costs.at[\"OCGT\", \"capital_cost\"] / costs.at[\"OCGT\", \"efficiency\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tech = \"hydrogen storage tank type 1 including compressor\"\n", + "\n", + "n.add(\n", + " \"Store\",\n", + " \"hydrogen storage\",\n", + " bus=\"hydrogen\",\n", + " carrier=\"hydrogen storage\",\n", + " capital_cost=costs.at[tech, \"capital_cost\"],\n", + " e_nom_extendable=True,\n", + " e_cyclic=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Carbon Dioxide\n", + "\n", + "Add liquid carbon dioxide storage and direct air capture, assuming for simplicity an electricity demand 2 MWh/tCO2 for heat and electricity needs of the process.\n", + "More detailed modelling would also model the heat supply for the direct air capture." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "electricity_input = 2 # MWh/tCO2\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " \"direct air capture\",\n", + " bus0=\"electricity\",\n", + " bus1=\"co2\",\n", + " carrier=\"direct air capture\",\n", + " p_nom_extendable=True,\n", + " efficiency=1 / electricity_input,\n", + " capital_cost=costs.at[\"direct air capture\", \"capital_cost\"] / electricity_input,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Store\",\n", + " \"co2 storage\",\n", + " bus=\"co2\",\n", + " carrier=\"co2 storage\",\n", + " capital_cost=costs.at[\"CO2 storage tank\", \"capital_cost\"],\n", + " e_nom_extendable=True,\n", + " e_cyclic=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Methanol\n", + "\n", + "Add methanolisation unit, which takes hydrogen, electricity and carbon dioxide as input, and methanol storage.\n", + "Efficiencies and capital costs need to be expressed in units of `bus0`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "eff_h2 = 1 / costs.at[\"methanolisation\", \"hydrogen-input\"]\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " \"methanolisation\",\n", + " bus0=\"hydrogen\",\n", + " bus1=\"methanol\",\n", + " bus2=\"electricity\",\n", + " bus3=\"co2\",\n", + " carrier=\"methanolisation\",\n", + " p_nom_extendable=True,\n", + " capital_cost=costs.at[\"methanolisation\", \"capital_cost\"] * eff_h2,\n", + " efficiency=eff_h2,\n", + " efficiency2=-costs.at[\"methanolisation\", \"electricity-input\"] * eff_h2,\n", + " efficiency3=-costs.at[\"methanolisation\", \"carbondioxide-input\"] * eff_h2,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Costs for g is given in €/m³. We need to convert it to €/MWh, using the volumetric energy density of methanol (15.6 MJ/L)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "capital_cost = costs.at[\n", + " \"General liquid hydrocarbon storage (crude)\", \"capital_cost\"\n", + "] / (15.6 * 1000 / 3600)\n", + "\n", + "n.add(\n", + " \"Store\",\n", + " \"methanol storage\",\n", + " bus=\"co2\",\n", + " carrier=\"methanol storage\",\n", + " capital_cost=capital_cost,\n", + " e_nom_extendable=True,\n", + " e_cyclic=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Optimisation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:35.306567Z", + "start_time": "2024-05-24T07:37:34.744060Z" + } + }, + "outputs": [], + "source": [ + "n.optimize(solver_name=\"highs\", log_to_console=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Model evaluation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Total system cost by technology (we only added CAPEX components):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:35.309989Z", + "start_time": "2024-05-24T07:37:35.307258Z" + } + }, + "outputs": [], + "source": [ + "n.statistics.capex().div(1e6).sort_values(ascending=False) # mn€/a" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Costs per unit of fuel (€/MWh):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.capex().sum() / (8760 * n.loads.p_set.sum())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The optimised capacities in MW (MWh for `Store` component):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:35.321024Z", + "start_time": "2024-05-24T07:37:35.316333Z" + } + }, + "outputs": [], + "source": [ + "n.statistics.optimal_capacity()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Utilisation rates and capacity factors for each technology (in percent):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.capacity_factor() * 100" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Curtailment for each technology (in TWh):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.curtailment().div(1e6)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "System operation on electricity, hydrogen, carbon dioxide and methanol sides (in MW):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.energy_balance.plot.area(linewidth=0, bus_carrier=\"electricity\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.energy_balance.plot.area(linewidth=0, bus_carrier=\"hydrogen\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.energy_balance.plot.area(linewidth=0, bus_carrier=\"co2\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.energy_balance.plot.area(linewidth=0, bus_carrier=\"methanol\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modifications: Biogenic Carbon Dioxide\n", + "\n", + "How are costs affected by the availability of biogenic carbon dioxide costed at 50 €/t?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Generator\",\n", + " \"biogenic co2\",\n", + " bus=\"co2\",\n", + " carrier=\"biogenic co2\",\n", + " p_nom=1000, # non-binding\n", + " marginal_cost=50,\n", + ")\n", + "n.add(\n", + " \"Carrier\",\n", + " \"biogenic co2\",\n", + " color=\"forestgreen\",\n", + ")\n", + "n.optimize(solver_name=\"highs\", log_to_console=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(n.statistics.capex().sum() + n.statistics.opex().sum()) / 1e6 # €/MWh" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.energy_balance(bus_carrier=\"co2\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modifications: Inflexible PtX\n", + "\n", + "How are costs affected by limited flexibility of electrolyer and methanolisation units (e.g. with a minimum part load of 80%)?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links.loc[\"electrolysis\", \"p_min_pu\"] = 0.8\n", + "n.links.loc[\"methanolisation\", \"p_min_pu\"] = 0.8\n", + "n.optimize(solver_name=\"highs\", log_to_console=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(n.statistics.capex().sum() + n.statistics.opex().sum()) / 1e6 # €/MWh" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.optimal_capacity()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/PyPSA/source/docs/examples/mga.ipynb b/PyPSA/source/docs/examples/mga.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..6c32ae055cc07a08011ec57ad110af2f5cc2c9db --- /dev/null +++ b/PyPSA/source/docs/examples/mga.ipynb @@ -0,0 +1,318 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Modelling to Generate Alternatives" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "In this example, we apply MGA ('modelling to generate alternatives') to a single node capacity expansion model in the style of [model.energy](https://model.energy).\n", + "\n", + "The MGA algorithm, which can be called with `n.optimize.optimize_mga()`, tries to minimize or maximize investment or dispatch in (groups of) technologies within a set cost budget.\n", + "\n", + "For instance, it can be used to minimize the amount of wind capacity while keeping costs within 5% of the cost-optimal solution in terms of system costs.\n", + "\n", + "See also [model.energy](https://model.energy) and [Neumann and Brown (2021)](https://doi.org/10.1016/j.epsr.2020.106690) which uses PyPSA for MGA-type analysis." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T08:46:20.357375Z", + "start_time": "2024-05-24T08:46:17.846410Z" + }, + "jp-MarkdownHeadingCollapsed": true, + "tags": [] + }, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "import pypsa" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Find cost-optimal solution\n", + "\n", + "Running MGA requires knowledge of what the total system costs are in the optimum. So first, we need to solve for the cost-optimal solution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:33.819917Z", + "start_time": "2024-05-24T07:37:33.743147Z" + } + }, + "outputs": [], + "source": [ + "n = pypsa.examples.model_energy()\n", + "n.optimize(solver_name=\"highs\", log_to_console=False)\n", + "n.statistics.capex().sum() + n.statistics.opex().sum()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Extract cost-optimal results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Optimal total system cost by technology:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:35.309989Z", + "start_time": "2024-05-24T07:37:35.307258Z" + } + }, + "outputs": [], + "source": [ + "tsc = (\n", + " pd.concat([n.statistics.capex(), n.statistics.opex()], axis=1).sum(axis=1).div(1e9)\n", + ")\n", + "optimal_cost = tsc.sum()\n", + "tsc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The optimised capacities in GW (GWh for `Store` component):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:35.321024Z", + "start_time": "2024-05-24T07:37:35.316333Z" + } + }, + "outputs": [], + "source": [ + "n.statistics.optimal_capacity().div(1e3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Energy balances on electricity side (in TWh):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:37:35.325340Z", + "start_time": "2024-05-24T07:37:35.321798Z" + } + }, + "outputs": [], + "source": [ + "n.statistics.energy_balance(bus_carrier=\"electricity\").sort_values().div(1e6)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Energy balances plot as time series (in MW):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.energy_balance.plot.area(linewidth=0, bus_carrier=\"electricity\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Find lowest wind capacity within 5% cost slack" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The function `n.optimize.optimize_mga` takes three main arguments:\n", + "1. The `slack` for the allowed relative cost deviation from the cost-optimum (0.05 corresponds to 5%).\n", + "2. A dictionary of weights for defining the new objective function. The first level defines the component (e.g. \"Generator\"), the second level defines the optimisation variable (e.g. `p_nom` for investment), and the third level defines the component name (e.g. from `n.generators.index`).\n", + "3. The `sense`, noting whether to minimizes (\"min\") or maximize (\"max\") the new objective." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "weights = {\"Generator\": {\"p_nom\": {\"wind\": 1}}}\n", + "n.optimize.optimize_mga(\n", + " slack=0.05, weights=weights, sense=\"min\", solver_name=\"highs\", log_to_console=False\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The breakdown of total system costs shifts from wind towards more solar." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tsc = (\n", + " pd.concat([n.statistics.capex(), n.statistics.opex()], axis=1).sum(axis=1).div(1e9)\n", + ")\n", + "tsc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Up to numeric differences, it is 5% more expensive overall:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "optimal_cost * 1.05" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tsc.sum()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Overall, the wind capacity is cut by roughly a third:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.optimal_capacity().div(1e3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is also evident in the energy balance:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.energy_balance(bus_carrier=\"electricity\").sort_values().div(1e6)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And also recognizable in the energy balance time series plots:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.energy_balance.plot.area(linewidth=0, bus_carrier=\"electricity\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/PyPSA/source/docs/examples/minimal-example-pf.ipynb b/PyPSA/source/docs/examples/minimal-example-pf.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..1913240aeaeed544098bee9832990b22675dc5f5 --- /dev/null +++ b/PyPSA/source/docs/examples/minimal-example-pf.ipynb @@ -0,0 +1,215 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Newton-Raphson Power Flow\n", + "\n", + "In this example, we are going to create a network with three nodes, three lines, one load, and one generator with given setpoints. We then solve the non-linear power flow equations using the Newton-Raphson method applied in `n.pf()`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "import pypsa\n", + "\n", + "n = pypsa.Network()\n", + "\n", + "N_BUSES = 3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add three buses:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for i in range(N_BUSES):\n", + " n.add(\"Bus\", f\"My bus {i}\", v_nom=20)\n", + "\n", + "n.buses" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add three lines in a ring" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for i in range(N_BUSES):\n", + " n.add(\n", + " \"Line\",\n", + " f\"My line {i}\",\n", + " bus0=f\"My bus {i}\",\n", + " bus1=f\"My bus {(i + 1) % N_BUSES}\",\n", + " x=0.1,\n", + " r=0.01,\n", + " )\n", + "\n", + "n.lines" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add a generator at bus 0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Generator\", \"My gen\", bus=\"My bus 0\", p_set=100, control=\"PQ\")\n", + "\n", + "n.generators" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add a load at bus 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Load\", \"My load\", bus=\"My bus 1\", p_set=100, q_set=100)\n", + "\n", + "n.loads" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Do a Newton-Raphson power flow" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.pf()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alright, it converged! Now, what is the active power flow on the lines?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.lines_t.p0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "...and what are the voltage angles on the buses?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.v_ang * 180 / np.pi" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "...and their mangitudes?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.v_mag_pu" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/PyPSA/source/docs/examples/modular-expansion.ipynb b/PyPSA/source/docs/examples/modular-expansion.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..b9fd3d8a3abec08ced22cfcbb7e1f8e4883647a9 --- /dev/null +++ b/PyPSA/source/docs/examples/modular-expansion.ipynb @@ -0,0 +1,129 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "faec52e7", + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "28d4278d", + "metadata": {}, + "source": [ + "# Modular Expansion\n", + "\n", + "In this example, we demonstrate how to handle the modular expansion feature in PyPSA. Modular expansion allows you to specify discrete steps for the expansion of components. This is particularly useful for technologies that can only be built in fixed block sizes, such as nuclear generators.\n", + "\n", + "We start by loading the 3-hourly resolved [model.energy example]() and adding a nuclear generator as expansion option (with ramp limits of 1%/h and a minimum part load of 70%). Initially, we allow the nuclear generator to be built in continuous sizes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aa1c537c", + "metadata": {}, + "outputs": [], + "source": [ + "import pypsa\n", + "from pypsa.common import annuity\n", + "\n", + "n = pypsa.examples.model_energy()\n", + "\n", + "n.add(\n", + " \"Generator\",\n", + " \"nuclear\",\n", + " bus=\"electricity\",\n", + " p_nom_extendable=True,\n", + " marginal_cost=15,\n", + " capital_cost=annuity(0.07, 50) * 8_000_000,\n", + " p_min_pu=0.7,\n", + " ramp_limit_up=0.03,\n", + " ramp_limit_down=0.03,\n", + ")\n", + "\n", + "n.optimize(log_to_console=False)\n", + "\n", + "n.generators.p_nom_opt" + ] + }, + { + "cell_type": "markdown", + "id": "68e21bf1", + "metadata": {}, + "source": [ + "As optimal capacity for the nuclear generator, we obtain 7,639 MW.\n", + "\n", + "Now, let's assume that the nuclear generator can only be built in discrete steps of 1,000 MW. We can set the `p_nom_mod` attribute to 1,000 MW, which introduces an integer variable that constraints the optimised capacity to be a multiple of 1,000 MW. To constrain the option space for the integer variable, and help the solver a bit, we can also set the upper limit `p_nom_max` to 10,000 MW.\n", + "\n", + "This problem is solved as a mixed-integer linear program (MILP), which will take longer to solve than the previous continuous linear program (LP)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cbfc4bcb", + "metadata": {}, + "outputs": [], + "source": [ + "n.generators.loc[\"nuclear\", \"p_nom_mod\"] = 1000\n", + "n.generators.loc[\"nuclear\", \"p_nom_max\"] = 10000\n", + "n.optimize(mip_rel_gap=0.001, log_to_console=False)\n", + "n.generators.p_nom_opt" + ] + }, + { + "cell_type": "markdown", + "id": "04322837", + "metadata": {}, + "source": [ + "From the re-optimised results, we can see that the optimal capacity for the nuclear generator is now 8,000 MW, a multiple of 1,000 MW.\n", + "\n", + "As a short concluding excursion into the results, we can also illustrate how the optimal dispatch of this nuclear generator is constrained by ramp limits and minimum part loads in January 2019." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8a7b04c", + "metadata": {}, + "outputs": [], + "source": [ + "n.generators_t.p.loc[\"2019-01\"].plot(figsize=(6, 3))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/examples/multi-investment-optimisation.ipynb b/PyPSA/source/docs/examples/multi-investment-optimisation.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..0254f92ad8796cda59c00e83d63e2d9d8f702147 --- /dev/null +++ b/PyPSA/source/docs/examples/multi-investment-optimisation.ipynb @@ -0,0 +1,387 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "c50840cf", + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "af0d5042", + "metadata": {}, + "source": [ + "# Pathway Planning\n", + "\n", + "In this example, we demonstrate how PyPSA can deal with optimisation problems spanning multiple investment periods, also known as pathway planning. \n", + "\n", + "For models with multiple investment periods, the total set of snapshots is divided into investment periods, which translates into multi-indexed snapshots with investment periods as the first level time steps as the second level. In each investment period, new components may be added to the system. Additionally, any component may only operate as long as allowed by their lifetime.\n", + "\n", + "In contrast to the models with a single investment period (overnight scenarios), the following concepts have to be taken into account.:\n", + "\n", + "1. The network attribute `n.investment_periods`: This is the set of periods which specify when new components may be built. These have to be the same as the first level values in the `n.snapshots` index.\n", + "2. The network attribute `n.investment_period_weightings`: These specify the weighting of each period in the objective function and the global constraints. \n", + "3. The component attribute `build_year`: Any one component may only be built when the build year is equal to the current investment period, or larger than the previous investment period. That means, components with `build_year=2029` are considered in the investment period `2030`, but not in the period `2025`. \n", + "4. The component attribute `lifetime`: Any one component is only considered for dispatch in an investment period if it is still active at the beginning of an investment period. That means, components with `build_year=2029` and `lifetime=30` are considered in the investment period `2055`, but not in the period `2060`. \n", + "\n", + "In the following, we set up a three bus network with generators, lines and storage units and run a optimisation for the investment periods 2020, 2030, 2040 and 2050." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f5e767ac", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import pypsa\n", + "\n", + "rng = np.random.default_rng() # Create a random number generator" + ] + }, + { + "cell_type": "markdown", + "id": "95e62e96", + "metadata": {}, + "source": [ + "We set up the network with investment periods and snapshots. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd8cdd68", + "metadata": {}, + "outputs": [], + "source": [ + "n = pypsa.Network()\n", + "years = [2020, 2030, 2040, 2050]\n", + "freq = 24\n", + "\n", + "snapshots = pd.DatetimeIndex([])\n", + "for year in years:\n", + " period = pd.date_range(\n", + " start=f\"{year}-01-01 00:00\",\n", + " freq=f\"{freq}h\",\n", + " periods=8760 // freq,\n", + " )\n", + " snapshots = snapshots.append(period)\n", + "\n", + "# convert to multiindex and assign to network\n", + "n.snapshots = pd.MultiIndex.from_arrays([snapshots.year, snapshots])\n", + "n.investment_periods = years\n", + "\n", + "n.snapshot_weightings" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84d2d14f", + "metadata": {}, + "outputs": [], + "source": [ + "n.investment_periods" + ] + }, + { + "cell_type": "markdown", + "id": "ba52e3eb", + "metadata": {}, + "source": [ + "Set the years and objective weighting per investment period. For the objective weighting, we consider a discount rate defined by \n", + "$$ D(t) = \\dfrac{1}{(1+r)^t} $$ \n", + "\n", + "where $r$ is the discount rate. For each period we sum up all discounts rates of the corresponding years which gives us the effective objective weighting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e0a025d2", + "metadata": {}, + "outputs": [], + "source": [ + "n.investment_period_weightings[\"years\"] = list(np.diff(years)) + [10]\n", + "\n", + "r = 0.01\n", + "T = 0\n", + "for period, nyears in n.investment_period_weightings.years.items():\n", + " discounts = [(1 / (1 + r) ** t) for t in range(T, T + nyears)]\n", + " n.investment_period_weightings.at[period, \"objective\"] = sum(discounts)\n", + " T += nyears\n", + "n.investment_period_weightings" + ] + }, + { + "cell_type": "markdown", + "id": "ae5e0f16", + "metadata": {}, + "source": [ + "Add the components" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4d90f0f0", + "metadata": {}, + "outputs": [], + "source": [ + "for i in range(3):\n", + " n.add(\"Bus\", f\"bus {i}\")\n", + "\n", + "# add three lines in a ring\n", + "n.add(\n", + " \"Line\",\n", + " \"line 0->1\",\n", + " bus0=\"bus 0\",\n", + " bus1=\"bus 1\",\n", + ")\n", + "\n", + "n.add(\n", + " \"Line\",\n", + " \"line 1->2\",\n", + " bus0=\"bus 1\",\n", + " bus1=\"bus 2\",\n", + " capital_cost=10,\n", + " build_year=2030,\n", + ")\n", + "\n", + "n.add(\n", + " \"Line\",\n", + " \"line 2->0\",\n", + " bus0=\"bus 2\",\n", + " bus1=\"bus 0\",\n", + ")\n", + "\n", + "n.lines[\"x\"] = 0.0001\n", + "n.lines[\"s_nom_extendable\"] = True\n", + "\n", + "n.lines" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ccdca3cd", + "metadata": {}, + "outputs": [], + "source": [ + "# add some generators\n", + "p_nom_max = pd.Series(\n", + " (rng.uniform() for sn in range(len(n.snapshots))),\n", + " index=n.snapshots,\n", + " name=\"generator ext 2020\",\n", + ")\n", + "\n", + "# renewable (can operate 2020, 2030)\n", + "n.add(\n", + " \"Generator\",\n", + " \"generator ext 0 2020\",\n", + " bus=\"bus 0\",\n", + " p_nom=50,\n", + " build_year=2020,\n", + " lifetime=20,\n", + " marginal_cost=2,\n", + " capital_cost=1,\n", + " p_max_pu=p_nom_max,\n", + " carrier=\"solar\",\n", + " p_nom_extendable=True,\n", + ")\n", + "\n", + "# can operate 2040, 2050\n", + "n.add(\n", + " \"Generator\",\n", + " \"generator ext 0 2040\",\n", + " bus=\"bus 0\",\n", + " p_nom=50,\n", + " build_year=2040,\n", + " lifetime=11,\n", + " marginal_cost=25,\n", + " capital_cost=10,\n", + " carrier=\"OCGT\",\n", + " p_nom_extendable=True,\n", + ")\n", + "\n", + "# can operate in 2040\n", + "n.add(\n", + " \"Generator\",\n", + " \"generator fix 1 2040\",\n", + " bus=\"bus 1\",\n", + " p_nom=50,\n", + " build_year=2040,\n", + " lifetime=10,\n", + " carrier=\"CCGT\",\n", + " marginal_cost=20,\n", + " capital_cost=1,\n", + ")\n", + "\n", + "n.generators" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5025e810", + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"StorageUnit\",\n", + " \"storageunit non-cyclic 2030\",\n", + " bus=\"bus 2\",\n", + " p_nom=0,\n", + " capital_cost=2,\n", + " build_year=2030,\n", + " lifetime=21,\n", + " cyclic_state_of_charge=False,\n", + " p_nom_extendable=False,\n", + ")\n", + "\n", + "n.add(\n", + " \"StorageUnit\",\n", + " \"storageunit periodic 2020\",\n", + " bus=\"bus 2\",\n", + " p_nom=0,\n", + " capital_cost=1,\n", + " build_year=2020,\n", + " lifetime=21,\n", + " cyclic_state_of_charge=True,\n", + " cyclic_state_of_charge_per_period=True,\n", + " p_nom_extendable=True,\n", + ")\n", + "\n", + "n.storage_units" + ] + }, + { + "cell_type": "markdown", + "id": "694ffb04", + "metadata": {}, + "source": [ + "Add the load" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7981acdf", + "metadata": {}, + "outputs": [], + "source": [ + "load_var = pd.Series(\n", + " 100 * rng.random(size=len(n.snapshots)), index=n.snapshots, name=\"load\"\n", + ")\n", + "n.add(\"Load\", \"load 2\", bus=\"bus 2\", p_set=load_var)\n", + "\n", + "load_fix = pd.Series(75, index=n.snapshots, name=\"load\")\n", + "n.add(\"Load\", \"load 1\", bus=\"bus 1\", p_set=load_fix)\n", + "\n", + "n.loads_t.p_set.head()" + ] + }, + { + "cell_type": "markdown", + "id": "09467ca4", + "metadata": {}, + "source": [ + "Run the optimization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "04e5db92", + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize(multi_investment_periods=True, log_to_console=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a5de32ee", + "metadata": {}, + "outputs": [], + "source": [ + "c = \"Generator\"\n", + "df = pd.concat(\n", + " {\n", + " period: n.components[c].get_active_assets(period)\n", + " * n.components[c].static.p_nom_opt\n", + " for period in n.investment_periods\n", + " },\n", + " axis=1,\n", + ")\n", + "df.T.plot.bar(\n", + " stacked=True,\n", + " edgecolor=\"white\",\n", + " width=1,\n", + " ylabel=\"Capacity (MW)\",\n", + " xlabel=\"Investment Period\",\n", + " rot=0,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ebd9a17b", + "metadata": {}, + "outputs": [], + "source": [ + "df = n.generators_t.p.sum(axis=0).T.div(1e3)\n", + "df.T.plot.bar(\n", + " stacked=True,\n", + " edgecolor=\"white\",\n", + " width=1,\n", + " ylabel=\"Generation (GWh)\",\n", + " xlabel=\"Investment Period\",\n", + " rot=0,\n", + ")" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/examples/myopic-pathway.ipynb b/PyPSA/source/docs/examples/myopic-pathway.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..b4335f65e0bcee6488c2ed5c58d0fea9f9480457 --- /dev/null +++ b/PyPSA/source/docs/examples/myopic-pathway.ipynb @@ -0,0 +1,483 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "3ae2fa82", + "metadata": {}, + "source": [ + "# Myopic Pathway Planning\n", + "\n", + "In this example, we demonstrate how to use PyPSA to plan a myopic pathway for emission reductions. That means that we optimize the system for each investment period sequentially subject to set emission reduction targets per period. In consequence, the model does not foresee future costs or benefits of investments. This is in contrast to a [pathway optimisation with perfect foresight](), where the model optimizes the system for all investment periods at once.\n", + "\n", + "Here, we build a model from the [single-node capacity expansion example]() and model a myopic decision process for the investment periods 2025, 2035, and 2045. The model is set up to reduce emissions from 30 MtCO2/a in 2025 to 10 MtCO2/a in 2035 and 3 MtCO2/a in 2045. Note that the goal here is not necessarily to showcase a realistic scenario, but rather to demonstrate how it is possible to model a myopic decision process in PyPSA." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f0afa06", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "import pypsa\n", + "from pypsa.common import annuity\n", + "\n", + "temp = pypsa.examples.model_energy()\n", + "temp.remove(\"Generator\", \"load shedding\")\n", + "n = temp.copy()\n", + "\n", + "INVESTMENT_PERIODS = [2025, 2035, 2045]\n", + "SOLVER_NAME = \"highs\"" + ] + }, + { + "cell_type": "markdown", + "id": "e2866903", + "metadata": {}, + "source": [ + "First, we add a new component for each investment period and carrier to the network. These components are used to model the individual investment decisions for each period. For instance, for solar, there will be a `Generator` component for each investment period, e.g. `solar-2025`, `solar-2035`, and `solar-2045` with the corresponding `build_year` attributes.\n", + "\n", + "We can also keep pre-existing capacities as non-extendable components. Here, we keep 5 GW each of wind and solar capacities, assuming 2015 as build year." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df8dd906", + "metadata": {}, + "outputs": [], + "source": [ + "# Assume 5 GW of wind and solar capacity each, built in 2015\n", + "n.generators.loc[:, [\"build_year\", \"p_nom\", \"p_nom_extendable\"]] = 2015, 5_000, False\n", + "\n", + "# all other storage components need to be removed\n", + "n.remove(\"Link\", n.links.index)\n", + "n.remove(\"StorageUnit\", n.storage_units.index)\n", + "n.remove(\"Store\", n.stores.index)\n", + "\n", + "for year in INVESTMENT_PERIODS:\n", + " n.add(\n", + " \"Generator\",\n", + " temp.generators.index + f\"-{year}\",\n", + " build_year=year,\n", + " p_max_pu=temp.generators_t.p_max_pu.rename(\n", + " columns=lambda s: s + f\"-{year}\"\n", + " ).loc[:, ::-1],\n", + " **temp.generators.rename(index=lambda s: s + f\"-{year}\").drop(\n", + " columns=[\"build_year\", \"p_max_pu\"]\n", + " ),\n", + " )\n", + "\n", + " n.add(\n", + " \"Link\",\n", + " temp.links.index,\n", + " suffix=f\"-{year}\",\n", + " build_year=year,\n", + " **temp.links.rename(index=lambda s: s + f\"-{year}\").drop(\n", + " columns=[\"build_year\"]\n", + " ),\n", + " )\n", + "\n", + " n.add(\n", + " \"StorageUnit\",\n", + " temp.storage_units.index,\n", + " suffix=f\"-{year}\",\n", + " build_year=year,\n", + " **temp.storage_units.rename(index=lambda s: s + f\"-{year}\").drop(\n", + " columns=[\"build_year\"]\n", + " ),\n", + " )\n", + "\n", + " n.add(\n", + " \"Store\",\n", + " temp.stores.index,\n", + " suffix=f\"-{year}\",\n", + " build_year=year,\n", + " **temp.stores.rename(index=lambda s: s + f\"-{year}\").drop(\n", + " columns=[\"build_year\"]\n", + " ),\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "ff4b1295", + "metadata": {}, + "source": [ + "In this way, the attributes of components can vary over the investment periods, e.g. the `capital_cost` of solar PV and battery storage can decrease over time, or the capacity factors (`p_max_pu`) of wind generators can increase over time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e5d53de5", + "metadata": {}, + "outputs": [], + "source": [ + "n.generators.loc[\"solar-2035\", \"capital_cost\"] *= 0.9\n", + "n.generators.loc[\"solar-2045\", \"capital_cost\"] *= 0.85\n", + "\n", + "n.storage_units.loc[\"battery storage-2035\", \"capital_cost\"] *= 0.9\n", + "n.storage_units.loc[\"battery storage-2045\", \"capital_cost\"] *= 0.85\n", + "\n", + "n.generators_t.p_max_pu.loc[:, \"wind-2035\"] *= 1.1\n", + "n.generators_t.p_max_pu.loc[:, \"wind-2045\"] *= 1.15" + ] + }, + { + "cell_type": "markdown", + "id": "c3c0b316", + "metadata": {}, + "source": [ + "Let us also add some pre-existing conventional generators to the network with emissions from burning fossil fuels.\n", + "\n", + "We add some old coal generators, which will retire by 2035, and some gas generators, which will retire by 2045.\n", + "We also let the model invest in new gas generators in 2035, with slightly higher cost but better efficiency." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8606777a", + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Carrier\",\n", + " [\"coal\", \"gas\"],\n", + " color=[\"gray\", \"lightcoral\"],\n", + " co2_emissions=[0.336, 0.198],\n", + ")\n", + "\n", + "n.add(\n", + " \"Generator\",\n", + " \"coal\",\n", + " carrier=\"coal\",\n", + " bus=\"electricity\",\n", + " p_nom=4_000,\n", + " p_nom_extendable=False,\n", + " build_year=1990,\n", + " lifetime=40,\n", + " capital_cost=annuity(0.07, 40) * 4_000_000,\n", + " efficiency=0.35,\n", + " marginal_cost=30,\n", + ")\n", + "\n", + "n.add(\n", + " \"Generator\",\n", + " \"gas\",\n", + " carrier=\"gas\",\n", + " bus=\"electricity\",\n", + " p_nom=6_000,\n", + " p_nom_extendable=False,\n", + " build_year=2005,\n", + " lifetime=35,\n", + " capital_cost=annuity(0.07, 35) * 800_000,\n", + " efficiency=0.5,\n", + " marginal_cost=60,\n", + ")\n", + "\n", + "n.add(\n", + " \"Generator\",\n", + " \"gas-2035\",\n", + " carrier=\"gas\",\n", + " bus=\"electricity\",\n", + " p_nom_extendable=True,\n", + " build_year=2035,\n", + " lifetime=35,\n", + " capital_cost=annuity(0.07, 35) * 900_000,\n", + " efficiency=0.55,\n", + " marginal_cost=55,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "62dc6f49", + "metadata": {}, + "source": [ + "In the next step, we define the investment periods and the emission reduction targets for each period.\n", + "Note that each investment period represents a duration of 10 years, hence the annual targets are scaled to represent total emissions over the entire period." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c167adb", + "metadata": {}, + "outputs": [], + "source": [ + "n.set_investment_periods(INVESTMENT_PERIODS)\n", + "n.investment_period_weightings *= 10\n", + "\n", + "REDUCTION_PATH = [300e6, 100e6, 20e6]\n", + "\n", + "for i, year in enumerate(INVESTMENT_PERIODS):\n", + " n.add(\n", + " \"GlobalConstraint\",\n", + " f\"co2-limit-{year}\",\n", + " type=\"primary_energy\",\n", + " carrier_attribute=\"co2_emissions\",\n", + " investment_period=year,\n", + " constant=REDUCTION_PATH[i],\n", + " sense=\"<=\",\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "57358f31", + "metadata": {}, + "source": [ + "To optimize only a single investment period at a time, we have to make a selection of the relevant snapshots (i.e. all that fall into the investment period).\n", + "Passing this selection to `n.optimize()` will exclude investment variables of any inactive components (where the build year falls into future investment periods), and also only optimise the dispatch variables for the time steps in the investment period." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5a2a1222", + "metadata": {}, + "outputs": [], + "source": [ + "def optimize_period(n: pypsa.Network, period: int):\n", + " snapshots = n.snapshots[n.snapshots.get_level_values(\"period\") == period]\n", + " return n.optimize(\n", + " multi_investment_periods=True,\n", + " solver_name=SOLVER_NAME,\n", + " snapshots=snapshots,\n", + " log_to_console=False,\n", + " )\n", + "\n", + "\n", + "optimize_period(n, 2025)\n", + "\n", + "display(n.generators.p_nom_opt)\n", + "\n", + "display(n.global_constraints)" + ] + }, + { + "cell_type": "markdown", + "id": "c1d54b75", + "metadata": {}, + "source": [ + "In the results we can see that any components after 2025 are not touched in the first investment period.\n", + "The CO2 constraint is mildly binding at 20 €/tCO2, which is the price of CO2 in the first period.\n", + "The energy mix is still dominated by coal and gas, with some solar and wind added to the system." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "069cc724", + "metadata": {}, + "outputs": [], + "source": [ + "n.statistics.energy_balance().div(1e6).round(2)" + ] + }, + { + "cell_type": "markdown", + "id": "1b44b587", + "metadata": {}, + "source": [ + "For the second investment period, we need to freeze the capacities that were built in the first period, so that they are not changed in the second period (see `freeze_period()` function below). Then we can again select the relevant snapshots and optimize the system for the second investment period -- and so on for any further investment periods." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c07e8d79", + "metadata": {}, + "outputs": [], + "source": [ + "def freeze_period(n: pypsa.Network, period: int):\n", + " for c in n.components[[\"Generator\", \"Link\", \"StorageUnit\", \"Store\"]]:\n", + " attr = \"e_nom\" if c.name == \"Store\" else \"p_nom\"\n", + " c.static[attr] = c.static[attr + \"_opt\"]\n", + " c.static.loc[c.static.build_year == period, attr + \"_extendable\"] = False\n", + "\n", + "\n", + "freeze_period(n, 2025)\n", + "optimize_period(n, 2035)\n", + "\n", + "freeze_period(n, 2035)\n", + "optimize_period(n, 2045)" + ] + }, + { + "cell_type": "markdown", + "id": "919a75be", + "metadata": {}, + "source": [ + "After the loop has finished, we can inspect the results of all investment periods.\n", + "For instance, we can see how the endogenous CO2 price increases with each investment period." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "521118b1", + "metadata": {}, + "outputs": [], + "source": [ + "n.global_constraints" + ] + }, + { + "cell_type": "markdown", + "id": "d0dcfad2", + "metadata": {}, + "source": [ + "The energy balance and CAPEX statistics also tell us the story that the coal power plants that are phased out in 2035 are largely replaced by a large amount of new gas power plants. Only in 2045, the model starts to double down on renewables and storage investments to meet more ambitious emission reduction targets.\n", + "By then, the new gas power plants are barely used, while their capital costs are still to be paid off." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74656968", + "metadata": {}, + "outputs": [], + "source": [ + "display(n.statistics.energy_balance().div(1e6).round(1))\n", + "\n", + "display(n.statistics.capex().div(1e6).round(1))" + ] + }, + { + "cell_type": "markdown", + "id": "ec9c9e34", + "metadata": {}, + "source": [ + "Total annual system costs increase as emissions must be reduced. The data shown does not account for a social discount rate." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10acf7f5", + "metadata": {}, + "outputs": [], + "source": [ + "(n.statistics.capex().sum() + n.statistics.opex().sum()).div(1e6).round(1)" + ] + }, + { + "cell_type": "markdown", + "id": "f03c7bed", + "metadata": {}, + "source": [ + "Some data wrangling is needed to plot the progression of operational generation capacities over time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "58fa41ba", + "metadata": {}, + "outputs": [], + "source": [ + "df = (\n", + " pd.concat(\n", + " {\n", + " year: n.generators.query(\n", + " f\"build_year <= {year} and build_year + lifetime > {year}\"\n", + " )\n", + " .groupby(\"carrier\")\n", + " .p_nom_opt.sum()\n", + " for year in INVESTMENT_PERIODS\n", + " },\n", + " axis=1,\n", + " )\n", + " .fillna(0)\n", + " .T.div(1e3)\n", + ")\n", + "\n", + "df.plot.area(\n", + " stacked=True,\n", + " linewidth=0,\n", + " ylabel=\"GW\",\n", + " xlabel=\"Year\",\n", + " title=\"Operational Capacity\",\n", + " color=df.columns.map(n.carriers.color),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "94a8d3f6", + "metadata": {}, + "source": [ + "**Where to go from here?**\n", + "\n", + "A few ideas to explore further in this notebook:\n", + "\n", + "- Try different exogenous emission reduction paths. How do they affect total costs and cumulative emissions?\n", + "- Decrease the capital cost of components over time in different ways. How does this affect the optimal build-out?\n", + "- Decrease the capital cost of components over time as a function of the cumulative installed capacity to simulate learning effects.\n", + "- Increase the capacity factors of wind and solar components over time to varying extents. How does this affect the optimal build-out?\n", + "- Compare the myopic pathway with a full pathway optimization. How do they differ in terms of costs, cumulative emissions, stranded assets? A code snippet is provided blow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bfd1055a", + "metadata": {}, + "outputs": [], + "source": [ + "# for c in n.iterate_components({\"Generator\", \"Link\", \"StorageUnit\", \"Store\"}):\n", + "# attr = \"e_nom\" if c.name == \"Store\" else \"p_nom\"\n", + "# c.df.loc[c.df.build_year >= 2025, attr + \"_extendable\"] = True\n", + "\n", + "# n.optimize(\n", + "# multi_investment_periods=True,\n", + "# solver_name=\"gurobi\",\n", + "# log_to_console=True,\n", + "# )" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/examples/near-opt-space.ipynb b/PyPSA/source/docs/examples/near-opt-space.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..3694117344ea2cd902dfe34f50bf9a22ab4a7e21 --- /dev/null +++ b/PyPSA/source/docs/examples/near-opt-space.ipynb @@ -0,0 +1,377 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "c3ee2f62", + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "cdcc69b5", + "metadata": {}, + "source": [ + "# Exploring the Near-Optimal Feasible Space" + ] + }, + { + "cell_type": "markdown", + "id": "6688c196", + "metadata": {}, + "source": [ + "Here, we use modelling to generate alternatives (MGA) to explore the near-opimal feasible space of a simple one-node model in the style of [model.energy](https://model.energy) (the same as in the previous MGA example).\n", + "\n", + "While we explored the use of `n.optimize.optimize_mga()` in the previous example, PyPSA includes a few additional functions making it convenient to work with near-optimal spaces in a geometric way. While the `optimize_mga` function solves a network with an alternative objective function (given by the `weights` argument), `n.optimize_mga_in_direction` is a simple alternative useful for exploring trade-offs between multiple alternative objectives. It takes two key arguments: a dictionary of `dimensions`, specifying multiple alternative objectives in the same format as the previously mentioned `weights`, and a `direction` representing a vector in the coordinate space defined by `dimensions`.\n", + "\n", + "In this example, we explore the trade-offs between wind and solar expansion in a simple renewables-based system. Therefore, we define our `dimensions` (or alternative objectives) as total installed wind capacity and total installed solar capacity in MW, respectively. These dimensions can also be seen as defining a projection of near-optimal space of the model down to two dimensions.\n", + "\n", + ":::{note}\n", + "See also [this research article](https://doi.org/10.1016/j.eneco.2022.106496) for a background on dimension reduction and approximation of near-optimal spaces.\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "143e6b28", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "from matplotlib.collections import LineCollection\n", + "from scipy.spatial import ConvexHull, convex_hull_plot_2d\n", + "\n", + "import pypsa" + ] + }, + { + "cell_type": "markdown", + "id": "fab356a0", + "metadata": {}, + "source": [ + "Before any near-optimal analysis can start, the model has to be solved to optimality first. This is in order to find the minimum system cost, which is later used to define a system cost bound for near-optimality (i.e. \"slack\", usually in percentage of minimum system cost)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a2db3f1", + "metadata": {}, + "outputs": [], + "source": [ + "n = pypsa.examples.model_energy()\n", + "n.snapshots = n.snapshots[::3] # Crude reduction of time resolution for speed\n", + "n.generators.at[\"load shedding\", \"marginal_cost\"] = 20000\n", + "n.optimize(solver_name=\"highs\", log_to_console=False)" + ] + }, + { + "cell_type": "markdown", + "id": "f1cc40f7", + "metadata": {}, + "source": [ + "Individual dimensions are specified just like the `weights` argument for the `n.optimize.optimize_mga` function: a nested dictionary of components, variables and finally a series or dictionary giving weights to individual components.\n", + "\n", + "Here, we define two dimensions, and give them the user-defined names \"wind\" and \"solar\" (the keys in the outer dictionary). These names are arbitrary and don't have to match names of underlaying components." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e20af25", + "metadata": {}, + "outputs": [], + "source": [ + "dimensions = {\n", + " \"wind\": {\"Generator\": {\"p_nom\": {\"wind\": 1}}},\n", + " \"solar\": {\"Generator\": {\"p_nom\": {\"solar\": 1}}},\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "56079a49", + "metadata": {}, + "source": [ + "Suppose we are interested in minimizing wind capacity alone, and also interested in minimizing the total capacity of wind and solar. These correspond to the directions (-1, 0) and (-1, -1) in wind-solar coordinate space, respectively. Let's see what is possible within a 5% total system cost slack." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "221a9df6", + "metadata": {}, + "outputs": [], + "source": [ + "_, _, min_wind = n.optimize.optimize_mga_in_direction(\n", + " dimensions=dimensions,\n", + " direction={\n", + " \"wind\": -1,\n", + " \"solar\": 0,\n", + " }, # Use coordinate names matching those given in `dimensions`\n", + " slack=0.05,\n", + " solver_name=\"highs\",\n", + " log_to_console=False,\n", + ")\n", + "_, _, min_wind_solar = n.optimize.optimize_mga_in_direction(\n", + " dimensions=dimensions,\n", + " direction={\"wind\": -1, \"solar\": -1},\n", + " slack=0.05,\n", + " solver_name=\"highs\",\n", + " log_to_console=False,\n", + ")\n", + "\n", + "display(min_wind)\n", + "display(min_wind_solar)" + ] + }, + { + "cell_type": "markdown", + "id": "3205c79b", + "metadata": {}, + "source": [ + "Results are returned as dictionaries represting coordinates in the wind-solar space. The dictionaries use the same user-given coordinate names that we passed in the `dimensions` argument. We see that minimizing wind alone leads to a large installation of solar, while minimizing both jointly leads to a more balanced solution." + ] + }, + { + "cell_type": "markdown", + "id": "4b1860e7", + "metadata": {}, + "source": [ + "In order to get a full picture of the near-optimal space (projected to the wind and solar dimensions), it is useful to optimize in many directions and consider the convex hull of the resulting points.\n", + "\n", + "Of course, you can use `optimize_mga_in_direction` manually to do this. For convenience, PyPSA also provides the `optimize_mga_in_multiple_directions` function. This function takes a list of directions (or a pandas DataFrame with rows for directions), and uses built-in Python multiprocessing functionality to optimize in these directions in parallel. In the event of any individual optimisations failing, only the successful ones are returned.\n", + "\n", + "While the user is free to select directions themselves, PyPSA also includes some simple functions (for instance, `pypsa.optimization.mga.generate_directions_randomber of random directions in the format expected by `optimize_mga_in_multiple_directions`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7a94dda", + "metadata": {}, + "outputs": [], + "source": [ + "directions = pypsa.optimization.mga.generate_directions_random(\n", + " dimensions.keys(), 10, seed=0\n", + ")\n", + "dirs, pts = n.optimize.optimize_mga_in_multiple_directions(\n", + " dimensions=dimensions,\n", + " directions=directions,\n", + " slack=0.05,\n", + " solver_name=\"highs\",\n", + " log_to_console=False,\n", + " # Solve in up to 8 directions in parallel.\n", + " max_parallel=8,\n", + " # For performance reasons, we select the direct linopy-highs interface here,\n", + " # and use the highs interior point method (ipm) for solving each LP.\n", + " io_api=\"direct\",\n", + " solver=\"ipm\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "bb7debd3", + "metadata": {}, + "source": [ + "The results are returned in the form of two pandas DataFrames; one for directions and one for resulting points / coordinates. Using scipy, we can compute and plot the convex hull of the points we generated." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c34f8eb7", + "metadata": {}, + "outputs": [], + "source": [ + "display(pts.head() / 1e3)\n", + "\n", + "fig, ax = plt.subplots(figsize=(8, 6))\n", + "convex_hull_plot_2d(ConvexHull(pts / 1e3), ax)\n", + "\n", + "ax.axis(\"equal\")\n", + "ax.set_xlabel(\"Wind capacity (GW)\")\n", + "ax.set_ylabel(\"Solar capacity (GW)\")\n", + "ax.set_title(\"Near-optimal space in wind-solar coordinate space\")\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "6b0c5a4a", + "metadata": {}, + "source": [ + "While random directions generally work well, they are not always perfectly spaced out. In two dimensions, we can easily generate evenly spaced directions (points on a circle); implemented in PyPSA in `pypsa.optimization.mga.generate_directions_evenly_spaced`. In higher dimensions, generating evenly spaced directions is not trivial, but Halton sequences (`pypsa.optimization.mga.generate_directions_halton`) do a better job than randomly generated directions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3716de66", + "metadata": {}, + "outputs": [], + "source": [ + "n_points = 20\n", + "directions_random = pypsa.optimization.mga.generate_directions_random(\n", + " dimensions.keys(), n_points, seed=0\n", + ")\n", + "directions_even = pypsa.optimization.mga.generate_directions_evenly_spaced(\n", + " dimensions.keys(), n_points\n", + ")\n", + "directions_halton = pypsa.optimization.mga.generate_directions_halton(\n", + " dimensions.keys(), n_points, seed=0\n", + ")\n", + "fig, axs = plt.subplots(1, 3, figsize=(12, 3), sharey=True)\n", + "directions_random.plot(kind=\"scatter\", x=\"wind\", y=\"solar\", ax=axs[0])\n", + "directions_even.plot(kind=\"scatter\", x=\"wind\", y=\"solar\", ax=axs[1])\n", + "directions_halton.plot(kind=\"scatter\", x=\"wind\", y=\"solar\", ax=axs[2])\n", + "for ax in axs:\n", + " ax.set_xlim(-1.1, 1.1)\n", + " ax.set_ylim(-1.1, 1.1)\n", + " ax.axis(\"equal\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "09b6f5af", + "metadata": {}, + "outputs": [], + "source": [ + "fig, axs = plt.subplots(1, 3, figsize=(12, 3), sharey=True)\n", + "\n", + "for ax, directions in zip(axs, [directions_random, directions_even, directions_halton]):\n", + " _, pts = n.optimize.optimize_mga_in_multiple_directions(\n", + " dimensions=dimensions,\n", + " directions=directions,\n", + " slack=0.05,\n", + " solver_name=\"highs\",\n", + " log_to_console=False,\n", + " max_parallel=8,\n", + " io_api=\"direct\",\n", + " solver=\"ipm\",\n", + " )\n", + " convex_hull_plot_2d(ConvexHull(pts / 1e3), ax)\n", + " ax.axis(\"equal\")\n", + " ax.set_xlabel(\"Wind capacity (GW)\")\n", + " ax.set_ylabel(\"Solar capacity (GW)\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "b1b87311", + "metadata": {}, + "source": [ + "While more difficult to visualize, more than two dimensions can be used to analyse near-optimal spaces. Suppose we are interested in the mutual trade-offs between wind, solar and energy storage. Storage, as a sum of battery and hydrogen storage, is easily defined as a third dimension. In this case, since we are comparing different kinds of components with heterogenous units, it makes sense to scale our dimensions by capital cost in order to get comparable investment numbers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bc21f478", + "metadata": {}, + "outputs": [], + "source": [ + "dimensions = {\n", + " \"wind\": {\"Generator\": {\"p_nom\": {\"wind\": n.generators.at[\"wind\", \"capital_cost\"]}}},\n", + " \"solar\": {\n", + " \"Generator\": {\"p_nom\": {\"solar\": n.generators.at[\"solar\", \"capital_cost\"]}}\n", + " },\n", + " \"storage\": {\n", + " \"StorageUnit\": {\n", + " \"p_nom\": {\n", + " \"battery storage\": n.storage_units.at[\"battery storage\", \"capital_cost\"]\n", + " }\n", + " },\n", + " \"Store\": {\n", + " \"e_nom\": {\n", + " \"hydrogen storage\": n.stores.at[\"hydrogen storage\", \"capital_cost\"]\n", + " }\n", + " },\n", + " \"Link\": {\"p_nom\": n.links.capital_cost},\n", + " },\n", + "}\n", + "directions = pypsa.optimization.mga.generate_directions_halton(\n", + " dimensions.keys(), 50, seed=0\n", + ")\n", + "dirs, pts = n.optimize.optimize_mga_in_multiple_directions(\n", + " dimensions=dimensions,\n", + " directions=directions,\n", + " slack=0.05,\n", + " solver_name=\"highs\",\n", + " log_to_console=False,\n", + " max_parallel=8,\n", + " io_api=\"direct\",\n", + " solver=\"ipm\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1edb2405", + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(8, 6))\n", + "ax.scatter(\n", + " pts[\"wind\"] / 1e6, pts[\"solar\"] / 1e6, c=pts[\"storage\"] / 1e6, cmap=\"viridis\"\n", + ")\n", + "hull = ConvexHull(pts[[\"wind\", \"solar\"]] / 1e6)\n", + "line_segments = [hull.points[simplex] for simplex in hull.simplices]\n", + "ax.add_collection(LineCollection(line_segments, colors=\"k\", linestyle=\"solid\"))\n", + "\n", + "ax.axis(\"equal\")\n", + "ax.set_xlabel(\"Wind investment (mEUR)\")\n", + "ax.set_ylabel(\"Solar investment (mEUR)\")\n", + "cbar = plt.colorbar(ax.collections[0], ax=ax)\n", + "cbar.set_label(\"Storage investment (mEUR)\")" + ] + }, + { + "cell_type": "markdown", + "id": "1f187d0c", + "metadata": {}, + "source": [ + "In the above example, we see that low investment in renewables necessitates higher investment in storage, while high investment in renewables excludes (via the total system cost bound) a high investment in storage." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/examples/negative-prices-kvl-baker.ipynb b/PyPSA/source/docs/examples/negative-prices-kvl-baker.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..aa31917adb7fc729736f07e64aabd6cb19dbd90f --- /dev/null +++ b/PyPSA/source/docs/examples/negative-prices-kvl-baker.ipynb @@ -0,0 +1,392 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "2ac6d0d9", + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "aa89fd4c", + "metadata": {}, + "source": [ + "# Negative LMPs from Line Congestion" + ] + }, + { + "cell_type": "markdown", + "id": "7814edb9", + "metadata": {}, + "source": [ + "This notebook demonstrates how negative locational marginal prices (LMPs) can occur in electricity systems due to line congestion. Using a simple 3-bus linearised DC power flow model, we reproduce the phenomenon implemented in [Kyri Baker's \"3bus_LMP\" example](#references). When cheap generation is trapped behind a congested line, LMPs can drop below zero as the system redistributes power flows to meet demand. This behaviour is a direct result of the duality of the DC-OPF (DC Optimal Power Flow) problem, where LMPs emerge as the shadow prices of nodal power balance. With this example, we illustrate how network topology, generator costs, and constraints jointly shape prices in modern electricity markets." + ] + }, + { + "cell_type": "markdown", + "id": "49b5a086", + "metadata": {}, + "source": [ + "## Model setup" + ] + }, + { + "cell_type": "markdown", + "id": "115c0da4", + "metadata": {}, + "source": [ + "First, define the example electricity system in PyPSA." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72c7262d", + "metadata": {}, + "outputs": [], + "source": [ + "import pypsa\n", + "\n", + "n = pypsa.Network()" + ] + }, + { + "cell_type": "markdown", + "id": "be31e850", + "metadata": {}, + "source": [ + "We create three buses with generators of marginal costs 10 €/MWh (Bus1), 20 €/MWh (Bus2), and 100 €/MWh (Bus3). We connect all buses with their neighbours. All lines have the same reactance of x=1. The line connecting Bus1 and Bus3 is bottlenecked at a maximum capacity of 10 MW. A single load of 100 MW is connected to Bus3. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60dd043f", + "metadata": {}, + "outputs": [], + "source": [ + "# Add three buses in a triangular layout\n", + "n.add(\"Bus\", \"Bus1\", x=0, y=2) # Top-left\n", + "n.add(\"Bus\", \"Bus2\", x=2, y=2) # Top-right\n", + "n.add(\"Bus\", \"Bus3\", x=1, y=0); # Bottom (load)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0af9785a", + "metadata": {}, + "outputs": [], + "source": [ + "# Add generators\n", + "n.add(\"Generator\", \"Gen1\", bus=\"Bus1\", p_nom=100, marginal_cost=10)\n", + "n.add(\"Generator\", \"Gen2\", bus=\"Bus2\", p_nom=100, marginal_cost=20)\n", + "n.add(\"Generator\", \"Gen3\", bus=\"Bus3\", p_nom=100, marginal_cost=100);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "626b5eb5", + "metadata": {}, + "outputs": [], + "source": [ + "# Add a load of 100 MW at Bus2\n", + "n.add(\"Load\", \"Load3\", bus=\"Bus3\", p_set=100);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a28f2f08", + "metadata": {}, + "outputs": [], + "source": [ + "# Add three lines\n", + "n.add(\"Line\", \"Line12\", bus0=\"Bus1\", bus1=\"Bus2\", x=1, s_nom=100)\n", + "n.add(\"Line\", \"Line23\", bus0=\"Bus2\", bus1=\"Bus3\", x=1, s_nom=100)\n", + "n.add(\"Line\", \"Line13\", bus0=\"Bus1\", bus1=\"Bus3\", x=1, s_nom=10);" + ] + }, + { + "cell_type": "markdown", + "id": "1fd0b8e9", + "metadata": {}, + "source": [ + "## Part 1: Negative LMPs in the DC-OPF solution" + ] + }, + { + "cell_type": "markdown", + "id": "c1ddcebf", + "metadata": {}, + "source": [ + "We solve the above network for a single timestep (\"now\"), representing one hour. As we have defined the components as `Line` components, Kirchhoff voltage law (KVL) applies. As no investments are allowed, this operational model is equivalent to a DC-OPF formulation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a64b3125", + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize(log_to_console=False)" + ] + }, + { + "cell_type": "markdown", + "id": "997e062b", + "metadata": {}, + "source": [ + "Solving the model yields an optimal solution with an objective value of 7600 €." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d31c652b", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"Objective value: {n.objective} €\")" + ] + }, + { + "cell_type": "markdown", + "id": "97ffab35", + "metadata": {}, + "source": [ + "We find that in the optimal solution, Gen2 and Gen3 provide 30 and 70 MW to serve the load at Bus3 respectively. Due to the KVL constraints, Gen1 is not dispatched at all, although being the cheapest, as the line connecting Bus1 and Bus3 is congested." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "112548dc", + "metadata": {}, + "outputs": [], + "source": [ + "n.generators_t.p" + ] + }, + { + "cell_type": "markdown", + "id": "3bec5114", + "metadata": {}, + "source": [ + "Given that all lines have equal reactances, two-thirds of Gen2's dispatch flow across Line23 and the remaining third flows across Line13 and Line12. Accordingly, Line13 carries 10 MW and is congested. Note that the Line12 is defined as from Bus1 to Bus2, hence the injection `p0` at Bus2 is negative." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16158656", + "metadata": {}, + "outputs": [], + "source": [ + "n.lines_t.p0" + ] + }, + { + "cell_type": "markdown", + "id": "89ac08da", + "metadata": {}, + "source": [ + "Looking at the marginal prices, we see that the LMP at Bus1 is negative: -60 €/MWh. As the LMP is the dual variable to the nodal balance constraint, this means we can improve (or reduce) the objective value by relieving the demand at Bus1 by 1 MW (see [Part 2](#part-2:-relieving-line-congestion))." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7e66b4b", + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.marginal_price" + ] + }, + { + "cell_type": "markdown", + "id": "f8cd0f2e", + "metadata": {}, + "source": [ + "## Part 2: Relieving line congestion" + ] + }, + { + "cell_type": "markdown", + "id": "91c908c9", + "metadata": {}, + "source": [ + "To see what happens when we relieve the nodal balance at Bus1, we attach a load of 1 MW." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8be2288", + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Load\", \"Load1\", bus=\"Bus1\", p_set=1);" + ] + }, + { + "cell_type": "markdown", + "id": "bc32cc0e", + "metadata": {}, + "source": [ + "... and resolve." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "992cf821", + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize(log_to_console=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d724b535", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"Objective value: {n.objective} €\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9843eeae", + "metadata": {}, + "outputs": [], + "source": [ + "n.generators_t.p" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65dddc9b", + "metadata": {}, + "outputs": [], + "source": [ + "n.lines_t.p0" + ] + }, + { + "cell_type": "markdown", + "id": "6844ed0a", + "metadata": {}, + "source": [ + "So what has happened? By attaching a 1 MW load at bus 1, line congestion is relieved: An additional off-take of 1 MW at bus 1 reduces the net flow from Bus1 to Bus3. Assuming Load3 to remain unchanged, this enables an injection of 2 MW at Bus1 (coming from Gen2), with 1 MW consumed and 1 MW flowing from Bus1 to Bus3. Gen2 essentially increases its dispatch by 2 MW (20 €/MWh x 2 MW x 1h = 40 €). Line13 is still fully utilised at 10 MW. Being the most expensive option, Gen3 decreases its output by 1 MW (100 €/MWh x (-1 MW) x 1h = -100 €). This combined effect creates a net reduction in total system costs of -100 € + 40 € = - 60 €." + ] + }, + { + "cell_type": "markdown", + "id": "3d1f9e8c", + "metadata": {}, + "source": [ + "## Part 3: Plotting the regional dispatch and flows" + ] + }, + { + "cell_type": "markdown", + "id": "2f6bbb7b", + "metadata": {}, + "source": [ + "In the following, we plot the nodal generation, LMPs, line flows and line loadings on a map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f89bcae3", + "metadata": {}, + "outputs": [], + "source": [ + "bus_size = (\n", + " n.statistics.supply(groupby=\"bus\", components=[\"Generator\", \"Load\"])\n", + " .groupby(\"bus\")\n", + " .sum()\n", + ")\n", + "line_flows = n.lines_t.p0.iloc[0]\n", + "bus_color = n.buses_t.marginal_price.iloc[0]\n", + "line_loading = n.lines_t.p0.iloc[0] / n.lines.s_nom" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bece3809", + "metadata": {}, + "outputs": [], + "source": [ + "n.plot.map(\n", + " bus_size=bus_size / 8000,\n", + " line_width=line_flows / 5,\n", + " line_flow=line_flows / 30,\n", + " bus_color=bus_color,\n", + " line_color=line_loading,\n", + ");" + ] + }, + { + "cell_type": "markdown", + "id": "dda7b2b9", + "metadata": {}, + "source": [ + "## References" + ] + }, + { + "cell_type": "markdown", + "id": "8e7e0714", + "metadata": {}, + "source": [ + "- Kyri Baker (2023). 3bus_LMPs. GitHub repository. https://github.com/kyribaker/3bus_LMPs\n", + "- Kyri Baker & Harsha Gangammanavar (2024). Locational marginal prices obey DC circuit laws. arXiv preprint arXiv:2403.19032. \n", + "https://doi.org/10.48550/arXiv.2403.19032" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/examples/power-to-gas-boiler-chp.ipynb b/PyPSA/source/docs/examples/power-to-gas-boiler-chp.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..81612add89ab3f4e3d8e4c41b2a07158e624c20f --- /dev/null +++ b/PyPSA/source/docs/examples/power-to-gas-boiler-chp.ipynb @@ -0,0 +1,376 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Extraction-Condensing CHP\n", + "\n", + "This example demonstrates how to model an extraction-condensing Combined Heat and Power (CHP) plant with flexible heat-power ratios.\n", + "For an example of a CHP plant with a fixed heat-power ratio, see the [backpressure CHP example]().\n", + "\n", + "In this example, a location has an electric, gas, and heat bus. The primary energy source is wind power, which can be converted to gas. The gas can be stored to convert into electricity and/or heat when needed, using either a boiler or a CHP unit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import pypsa" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## CHP parametrisation\n", + "\n", + "The setup roughly follows [Grohnheit (1993)](http://www.sciencedirect.com/science/article/pii/030142159390282K).\n", + "We define the ratio between maximum heat and power output of the CHP unit as `nom_r`, the backpressure limit as `c_m`, and the marginal loss for each additional generation of heat as `c_v`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nom_r = 1.0\n", + "c_m = 0.75\n", + "c_v = 0.15" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The feasible operational space of the CHP unit is shown in the graph below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots()\n", + "\n", + "t = 0.01\n", + "ph = np.arange(0, 1.0001, t)\n", + "\n", + "ax.plot(ph, c_m * ph, color=\"k\")\n", + "ax.set_xlabel(\"P_heat_out\")\n", + "ax.set_ylabel(\"P_elec_out\")\n", + "ax.grid(True)\n", + "\n", + "ax.set_xlim([0, 1.1])\n", + "ax.set_ylim([0, 1.1])\n", + "ax.text(0.1, 0.7, \"Allowed output\", color=\"gray\")\n", + "ax.plot(ph, 1 - c_v * ph, color=\"k\")\n", + "\n", + "for i in range(1, 10):\n", + " k = 0.1 * i\n", + " x = np.arange(0, k / (c_m + c_v), t)\n", + " ax.plot(x, k - c_v * x, color=\"orange\", linestyle=\"--\", linewidth=0.75)\n", + "\n", + "ax.text(0.05, 0.41, \"iso-fuel-lines\", color=\"orange\", rotation=-7)\n", + "ax.fill_between(ph, c_m * ph, 1 - c_v * ph, facecolor=\"#ddd\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Optimisation\n", + "\n", + "First, we add the power and gas sectors with a wind generator, a power-to-gas unit, a gas storage unit, and the turbine of the CHP unit:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n = pypsa.Network()\n", + "n.set_snapshots(pd.date_range(\"2025-01-01 00:00\", \"2025-01-01 03:00\", freq=\"h\"))\n", + "\n", + "n.add(\"Bus\", \"0 power\", carrier=\"AC\")\n", + "n.add(\"Bus\", \"0 gas\", carrier=\"gas\")\n", + "\n", + "n.add(\"Carrier\", [\"wind\", \"gas\"])\n", + "\n", + "n.add(\n", + " \"Generator\",\n", + " \"wind turbine\",\n", + " bus=\"0 power\",\n", + " carrier=\"wind\",\n", + " p_nom_extendable=True,\n", + " p_max_pu=[0.0, 0.2, 0.7, 0.4],\n", + " capital_cost=1000,\n", + ")\n", + "\n", + "n.add(\"Load\", \"load\", bus=\"0 power\", p_set=5)\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " \"power-to-gas\",\n", + " bus0=\"0 power\",\n", + " bus1=\"0 gas\",\n", + " efficiency=0.6,\n", + " capital_cost=1000,\n", + " p_nom_extendable=True,\n", + ")\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " \"generator\",\n", + " bus0=\"0 gas\",\n", + " bus1=\"0 power\",\n", + " efficiency=0.468,\n", + " capital_cost=400,\n", + " p_nom_extendable=True,\n", + ")\n", + "\n", + "n.add(\"Store\", \"gas depot\", bus=\"0 gas\", e_cyclic=True, e_nom=1000);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we add the heat sector with the boiler of the CHP unit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\"Bus\", \"0 heat\", carrier=\"heat\")\n", + "\n", + "n.add(\"Carrier\", \"heat\")\n", + "\n", + "n.add(\"Load\", \"heat load\", bus=\"0 heat\", p_set=10)\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " \"boiler\",\n", + " bus0=\"0 gas\",\n", + " bus1=\"0 heat\",\n", + " efficiency=0.9,\n", + " capital_cost=300,\n", + " p_nom_extendable=True,\n", + ")\n", + "\n", + "n.add(\"Store\", \"water tank\", bus=\"0 heat\", e_cyclic=True, e_nom_extendable=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we need to add some constraints that ensure that combination of turbine and boiler output of the CHP is operationally feasible.\n", + "This is done by implementing the operational space shown in the graph above with a set of linear constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Guarantees ISO fuel lines, i.e. fuel consumption p_b0 + p_g0 = constant along p_g1 + c_v p_b1 = constant (b=boiler, g=generator)\n", + "n.links.at[\"boiler\", \"efficiency\"] = n.links.at[\"generator\", \"efficiency\"] / c_v\n", + "boiler_eff = float(n.links.at[\"boiler\", \"efficiency\"])\n", + "generator_eff = float(n.links.at[\"generator\", \"efficiency\"])\n", + "\n", + "m = n.optimize.create_model()\n", + "\n", + "p = m.variables[\"Link-p\"]\n", + "p_nom = m.variables[\"Link-p_nom\"]\n", + "\n", + "# Guarantees heat output and electric output nominal powers are proportional\n", + "m.add_constraints(\n", + " generator_eff * nom_r * p_nom.loc[\"generator\"] - boiler_eff * p_nom.loc[\"boiler\"]\n", + " == 0,\n", + " name=\"heat-power output proportionality\",\n", + ")\n", + "\n", + "# Guarantees c_m p_b1 <= p_g1\n", + "m.add_constraints(\n", + " p.loc[:, \"boiler\"] * c_m * boiler_eff - p.loc[:, \"generator\"] * generator_eff <= 0,\n", + " name=\"backpressure\",\n", + ")\n", + "\n", + "# Guarantees p_g1 +c_v p_b1 <= p_g1_nom\n", + "m.add_constraints(\n", + " p.loc[:, \"boiler\"] + p.loc[:, \"generator\"] - p_nom.loc[\"generator\"] <= 0,\n", + " name=\"top_iso_fuel_line\",\n", + ")\n", + "\n", + "n.optimize.solve_model(log_to_console=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.objective" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Results\n", + "\n", + "Let's start by inspecting the optimised conversion capacities:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links.p_nom_opt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The CHP boiler is dimensioned by the heat demand met in three hours when there is no wind supply.\n", + "The CHP generator is set by the heat demand." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "display(4 * 10 / 3 / float(n.links.at[\"boiler\", \"efficiency\"]))\n", + "display(28.490028 * 0.15)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links_t.p0.round(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links_t.p1.round(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pd.DataFrame({attr: n.stores_t[attr][\"gas depot\"] for attr in [\"p\", \"e\"]}).round(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pd.DataFrame({attr: n.stores_t[attr][\"water tank\"] for attr in [\"p\", \"e\"]}).round(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pd.DataFrame({attr: n.links_t[attr][\"boiler\"] for attr in [\"p0\", \"p1\"]}).round(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, let's calculate the overall efficiency of the CHP system." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "eta_elec = n.links.at[\"generator\", \"efficiency\"]\n", + "\n", + "r = 1 / c_m\n", + "\n", + "# P_h = r*P_e\n", + "(1 + r) / ((1 / eta_elec) * (1 + c_v * r))" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/PyPSA/source/docs/examples/power-to-heat-water-tank.ipynb b/PyPSA/source/docs/examples/power-to-heat-water-tank.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..2cf3f5e4777455873b9a5575aa6359ed4cb992ab --- /dev/null +++ b/PyPSA/source/docs/examples/power-to-heat-water-tank.ipynb @@ -0,0 +1,165 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Heat Pumps and Thermal Storage\n", + "\n", + "In this example, a heat demand is supplied by a wind turbine in combination with a heat pump and a water tank that stores hot water with a standing loss." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "import pypsa\n", + "\n", + "n = pypsa.Network()\n", + "n.set_snapshots(pd.date_range(\"2025-01-01 00:00\", \"2025-01-01 03:00\", freq=\"H\"))\n", + "\n", + "n.add(\"Bus\", \"power\", carrier=\"AC\")\n", + "n.add(\"Bus\", \"heat\", carrier=\"heat\")\n", + "\n", + "n.add(\n", + " \"Generator\",\n", + " \"wind turbine\",\n", + " bus=\"power\",\n", + " carrier=\"wind\",\n", + " p_nom_extendable=True,\n", + " p_max_pu=[0.0, 0.2, 0.7, 0.4],\n", + " capital_cost=500,\n", + ")\n", + "\n", + "n.add(\"Load\", \"heat demand\", bus=\"heat\", p_set=20);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The heat pump has time-varying efficiency (i.e. its coefficient of performance, COP) due to changing ambient temperatures." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"heat pump\",\n", + " bus0=\"power\",\n", + " bus1=\"heat\",\n", + " efficiency=[2.5, 3.0, 3.2, 3.0],\n", + " capital_cost=1000,\n", + " p_nom_extendable=True,\n", + ");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The hot water tank has a standing loss of 1% of its state of charge per hour." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Store\",\n", + " \"water tank\",\n", + " bus=\"heat\",\n", + " e_cyclic=True,\n", + " e_nom=100,\n", + " standing_loss=0.01,\n", + ");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The wind turbine and the heat pump can be sized by the optimisation, while the water tank has a fixed size of 100 MWh." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize(log_to_console=False);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pd.DataFrame({attr: n.stores_t[attr][\"water tank\"] for attr in [\"p\", \"e\"]}).round(3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pd.DataFrame({attr: n.links_t[attr][\"heat pump\"] for attr in [\"p0\", \"p1\"]}).round(3)" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/PyPSA/source/docs/examples/replace-generator-storage-units-with-store.ipynb b/PyPSA/source/docs/examples/replace-generator-storage-units-with-store.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..a5b72ea6f164a33438d4e5680c0ae9177e5fc377 --- /dev/null +++ b/PyPSA/source/docs/examples/replace-generator-storage-units-with-store.ipynb @@ -0,0 +1,286 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# StorageUnit as Link and Store Components\n", + "\n", + "This example demonstrates how the `StorageUnit` component can be replaced by the more fundamental `Store` and `Link` components, and how their parameters map to each other." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "from numpy.testing import assert_almost_equal, assert_array_almost_equal\n", + "\n", + "import pypsa" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We define two functions we use in the following." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def replace_storage_unit(n: pypsa.Network, name: str) -> tuple:\n", + " \"\"\"\n", + " Replace the storage unit with `name` with a bus for the energy\n", + " carrier, two links for the conversion of the energy carrier to and from electricity,\n", + " a store to keep track of the depletion of the energy carrier and its\n", + " CO2 emissions, and a variable generator for the storage inflow.\n", + "\n", + " Because the energy size and power size are linked in the storage unit by the `max_hours`,\n", + " `extra_functionality` must be added to the optimisation to implement this constraint.\n", + " \"\"\"\n", + " su = n.storage_units.loc[name]\n", + "\n", + " bus_name = f\"{su['bus']} {su['carrier']}\"\n", + " link_1_name = f\"{name} converter {su['carrier']} to AC\"\n", + " link_2_name = f\"{name} converter AC to {su['carrier']}\"\n", + " store_name = f\"{name} store {su['carrier']}\"\n", + " gen_name = f\"{name} inflow\"\n", + "\n", + " n.add(\"Bus\", bus_name, carrier=su[\"carrier\"])\n", + "\n", + " # dispatch link\n", + " n.add(\n", + " \"Link\",\n", + " link_1_name,\n", + " bus0=bus_name,\n", + " bus1=su[\"bus\"],\n", + " capital_cost=su[\"capital_cost\"] * su[\"efficiency_dispatch\"],\n", + " p_nom=su[\"p_nom\"] / su[\"efficiency_dispatch\"],\n", + " p_nom_extendable=su[\"p_nom_extendable\"],\n", + " p_nom_max=su[\"p_nom_max\"] / su[\"efficiency_dispatch\"],\n", + " p_nom_min=su[\"p_nom_min\"] / su[\"efficiency_dispatch\"],\n", + " p_max_pu=su[\"p_max_pu\"],\n", + " marginal_cost=su[\"marginal_cost\"] * su[\"efficiency_dispatch\"],\n", + " efficiency=su[\"efficiency_dispatch\"],\n", + " )\n", + "\n", + " # store link\n", + " n.add(\n", + " \"Link\",\n", + " link_2_name,\n", + " bus0=su[\"bus\"],\n", + " bus1=bus_name,\n", + " p_nom=su[\"p_nom\"],\n", + " p_nom_extendable=su[\"p_nom_extendable\"],\n", + " p_nom_max=su[\"p_nom_max\"],\n", + " p_nom_min=su[\"p_nom_min\"],\n", + " p_max_pu=-su[\"p_min_pu\"],\n", + " efficiency=su[\"efficiency_store\"],\n", + " )\n", + "\n", + " if (\n", + " name in n.storage_units_t.state_of_charge_set.columns\n", + " and (~pd.isnull(n.storage_units_t.state_of_charge_set[name])).any()\n", + " ):\n", + " e_max_pu = pd.Series(data=1, index=n.snapshots)\n", + " e_min_pu = pd.Series(data=0, index=n.snapshots)\n", + " non_null = ~pd.isnull(n.storage_units_t.state_of_charge_set[name])\n", + " e_max_pu[non_null] = n.storage_units_t.state_of_charge_set[name][non_null]\n", + " e_min_pu[non_null] = n.storage_units_t.state_of_charge_set[name][non_null]\n", + " else:\n", + " e_max_pu = 1\n", + " e_min_pu = 0\n", + "\n", + " n.add(\n", + " \"Store\",\n", + " store_name,\n", + " bus=bus_name,\n", + " e_nom=su[\"p_nom\"] * su[\"max_hours\"],\n", + " e_nom_min=su[\"p_nom_min\"] / su[\"efficiency_dispatch\"] * su[\"max_hours\"],\n", + " e_nom_max=su[\"p_nom_max\"] / su[\"efficiency_dispatch\"] * su[\"max_hours\"],\n", + " e_nom_extendable=su[\"p_nom_extendable\"],\n", + " e_max_pu=e_max_pu,\n", + " e_min_pu=e_min_pu,\n", + " standing_loss=su[\"standing_loss\"],\n", + " e_cyclic=su[\"cyclic_state_of_charge\"],\n", + " e_initial=su[\"state_of_charge_initial\"],\n", + " )\n", + "\n", + " n.add(\"Carrier\", \"rain\", co2_emissions=0)\n", + "\n", + " # inflow from a variable generator, which can be curtailed (i.e. spilled)\n", + " inflow_max = n.storage_units_t.inflow[name].max()\n", + "\n", + " if inflow_max == 0:\n", + " inflow_pu = 0\n", + " else:\n", + " inflow_pu = n.storage_units_t.inflow[name] / inflow_max\n", + "\n", + " n.add(\n", + " \"Generator\",\n", + " gen_name,\n", + " bus=bus_name,\n", + " carrier=\"rain\",\n", + " p_nom=inflow_max,\n", + " p_max_pu=inflow_pu,\n", + " )\n", + "\n", + " if su[\"p_nom_extendable\"]:\n", + " ratio2 = su[\"max_hours\"]\n", + " ratio1 = ratio2 * su[\"efficiency_dispatch\"]\n", + "\n", + " def extra_functionality(n: pypsa.Network, sns: pd.Index) -> None:\n", + " m = n.model\n", + " lhs = (\n", + " m[\"Store-e_nom\"].at[store_name]\n", + " - m[\"Link-p_nom\"].at[link_1_name] * ratio1\n", + " )\n", + " m.add_constraints(lhs == 0, name=\"store_fix_1\")\n", + "\n", + " lhs = (\n", + " m[\"Store-e_nom\"].at[store_name]\n", + " - m[\"Link-p_nom\"].at[link_2_name] * ratio2\n", + " )\n", + " m.add_constraints(lhs == 0, name=\"store_fix_2\")\n", + "\n", + " else:\n", + " extra_functionality = None\n", + "\n", + " n.remove(\"StorageUnit\", name)\n", + "\n", + " return bus_name, link_1_name, link_2_name, store_name, gen_name, extra_functionality" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, take an existing example which has already been solved as a reference case:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_r = pypsa.examples.storage_hvdc()\n", + "n_r.optimize(log_to_console=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We load it again, but now we replace the `StorageUnit` components with `Store` and `Link` components." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n = pypsa.examples.storage_hvdc()\n", + "\n", + "name = \"Storage 0\"\n", + "\n", + "(\n", + " bus_name,\n", + " link_1_name,\n", + " link_2_name,\n", + " store_name,\n", + " gen_name,\n", + " extra_functionality,\n", + ") = replace_storage_unit(n, name)\n", + "n.optimize(extra_functionality=extra_functionality)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's ensure that the conversion was accurate by comparing the results of the original and modified networks." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert_almost_equal(n_r.objective, n.objective, decimal=2)\n", + "\n", + "assert_array_almost_equal(\n", + " n_r.storage_units_t.state_of_charge[name],\n", + " n.stores_t.e[store_name],\n", + ")\n", + "\n", + "assert_array_almost_equal(\n", + " n_r.storage_units_t.p[name],\n", + " -n.links_t.p1[link_1_name] - n.links_t.p0[link_2_name],\n", + ")\n", + "\n", + "assert_array_almost_equal(\n", + " n_r.storage_units.at[name, \"p_nom_opt\"],\n", + " n.links.at[link_2_name, \"p_nom_opt\"],\n", + ")\n", + "\n", + "assert_array_almost_equal(\n", + " n_r.storage_units.at[name, \"p_nom_opt\"],\n", + " n.links.at[link_1_name, \"p_nom_opt\"]\n", + " * n_r.storage_units.at[name, \"efficiency_dispatch\"],\n", + ")" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/PyPSA/source/docs/examples/reserve-power.ipynb b/PyPSA/source/docs/examples/reserve-power.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..3cd04a0267aa3b97c0cf37b2b4eb3b4acbbfeb2e --- /dev/null +++ b/PyPSA/source/docs/examples/reserve-power.ipynb @@ -0,0 +1,471 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Reserve Constraints\n", + "\n", + "## Objective\n", + "\n", + "When using a linear programming approach in an energy system, the optimization usually results in all generators either running at full capacity or not running at all, depending on their position in the merit order.\n", + "In the real world, generators often run at partial load.\n", + "Unfortunately, most of the reasons why generators run at partial load are difficult to account for in modeling.\n", + "One reason for generators to run at partial load is to be able to act as spinning reserves, i.e. to be able to ramp up or down quickly when needed to maintain system stability.\n", + "\n", + "In this example we will implement spinning reserve constraints in PyPSA in a very simplified way.\n", + "The objective is to force some generators to provide reserve power by running below their rated capacity, but still maintain a linear programming problem formulation. \n", + "To do this, we need to implement additional variables and additional constraints in the model.\n", + "\n", + "\n", + "## Methodology\n", + "We follow the approach presented by Andreas Hösl et al. in [this video](https://www.youtube.com/watch?v=fmwDxNpSMM4&t=8043s).\n", + "\n", + "The basic idea is that each generator must provide reserve power **symmetrically**. This means that it must be able to increase and decrease its output by the same amount in order to contribute to meeting reserve requirements. This ensures that generators must operate at partial load to provide reserve power. \n", + "\n", + "The following changes need to be made to the linopy model in a PyPSA network:\n", + "\n", + "- a new variable $p_{\\text{reserve}}(g,t)$ representing the reserve power provided by generator $g$ at time step $t$. \n", + "\n", + "- a constraint that ensures that for each time step $t$, the sum of all reserve power provided is greater than or equal to the required reserves.\n", + "$$\n", + "\\forall t: \\sum_{g} p_{\\text{reserve}}(g,t) \\geq \\text{reserve requirement}\n", + "$$\n", + "\n", + "- A constraint to ensure that the reserve power of a generator is less than or equal to the difference between its power $p$ and its nominal power $p_\\text{nom}$, multiplied by a scalar coefficient $a$. This coefficient can take any value between 0 and 1 and represents the technical availability of a generator to provide reserve power. \n", + "$$\n", + "\\forall g, t: p_\\text{reserve}(g, t) \\leq a(g) p_\\text{nom}(g) - p(g,t)\n", + "$$\n", + "\n", + "- a constraint to ensure that the reserve power of a generator is less than or equal to its actual power $p$ multiplied by a scalar coefficient $b$. This coefficient can take any value between 0 and 1 and represents the technical availability of a generator to provide reserve power. \n", + "\n", + "$$\n", + "\\forall g, t: p_\\text{reserve}(g, t) \\leq b(g) p(g,t)\n", + "$$\n", + "\n", + "The relationships between the variables $a$, $b$, $p_\\text{nom}$, $p$ and $p_\\text{reserve}$ are shown in the following schematic diagram.\n", + "\n", + "![reserve-power-graph](../../doc/img/reserve-power-graph.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Limitations and other approaches\n", + "\n", + "Note that this is an oversimplified approach that has significant limitations:\n", + "- It does not distinguish between different categories of reserves, such as primary or secondary reserves. \n", + "- Reserves are provided symmetrically; there is no distinction between positive and negative reserves.\n", + "- The approach only considers the provision of reserve power, not the actual delivery.\n", + " The additional constraints simply force some generators to run at partial load so that they **could** ramp up or down when reserves are required. \n", + "\n", + "All of these issues can be addressed in a MIP unit commitment model, albeit at a much higher numerical cost. \n", + "\n", + "\n", + "## Implementation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import pypsa" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Basic model\n", + "\n", + "Our toy model consists of a single bus with generators that have different marginal costs. We use a sine function for the load profile." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_basic = pypsa.Network()\n", + "n_basic.add(\"Carrier\", name=\"carrier1\")\n", + "n_basic.add(\"Bus\", name=\"bus1\", carrier=\"carrier1\")\n", + "\n", + "# add generators with increasing marginal cost\n", + "n_basic.add(\"Generator\", name=\"gen1\", bus=\"bus1\", p_nom=10, marginal_cost=1)\n", + "n_basic.add(\"Generator\", name=\"gen2\", bus=\"bus1\", p_nom=10, marginal_cost=2)\n", + "n_basic.add(\"Generator\", name=\"gen3\", bus=\"bus1\", p_nom=10, marginal_cost=3)\n", + "n_basic.add(\"Generator\", name=\"gen4\", bus=\"bus1\", p_nom=10, marginal_cost=4)\n", + "\n", + "# create 48 snapshots\n", + "snapshots = np.arange(1, 49)\n", + "n_basic.set_snapshots(snapshots)\n", + "\n", + "# create load\n", + "load_max = 30\n", + "load_profile = np.sin(snapshots / 12 * np.pi) + 3.5\n", + "load_profile = load_profile / load_profile.max() * load_max\n", + "n_basic.add(\"Load\", name=\"load1\", bus=\"bus1\", p_set=load_profile)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We make a copy of the basic model, which we later modify to add reserve power constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_reserve = n_basic.copy()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As a reference point, we solve the model without any additional constraints.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_basic.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We plot the dispatch over time. As expected, the generators are dispatched strictly according to their marginal cost, each one running at nominal capacity until demand is met. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_basic.generators_t[\"p\"].plot.area(lw=0).legend(\n", + " loc=\"upper left\", bbox_to_anchor=(1.0, 1.0)\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Modified model with custum variable and constraints\n", + "\n", + "Now let's modify the model by adding some additional constraints.\n", + "We create a new network and create a model instance attached to it.\n", + "Now we can inspect the model instance to get a list of variables and constraints: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_reserve.optimize.create_model()\n", + "n_reserve.model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now add a new variable ``p_reserve`` which represents the reserve power.\n", + "It has a lower bound of zero, is defined for all dispatchable generators and has a time index." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "v_rp = n_reserve.model.add_variables(\n", + " lower=0,\n", + " coords=[n_reserve.snapshots, n_reserve.generators.index],\n", + " name=\"Generator-p_reserve\",\n", + ")\n", + "v_rp" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we define a new constraint which ensures that for each snapshot the total reserve requirement is satisfied by the sum of the reserve power provided by all generators.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "reserve_req = 10\n", + "\n", + "c_sum = n_reserve.model.add_constraints(\n", + " v_rp.sum(\"name\") >= reserve_req, name=\"GlobalConstraint-sum_of_reserves\"\n", + ")\n", + "c_sum" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we need to limit the amount of reserve power that each generator can provide. The following constraint ensures that the reserve power provided by a generator must be less than or equal to the difference between its power ``p`` and its nominal power ``p_nom``:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "a = 1\n", + "\n", + "c_rpos = n_reserve.model.add_constraints(\n", + " v_rp\n", + " <= -n_reserve.model.variables[\"Generator-p\"] + a * n_reserve.generators[\"p_nom\"],\n", + " name=\"Generator-reserve_upper_limit\",\n", + ")\n", + "c_rpos" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we add a constraint to ensure that the reserve power provided by a generator must be less than or equal to its actual power ``p`` multiplied by a scalar coefficient ``b``.\n", + "This coefficient can take any value between 0 and 1 and represents the technical availability of a generator to provide reserve power." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "b = 0.7\n", + "\n", + "c_rneg = n_reserve.model.add_constraints(\n", + " v_rp <= b * n_reserve.model.variables[\"Generator-p\"],\n", + " name=\"Generator-reserve_lower_limit\",\n", + ")\n", + "c_rneg" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now inspect the model formulation. We can see that our new variables and constraints have been successfully added:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_reserve.model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now solve the modified model:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_reserve.optimize.solve_model()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Examine the results\n", + "\n", + "We create a plot to examine the results of the modified model.\n", + "On the left subplot we plot the active power generation `p` over time for each generator.\n", + "On the right subplot we plot the reserve power `p_reserve` over time for each generator.\n", + "The following observations can be made:\n", + "\n", + "- The reserve requirement of 20 MW is met in every time step.\n", + "- In order to provide reserves, some generators must always run below their nominal power.\n", + "- Among all running generators, those with the highest marginal costs provide as much reserve capacity as possible. \n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axs = plt.subplots(1, 2, sharey=True, figsize=(10, 5))\n", + "n_reserve.generators_t[\"p\"].plot.area(\n", + " ax=axs[0], title=\"p\", legend=False, ylabel=\"p [MW]\"\n", + ")\n", + "n_reserve.generators_t[\"p_reserve\"].plot.area(ax=axs[1], title=\"p_reserve\")\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Looking at the average reserve power provided by a generator, we can see that the cheapest and most expensive generators provide less reserve power on average than the other two generators." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_reserve.generators_t[\"p_reserve\"].mean().plot(\n", + " kind=\"bar\", ylabel=\"mean(p_reserve) [MW]\"\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Comparison of the two model versions\n", + "\n", + "To visually compare the base model with the modified model, we plot the active power generation `p` over time for both models. On the left we plot the base model, on the right we plot the model with additional constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axs = plt.subplots(1, 2, sharex=True, sharey=True, figsize=(10, 4))\n", + "for i, (n, r) in enumerate([(n_basic, 0), (n_reserve, reserve_req)]):\n", + " n.generators_t[\"p\"].plot.area(\n", + " ax=axs[i], ylabel=\"p [MW]\", title=f\"{r} MW reserve required\", legend=False, lw=0\n", + " )\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also compare average power and reserve power over time.\n", + "The graph shows that adding reserve constraints reduces the average generation of the cheaper generators and increases the average generation of the more expensive generators." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = pd.concat(\n", + " [n.generators_t.get(\"p\").mean() for n in [n_basic, n_reserve]],\n", + " axis=1,\n", + " keys=[\"0 MW\", f\"{reserve_req} MW\"],\n", + ")\n", + "data.plot(kind=\"bar\", ylabel=\"mean(p) [MW]\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And that's it. Feel free to change the values of ``reserve_req``, ``a`` and ``b`` and see how this affects the results.\n", + "However, be aware that reserve requirements that are too high can make the model infeasible.\n", + "\n", + "You can also try out an interactive dashboard to play around with a small example model where you can try out different reserve settings at https://pypsa-reserves-dashboard.streamlit.app/ " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Acknowledgements\n", + "\n", + "The contribution of this example has been funded as part of the IND-E project.\n", + "\n", + "![reserve-power-funding](../../docs/assets/images/reserve-power-funding.gif)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/PyPSA/source/docs/examples/rh_example.png b/PyPSA/source/docs/examples/rh_example.png new file mode 100644 index 0000000000000000000000000000000000000000..7c4c79af5538d769ae6a6fb4554fa2eafa77f5bf Binary files /dev/null and b/PyPSA/source/docs/examples/rh_example.png differ diff --git a/PyPSA/source/docs/examples/rolling-horizon.ipynb b/PyPSA/source/docs/examples/rolling-horizon.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..cba1fac9747976c8d4bfc768d2556aee6c5d83ad --- /dev/null +++ b/PyPSA/source/docs/examples/rolling-horizon.ipynb @@ -0,0 +1,455 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "5974c3c2", + "metadata": {}, + "source": [ + "# Rolling-Horizon Optimization\n", + "\n", + "An electric utility company has invested in a 200 MW wind farm, including a 20 MW battery storage system at their facilities. Suppose the facility has a constant demand of 25 MW and that the company can also obtain power from the grid at 150 €/MWh while selling excess electricity into the grid is not permitted. Given the available information, the electric utility company wants to find an optimal operational schedule for three days in March." + ] + }, + { + "cell_type": "markdown", + "id": "394aed90", + "metadata": {}, + "source": [ + "## Initialize Network\n", + "\n", + "We start by modelling the electric utility company as a single node, with the load attached. The grid electricity is modelled as a generator with marginal cost of 150 €/MWh and a capacity of 30 MW." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79c94949", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "\n", + "import pypsa\n", + "\n", + "n = pypsa.Network()\n", + "n.add(\"Bus\", \"company\")\n", + "n.add(\"Load\", \"demand\", bus=\"company\", p_set=25, carrier=\"load\")\n", + "n.add(\"Generator\", \"grid\", bus=\"company\", p_nom=30, marginal_cost=150, carrier=\"grid\")" + ] + }, + { + "cell_type": "markdown", + "id": "9a9c7765", + "metadata": {}, + "source": [ + "Next, we read in the capacity factor time series for onshore wind, which covers hourly data from 2011. We are interested in the period from 1st to 4th March. The capacity factors indicate relatively high wind availability on the 1st and 2nd of March, which decreases towards the end of the period." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d22091ff", + "metadata": {}, + "outputs": [], + "source": [ + "p_max_pu = pd.read_csv(\n", + " \"https://model.energy/data/time-series-f17c3736a2719ce7da58484180d89e2d.csv\",\n", + " index_col=0,\n", + " parse_dates=True,\n", + ")[\"onwind\"]\n", + "\n", + "start_date = \"2011-03-01 00:00:00\"\n", + "end_date = \"2011-03-03 23:00:00\"\n", + "\n", + "p_max_pu_select = p_max_pu[start_date:end_date]\n", + "p_max_pu_select.plot(ylabel=\"Capacity Factor\");" + ] + }, + { + "cell_type": "markdown", + "id": "45ccb41f", + "metadata": {}, + "source": [ + "Next, we need to tell PyPSA that theses are the snapshots (time steps) we want to optimize over. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bc98aff0", + "metadata": {}, + "outputs": [], + "source": [ + "n.set_snapshots(p_max_pu_select.index)" + ] + }, + { + "cell_type": "markdown", + "id": "af262d51", + "metadata": {}, + "source": [ + "Now, we add the wind farm with the availability time series as `p_max_pu`, and the capacity of 200 MW as `p_nom`. Then, we also add the battery storage with a dispatch capacity of 20 MW, a charge and discharge efficiency of 95% and an energy-to-power ratio of 8 hours." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77b76c61", + "metadata": {}, + "outputs": [], + "source": [ + "n.add(\n", + " \"Generator\",\n", + " \"onwind\",\n", + " bus=\"company\",\n", + " p_max_pu=p_max_pu_select,\n", + " p_nom=200,\n", + " carrier=\"onwind\",\n", + ")\n", + "\n", + "n.add(\n", + " \"StorageUnit\",\n", + " \"battery\",\n", + " bus=\"company\",\n", + " p_nom=20,\n", + " carrier=\"battery\",\n", + " efficiency_store=0.95,\n", + " efficiency_dispatch=0.95,\n", + " max_hours=8,\n", + ");" + ] + }, + { + "cell_type": "markdown", + "id": "8dfbe1ec", + "metadata": {}, + "source": [ + "Now, we have received our initialised PyPSA network. In order to compare it to a system solved with rolling horizon after optimisation, we copy the initial network for convenience." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a7c4a49", + "metadata": {}, + "outputs": [], + "source": [ + "m = n.copy()" + ] + }, + { + "cell_type": "markdown", + "id": "faa5cf94", + "metadata": {}, + "source": [ + "As a first step, we solve the network optimization over the entire period under the assumption of perfect foresight. This approach assumes complete knowledge of future conditions, including weather patterns and load development." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba1f6268", + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize(log_to_console=False)" + ] + }, + { + "cell_type": "markdown", + "id": "6ebefe61", + "metadata": {}, + "source": [ + "In a second step, we apply a rolling-horizon optimization to simulate limited operational foresight using the function [`n.optimize.optimize_with_rolling_horizon()`][pypsa.optimization.OptimizationAccessor.optimize_with_rolling_horizon]. Instead of optimizing over the entire period at once, the model solves shorter time windows sequentially, using only information available up to the end of the current horizon. This approach considers that future developments in weather and load conditions are uncertain. By updating the optimization as new information becomes available, the model captures more realistic operational decisions under uncertainty.\n", + "\n", + "For this example, we initially assume that wind capacity factor forecasts are available only 24 hours into the future (`horizon=24`) and the forecast of the next 24 hours only comes available after 24 hours (`overlap=0`). Note that we only consider limited foresight but do not account for forecast errors in this case." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c2b5b320", + "metadata": {}, + "outputs": [], + "source": [ + "n_rh_24_0 = m.copy()\n", + "n_rh_24_0.optimize.optimize_with_rolling_horizon(\n", + " horizon=24, overlap=0, log_to_console=False\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "c32203d9", + "metadata": {}, + "source": [ + "We can now compare electricity supply between the two systems. With perfect operational foresight, the model is able to integrate a higher share of wind generation, as it optimally dispatches the storage based on full knowledge of future conditions. In contrast, the limited foresight model, which lacks information about future wind scarcity, tends to deplete storage prematurely, resulting in less efficient use of wind energy." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4624dbc3", + "metadata": {}, + "outputs": [], + "source": [ + "supply_comparison = pd.concat(\n", + " {\n", + " \"Perfect Foresight\": n.statistics.supply(),\n", + " \"Rolling Horizon\": n_rh_24_0.statistics.supply(),\n", + " },\n", + " axis=1,\n", + ").round(2)\n", + "supply_comparison" + ] + }, + { + "cell_type": "markdown", + "id": "4b3f3f18", + "metadata": {}, + "source": [ + "A comparison of the battery state of charge in both systems reveals the underlying cause. The system with perfect foresight anticipates upcoming drops in wind availability and therefore charges the battery fully during periods of high wind generation. This enables it to supply low-cost wind electricity to the system over a longer period. In contrast, the limited foresight model does not anticipate the decline in wind availability and, thus, discharges completely after 24 hours. Therefore, it does not see an incentive to keep the battery SOC as high as in the perfect foresight case (as long as no value is assigned to the stored energy)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2520ba4", + "metadata": {}, + "outputs": [], + "source": [ + "fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)\n", + "\n", + "ax1.plot(\n", + " n.snapshots,\n", + " n.storage_units_t.state_of_charge.loc[n.snapshots],\n", + " label=\"Perfect Foresight\",\n", + ")\n", + "ax1.plot(\n", + " n_rh_24_0.snapshots,\n", + " n_rh_24_0.storage_units_t.state_of_charge.loc[n_rh_24_0.snapshots],\n", + " label=\"Rolling Horizon (24, 0)\",\n", + ")\n", + "ax1.set_ylabel(\"State of Charge (MWh)\")\n", + "ax1.set_title(\"Battery State of Charge\")\n", + "ax1.legend(loc=\"upper left\")\n", + "\n", + "ax2.plot(p_max_pu_select.index, p_max_pu_select.values)\n", + "ax2.set_ylabel(\"Capacity Factor\")\n", + "ax2.set_title(\"Onshore Wind Capacity Factor\")" + ] + }, + { + "cell_type": "markdown", + "id": "529427c6", + "metadata": {}, + "source": [ + "If we look at the dispatch of the different components, we can see where the perfect foresight model manages to utilize the battery better to maximise the cheaper wind energy generation. The onshore wind generation in the second half of 1st March is not utilized by the rolling horizon model. As a result the grid has to provide more electricity on 3rd of March." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e34e1345", + "metadata": {}, + "outputs": [], + "source": [ + "fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 7))\n", + "\n", + "# Top subplot - Perfect Foresight Network\n", + "n.statistics.energy_balance(groupby_time=False).droplevel(0).groupby(\n", + " \"carrier\"\n", + ").sum().drop(\"load\").T.plot(\n", + " ax=ax1,\n", + " title=\"Dispatch - Perfect Foresight\",\n", + ")\n", + "ax1.axhline(y=25, color=\"k\", linestyle=\"--\", label=\"Load\")\n", + "ax1.axhline(y=0, color=\"k\", linestyle=\":\")\n", + "ax1.set_ylabel(\"MW\")\n", + "ax1.legend(ncol=4)\n", + "\n", + "# Bottom subplot - Rolling Horizon Network\n", + "n_rh_24_0.statistics.energy_balance(groupby_time=False).droplevel(0).groupby(\n", + " \"carrier\"\n", + ").sum().drop(\"load\").T.plot(\n", + " ax=ax2,\n", + " title=\"Dispatch - Rolling Horizon\",\n", + ")\n", + "ax2.axhline(y=25, color=\"k\", linestyle=\"--\", label=\"Load\")\n", + "ax2.axhline(y=0, color=\"k\", linestyle=\":\")\n", + "ax2.set_ylabel(\"MW\")\n", + "ax2.legend(ncol=4)" + ] + }, + { + "attachments": { }, + "cell_type": "markdown", + "id": "0600bc99", + "metadata": {}, + "source": [ + "To mitigate boundary effects between consecutive optimization windows, we can introduce an overlap. This overlap will reduce the model's tendency (in myopic decision making) to deplete storage assets at the end of each optimization horizon. For example, we can investigate a 24-hour forecast horizon with a forecast update frequency of 22 hours. If the forecast update frequency is shorter than the horizon lengh, there is an overlap of the rolling horizons.\n" + ] + }, + { + "cell_type": "markdown", + "id": "2bf26d3c", + "metadata": {}, + "source": [ + "We now investigate which effects different forecast horizon and overlap values have on the optimization, by first defining different horizon-overlap pairs and then solving them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0dd7a29a", + "metadata": {}, + "outputs": [], + "source": [ + "configs = [(12, 0), (24, 2), (28, 4), (36, 12), (48, 24)]\n", + "scenario_names = (\n", + " [\"Perfect Foresight\"] + [\"RH (24,0)\"] + [f\"RH ({h},{o})\" for h, o in configs]\n", + ")\n", + "\n", + "results = {}\n", + "results[\"Perfect Foresight\"] = n\n", + "results[\"RH (24,0)\"] = n_rh_24_0\n", + "\n", + "for h, o in configs:\n", + " name = f\"RH ({h},{o})\"\n", + " n_temp = m.copy()\n", + " n_temp.optimize.optimize_with_rolling_horizon(\n", + " horizon=h, overlap=o, log_to_console=False\n", + " )\n", + " results[name] = n_temp" + ] + }, + { + "cell_type": "markdown", + "id": "c81662d8", + "metadata": {}, + "source": [ + "If we look at the electricty supply of the different models, we can see that the perfect foresight model has the highest onwind generation and the lowest grid electricity consumption and vice versa the model with 12-hour foresight (no surprises!)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6cd0ea72", + "metadata": {}, + "outputs": [], + "source": [ + "supply_comparison = pd.concat(\n", + " [results[s].statistics.supply() for s in scenario_names], axis=1\n", + ")\n", + "supply_comparison.columns = scenario_names\n", + "supply_comparison" + ] + }, + { + "cell_type": "markdown", + "id": "b603acf1", + "metadata": {}, + "source": [ + "The battery SOC illustrates the impact of different horizon and overlap parameters.\n", + "The RH (12, 0) scenario has the least ability to anticipate future conditions and therefore fully discharges every 12 hours at the end of its rolling window.\n", + "Compared to RH (24, 0), the RH (24, 2) scenario charges less on 2 March, which leads to a faster discharge and an earlier reliance on grid electricity.\n", + "The RH (28, 4) and RH (36, 12) scenarios are better at anticipating the reduced wind supply toward the end of the period and, thus, discharge more slowly.\n", + "Finally, the RH (48, 24) scenario has the longest foresight and the highest overlap, enabling it to maintain a high battery charge during the low-wind generation period and to discharge only gradually.\n", + "Still, there is a substantial gap to the results with perfect operational foresight since the optimal charging and discharging events are more than 48 hours apart." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "02b6e2e8", + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(10, 6))\n", + "\n", + "for i, s in enumerate(scenario_names):\n", + " ax.plot(\n", + " results[s].snapshots,\n", + " results[s].storage_units_t.state_of_charge.loc[results[s].snapshots],\n", + " label=s,\n", + " ls=\"-\" if i == 0 else \"--\",\n", + " )\n", + "\n", + "ax.legend()\n", + "ax.set_ylabel(\"State of Charge (MWh)\")" + ] + }, + { + "cell_type": "markdown", + "id": "022a9e26", + "metadata": {}, + "source": [ + "The limited foresight leads to suboptimal operational decisions, resulting in additional system costs compared to the perfect foresight case. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dec2f3ed", + "metadata": {}, + "outputs": [], + "source": [ + "opex_comparison = pd.concat(\n", + " [results[s].statistics.opex() for s in scenario_names], axis=1\n", + ")\n", + "opex_comparison.columns = scenario_names\n", + "opex_comparison_diff = round(\n", + " (opex_comparison - results[\"Perfect Foresight\"].statistics.opex().iloc[0]) / 1e3, 2\n", + ")\n", + "opex_comparison_diff" + ] + }, + { + "cell_type": "markdown", + "id": "33a2043c", + "metadata": {}, + "source": [ + "Besides accounting for imperfect forecast horizons in reality, rolling horizon optimization can also help manage computational complexity by breaking up large problems into smaller chunks. In such settings, boundary effects can be mitigated by sufficient overlap between the rolling windows or assigning heuristic values for stored energy (e.g. known as water values in the hydro-electricity literature)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/examples/scigrid-lopf-then-pf.ipynb b/PyPSA/source/docs/examples/scigrid-lopf-then-pf.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..d4bd906bbe6a3ecd489469ebafb35743de40591b --- /dev/null +++ b/PyPSA/source/docs/examples/scigrid-lopf-then-pf.ipynb @@ -0,0 +1,722 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# SciGRID Network\n", + "\n", + "In this example, the dispatch of generators is optimised using the linear optimisation, then a non-linear power flow is run on the resulting dispatch.\n", + "This example covers the German power system (roughly in the mid 2010s) for a single day at hourly resolution.\n", + "\n", + "## Data sources\n", + "\n", + "- **Grid:** Based on [SciGRID](https://openenergyplatform.org/factsheets/models/22/) which is based on [OpenStreetMap](http://www.openstreetmap.org/).\n", + "\n", + "- **Load size and location:** Distributed proportional to NUTS3 GDP and population. In doubt, load and generation is attached to the 220kV substation.\n", + "\n", + "- **Load time series:** From ENTSO-E hourly data, scaled up uniformly by factor 1.12 (a simplification of the methodology in [Schumacher and Hirth (2015)](https://ideas.repec.org/p/fem/femwpa/2015.88.html)).\n", + "\n", + "- **Conventional power plant capacities and locations:** Based on list of [BNetzA](https://www.bnetza.de/) (German Federal Network Agency).\n", + "\n", + "- **Wind and solar capacities and locations:** Based on [EEG Stammdaten](https://www.netztransparenz.de/de-de/Erneuerbare-Energien-und-Umlagen/EEG/EEG-Abrechnungen/EEG-Jahresabrechnungen/EEG-Anlagenstammdaten), [EnergyMap](http://www.energymap.info/download.html), which represents capacities at the end of 2014.\n", + "\n", + "- **Wind and solar time series:** Generated with REatlas tool based on [Andresen et al. (2015)](https://doi.org/10.1016/j.energy.2015.09.071).\n", + "\n", + "## Warnings\n", + "\n", + "**The data behind this example is no longer supported or updated. The dataset is only intended to demonstrate the capabilities of PyPSA and is not suitable for research purposes. Have a look at [PyPSA-Eur](https://github.com/PyPSA/pypsa-eur) for a newer grid model with active support.**\n", + "\n", + "Known problems include:\n", + "\n", + "1. Rough approximations have been made for missing grid data, e.g. 220kV-380kV transformers and connections between close sub-stations missing from OSM.\n", + "\n", + "2. There appears to be some unexpected congestion in parts of the network, which may mean for example that the load attachment method is inaccurate, particularly in regions with a high density of substations.\n", + "\n", + "3. Attaching power plants to the nearest high voltage substation may not reflect reality.\n", + "\n", + "4. The borders and neighbouring countries are not represented.\n", + "\n", + "5. Hydroelectric power stations are not modelled accurately.\n", + "\n", + "6. The marginal costs of generators are illustrative, not accurate.\n", + "\n", + "7. Only the first day of 2011 is in the example, which is not representative.\n", + "\n", + "8. The ENTSO-E total load for Germany may not be scaled correctly.\n", + "\n", + "9. Biomass from the EEG reference data (Stammdaten) are not read in.\n", + "\n", + "10. Power plant start up costs, ramping limits, and minimum part loads are not considered." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Input Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import cartopy.crs as ccrs\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import pypsa\n", + "\n", + "n = pypsa.examples.scigrid_de()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plot the distribution of the load and of generating tech" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(\n", + " 1,\n", + " 1,\n", + " subplot_kw={\"projection\": ccrs.EqualEarth()},\n", + ")\n", + "\n", + "load_distribution = n.loads_t.p_set.loc[n.snapshots[0]].groupby(n.loads.bus).sum()\n", + "n.plot(bus_size=load_distribution / 30000, ax=ax, title=\"Load distribution\");" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "n.generators.groupby(\"carrier\")[\"p_nom\"].sum().round(1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "n.storage_units.groupby(\"carrier\")[\"p_nom\"].sum().round(1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "techs = [\"Gas\", \"Brown Coal\", \"Hard Coal\", \"Wind Offshore\", \"Wind Onshore\", \"Solar\"]\n", + "\n", + "n_graphs = len(techs)\n", + "n_cols = 3\n", + "if n_graphs % n_cols == 0:\n", + " n_rows = n_graphs // n_cols\n", + "else:\n", + " n_rows = n_graphs // n_cols + 1\n", + "\n", + "\n", + "fig, axes = plt.subplots(\n", + " nrows=n_rows, ncols=n_cols, subplot_kw={\"projection\": ccrs.EqualEarth()}\n", + ")\n", + "size = 6\n", + "fig.set_size_inches(size * n_cols, size * n_rows)\n", + "\n", + "for i, tech in enumerate(techs):\n", + " i_row = i // n_cols\n", + " i_col = i % n_cols\n", + "\n", + " ax = axes[i_row, i_col]\n", + " gens = n.generators[n.generators.carrier == tech]\n", + " gen_distribution = (\n", + " gens.groupby(\"bus\").sum()[\"p_nom\"].reindex(n.buses.index, fill_value=0)\n", + " )\n", + " n.plot(ax=ax, bus_size=gen_distribution / 20000)\n", + " ax.set_title(tech)\n", + "fig.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Rolling-Horizon Optimisation\n", + "\n", + "Run optimisation (linear optimal power flow) on the first day of 2011.\n", + "\n", + "To approximate $N-1$ security and allow room for reactive power flows, we do not allow any line to be loaded above 70% of their thermal rating:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "contingency_factor = 0.7\n", + "n.lines.s_max_pu = contingency_factor" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are some infeasibilities without small extensions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.lines.loc[[\"316\", \"527\", \"602\"], \"s_nom\"] = 1715" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are performing the optimisation for one day, 4 snapshots at a time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + }, + "scrolled": true + }, + "outputs": [], + "source": [ + "n.optimize.optimize_with_rolling_horizon(horizon=4, overlap=0, log_to_console=False);" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Plot dispatch time series" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "p_by_carrier = n.generators_t.p.T.groupby(n.generators.carrier).sum().T\n", + "to_drop = p_by_carrier.max()[p_by_carrier.max() < 1700].index\n", + "p_by_carrier.drop(to_drop, axis=1, inplace=True)\n", + "p_by_carrier.columns" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "colors = {\n", + " \"Brown Coal\": \"brown\",\n", + " \"Hard Coal\": \"k\",\n", + " \"Nuclear\": \"r\",\n", + " \"Run of River\": \"green\",\n", + " \"Wind Onshore\": \"blue\",\n", + " \"Solar\": \"yellow\",\n", + " \"Wind Offshore\": \"cyan\",\n", + " \"Waste\": \"orange\",\n", + " \"Gas\": \"orange\",\n", + "}\n", + "# reorder\n", + "cols = [\n", + " \"Nuclear\",\n", + " \"Run of River\",\n", + " \"Brown Coal\",\n", + " \"Hard Coal\",\n", + " \"Gas\",\n", + " \"Wind Offshore\",\n", + " \"Wind Onshore\",\n", + " \"Solar\",\n", + "]\n", + "p_by_carrier = p_by_carrier[cols]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "c = [colors[col] for col in p_by_carrier.columns]\n", + "fig, ax = plt.subplots()\n", + "p_by_carrier.div(1e3).plot(kind=\"area\", ax=ax, lw=0, color=c, alpha=0.7)\n", + "ax.legend(ncol=3, loc=\"upper left\", bbox_to_anchor=(0, 1.02, 1, 0.2), frameon=False)\n", + "ax.set_ylabel(\"GW\")\n", + "ax.set_xlabel(\"\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Plot storage time series" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots()\n", + "\n", + "p_storage = n.storage_units_t.p.sum(axis=1)\n", + "state_of_charge = n.storage_units_t.state_of_charge.sum(axis=1)\n", + "p_storage.plot(label=\"Pumped hydro dispatch\", ax=ax)\n", + "state_of_charge.plot(label=\"State of charge\", ax=ax)\n", + "\n", + "ax.axhline(0, color=\"k\", lw=0.5, ls=\"--\")\n", + "ax.legend()\n", + "ax.set_ylabel(\"MWh\")\n", + "ax.set_xlabel(\"\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Line loading from optimisation\n", + "\n", + "With the linear power flow, there is the following per unit loading at 3 AM:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "now = n.snapshots[4]\n", + "loading = n.lines_t.p0.loc[now] / n.lines.s_nom\n", + "loading.describe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(subplot_kw={\"projection\": ccrs.EqualEarth()})\n", + "n.plot(\n", + " ax=ax,\n", + " line_color=loading.abs(),\n", + " line_cmap=\"viridis\",\n", + " title=\"Line loading\",\n", + " bus_size=1e-3,\n", + ");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Locational marginal prices" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's have a look at the distribution of marginal prices:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "n.buses_t.marginal_price.loc[now].describe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(subplot_kw={\"projection\": ccrs.PlateCarree()})\n", + "\n", + "plt.hexbin(\n", + " n.buses.x,\n", + " n.buses.y,\n", + " gridsize=20,\n", + " C=n.buses_t.marginal_price.loc[now],\n", + " cmap=\"viridis\",\n", + " zorder=-1,\n", + ")\n", + "n.plot(ax=ax, line_width=1, bus_size=0)\n", + "\n", + "cb = plt.colorbar(location=\"right\")\n", + "cb.set_label(\"Locational Marginal Price (€/MWh)\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Curtailment\n", + "\n", + "By considering how much power is available and how much is generated, you can see what share is curtailed: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "carrier = \"Wind Onshore\"\n", + "\n", + "capacity = n.generators.groupby(\"carrier\").sum().at[carrier, \"p_nom\"]\n", + "p_available = n.generators_t.p_max_pu.multiply(n.generators[\"p_nom\"])\n", + "p_available_by_carrier = p_available.T.groupby(n.generators.carrier).sum().T\n", + "p_curtailed_by_carrier = p_available_by_carrier - p_by_carrier" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "p_df = pd.DataFrame(\n", + " {\n", + " carrier + \" available\": p_available_by_carrier[carrier],\n", + " carrier + \" dispatched\": p_by_carrier[carrier],\n", + " carrier + \" curtailed\": p_curtailed_by_carrier[carrier],\n", + " }\n", + ")\n", + "\n", + "p_df[carrier + \" capacity\"] = capacity" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "p_df.loc[p_df[\"Wind Onshore curtailed\"] < 0, \"Wind Onshore curtailed\"] = 0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots()\n", + "p_df[[carrier + \" dispatched\", carrier + \" curtailed\"]].plot(kind=\"area\", ax=ax, lw=0)\n", + "p_df[[carrier + \" available\", carrier + \" capacity\"]].plot(ax=ax)\n", + "\n", + "ax.set_xlabel(\"\")\n", + "ax.set_ylabel(\"Power [MW]\")\n", + "ax.set_ylim([0, 40_000])\n", + "ax.legend()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Non-Linear Power Flow\n", + "\n", + "Now perform a full Newton-Raphson power flow on the first day. For the power flow, provide the setpoints from the optimisation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "n.optimize.fix_optimal_dispatch()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Set nearly all buses to PV, since we don't know what Q set points are. But we needs some PQ buses to ensure that the Jacobian is not singular.:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "n.generators.control = \"PV\"\n", + "\n", + "f = n.generators[n.generators.bus == \"492\"]\n", + "n.generators.loc[f.index, \"control\"] = \"PQ\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, perform the non-linear PF." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "info = n.pf();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Any failed to converge?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "(~info.converged).any().any()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With the non-linear load flow, there is the following per unit loading of the full thermal rating." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "(n.lines_t.p0.loc[now] / n.lines.s_nom).describe()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's inspect the voltage angle differences across the lines have (in degrees)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "df = n.lines.copy()\n", + "\n", + "for b in [\"bus0\", \"bus1\"]:\n", + " df = pd.merge(\n", + " df, n.buses_t.v_ang.loc[[now]].T, how=\"left\", left_on=b, right_index=True\n", + " )\n", + "\n", + "s = df[str(now) + \"_x\"] - df[str(now) + \"_y\"]\n", + "\n", + "(s * 180 / np.pi).describe()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plot the reactive power" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(subplot_kw={\"projection\": ccrs.EqualEarth()})\n", + "\n", + "q = n.buses_t.q.loc[now]\n", + "bus_color = q.map(lambda x: \"b\" if x < 0 else \"r\")\n", + "\n", + "n.plot(\n", + " bus_size=q.abs() / 1e4,\n", + " ax=ax,\n", + " bus_color=bus_color,\n", + " title=\"Reactive power feed-in\\n(red = positive, blue = negative)\",\n", + ");" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/PyPSA/source/docs/examples/scigrid-redispatch.ipynb b/PyPSA/source/docs/examples/scigrid-redispatch.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..de1dff29365ca02fa96336fa874313570195bc3f --- /dev/null +++ b/PyPSA/source/docs/examples/scigrid-redispatch.ipynb @@ -0,0 +1,481 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Redispatch Example\n", + "\n", + "In this example, we compare a 2-stage market with an initial market clearing in two bidding zones with flow-based market coupling and a subsequent redispatch market (incl. curtailment) to an idealised nodal pricing scheme." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "import cartopy.crs as ccrs\n", + "import matplotlib.pyplot as plt\n", + "\n", + "import pypsa" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Load example network" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "o = pypsa.examples.scigrid_de()\n", + "o.lines.s_max_pu = 0.7\n", + "o.lines.loc[[\"316\", \"527\", \"602\"], \"s_nom\"] = 1715\n", + "o.set_snapshots([o.snapshots[12]])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n = o.copy() # for redispatch model\n", + "m = o.copy() # for market model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "o.plot();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solve original nodal market model `o`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, let us solve a nodal market using the original model `o`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "o.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Costs are 301 k€." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Build market model `m` with two bidding zones" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For this example, we split the German transmission network into two market zones at latitude 51 degrees.\n", + "\n", + "You can build any other market zones by providing an alternative mapping from bus to zone. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "zones = (n.buses.y > 51).map(lambda x: \"North\" if x else \"South\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we assign this mapping to the market model `m`.\n", + "\n", + "We re-assign the buses of all generators and loads, and remove all transmission lines within each bidding zone.\n", + "\n", + "Here, we assume that the bidding zones are coupled through the transmission lines that connect them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for c in m.components:\n", + " if c.name not in m.one_port_components:\n", + " continue\n", + " c.static.bus = c.static.bus.map(zones)\n", + "\n", + "for c in m.components:\n", + " if c.name not in m.branch_components:\n", + " continue\n", + " c.static.bus0 = c.static.bus0.map(zones)\n", + " c.static.bus1 = c.static.bus1.map(zones)\n", + " internal = c.static.bus0 == c.static.bus1\n", + " m.remove(c.name, c.static.loc[internal].index)\n", + "\n", + "m.remove(\"Bus\", m.buses.index)\n", + "m.add(\"Bus\", [\"North\", \"South\"]);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we can solve the coupled market with two bidding zones." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Costs are 214 k€, which is much lower than the 301 k€ of the nodal market.\n", + "\n", + "This is because network restrictions apart from the North/South division are not taken into account yet." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can look at the market clearing prices of each zone:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m.buses_t.marginal_price" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Build redispatch model `n`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, based on the market outcome with two bidding zones `m`, we build a secondary redispatch market `n` that rectifies transmission constraints through curtailment and ramping up/down thermal generators.\n", + "\n", + "First, we fix the dispatch of generators to the results from the market simulation. (For simplicity, this example disregards storage units.)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "p = m.generators_t.p / m.generators.p_nom\n", + "n.generators_t.p_min_pu = p\n", + "n.generators_t.p_max_pu = p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then, we add generators bidding into redispatch market using the following assumptions:\n", + "\n", + "* All generators can reduce their dispatch to zero. This includes also curtailment of renewables.\n", + "* All generators can increase their dispatch to their available/nominal capacity.\n", + "* No changes to the marginal costs, i.e. reducing dispatch lowers costs.\n", + "\n", + "With these settings, the 2-stage market should result in the same cost as the nodal market." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "g_up = n.generators.copy()\n", + "g_down = n.generators.copy()\n", + "\n", + "g_up.index = g_up.index.map(lambda x: x + \" ramp up\")\n", + "g_down.index = g_down.index.map(lambda x: x + \" ramp down\")\n", + "\n", + "up = (\n", + " m.get_switchable_as_dense(\"Generator\", \"p_max_pu\") * m.generators.p_nom\n", + " - m.generators_t.p\n", + ").clip(0) / m.generators.p_nom\n", + "down = -m.generators_t.p / m.generators.p_nom\n", + "\n", + "up.columns = up.columns.map(lambda x: x + \" ramp up\")\n", + "down.columns = down.columns.map(lambda x: x + \" ramp down\")\n", + "\n", + "n.add(\"Generator\", g_up.index, p_max_pu=up, **g_up.drop(\"p_max_pu\", axis=1))\n", + "\n", + "n.add(\n", + " \"Generator\",\n", + " g_down.index,\n", + " p_min_pu=down,\n", + " p_max_pu=0,\n", + " **g_down.drop([\"p_max_pu\", \"p_min_pu\"], axis=1),\n", + ");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's solve the redispatch market:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And, as expected, the costs are the same as for the nodal market: 301 k€." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we can plot both the market results of the 2 bidding zone market and the redispatch results:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, axs = plt.subplots(\n", + " 1, 3, figsize=(20, 10), subplot_kw={\"projection\": ccrs.AlbersEqualArea()}\n", + ")\n", + "\n", + "market = (\n", + " n.generators_t.p[m.generators.index]\n", + " .T.squeeze()\n", + " .groupby(n.generators.bus)\n", + " .sum()\n", + " .div(2e4)\n", + ")\n", + "n.plot(ax=axs[0], bus_size=market, title=\"2 bidding zones market simulation\")\n", + "\n", + "redispatch_up = (\n", + " n.generators_t.p.filter(like=\"ramp up\")\n", + " .T.squeeze()\n", + " .groupby(n.generators.bus)\n", + " .sum()\n", + " .div(2e4)\n", + ")\n", + "n.plot(ax=axs[1], bus_size=redispatch_up, bus_color=\"blue\", title=\"Redispatch: ramp up\")\n", + "\n", + "redispatch_down = (\n", + " n.generators_t.p.filter(like=\"ramp down\")\n", + " .T.squeeze()\n", + " .groupby(n.generators.bus)\n", + " .sum()\n", + " .div(-2e4)\n", + ")\n", + "n.plot(\n", + " ax=axs[2],\n", + " bus_size=redispatch_down,\n", + " bus_color=\"red\",\n", + " title=\"Redispatch: ramp down / curtail\",\n", + ");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also read out the final dispatch of each generator:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "grouper = n.generators.index.str.split(\" ramp\", expand=True).get_level_values(0)\n", + "\n", + "n.generators_t.p.groupby(grouper, axis=1).sum().squeeze()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Changing bidding strategies in redispatch market" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also formulate other bidding strategies or compensation mechanisms for the redispatch market." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For example, that ramping up a generator is twice as expensive." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators.loc[n.generators.index.str.contains(\"ramp up\"), \"marginal_cost\"] *= 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Or that generators need to be compensated for curtailing them or ramping them down at 50% of their marginal cost." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators.loc[n.generators.index.str.contains(\"ramp down\"), \"marginal_cost\"] *= -0.5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this way, the outcome should be more expensive than the ideal nodal market:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "n.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Costs are now 502 k€ compared to 301 k€." + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/PyPSA/source/docs/examples/scigrid-sclopf.ipynb b/PyPSA/source/docs/examples/scigrid-sclopf.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..ac359205aa4abc2c8bbf2b708fda8faf873a0d23 --- /dev/null +++ b/PyPSA/source/docs/examples/scigrid-sclopf.ipynb @@ -0,0 +1,247 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Security-Constrained LOPF\n", + "\n", + "In this example, the dispatch of generators is optimised using the security-constrained linear optimal power flow (SCLOPF) functionality of PyPSA, to guarantee that no branches are overloaded in the event of certain branch outages." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pypsa\n", + "\n", + "n = pypsa.examples.scigrid_de()\n", + "\n", + "# correct some infeasibilties in the network\n", + "for line_name in [\"316\", \"527\", \"602\"]:\n", + " n.lines.loc[line_name, \"s_nom\"] = 1200\n", + "\n", + "now = n.snapshots[0]\n", + "\n", + "n.plot(bus_size=0);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, let's run the network without any $N-1$ security constraints and see how much it costs to operate the system.:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n0 = n.copy()\n", + "n0.optimize(snapshots=now, log_to_console=False)\n", + "n0.statistics.opex().sum()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The security-constrained linear optimal power flow (SCLOPF) is executed using the `n.optimize.optimize_security_constrained()` method.\n", + "This method takes a list of snapshots (here: `now`) and a list of branches that are to be considered as outages (here, the 30 lines with the highest loading)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "branch_outages = (n0.lines_t.p0.loc[now] / n0.lines.s_nom).nlargest(30).index\n", + "n.optimize.optimize_security_constrained(\n", + " now, branch_outages=branch_outages, log_to_console=False\n", + ")\n", + "n.statistics.opex().sum()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can see that the cost of operating the system in the given hour rises from 332 k€ to 427 k€ when the security constraints are applied.\n", + "\n", + "The maps below indicate the difference in the dispatch patterns, line flows and marginal prices between the $N-0$ (first) and $N-1$ (second) cases." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def plot_network(n, snapshot):\n", + " bus_size = (\n", + " n.statistics.supply(groupby=\"bus\", components=[\"Generator\", \"StorageUnit\"])\n", + " .groupby(\"bus\")\n", + " .sum()\n", + " )\n", + " line_flows = n.lines_t.p0.loc[snapshot]\n", + " bus_color = n.buses_t.marginal_price.loc[snapshot]\n", + " line_loading = n.lines_t.p0.abs().loc[snapshot] / n.lines.s_nom\n", + "\n", + " n.plot(\n", + " bus_size=bus_size / 30000,\n", + " bus_color=bus_color,\n", + " bus_cmap=\"Reds\",\n", + " line_color=line_loading,\n", + " line_flow=line_flows / 50,\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plot_network(n0, now)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plot_network(n, now)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also look at where the nodal dispatch is ramped up (red) or down (blue) in the $N-1$ case compared to the $N-0$ case. Mostly this means ramp down upstream of the potential outages, and ramp up downstream of the potential outages." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "bus_size0 = (\n", + " (\n", + " n0.statistics.supply(groupby=\"bus\", components=[\"Generator\", \"StorageUnit\"])\n", + " .groupby(\"bus\")\n", + " .sum()\n", + " )\n", + " .reindex(index=n.buses.index)\n", + " .fillna(0)\n", + ")\n", + "\n", + "bus_size1 = (\n", + " (\n", + " n.statistics.supply(groupby=\"bus\", components=[\"Generator\", \"StorageUnit\"])\n", + " .groupby(\"bus\")\n", + " .sum()\n", + " )\n", + " .reindex(index=n.buses.index)\n", + " .fillna(0)\n", + ")\n", + "\n", + "bus_size = bus_size1 - bus_size0\n", + "\n", + "n.plot(\n", + " bus_size=bus_size.abs() / 30000,\n", + " bus_color=bus_size.map(lambda x: \"b\" if x < 0 else \"r\"),\n", + ");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also double-check that the $N-1$ constraints are satisfied and no lines are overloaded in the event of the outages considered:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize.fix_optimal_dispatch()\n", + "p0_test = n.lpf_contingency(now, branch_outages=branch_outages)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Check the maximum loading as per unit of `s_nom` in each contingency:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "max_loading = abs(p0_test.divide(n.passive_branches().s_nom, axis=0)).max()\n", + "max_loading" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/PyPSA/source/docs/examples/sector-coupling-single-node.ipynb b/PyPSA/source/docs/examples/sector-coupling-single-node.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..9c4304b820e624a9b197f3c56b41cfa5faa92627 --- /dev/null +++ b/PyPSA/source/docs/examples/sector-coupling-single-node.ipynb @@ -0,0 +1,853 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Single Node Sector Coupling" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:12.661289Z", + "start_time": "2024-05-24T07:42:11.505585Z" + } + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "\n", + "import pypsa\n", + "\n", + "plt.style.use(\"bmh\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Previous Capacity Expansion Model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To explore sector-coupling options with PyPSA, let's load the [capacity expansion model we built for the electricity system](capacity-expansion-planning-single-node.ipynb) and add sector-coupling technologies and demands on top. This example has single node for Germany and 4-hourly temporal resolution for a year. It has wind and solar solar generation, an OCGT generator as well as battery and hydrogen storage to supply a fixed electricity demand. Some sector-coupling technologies have multiple ouputs (e.g. CHP plants producing heat and power). PyPSA can automatically handle links have more than one input (`bus0`) and/or output (i.e. `bus1`, `bus2`, `bus3`) with a given efficieny (`efficiency`, `efficiency2`, `efficiency3`)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:13.279493Z", + "start_time": "2024-05-24T07:42:13.071020Z" + } + }, + "outputs": [], + "source": [ + "n = pypsa.Network(\n", + " \"https://tubcloud.tu-berlin.de/s/pzytNg9gtkgPpXc/download/network-cem.nc\"\n", + ")\n", + "n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Hydrogen Production" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following example shows how to model the components of hydrogen storage separately, i.e. electrolysis, fuel cell and storage.\n", + "\n", + "First, let's remove the simplified hydrogen storage representation:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:13.296471Z", + "start_time": "2024-05-24T07:42:13.293469Z" + } + }, + "outputs": [], + "source": [ + "n.remove(\"StorageUnit\", \"hydrogen storage underground\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add a separate `Bus` for the hydrogen energy carrier:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:13.301155Z", + "start_time": "2024-05-24T07:42:13.297271Z" + } + }, + "outputs": [], + "source": [ + "n.add(\"Bus\", \"hydrogen\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add a `Link` for the hydrogen electrolysis:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:13.306176Z", + "start_time": "2024-05-24T07:42:13.302049Z" + } + }, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"electrolysis\",\n", + " bus0=\"electricity\",\n", + " bus1=\"hydrogen\",\n", + " carrier=\"electrolysis\",\n", + " p_nom_extendable=True,\n", + " efficiency=0.7,\n", + " capital_cost=50e3, # €/MW/a\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add a `Link` for the fuel cell which reconverts hydrogen to electricity:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:13.310502Z", + "start_time": "2024-05-24T07:42:13.306923Z" + } + }, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"fuel cell\",\n", + " bus0=\"hydrogen\",\n", + " bus1=\"electricity\",\n", + " carrier=\"fuel cell\",\n", + " p_nom_extendable=True,\n", + " efficiency=0.5,\n", + " capital_cost=120e3, # €/MW/a\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add a `Store` for the hydrogen storage:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:13.314801Z", + "start_time": "2024-05-24T07:42:13.311271Z" + } + }, + "outputs": [], + "source": [ + "n.add(\n", + " \"Store\",\n", + " \"hydrogen storage\",\n", + " bus=\"hydrogen\",\n", + " carrier=\"hydrogen storage\",\n", + " capital_cost=140, # €/MWh/a\n", + " e_nom_extendable=True,\n", + " e_cyclic=True, # cyclic state of charge\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also add a hydrogen demand to the hydrogen bus.\n", + "\n", + "In the example below, we add a constant hydrogen demand the size of the electricity demand." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:13.317310Z", + "start_time": "2024-05-24T07:42:13.315403Z" + } + }, + "outputs": [], + "source": [ + "p_set = n.loads_t.p_set[\"demand\"].mean()\n", + "\n", + "n.add(\"Load\", \"hydrogen demand\", bus=\"hydrogen\", carrier=\"hydrogen\", p_set=p_set) # MW\n", + "\n", + "p_set" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Heat Demand" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the heat demand, we create another bus and connect a load with the heat demand time series to it:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:13.713847Z", + "start_time": "2024-05-24T07:42:13.330846Z" + } + }, + "outputs": [], + "source": [ + "n.add(\"Bus\", \"heat\")\n", + "\n", + "url = \"https://tubcloud.tu-berlin.de/s/mSkHERH8fJCKNXx/download/heat-load-example.csv\"\n", + "p_set = pd.read_csv(url, index_col=0, parse_dates=True).squeeze()\n", + "\n", + "n.add(\"Load\", \"heat demand\", carrier=\"heat\", bus=\"heat\", p_set=p_set)\n", + "\n", + "n.loads_t.p_set.div(1e3).plot(figsize=(12, 4), ylabel=\"GW\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Heat pumps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To model heat pumps, first we have to calculate the **coefficient of performance (COP)** profile based on the temperature profile of the heat source.\n", + "\n", + "In the example below, we calculate the COP for an air-sourced heat pump with a sink temperature of 55° C and a population-weighted ambient temperature profile for Germany.\n", + "\n", + "The heat pump performance is given by the following function:\n", + "\n", + "$$\n", + "COP(\\Delta T) = 6.81 - 0.121 \\Delta T + 0.00063^\\Delta T^2\n", + "$$\n", + "where $\\Delta T = T_{sink} - T_{source}$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:15.829886Z", + "start_time": "2024-05-24T07:42:14.300214Z" + } + }, + "outputs": [], + "source": [ + "def cop(t_source, t_sink=55):\n", + " delta_t = t_sink - t_source\n", + " return 6.81 - 0.121 * delta_t + 0.000630 * delta_t**2\n", + "\n", + "\n", + "url = \"https://tubcloud.tu-berlin.de/s/S4jRAQMP5Te96jW/download/ninja_weather_country_DE_merra-2_population_weighted.csv\"\n", + "temp = pd.read_csv(url, skiprows=2, index_col=0, parse_dates=True).loc[\n", + " \"2015\", \"temperature\"\n", + "][::4]\n", + "\n", + "cop(temp).plot(figsize=(10, 2), ylabel=\"COP\");" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:16.298001Z", + "start_time": "2024-05-24T07:42:16.227657Z" + } + }, + "outputs": [], + "source": [ + "plt.scatter(temp, cop(temp))\n", + "plt.xlabel(\"temperature [°C]\")\n", + "plt.ylabel(\"COP [-]\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once we have calculated the heat pump coefficient of performance, we can add the heat pump to the network as a `Link`. We use the parameter `efficiency` to incorporate the COP." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:16.303741Z", + "start_time": "2024-05-24T07:42:16.298777Z" + } + }, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"heat pump\",\n", + " carrier=\"heat pump\",\n", + " bus0=\"electricity\",\n", + " bus1=\"heat\",\n", + " efficiency=cop(temp),\n", + " p_nom_extendable=True,\n", + " capital_cost=3e5, # €/MWe/a\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's also add a resistive heater as backup technology:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:16.308918Z", + "start_time": "2024-05-24T07:42:16.304634Z" + } + }, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"resistive heater\",\n", + " carrier=\"resistive heater\",\n", + " bus0=\"electricity\",\n", + " bus1=\"heat\",\n", + " efficiency=0.9,\n", + " capital_cost=1e4, # €/MWe/a\n", + " p_nom_extendable=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Combined Heat-and-Power (CHP)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the following, we are going to add gas-fired combined heat-and-power plants (CHPs). Today, these would use fossil gas, but in the example below we assume green methane with relatively high marginal costs. Since we have no other net emission technology, we can remove the CO$_2$ limit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:16.312063Z", + "start_time": "2024-05-24T07:42:16.309601Z" + } + }, + "outputs": [], + "source": [ + "n.remove(\"GlobalConstraint\", \"CO2Limit\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then, we explicitly represent the energy carrier gas:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:16.316389Z", + "start_time": "2024-05-24T07:42:16.313095Z" + } + }, + "outputs": [], + "source": [ + "n.add(\"Bus\", \"gas\", carrier=\"gas\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And add a `Store` of gas, which can be depleted (up to 100 TWh) with fuel costs of 150 €/MWh." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:16.320971Z", + "start_time": "2024-05-24T07:42:16.317343Z" + } + }, + "outputs": [], + "source": [ + "n.add(\n", + " \"Store\",\n", + " \"gas storage\",\n", + " carrier=\"gas storage\",\n", + " e_initial=100e6, # MWh\n", + " e_nom=100e6, # MWh\n", + " bus=\"gas\",\n", + " marginal_cost=150, # €/MWh_th\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we do this, we have to model the OCGT power plant as link which converts gas to electricity, not as generator." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:16.324170Z", + "start_time": "2024-05-24T07:42:16.321657Z" + } + }, + "outputs": [], + "source": [ + "n.remove(\"Generator\", \"OCGT\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:16.328332Z", + "start_time": "2024-05-24T07:42:16.324708Z" + } + }, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"OCGT\",\n", + " bus0=\"gas\",\n", + " bus1=\"electricity\",\n", + " carrier=\"OCGT\",\n", + " p_nom_extendable=True,\n", + " capital_cost=20000, # €/MW/a\n", + " efficiency=0.4,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we are going to add a combined heat-and-power (CHP) plant with fixed heat-power ratio (i.e. backpressure operation)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:16.337212Z", + "start_time": "2024-05-24T07:42:16.328886Z" + } + }, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"CHP\",\n", + " bus0=\"gas\",\n", + " bus1=\"electricity\",\n", + " bus2=\"heat\",\n", + " carrier=\"CHP\",\n", + " p_nom_extendable=True,\n", + " capital_cost=40000,\n", + " efficiency=0.4,\n", + " efficiency2=0.4,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Electric Vehicles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To model electric vehicles, we first create another bus for the electric vehicles." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:16.341026Z", + "start_time": "2024-05-24T07:42:16.337923Z" + } + }, + "outputs": [], + "source": [ + "n.add(\"Bus\", \"EV\", carrier=\"EV\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then, we can attach the electricity consumption of electric vehicles to this bus:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:16.799320Z", + "start_time": "2024-05-24T07:42:16.341707Z" + } + }, + "outputs": [], + "source": [ + "url = \"https://tubcloud.tu-berlin.de/s/9r5bMSbzzQiqG7H/download/electric-vehicle-profile-example.csv\"\n", + "p_set = pd.read_csv(url, index_col=0, parse_dates=True).squeeze()\n", + "\n", + "n.add(\"Load\", \"EVa demand\", bus=\"EV\", carrier=\"EV demand\", p_set=p_set)\n", + "\n", + "p_set.loc[\"2015-01-01\"].div(1e3).plot(figsize=(4, 4), ylabel=\"GW\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's have a quick look at how the heat, electricity, constant hydrogen and electric vehicle demands relate to each other:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:17.376681Z", + "start_time": "2024-05-24T07:42:16.920131Z" + } + }, + "outputs": [], + "source": [ + "n.loads_t.p_set.div(1e3).plot(figsize=(10, 3), ylabel=\"GW\")\n", + "plt.axhline(\n", + " n.loads.loc[\"hydrogen demand\", \"p_set\"] / 1e3, label=\"hydrogen demand\", color=\"m\"\n", + ")\n", + "plt.legend()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The electric vehicles can only be charged when they are plugged-in. Below we load an availability profile\n", + "telling us what share of electric vehicles is plugged-in **at home** -- we only assume home charging in this example." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:17.827888Z", + "start_time": "2024-05-24T07:42:17.377590Z" + } + }, + "outputs": [], + "source": [ + "url = \"https://tubcloud.tu-berlin.de/s/E3PBWPfYaWwCq7a/download/electric-vehicle-availability-example.csv\"\n", + "availability_profile = pd.read_csv(url, index_col=0, parse_dates=True).squeeze()\n", + "\n", + "availability_profile.loc[\"2015-01-01\"].plot(ylim=(0, 1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then, we can add a link for the electric vehicle charger using assumption about the number of EVs and their charging rates." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:17.957383Z", + "start_time": "2024-05-24T07:42:17.952895Z" + } + }, + "outputs": [], + "source": [ + "number_cars = 40e6 # number of EV cars\n", + "bev_charger_rate = 0.011 # 3-phase EV charger with 11 kW\n", + "p_nom = number_cars * bev_charger_rate\n", + "\n", + "n.add(\n", + " \"Link\",\n", + " \"EV charger\",\n", + " bus0=\"electricity\",\n", + " bus1=\"EV\",\n", + " p_nom=p_nom,\n", + " carrier=\"EV charger\",\n", + " p_max_pu=availability_profile,\n", + " efficiency=0.9,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also allow vehicle-to-grid operation (i.e. electric vehicles inject power into the grid):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:17.962277Z", + "start_time": "2024-05-24T07:42:17.957959Z" + } + }, + "outputs": [], + "source": [ + "n.add(\n", + " \"Link\",\n", + " \"V2G\",\n", + " bus0=\"EV\",\n", + " bus1=\"electricity\",\n", + " p_nom=p_nom,\n", + " carrier=\"V2G\",\n", + " p_max_pu=availability_profile,\n", + " efficiency=0.9,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The demand-side management potential we model as a store. This is not unlike a battery storage, but we impose additional constraints on when the store needs to be charged to a certain level (e.g. 75% full every morning)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:18.398795Z", + "start_time": "2024-05-24T07:42:17.965559Z" + } + }, + "outputs": [], + "source": [ + "bev_energy = 0.05 # average battery size of EV in MWh\n", + "bev_dsm_participants = 0.5 # share of cars that do smart charging\n", + "\n", + "e_nom = number_cars * bev_energy * bev_dsm_participants\n", + "\n", + "url = \"https://tubcloud.tu-berlin.de/s/K62yACBRTrxLTia/download/dsm-profile-example.csv\"\n", + "dsm_profile = (\n", + " pd.read_csv(url, index_col=0, parse_dates=True).squeeze().shift(2, fill_value=0)\n", + ")\n", + "\n", + "dsm_profile.loc[\"2015-01-01\"].plot(figsize=(5, 2), ylim=(0, 1))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:42:18.508811Z", + "start_time": "2024-05-24T07:42:18.504935Z" + } + }, + "outputs": [], + "source": [ + "n.add(\n", + " \"Store\",\n", + " \"EV DSM\",\n", + " bus=\"EV\",\n", + " carrier=\"EV battery\",\n", + " e_cyclic=True, # state of charge at beginning = state of charge at the end\n", + " e_nom=e_nom,\n", + " e_min_pu=dsm_profile.loc[n.snapshots],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then, we can solve the fully sector-coupled model altogether including electricity, passenger transport, hydrogen and heating." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:43:36.334002Z", + "start_time": "2024-05-24T07:42:18.509542Z" + }, + "tags": [ + "hide-output" + ] + }, + "outputs": [], + "source": [ + "n.optimize(log_to_console=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:43:36.477107Z", + "start_time": "2024-05-24T07:43:36.335213Z" + } + }, + "outputs": [], + "source": [ + "n.statistics()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T07:43:36.739941Z", + "start_time": "2024-05-24T07:43:36.478646Z" + } + }, + "outputs": [], + "source": [ + "n.statistics.capex().div(1e9).sort_values().dropna().plot.bar(\n", + " ylabel=\"bn€/a\", cmap=\"tab20c\", figsize=(7, 3)\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/PyPSA/source/docs/examples/simple-electricity-market-examples.ipynb b/PyPSA/source/docs/examples/simple-electricity-market-examples.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..753398a63941836a0bf8539433d6e589d07b073a --- /dev/null +++ b/PyPSA/source/docs/examples/simple-electricity-market-examples.ipynb @@ -0,0 +1,717 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Electricity Markets\n", + "\n", + "This example gradually builds up more and more complicated energy-only electricity markets in PyPSA, starting from a single bidding zone, going up to multiple bidding zones connected with transmission (NTCs) along with variable renewables and storage." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Preliminaries\n", + "\n", + "Here libraries are imported and data is defined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "import pypsa" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# marginal costs in EUR/MWh\n", + "marginal_costs = {\"Wind\": 0, \"Hydro\": 0, \"Coal\": 30, \"Gas\": 60, \"Oil\": 80}\n", + "\n", + "# power plant capacities (nominal powers in MW) in each country (not necessarily realistic)\n", + "power_plant_p_nom = {\n", + " \"South Africa\": {\"Coal\": 35000, \"Wind\": 3000, \"Gas\": 8000, \"Oil\": 2000},\n", + " \"Mozambique\": {\n", + " \"Hydro\": 1200,\n", + " },\n", + " \"Eswatini\": {\n", + " \"Hydro\": 600,\n", + " },\n", + "}\n", + "\n", + "# transmission capacities in MW (not necessarily realistic)\n", + "transmission = {\n", + " \"South Africa\": {\"Mozambique\": 500, \"Eswatini\": 250},\n", + " \"Mozambique\": {\"Eswatini\": 100},\n", + "}\n", + "\n", + "# country electrical loads in MW (not necessarily realistic)\n", + "loads = {\"South Africa\": 42000, \"Mozambique\": 650, \"Eswatini\": 250}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Single bidding zone with fixed load, one period\n", + "\n", + "In this example we consider a single market bidding zone, South Africa.\n", + "\n", + "The inelastic load has essentially infinite marginal utility (or higher than the marginal cost of any generator)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "country = \"South Africa\"\n", + "\n", + "n = pypsa.Network()\n", + "\n", + "n.add(\"Bus\", country)\n", + "\n", + "for tech in power_plant_p_nom[country]:\n", + " n.add(\n", + " \"Generator\",\n", + " f\"{country} {tech}\",\n", + " bus=country,\n", + " p_nom=power_plant_p_nom[country][tech],\n", + " marginal_cost=marginal_costs[tech],\n", + " )\n", + "\n", + "\n", + "n.add(\"Load\", f\"{country} load\", bus=country, p_set=loads[country]);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run optimisation to determine market dispatch:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Print the load active power (P) consumption:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.loads_t.p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Print the generator active power (P) dispatch:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators_t.p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Print the clearing price, which corresponds to gas:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.marginal_price" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Two bidding zones connected by transmission, one period\n", + "\n", + "In this example we have bidirectional lossless transmission capacity between two bidding zones. The power transfer is treated as controllable (like an A/NTC (Available/Net Transfer Capacity) or HVDC line). Note that in the physical grid, power would flow passively according to the network impedances." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n = pypsa.Network()\n", + "\n", + "countries = [\"Mozambique\", \"South Africa\"]\n", + "\n", + "for country in countries:\n", + " n.add(\"Bus\", country)\n", + "\n", + " for tech in power_plant_p_nom[country]:\n", + " n.add(\n", + " \"Generator\",\n", + " f\"{country} {tech}\",\n", + " bus=country,\n", + " p_nom=power_plant_p_nom[country][tech],\n", + " marginal_cost=marginal_costs[tech],\n", + " )\n", + "\n", + " n.add(\"Load\", f\"{country} load\", bus=country, p_set=loads[country])\n", + "\n", + " # add transmission as controllable Link\n", + " if country not in transmission:\n", + " continue\n", + "\n", + " for other_country in countries:\n", + " if other_country not in transmission[country]:\n", + " continue\n", + "\n", + " # NB: Link is by default unidirectional, so have to set p_min_pu = -1\n", + " # to allow bidirectional (i.e. also negative) flow\n", + " n.add(\n", + " \"Link\",\n", + " f\"{country} - {other_country} link\",\n", + " bus0=country,\n", + " bus1=other_country,\n", + " p_nom=transmission[country][other_country],\n", + " p_min_pu=-1,\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.loads_t.p" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators_t.p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The clearing price corresponds to hydro in Mozambique and gas in South Africa." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.marginal_price" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links_t.p0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The shadow prices of the links measure the inframarginal rent of the link, i.e. the difference between the marginal price of the two bidding zones." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links_t.mu_lower" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Three bidding zones connected by transmission, one period\n", + "\n", + "In this example we have bidirectional lossless transmission capacity between three bidding zones. The power transfer is treated as controllable (like an A/NTC (Available/Net Transfer Capacity) or HVDC line). Note that in the physical grid, power would flow passively according to the network impedances." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n = pypsa.Network()\n", + "\n", + "countries = [\"Eswatini\", \"Mozambique\", \"South Africa\"]\n", + "\n", + "for country in countries:\n", + " n.add(\"Bus\", country)\n", + "\n", + " for tech in power_plant_p_nom[country]:\n", + " n.add(\n", + " \"Generator\",\n", + " f\"{country} {tech}\",\n", + " bus=country,\n", + " p_nom=power_plant_p_nom[country][tech],\n", + " marginal_cost=marginal_costs[tech],\n", + " )\n", + "\n", + " n.add(\"Load\", f\"{country} load\", bus=country, p_set=loads[country])\n", + "\n", + " if country not in transmission:\n", + " continue\n", + "\n", + " for other_country in countries:\n", + " if other_country not in transmission[country]:\n", + " continue\n", + "\n", + " n.add(\n", + " \"Link\",\n", + " f\"{country} - {other_country} link\",\n", + " bus0=country,\n", + " bus1=other_country,\n", + " p_nom=transmission[country][other_country],\n", + " p_min_pu=-1,\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.loads_t.p" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators_t.p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The clearing prices correspond to hydro in Eswatini and Mozambique, and gas in South Africa." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.marginal_price" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links_t.p0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.links_t.mu_lower" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Single bidding zone with price-sensitive industrial load, one period\n", + "\n", + "In this example we consider a single market bidding zone, South Africa.\n", + "\n", + "Now there is a large industrial load with a marginal utility which is low enough to interact with the generation marginal cost. See also the [demand elasticity example]()." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "country = \"South Africa\"\n", + "\n", + "n = pypsa.Network()\n", + "\n", + "n.add(\"Bus\", country)\n", + "\n", + "for tech in power_plant_p_nom[country]:\n", + " n.add(\n", + " \"Generator\",\n", + " f\"{country} {tech}\",\n", + " bus=country,\n", + " p_nom=power_plant_p_nom[country][tech],\n", + " marginal_cost=marginal_costs[tech],\n", + " )\n", + "\n", + "# standard high marginal utility consumers\n", + "n.add(\"Load\", f\"{country} load\", bus=country, p_set=loads[country])\n", + "\n", + "# add an industrial load as a negative-dispatch generator with marginal utility of 70 EUR/MWh for 8000 MW\n", + "n.add(\n", + " \"Generator\",\n", + " f\"{country} industrial load\",\n", + " bus=country,\n", + " p_max_pu=0,\n", + " p_min_pu=-1,\n", + " p_nom=8000,\n", + " marginal_cost=70,\n", + ");" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.loads_t.p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A look at the generator dispatch shows that only half of industrial load is served, because this maxes out gas; oil is too expensive with a marginal cost of 80 EUR/MWh compared to the industrial load marginal utility of 70 EUR/MWh." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators_t.p" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.marginal_price" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Single bidding zone with fixed load, several periods\n", + "\n", + "In this example we consider a single market bidding zone, South Africa.\n", + "\n", + "We consider multiple time periods (labelled [0,1,2,3]) to represent variable wind generation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "country = \"South Africa\"\n", + "\n", + "n = pypsa.Network()\n", + "\n", + "n.set_snapshots(range(4))\n", + "\n", + "n.add(\"Bus\", country)\n", + "\n", + "# availability (p_max_pu) is variable for wind\n", + "for tech in power_plant_p_nom[country]:\n", + " n.add(\n", + " \"Generator\",\n", + " f\"{country} {tech}\",\n", + " bus=country,\n", + " p_nom=power_plant_p_nom[country][tech],\n", + " marginal_cost=marginal_costs[tech],\n", + " p_max_pu=([0.3, 0.6, 0.4, 0.5] if tech == \"Wind\" else 1),\n", + " )\n", + "\n", + "# load which varies over the snapshots\n", + "n.add(\n", + " \"Load\",\n", + " f\"{country} load\",\n", + " bus=country,\n", + " p_set=loads[country] + np.array([0, 1000, 3000, 4000]),\n", + ");" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.loads_t.p" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators_t.p" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.marginal_price" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Single bidding zone with fixed load and storage, several periods\n", + "\n", + "In this example we consider a single market bidding zone, South Africa.\n", + "\n", + "We consider multiple time periods (labelled [0,1,2,3]) to represent variable wind generation. Storage is allowed to do price arbitrage to reduce oil consumption." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "country = \"South Africa\"\n", + "\n", + "n = pypsa.Network()\n", + "\n", + "# snapshots labelled by [0,1,2,3]\n", + "n.set_snapshots(range(4))\n", + "\n", + "n.add(\"Bus\", country)\n", + "\n", + "# p_max_pu is variable for wind\n", + "for tech in power_plant_p_nom[country]:\n", + " n.add(\n", + " \"Generator\",\n", + " f\"{country} {tech}\",\n", + " bus=country,\n", + " p_nom=power_plant_p_nom[country][tech],\n", + " marginal_cost=marginal_costs[tech],\n", + " p_max_pu=([0.3, 0.6, 0.4, 0.5] if tech == \"Wind\" else 1),\n", + " )\n", + "\n", + "# load which varies over the snapshots\n", + "n.add(\n", + " \"Load\",\n", + " f\"{country} load\",\n", + " bus=country,\n", + " p_set=loads[country] + np.array([0, 1000, 3000, 4000]),\n", + ")\n", + "\n", + "# storage unit to do price arbitrage\n", + "n.add(\n", + " \"StorageUnit\",\n", + " f\"{country} pumped hydro\",\n", + " bus=country,\n", + " p_nom=1000,\n", + " max_hours=6, # energy storage in terms of hours at full power\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.loads_t.p" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators_t.p" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.storage_units_t.p" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.storage_units_t.state_of_charge" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.buses_t.marginal_price" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.5" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/PyPSA/source/docs/examples/stochastic-optimization.ipynb b/PyPSA/source/docs/examples/stochastic-optimization.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..0ba51ac7e7b9a38f7e63880733d134f7a7e54f91 --- /dev/null +++ b/PyPSA/source/docs/examples/stochastic-optimization.ipynb @@ -0,0 +1,539 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "78d307d1", + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "ff11c8e4", + "metadata": {}, + "source": [ + "# Stochastic Optimization\n", + "\n", + "This example demonstrates principles of stochastic optimization for energy system planning under uncertainty and their implementation in PyPSA. See [User Guide - Stochastic Optimization](https://docs.pypsa.org/latest/user-guide/optimization/stochastic/). We will consider a stylized capacity expansion model with three scenarios with low, medium, and high gas prices. The network will have a single-bus with a constant load and solar, wind, gas and lignite as extendable generators and the following gas price scenarios:\n", + "\n", + "| Scenario | Gas Price | Probability |\n", + "|----------------|-----------|-------------|\n", + "| Low | 40 €/MWh | 40% |\n", + "| Medium | 70 €/MWh | 30% |\n", + "| High | 100 €/MWh | 30% |\n" + ] + }, + { + "cell_type": "markdown", + "id": "e0f84861", + "metadata": {}, + "source": [ + "## Import Packages\n", + "\n", + "Let's first import the necessary packages." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d07a7c45", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "\n", + "import pypsa\n", + "from pypsa.common import annuity" + ] + }, + { + "cell_type": "markdown", + "id": "b490a45d", + "metadata": {}, + "source": [ + "## Prepare Input Data\n", + "\n", + "Then, we set a few general parameters, load capacity factor time series and techno-economic data such as investment costs and conversion efficiencies." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a435f1ff", + "metadata": {}, + "outputs": [], + "source": [ + "# Scenario definitions - Gas price uncertainty\n", + "SCENARIOS = [\"low\", \"med\", \"high\"]\n", + "GAS_PRICES = {\"low\": 40, \"med\": 70, \"high\": 100} # EUR/MWh_th\n", + "PROB = {\"low\": 0.4, \"med\": 0.3, \"high\": 0.3} # Scenario probabilities\n", + "BASE = \"low\" # Base scenario for network construction\n", + "\n", + "# System parameters\n", + "FREQ = \"3h\" # Time resolution\n", + "LOAD_MW = 1 # Constant load (MW)\n", + "SOLVER = \"highs\" # Optimization solver\n", + "\n", + "# Time series data URL\n", + "TS_URL = (\n", + " \"https://tubcloud.tu-berlin.de/s/pKttFadrbTKSJKF/download/time-series-lecture-2.csv\"\n", + ")\n", + "\n", + "# Load and process time series data\n", + "ts = pd.read_csv(TS_URL, index_col=0, parse_dates=True)\n", + "ts = ts.resample(FREQ).asfreq() # Resample to 3-hour resolution\n", + "\n", + "\n", + "# Technology data: investment costs, efficiencies, marginal costs\n", + "TECH = {\n", + " \"solar\": {\"profile\": \"solar\", \"inv\": 1e6, \"m_cost\": 0.01},\n", + " \"wind\": {\"profile\": \"onwind\", \"inv\": 2e6, \"m_cost\": 0.02},\n", + " \"gas\": {\"inv\": 7e5, \"eff\": 0.6},\n", + " \"lignite\": {\"inv\": 1.3e6, \"eff\": 0.4, \"m_cost\": 130},\n", + "}\n", + "\n", + "# Financial parameters\n", + "FOM, DR, LIFE = 3.0, 0.03, 25 # Fixed O&M (%), discount rate, lifetime (years)\n", + "\n", + "# Calculate annualized capital costs\n", + "for cfg in TECH.values():\n", + " cfg[\"fixed_cost\"] = (annuity(DR, LIFE) + FOM / 100) * cfg[\"inv\"]\n", + "\n", + "\n", + "COLOR_MAP = {\n", + " \"solar\": \"gold\",\n", + " \"wind\": \"skyblue\",\n", + " \"gas\": \"brown\",\n", + " \"lignite\": \"black\",\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "c909afb8", + "metadata": {}, + "source": [ + "## Create PyPSA Network\n", + "\n", + "Next, we build the PyPSA network from the provided input data. We wrap it in a function for easy reuse for different scenarios." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a51a3e11", + "metadata": {}, + "outputs": [], + "source": [ + "def build_network(gas_price: float) -> pypsa.Network:\n", + " n = pypsa.Network()\n", + " n.set_snapshots(ts.index)\n", + " n.snapshot_weightings = pd.Series(int(FREQ[:-1]), index=ts.index) # 3-hour weights\n", + "\n", + " # Add bus and load\n", + " n.add(\"Bus\", \"DE\")\n", + " n.add(\"Load\", \"DE_load\", bus=\"DE\", p_set=LOAD_MW)\n", + "\n", + " # Add renewable generators (variable renewable energy)\n", + " for tech in [\"solar\", \"wind\"]:\n", + " cfg = TECH[tech]\n", + " n.add(\n", + " \"Generator\",\n", + " tech,\n", + " bus=\"DE\",\n", + " p_nom_extendable=True,\n", + " p_max_pu=ts[cfg[\"profile\"]], # Renewable availability profile\n", + " capital_cost=cfg[\"fixed_cost\"],\n", + " marginal_cost=cfg[\"m_cost\"],\n", + " )\n", + "\n", + " # Add conventional generators (dispatchable)\n", + " for tech in [\"gas\", \"lignite\"]:\n", + " cfg = TECH[tech]\n", + " # Gas marginal cost depends on gas price and efficiency\n", + " mc = (gas_price / cfg.get(\"eff\")) if tech == \"gas\" else cfg[\"m_cost\"]\n", + " n.add(\n", + " \"Generator\",\n", + " tech,\n", + " bus=\"DE\",\n", + " p_nom_extendable=True,\n", + " efficiency=cfg.get(\"eff\"),\n", + " capital_cost=cfg[\"fixed_cost\"],\n", + " marginal_cost=mc,\n", + " )\n", + " return n" + ] + }, + { + "cell_type": "markdown", + "id": "b29ed3d7", + "metadata": {}, + "source": [ + "## Deterministic Optimization\n", + "\n", + "Initially, let's solve separate deterministic optimization problems for each gas price scenario. This represents the approach where we assume perfect foresight on the uncertain parameter. That means we solve the problem for each scenario independently." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7eccfc50", + "metadata": {}, + "outputs": [], + "source": [ + "caps_det = pd.DataFrame(index=SCENARIOS, columns=TECH.keys())\n", + "objs_det = pd.Series(index=SCENARIOS)\n", + "\n", + "for sc in SCENARIOS:\n", + " n = build_network(GAS_PRICES[sc])\n", + " n.optimize(solver_name=SOLVER, log_to_console=False)\n", + " caps_det.loc[sc] = n.generators.p_nom_opt\n", + " objs_det.loc[sc] = n.objective" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a84c3ef8", + "metadata": {}, + "outputs": [], + "source": [ + "# Visualize deterministic results\n", + "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))\n", + "\n", + "colors = [COLOR_MAP.get(c, \"gray\") for c in caps_det.columns]\n", + "caps_det.plot(kind=\"bar\", stacked=True, ax=ax1, color=colors)\n", + "ax1.set_title(\"Deterministic Capacity Mix by Scenario\")\n", + "ax1.set_ylabel(\"Capacity (MW)\")\n", + "ax1.legend(loc=\"upper left\")\n", + "\n", + "(objs_det / 1e6).plot(kind=\"bar\", ax=ax2, color=\"steelblue\")\n", + "ax2.set_title(\"Deterministic Total Costs by Scenario\")\n", + "ax2.set_ylabel(\"Total Cost (M€/year)\")" + ] + }, + { + "cell_type": "markdown", + "id": "60656ffd", + "metadata": {}, + "source": [ + "We observe that higher gas prices lead to more renewable capacity expansion and less gas capacity. As gas prices increase, total system costs rise." + ] + }, + { + "cell_type": "markdown", + "id": "e6f73aff", + "metadata": {}, + "source": [ + "## Stochastic Optimization\n", + "\n", + "The key to stochastic optimization in PyPSA is the `n.set_scenarios()` method.\n", + "This method transforms a regular PyPSA network into a stochastic network by\n", + "**adding scenario dimensions** to all component data which allows specifying\n", + "scenario-specific parameters. Once scenarios are set, PyPSA treats investment\n", + "decisions as **first-stage variables** that must be the same across all\n", + "scenarios, while dispatch decisions become **second-stage variables** that can\n", + "differ by scenario.\n", + "\n", + "We start by building a deterministic network for our (arbitrarily selected) base scenario.\n", + "Subsequently, we add our scenarios to the network." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "809efd37", + "metadata": {}, + "outputs": [], + "source": [ + "n_stoch = build_network(GAS_PRICES[BASE])\n", + "n_stoch.set_scenarios(PROB)" + ] + }, + { + "cell_type": "markdown", + "id": "d03beea9", + "metadata": {}, + "source": [ + "After that, we can set the scenario-specific parameters, i.e. the varying gas prices by scenario." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3fe27a5", + "metadata": {}, + "outputs": [], + "source": [ + "for sc in SCENARIOS:\n", + " n_stoch.generators.loc[(sc, \"gas\"), \"marginal_cost\"] = (\n", + " GAS_PRICES[sc] / n_stoch.generators.loc[(sc, \"gas\"), \"efficiency\"]\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "7bda08f2", + "metadata": {}, + "source": [ + "Now, we can solve the stochastic optimisation model:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d6056c16", + "metadata": {}, + "outputs": [], + "source": [ + "n_stoch.optimize(solver_name=SOLVER, log_to_console=False)\n", + "\n", + "print(f\"Total expected cost: {n_stoch.objective / 1e6:.3f} M€/year\")\n", + "\n", + "caps_api = n_stoch.generators.p_nom_opt.xs(BASE, level=\"scenario\")\n", + "obj_api = n_stoch.objective" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4996f9e7", + "metadata": {}, + "outputs": [], + "source": [ + "caps_comparison = caps_det.copy()\n", + "caps_comparison.loc[\"Stochastic\"] = caps_api\n", + "\n", + "objs_comparison = objs_det.copy()\n", + "objs_comparison.loc[\"Stochastic\"] = obj_api\n", + "\n", + "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\n", + "\n", + "colors = [COLOR_MAP.get(c, \"gray\") for c in caps_comparison.columns]\n", + "caps_comparison.plot(kind=\"bar\", stacked=True, ax=ax1, color=colors)\n", + "ax1.set_title(\"Capacity Mix: Deterministic vs. Stochastic\")\n", + "ax1.set_ylabel(\"Capacity (MW)\")\n", + "ax1.legend(loc=\"upper left\")\n", + "\n", + "(objs_comparison / 1e6).plot(kind=\"bar\", ax=ax2, color=\"steelblue\")\n", + "ax2.set_title(\"Total Cost: Deterministic vs. Stochastic\")\n", + "ax2.set_ylabel(\"Total Cost (M€/year)\")" + ] + }, + { + "cell_type": "markdown", + "id": "9cd0b7d3", + "metadata": {}, + "source": [ + "## Value of Information Metrics\n", + "\n", + "We can also now calculate the Value of Information (VoI) metrics, which include the Expected Value of Perfect Information (EVPI) and the Value of Stochastic Solution (VSS). These metrics help us understand the benefits of incorporating uncertainty into our optimization model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5abcb081", + "metadata": {}, + "outputs": [], + "source": [ + "# Wait-and-See (perfect information) expected cost\n", + "ws_cost = sum(objs_det[sc] * PROB[sc] for sc in SCENARIOS)\n", + "print(f\"Wait-and-See (WS) cost: {ws_cost / 1e6:.3f} M€/year\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96b4dfa6", + "metadata": {}, + "outputs": [], + "source": [ + "# Expected Value of Perfect Information\n", + "evpi = obj_api - ws_cost\n", + "print(f\"EVPI (SP - WS): {evpi / 1e6:.3f} M€/year\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ab4a725", + "metadata": {}, + "outputs": [], + "source": [ + "# Expected gas price\n", + "expected_gas_price = sum(GAS_PRICES[sc] * PROB[sc] for sc in SCENARIOS)\n", + "print(f\"Expected gas price: {expected_gas_price:.1f} EUR/MWh_th\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "566eebca", + "metadata": {}, + "outputs": [], + "source": [ + "# Report stochastic programming (SP) cost\n", + "print(f\"Stochastic Prog (SP) cost: {obj_api / 1e6:.3f} M€/year\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7b458f8b", + "metadata": {}, + "outputs": [], + "source": [ + "# Solve deterministic problem with expected gas price (EEV solution)\n", + "n_eev = build_network(expected_gas_price)\n", + "n_eev.optimize(solver_name=SOLVER, log_to_console=False)\n", + "eev_capacities = n_eev.generators.p_nom_opt\n", + "\n", + "# Evaluate EEV capacities under each scenario\n", + "eev_costs = []\n", + "for sc in SCENARIOS:\n", + " n_eval = build_network(GAS_PRICES[sc])\n", + " for tech in TECH.keys():\n", + " n_eval.generators.loc[tech, \"p_nom_max\"] = eev_capacities[tech]\n", + " n_eval.generators.loc[tech, \"p_nom_min\"] = eev_capacities[tech]\n", + " n_eval.optimize(solver_name=SOLVER, log_to_console=False)\n", + " eev_costs.append(n_eval.objective)\n", + "\n", + "eev_expected_cost = sum(eev_costs[i] * PROB[sc] for i, sc in enumerate(SCENARIOS))\n", + "print(f\"Expected Value (EEV) cost: {eev_expected_cost / 1e6:.3f} M€/year\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82347660", + "metadata": {}, + "outputs": [], + "source": [ + "# Value of Stochastic Solution\n", + "vss = eev_expected_cost - obj_api\n", + "print(f\"VSS (EEV - SP): {vss / 1e6:.3f} M€/year\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ffee9ff7", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"\\nTheoretical Ordering Check:\")\n", + "print(f\" WS ≤ SP: {ws_cost <= obj_api} ({ws_cost:.0f} ≤ {obj_api:.0f})\")\n", + "print(\n", + " f\" SP ≤ EEV: {obj_api <= eev_expected_cost} ({obj_api:.0f} ≤ {eev_expected_cost:.0f})\"\n", + ")\n", + "print(f\" EVPI ≥ 0: {evpi >= 0} ({evpi:.0f} ≥ 0)\")\n", + "print(f\" VSS ≥ 0: {vss >= 0} ({vss:.0f} ≥ 0)\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cdb86eed", + "metadata": {}, + "outputs": [], + "source": [ + "costs_voi = pd.Series(\n", + " {\n", + " \"Wait-and-See\\n(Perfect Info)\": ws_cost,\n", + " \"Stochastic\\nProgramming\": obj_api,\n", + " \"Expected Value\\n(Ignore Uncertainty)\": eev_expected_cost,\n", + " }\n", + ")\n", + "\n", + "\n", + "fig, ax1 = plt.subplots(figsize=(7, 5))\n", + "\n", + "# Cost comparison\n", + "(costs_voi / 1e6).plot(kind=\"bar\", ax=ax1, color=[\"green\", \"blue\", \"red\"], alpha=0.7)\n", + "ax1.set_title(\"Value of Information: Cost Comparison\", fontsize=14, fontweight=\"bold\")\n", + "ax1.set_ylabel(\"Total Cost (M€/year)\")\n", + "ax1.tick_params(axis=\"x\", rotation=45)\n", + "ax1.grid(True, alpha=0.3)\n", + "\n", + "# Add EVPI and VSS arrows\n", + "ax1.annotate(\n", + " \"\",\n", + " xy=(0, ws_cost / 1e6),\n", + " xytext=(1, obj_api / 1e6),\n", + " arrowprops={\"arrowstyle\": \"<->\", \"color\": \"purple\", \"lw\": 2},\n", + ")\n", + "ax1.text(\n", + " 0.5,\n", + " (ws_cost + obj_api) / (2 * 1e6),\n", + " \"EVPI\",\n", + " color=\"purple\",\n", + " fontweight=\"bold\",\n", + " fontsize=10,\n", + " ha=\"center\",\n", + ")\n", + "\n", + "ax1.annotate(\n", + " \"\",\n", + " xy=(1, obj_api / 1e6),\n", + " xytext=(2, eev_expected_cost / 1e6),\n", + " arrowprops={\"arrowstyle\": \"<->\", \"color\": \"orange\", \"lw\": 2},\n", + ")\n", + "ax1.text(\n", + " 1.5,\n", + " (eev_expected_cost + obj_api) / (2 * 1e6),\n", + " \"VSS\",\n", + " color=\"orange\",\n", + " fontweight=\"bold\",\n", + " fontsize=10,\n", + " ha=\"center\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "bc1b508f", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "[1] Birge, J. R., & Louveaux, F. (2011). [**Introduction to Stochastic Programming**](https://link.springer.com/book/10.1007/978-1-4614-0237-4).\n", + "\n", + "[2] Birge, J. R. (1982). [**The value of the stochastic solution in stochastic linear programs with fixed recourse.**](https://deepblue.lib.umich.edu/bitstream/handle/2027.42/47912/10107_2005_Article_BF01585113.pdf) *Mathematical Programming*, 24(1), 314-325." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/examples/time-series-aggregation.ipynb b/PyPSA/source/docs/examples/time-series-aggregation.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..7ba12854e72a1c037a27ecc40c13279c6d248a7c --- /dev/null +++ b/PyPSA/source/docs/examples/time-series-aggregation.ipynb @@ -0,0 +1,389 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "28b5f8f8", + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "596305f2", + "metadata": {}, + "source": [ + "# Time Series Aggregation\n", + "\n", + "In this example, we are going to explore different ways to cluster the temporal resolution of PyPSA models, and what impact they have on the optimisation results and solving times. Using an hourly resolved variant of the [single-node capacity expansion example](), we will compare three different approaches to reduce the number of time steps in the model:\n", + "\n", + "- **Sampling**: Selecting a subset of the given snapshots based on a given frequency,\n", + "- **Averaging**: Aggregating the snapshots by averaging them over a given frequency, and\n", + "- **Segmentation**: Clustering the snapshots into segments of a given frequency and using [`tsam`](https://tsam.readthedocs.io/en/latest/) library.\n", + "\n", + "We start with the usual imports and loading the hourly resolved model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef920172", + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "import time\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import tsam.timeseriesaggregation as tsam\n", + "\n", + "import pypsa\n", + "\n", + "logging.getLogger().setLevel(logging.WARNING)\n", + "\n", + "SOLVER = \"highs\" # or \"gurobi\"\n", + "\n", + "template_n = pypsa.Network(\n", + " \"https://tubcloud.tu-berlin.de/s/4ra3NKrLGzE42of/download/model-energy-hourly.nc\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "039bcabc", + "metadata": {}, + "source": [ + "Since we also want to monitor the solving times, we will use a small utility function that wraps around the solving process and returns the seconds it took to solve the model:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c55e99fc", + "metadata": {}, + "outputs": [], + "source": [ + "def time_it(func, *args, **kwargs):\n", + " \"\"\"Time the execution of a function and return the elapsed time in seconds.\"\"\"\n", + " start_time = time.time()\n", + " result = func(*args, **kwargs)\n", + " elapsed_time = time.time() - start_time\n", + " return result, elapsed_time\n", + "\n", + "\n", + "logging.getLogger().setLevel(logging.WARNING)" + ] + }, + { + "cell_type": "markdown", + "id": "1a975e1e", + "metadata": {}, + "source": [ + "## Hourly Baseline\n", + "\n", + "Additionally, we need a baseline model to compare aggregated models against. We will use the hourly resolved model for this." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e60e54d0", + "metadata": {}, + "outputs": [], + "source": [ + "n_hourly = template_n.copy()\n", + "\n", + "_, s_hourly = time_it(n_hourly.optimize, solver_name=SOLVER, log_to_console=False)\n", + "s_hourly" + ] + }, + { + "cell_type": "markdown", + "id": "ad7526bc", + "metadata": {}, + "source": [ + "## Sampling\n", + "\n", + "We will start with the sampling approach, which is the simplest one. We simply select every $N$-th snapshot from the model. The important part here is that we need to adjust the snapshot weightings accordingly, as each remaining snapshot now represents $N$ hours. We iterate over N from 2 to 11, i.e. from 2-hourly to 11-hourly resolved models." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a019dc4", + "metadata": {}, + "outputs": [], + "source": [ + "sampling_n = {1: n_hourly}\n", + "sampling_s = {1: s_hourly}\n", + "\n", + "for resolution in range(2, 12):\n", + " n = template_n.copy()\n", + "\n", + " # set the sampled snapshots (time series are automatically reduced)\n", + " n.set_snapshots(n.snapshots[::resolution])\n", + " n.snapshot_weightings.loc[:, :] = resolution\n", + "\n", + " _, s = time_it(n.optimize, solver_name=SOLVER, log_to_console=False)\n", + "\n", + " sampling_n[resolution] = n\n", + " sampling_s[resolution] = s" + ] + }, + { + "cell_type": "markdown", + "id": "7767da0f", + "metadata": {}, + "source": [ + "## Averaging\n", + "\n", + "The averaging approach also has equal snapshot durations, but instead of selecting data from every $N$-th snapshots, we average time series data for every $N$ snapshots. This means that the resulting model has $N$ times fewer snapshots, but each snapshot represents the average of $N$ original snapshots. Again, we need to adjust the snapshot weightings accordingly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1589ff92", + "metadata": {}, + "outputs": [], + "source": [ + "averaging_n = {1: n_hourly}\n", + "averaging_s = {1: s_hourly}\n", + "\n", + "for resolution in range(2, 12):\n", + " n = template_n.copy()\n", + "\n", + " # resample the time series data by averaging\n", + " n.loads_t.p_set = n.loads_t.p_set.resample(f\"{resolution}h\").mean()\n", + " n.generators_t.p_max_pu = n.generators_t.p_max_pu.resample(f\"{resolution}h\").mean()\n", + "\n", + " # set the new snapshtos and adjusted snapshot weightings\n", + " n.set_snapshots(n.snapshots[::resolution])\n", + " n.snapshot_weightings.loc[:, :] = resolution\n", + "\n", + " _, s = time_it(n.optimize, solver_name=SOLVER, log_to_console=False)\n", + "\n", + " averaging_n[resolution] = n\n", + " averaging_s[resolution] = s" + ] + }, + { + "cell_type": "markdown", + "id": "3a6677b7", + "metadata": {}, + "source": [ + "## Segmentation\n", + "\n", + "The segmentation approach is more complex. It uses a separate library called [`tsam`](https://tsam.readthedocs.io/en/latest/) to cluster the snapshots into segments of varying lengths. The sequence of snapshots is preserved as segments are only formed from neighbouring snapshots based on their similarity. For measuring similarity, it is advisable to normalise the time series data. This approach promises to capture the temporal patterns more effectively, as it can opt for higher resolution during periods of high variability and lower resolution during periods with low variability. The snapshot weightings are adjusted based on the number of snapshots in each segment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46398e54", + "metadata": {}, + "outputs": [], + "source": [ + "segmentation_n = {1: n_hourly}\n", + "segmentation_s = {1: s_hourly}\n", + "\n", + "for resolution in range(2, 12):\n", + " n = template_n.copy()\n", + "\n", + " # calculate number of segments equivalent to resolution\n", + " segments = int(8760 / resolution)\n", + "\n", + " # concatenate and normalize all time series with min-max normalization\n", + " df = pd.concat([n.generators_t.p_max_pu, n.loads_t.p_set], axis=1)\n", + " df_norm = (df - df.min()) / (df.max() - df.min())\n", + "\n", + " # use `tsam` to run segmentation clustering algorithm\n", + " agg = tsam.TimeSeriesAggregation(\n", + " df_norm,\n", + " hoursPerPeriod=len(df_norm),\n", + " noTypicalPeriods=1,\n", + " noSegments=segments,\n", + " segmentation=True,\n", + " solver=SOLVER,\n", + " )\n", + " agg = agg.createTypicalPeriods()\n", + "\n", + " # translate segments into time stamps and calculate new weightings\n", + " weightings = agg.index.get_level_values(\"Segment Duration\")\n", + " offsets = np.insert(np.cumsum(weightings[:-1]), 0, 0)\n", + " weightings = n.snapshot_weightings.loc[n.snapshots[offsets]].mul(weightings, axis=0)\n", + "\n", + " # aggregate the hourly time series by averaging over the segments\n", + " mapping = (\n", + " pd.Series(weightings.index, index=weightings.index).reindex(n.snapshots).ffill()\n", + " )\n", + " n.generators_t.p_max_pu = n.generators_t.p_max_pu.groupby(mapping).mean()\n", + " n.loads_t.p_set = n.loads_t.p_set.groupby(mapping).mean()\n", + "\n", + " # set new segmented snapshots and adjust weightings\n", + " n.set_snapshots(weightings.index)\n", + " n.snapshot_weightings = weightings\n", + "\n", + " # run optimization\n", + " _, s = time_it(n.optimize, solver_name=SOLVER, log_to_console=False)\n", + "\n", + " segmentation_n[resolution] = n\n", + " segmentation_s[resolution] = s" + ] + }, + { + "cell_type": "markdown", + "id": "823ce5e4", + "metadata": {}, + "source": [ + "Now before we go ahead with the evaluation of the different approaches, let's quickly glance at the distribution of snapshot durations obtained from the segmentation approach for a resolution equivalent to a 3-hourly model. We can see quite some variability in the segment length." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "523ef52d", + "metadata": {}, + "outputs": [], + "source": [ + "segmentation_n[3].snapshot_weightings.generators.value_counts().sort_index(\n", + " ascending=True\n", + ").plot.bar(ylabel=\"snapshots [number]\", xlabel=\"snapshot duration [h]\")" + ] + }, + { + "cell_type": "markdown", + "id": "dc2e9675", + "metadata": {}, + "source": [ + "## Evaluation\n", + "\n", + "Let's start our evaluation with a look at the solving times. We can see that across all approaches, the solving times quickly decay, especially as we go from hourly to 2-hourly resolved models and decrease less substantially afterwards." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f66f166b", + "metadata": {}, + "outputs": [], + "source": [ + "pd.Series(sampling_s).plot(label=\"sampling\", legend=True)\n", + "pd.Series(averaging_s).plot(label=\"averaging\", legend=True)\n", + "pd.Series(segmentation_s).plot(\n", + " label=\"segmentation\", ylabel=\"time [s]\", xlabel=\"resolution [h]\", legend=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "98287aea", + "metadata": {}, + "source": [ + "Furthermore, we compare the relative error in total system costs compared to the hourly resolved model. We can see how the segmentation approach remains more stable than the other two approaches, especially for lower resolutions. Even with 11-hourly equivalent resolution, the segmentation approach only has a relative error of -1% compared to the hourly resolved model (i.e. it appears to be 1% cheaper). Similar patterns can be observed for the relative error in the total installed capacity of solar and batteries, two technologies that are particularly sensitive to the temporal resolution of the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e4848f91", + "metadata": {}, + "outputs": [], + "source": [ + "def tsc(n):\n", + " return (n.statistics.capex().sum() + n.statistics.opex().sum()) / 1e9\n", + "\n", + "\n", + "pd.concat(\n", + " [\n", + " pd.Series({(\"sampling\", res): tsc(n) for res, n in sampling_n.items()}),\n", + " pd.Series({(\"averaging\", res): tsc(n) for res, n in averaging_n.items()}),\n", + " pd.Series({(\"segmentation\", res): tsc(n) for res, n in segmentation_n.items()}),\n", + " ]\n", + ").unstack(0).div(tsc(n_hourly)).sub(1).mul(100).plot(\n", + " ylabel=\"relative objective error [%]\", xlabel=\"resolution [h]\", legend=True\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "833a6780", + "metadata": {}, + "outputs": [], + "source": [ + "def solar(n):\n", + " return n.generators.loc[\"solar\", \"p_nom_opt\"]\n", + "\n", + "\n", + "pd.concat(\n", + " [\n", + " pd.Series({(\"sampling\", res): solar(n) for res, n in sampling_n.items()}),\n", + " pd.Series({(\"averaging\", res): solar(n) for res, n in averaging_n.items()}),\n", + " pd.Series(\n", + " {(\"segmentation\", res): solar(n) for res, n in segmentation_n.items()}\n", + " ),\n", + " ]\n", + ").unstack(0).div(solar(n_hourly)).sub(1).mul(100).plot(\n", + " ylabel=\"relative solar capacity error [%]\", xlabel=\"resolution [h]\", legend=True\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dba10074", + "metadata": {}, + "outputs": [], + "source": [ + "def battery(n):\n", + " return n.storage_units.loc[\"battery storage\", \"p_nom_opt\"]\n", + "\n", + "\n", + "pd.concat(\n", + " [\n", + " pd.Series({(\"sampling\", res): battery(n) for res, n in sampling_n.items()}),\n", + " pd.Series({(\"averaging\", res): battery(n) for res, n in averaging_n.items()}),\n", + " pd.Series(\n", + " {(\"segmentation\", res): battery(n) for res, n in segmentation_n.items()}\n", + " ),\n", + " ]\n", + ").unstack(0).div(battery(n_hourly)).sub(1).mul(100).plot(\n", + " ylabel=\"relative battery capacity error [%]\", xlabel=\"resolution [h]\", legend=True\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/examples/tracing-infeasibilities.ipynb b/PyPSA/source/docs/examples/tracing-infeasibilities.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..c1a4eeff7fbcb45109480c8d06fd50f06fcdc22a --- /dev/null +++ b/PyPSA/source/docs/examples/tracing-infeasibilities.ipynb @@ -0,0 +1,230 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "63dd35f6", + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "1426ac78", + "metadata": {}, + "source": [ + "# Tracing Infeasibilities\n", + "\n", + "This tutorial demonstrates how to identify and trace infeasibilities in PyPSA optimization models using built-in functionality. When an optimization problem becomes infeasible, PyPSA provides tools to help you understand what constraints are conflicting and causing the infeasibility.\n", + "\n", + "In this example, we'll deliberately create an infeasible network and then use PyPSA's built-in methods to diagnose the problem." + ] + }, + { + "cell_type": "markdown", + "id": "ab5c0398", + "metadata": {}, + "source": [ + "## Getting ready\n", + "\n", + "First, let's import PyPSA and load a example network from our example suite that we'll use for demonstration.\n", + "\n", + "We'll use PyPSA's built-in AC-DC meshed example network, which includes:\n", + "- AC transmission lines connecting different regions\n", + "- A DC link providing additional transmission capacity\n", + "- Generators and loads across multiple buses" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35033215", + "metadata": {}, + "outputs": [], + "source": [ + "import pypsa\n", + "\n", + "n = pypsa.examples.ac_dc_meshed()\n", + "\n", + "n" + ] + }, + { + "cell_type": "markdown", + "id": "18437def", + "metadata": {}, + "source": [ + "For this demonstration, we'll limit the analysis to just the first snapshot to keep the example simple and focused on the infeasibility tracing concept." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "425f2513", + "metadata": {}, + "outputs": [], + "source": [ + "## Solve only the first period\n", + "n.snapshots = n.snapshots[:1]" + ] + }, + { + "cell_type": "markdown", + "id": "626fee86", + "metadata": {}, + "source": [ + "This network is normally feasible, but let's modify it to create infeasibilities:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "03613350", + "metadata": {}, + "outputs": [], + "source": [ + "# Remove AC transmission lines connecting to London\n", + "n.remove(\"Line\", \"0\") # First AC line to London\n", + "n.remove(\"Line\", \"5\") # Second AC line to London\n", + "\n", + "# Disable the DC link by setting capacity to zero\n", + "n.links.loc[\"DC link\", \"p_nom\"] = 0.0\n", + "n.links.loc[\"DC link\", \"p_nom_extendable\"] = False" + ] + }, + { + "cell_type": "markdown", + "id": "680f05a1", + "metadata": {}, + "source": [ + "An alternative approach would be to remove the DC link entirely, which would also create an infeasible situation (London with load but no connections):\n", + "```python\n", + "n.remove(\"Link\", \"DC link\")\n", + "```\n", + "This would completely remove the DC link from the network. However, when `n.optimize()` is called, PyPSA would detect an \"empty LHS and non-empty RHS\" error during the constraint creation phase (specifically when building nodal balance constraints) and raise a `ValueError` before the optimization model is even passed to the solver." + ] + }, + { + "cell_type": "markdown", + "id": "be6140cf", + "metadata": {}, + "source": [ + "Let's ensure that London has power load but no generators or connections:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "795968a7", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"London load: {n.loads_t.p_set.loc['2015-01-01', 'London']:.1f} MW\")\n", + "print(\n", + " f\"London generation: {n.generators[n.generators.bus == 'London']['p_nom'].sum():.1f} MW\"\n", + ")\n", + "print(\n", + " f\"London AC lines connected: {len(n.lines[(n.lines.bus0 == 'London') | (n.lines.bus1 == 'London')])}\"\n", + ")\n", + "print(f\"London DC link capacity: {n.links.loc['DC link', 'p_nom']:.1f} MW\")\n", + "print(\"→ Infeasible: London has power load (RHS) but no supply options!\")" + ] + }, + { + "cell_type": "markdown", + "id": "e1c72410", + "metadata": {}, + "source": [ + "## Attempting Optimization\n", + "\n", + "Now let's try to optimize this infeasible network. The optimization will fail because London has load but no way to meet it (no local generation and no transmission connections)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1c11519", + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize(solver_name=\"gurobi\")" + ] + }, + { + "cell_type": "markdown", + "id": "d3a8d32d", + "metadata": {}, + "source": [ + "## Tracing the Infeasibility\n", + "\n", + "When the optimization fails due to infeasibility, PyPSA provides a convenient method to diagnose the problem. The `print_infeasibilities()` method on the optimization model will show us exactly which constraints are causing the infeasibility." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4989a457", + "metadata": {}, + "outputs": [], + "source": [ + "n.model.print_infeasibilities()" + ] + }, + { + "cell_type": "markdown", + "id": "ee963ad8", + "metadata": {}, + "source": [ + "### Interpreting the Infeasibility Output\n", + "\n", + "The infeasibility trace above shows us exactly what's wrong:\n", + "\n", + "1. **`Link-fix-p-lower[2015-01-01 00:00:00, DC link]: +1 Link-p[2015-01-01 00:00:00, DC link] ≥ -0`**\n", + " - This constraint says the DC link power flow must be ≥ 0 (can't flow negative)\n", + " - But we set the DC link capacity to 0, so Link-p value is bounded to 0\n", + "\n", + "2. **`Bus-nodal_balance[London, 2015-01-01 00:00:00]: -1 Link-p[2015-01-01 00:00:00, DC link] = 35.7962441027`**\n", + " - This is London's nodal balance equation: generation ± trade ± storage = load\n", + " - London has no generation, 35.8 MW load, and no AC interconnectors\n", + " - The DC link (the only remaining connection) can't provide any power because its capacity is 0\n", + "\n", + "Under the hood, `print_infeasibilities()` uses Gurobi's infeasibility analysis to identify conflicting constraints.\n", + " **Irreducible Inconsistent Set (IIS)** identified by Gurobi shows these two constraints are fundamentally incompatible:\n", + "- London needs power inflow via the DC link to meet its load\n", + "- But the DC link is constrained to zero power\n", + "- This creates an impossible situation that no solution can satisfy" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/examples/transformer-example.ipynb b/PyPSA/source/docs/examples/transformer-example.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..62a003ba4f46e4f11909c1901375aecd36f6e52f --- /dev/null +++ b/PyPSA/source/docs/examples/transformer-example.ipynb @@ -0,0 +1,207 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Transformers\n", + "\n", + "This example illustrates the use of transformers with non-trivial phase shift and tap ratio.\n", + "The example is a copy of the [pandapower minimal example](https://github.com/e2nIEE/pandapower/blob/master/tutorials/minimal_example.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import pypsa\n", + "\n", + "n = pypsa.Network()\n", + "\n", + "n.add(\"Bus\", \"MV bus\", v_nom=20, v_mag_pu_set=1.02)\n", + "n.add(\"Bus\", \"LV1 bus\", v_nom=0.4)\n", + "n.add(\"Bus\", \"LV2 bus\", v_nom=0.4)\n", + "\n", + "n.add(\n", + " \"Transformer\",\n", + " \"MV-LV trafo\",\n", + " type=\"0.4 MVA 20/0.4 kV\",\n", + " bus0=\"MV bus\",\n", + " bus1=\"LV1 bus\",\n", + ")\n", + "n.add(\n", + " \"Line\", \"LV cable\", type=\"NAYY 4x50 SE\", bus0=\"LV1 bus\", bus1=\"LV2 bus\", length=0.1\n", + ")\n", + "n.add(\"Generator\", \"External Grid\", bus=\"MV bus\", control=\"Slack\", marginal_cost=10)\n", + "n.add(\"Load\", \"LV load\", bus=\"LV2 bus\", p_set=0.1, q_set=0.05);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def run_power_flow(n: pypsa.Network) -> pd.DataFrame:\n", + " n.lpf()\n", + " n.pf(use_seed=True)\n", + " return pd.DataFrame(\n", + " {\n", + " \"Voltage Angles\": n.buses_t.v_ang.loc[\"now\"] * 180.0 / np.pi,\n", + " \"Voltage Magnitude\": n.buses_t.v_mag_pu.loc[\"now\"],\n", + " }\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "run_power_flow(n)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.transformers.tap_position = 2\n", + "run_power_flow(n)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.transformers.tap_position = -2\n", + "run_power_flow(n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now play with tap changer on LV side" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "new_trafo_lv_tap = n.transformer_types.loc[[\"0.4 MVA 20/0.4 kV\"]]\n", + "new_trafo_lv_tap.index = [\"New trafo\"]\n", + "new_trafo_lv_tap.tap_side = 1\n", + "new_trafo_lv_tap.T" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.transformer_types = pd.concat([n.transformer_types, new_trafo_lv_tap])\n", + "n.transformers.type = \"New trafo\"\n", + "n.transformers.tap_position = 2\n", + "run_power_flow(n)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.transformers.T" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.transformers.tap_position = -2\n", + "run_power_flow(n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, let's double-check that the phase shift is also there in the linear optimal power flow (optimisation) solution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.generators.p_nom = 1\n", + "n.lines.s_nom = 1\n", + "n.optimize(log_to_console=False)\n", + "pd.DataFrame(\n", + " {\n", + " \"Voltage Angles\": n.buses_t.v_ang.loc[\"now\"] * 180.0 / np.pi,\n", + " \"Voltage Magnitude\": n.buses_t.v_mag_pu.loc[\"now\"],\n", + " }\n", + ")" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/PyPSA/source/docs/examples/uc-prices.ipynb b/PyPSA/source/docs/examples/uc-prices.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..be31152247a15a6709a2463f52954f6b613dc29d --- /dev/null +++ b/PyPSA/source/docs/examples/uc-prices.ipynb @@ -0,0 +1,330 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Negative Prices in Linearized Unit Commitment\n", + "This notebook shows how negative electricity prices can be reproduced with a linearized unit commitment (UC) model. Such prices appear in real markets when generators with start-up costs and minimum generation limits find it more economical to offer electricity at a negative price (effectively paying to stay online) rather than shutting down and restarting later.\n", + "\n", + "### Real-world context\n", + "\n", + "Negative prices are a recurring feature of modern electricity markets. For example, such a situation occurred in Germany (e.g., [Week 15 of 2025](https://www.energy-charts.info/charts/price_spot_market/chart.htm?l=en&c=DE&week=15)), when high renewable output combined with limited flexibility in conventional generation pushed spot prices below zero. This typically happens when:\n", + "\n", + "- Wind and solar generation produce more power than demand in a given period\n", + "- Conventional generators, facing high start-up and shut-down costs, prefer to remain online even at negative prices\n", + "\n", + "In this tutorial, we use PyPSA’s linearized unit commitment formulation to model and explore these dynamics in a simplified system." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Model setup\n", + "\n", + "We model a single-bus system with two generators: one base-load and one peak-load, and a variable load over five time steps. The base-load unit has low marginal costs but high start-up costs and limited flexibility, while the peak unit is smaller, more expensive, and more flexible.\n", + "\n", + "This setup allows negative prices to emerge during low-demand periods due to the trade-off between cycling costs and operating at minimum load." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create the Network" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "import pypsa" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n = pypsa.Network()\n", + "n.snapshots = range(5) # snapshots 0..4 (five periods)\n", + "\n", + "# Add carrier definition\n", + "n.add(\"Carrier\", \"AC\", color=\"lightblue\")\n", + "\n", + "# Add a single bus\n", + "n.add(\"Bus\", \"bus\", carrier=\"AC\")\n", + "\n", + "# Add time-varying load with one low-demand valley (period index 3)\n", + "n.add(\"Load\", \"load\", p_set=[50, 120, 50, 20, 50], bus=\"bus\")\n", + "\n", + "# Base-load generator: cheap marginal cost, inflexible, costly to cycle\n", + "n.add(\n", + " \"Generator\",\n", + " \"base\",\n", + " bus=\"bus\",\n", + " p_nom=100,\n", + " marginal_cost=20,\n", + " p_min_pu=0.4,\n", + " committable=True, # Enable unit commitment\n", + " start_up_cost=4000,\n", + " shut_down_cost=2000,\n", + ")\n", + "\n", + "# Peak generator: flexible but expensive\n", + "n.add(\n", + " \"Generator\",\n", + " \"peak\",\n", + " bus=\"bus\",\n", + " p_nom=50,\n", + " marginal_cost=70,\n", + " p_min_pu=0.2,\n", + " committable=True, # Enable unit commitment\n", + " start_up_cost=250,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Optimize with linearized unit commitment\n", + "\n", + "We enable the linearized UC constraints by setting `linearized_unit_commitment=True` in the `optimize` method:\n", + "\n", + "```python\n", + "n.optimize(linearized_unit_commitment=True)\n", + "```\n", + "\n", + "This activates PyPSA’s linearized UC formulation, which relaxes binary commitment variables to continuous values in [0, 1]. Fractional values (e.g., 0.5) represent partial commitment in the relaxed model and make the problem convex.\n", + "\n", + "Included effects:\n", + "- Start-up and shut-down costs\n", + "- Minimum stable generation (`p_min_pu`)\n", + "- Approximate commitment status and ramping constraints\n", + "\n", + "Compared to the full mixed-integer formulation, the linearized version is more tractable and its dual variables (e.g. nodal prices) remain economically interpretable.\n", + "\n", + "See the documentation: [Linearized Unit Commitment](https://docs.pypsa.org/latest/user-guide/optimization/unit-commitment/#linearization)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n.optimize(\n", + " linearized_unit_commitment=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Results Analysis" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's examine the **nodal prices** (locational marginal prices, LMPs) at each time period. These prices represent the system's marginal cost of serving one additional MW of demand." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "prices = n.buses_t.marginal_price\n", + "prices" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that during the low-demand snapshot, the model can produce a negative price. This indicates that the system would reduce its total cost if an additional MWh was consumed at that moment. This is a direct outcome of unit-commitment constraints and limited operational flexibility." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's look at the actual power output from each generator:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dispatch = n.generators_t.p\n", + "dispatch" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The **commitment status** indicates whether a unit is online (1) or offline (0).\n", + "\n", + "Note that **mixed-integer UC formulation** (with binary variables for start-up/down) is non-convex. Dual variables from a mixed-integer program are not strictly interpretable for the original problem. Any duals a solver reports pertain to the LP relaxation of the branch-and-bound nodes, not to the final integer solution, so they cannot be treated as market prices.\n", + "\n", + "**Linearized/relaxed UC** replaces the binary commitment with a continuous variable in [0,1], yielding a convex LP. In this setting, strong duality holds and the dual of the power balance constraint is a well-defined shadow price (the marginal value of 1 MWh). The fractional commitment technique is a modeling workaround that provides consistent marginal cost signals and allows direct interpretation of dual variables." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "status = n.generators_t.status\n", + "status" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "summary = pd.DataFrame(\n", + " {\n", + " \"Load (MW)\": n.loads_t.p_set[\"load\"].values,\n", + " \"Base Gen (MW)\": n.generators_t.p[\"base\"].values,\n", + " \"Peak Gen (MW)\": n.generators_t.p[\"peak\"].values,\n", + " \"Total Gen (MW)\": n.generators_t.p.sum(axis=1).values,\n", + " \"Base Status\": n.generators_t.status[\"base\"].values,\n", + " \"Peak Status\": n.generators_t.status[\"peak\"].values,\n", + " \"Price (€/MWh)\": n.buses_t.marginal_price[\"bus\"].values,\n", + " }\n", + ")\n", + "summary.index.name = \"Time Period\"\n", + "summary" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Understanding the negative price\n", + "\n", + "At the low-demand period, the model produces a negative price. This occurs because keeping the base generator online is cheaper than cycling it off and on:\n", + "\n", + "- Cycling cost: 6,000 € (4,000 € start-up + 2,000 € shut-down) \n", + "- Operational cost over the low-demand window: energy produced × marginal cost (with the parameters above: 120 MWh × 20 €/MWh = 2,400 €) \n", + "\n", + "Since 2,400 € is lower than the 6,000 € cycling cost, the model finds it more economical to keep the base generator running through the low-demand period. \n", + "In market terms, this corresponds to a **negative bid** that the generator effectively offers to pay for staying online to avoid the higher cost of shutting down and restarting.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "base = \"base\"\n", + "periods_low = [2, 3, 4]\n", + "\n", + "su = float(n.generators.at[base, \"start_up_cost\"])\n", + "sd = float(n.generators.at[base, \"shut_down_cost\"])\n", + "cycle_cost = su + sd\n", + "\n", + "mc = float(n.generators.at[base, \"marginal_cost\"])\n", + "gen_low = float(dispatch.loc[periods_low, base].sum()) # MWh over snapshots 2–4\n", + "op_cost = gen_low * mc\n", + "\n", + "print(\"Why stay online?\")\n", + "print(\"=\" * 25)\n", + "print(f\"Start-up cost: {su:,.0f} €\")\n", + "print(f\"Shut-down cost: {sd:,.0f} €\")\n", + "print(f\"Total cycling cost: {cycle_cost:,.0f} €\\n\")\n", + "\n", + "print(f\"Output (snapshots 2-4): {gen_low:.1f} MWh\")\n", + "print(f\"Operational cost: {op_cost:,.0f} €\\n\")\n", + "\n", + "decision = \"Stay online\" if op_cost < cycle_cost else \"Cycle off/on\"\n", + "savings = abs(cycle_cost - op_cost)\n", + "\n", + "print(f\"Decision: {decision} is cheaper.\")\n", + "print(f\"Savings vs alternative: {savings:,.0f} €\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Enumerating the -30 €/MWh (back-of-the-envelope)\n", + "\n", + "To understand the **-30 €/MWh** price at snapshot 3, recall that the base generator faces high cycling costs (4,000 € start-up + 2,000 € shut-down = 6,000 € total). Turning it off at t = 3 and restarting at t = 4 would incur this full cost.\n", + "\n", + "Instead, the optimizer keeps the unit **partially online** through the low-demand valley (snapshots 2–4), producing 50 + 20 + 50 = 120 MWh in total. By staying online, the system **avoids** the 6,000 € cycle, effectively spreading that saving across these 120 MWh:\n", + "\n", + "$$\n", + "\\frac{6{,}000\\,\\text{€}}{120\\,\\text{MWh}} = 50\\,\\text{€/MWh}.\n", + "$$\n", + "\n", + "With a variable generation cost of 20 €/MWh, the marginal price at t = 3 becomes:\n", + "\n", + "$$\n", + "\\text{LMP}_{t=3} = 20 - 50 = -30\\,\\text{€/MWh}.\n", + "$$\n", + "\n", + "**Interpretation:** \n", + "The system would save 30 € for each additional MWh consumed at t = 3, \n", + "which is precisely why the model reports a **negative price**" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/PyPSA/source/docs/examples/unit-commitment.ipynb b/PyPSA/source/docs/examples/unit-commitment.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..f7d589ab410d5b82b4663648564ed77233c364bb --- /dev/null +++ b/PyPSA/source/docs/examples/unit-commitment.ipynb @@ -0,0 +1,637 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Unit Commitment\n", + "\n", + "This tutorial runs through examples of unit commitment for generators at a single bus. Examples of minimum part-load, minimum up time, minimum down time, start up costs, shut down costs and ramp rate restrictions are shown, as well as how to set up a rolling horizon optimization.\n", + "\n", + "To enable unit commitment on a component (`Link` or `Generator`), set its attribute `committable=True`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "import pypsa" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Minimum Part Load\n", + "\n", + "In the final snapshot, the load goes below the part-load limit of the coal generator (30%), forcing gas to commit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu = pypsa.Network(snapshots=range(4))\n", + "\n", + "nu.add(\"Bus\", \"bus\")\n", + "\n", + "nu.add(\n", + " \"Generator\",\n", + " \"coal\",\n", + " bus=\"bus\",\n", + " committable=True,\n", + " p_min_pu=0.3,\n", + " marginal_cost=20,\n", + " p_nom=10_000,\n", + ")\n", + "\n", + "nu.add(\n", + " \"Generator\",\n", + " \"gas\",\n", + " bus=\"bus\",\n", + " committable=True,\n", + " marginal_cost=70,\n", + " p_min_pu=0.1,\n", + " p_nom=1_000,\n", + ")\n", + "\n", + "nu.add(\"Load\", \"load\", bus=\"bus\", p_set=[4_000, 6_000, 5_000, 800])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.optimize(log_to_console=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.generators_t.status" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.generators_t.p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Minimum Up Time\n", + "\n", + "Gas has a minimum up time, forcing it to be online longer than otherwise necessary, which incurs a standby cost for status up without generation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu = pypsa.Network(snapshots=range(4))\n", + "\n", + "nu.add(\"Bus\", \"bus\")\n", + "\n", + "nu.add(\n", + " \"Generator\",\n", + " \"coal\",\n", + " bus=\"bus\",\n", + " committable=True,\n", + " p_min_pu=0.3,\n", + " marginal_cost=20,\n", + " p_nom=10000,\n", + ")\n", + "\n", + "nu.add(\n", + " \"Generator\",\n", + " \"gas\",\n", + " bus=\"bus\",\n", + " committable=True,\n", + " stand_by_cost=50,\n", + " marginal_cost=70,\n", + " p_min_pu=0.1,\n", + " up_time_before=0,\n", + " min_up_time=3,\n", + " p_nom=1_000,\n", + ")\n", + "\n", + "nu.add(\"Load\", \"load\", bus=\"bus\", p_set=[4_000, 800, 5_000, 3_000])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.optimize(log_to_console=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.generators_t.status" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.objective" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.generators_t.p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Minimum Down Time\n", + "\n", + "Coal has a minimum down time, forcing it to go off longer than otherwise cost-optimal." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu = pypsa.Network(snapshots=range(4))\n", + "\n", + "nu.add(\"Bus\", \"bus\")\n", + "\n", + "nu.add(\n", + " \"Generator\",\n", + " \"coal\",\n", + " bus=\"bus\",\n", + " committable=True,\n", + " p_min_pu=0.3,\n", + " marginal_cost=20,\n", + " min_down_time=2,\n", + " down_time_before=1,\n", + " p_nom=10_000,\n", + ")\n", + "\n", + "nu.add(\n", + " \"Generator\",\n", + " \"gas\",\n", + " bus=\"bus\",\n", + " committable=True,\n", + " marginal_cost=70,\n", + " p_min_pu=0.1,\n", + " p_nom=4_000,\n", + ")\n", + "\n", + "nu.add(\"Load\", \"load\", bus=\"bus\", p_set=[3_000, 800, 3_000, 8_000])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.optimize(log_to_console=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.objective" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.generators_t.status" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.generators_t.p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Start Up and Shut Down Costs\n", + "\n", + "Now there are costs associated with shut down and start up events, which could incentivise longer up times of generators with high start-up and shut-down costs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu = pypsa.Network(snapshots=range(4))\n", + "\n", + "nu.add(\"Bus\", \"bus\")\n", + "\n", + "nu.add(\n", + " \"Generator\",\n", + " \"coal\",\n", + " bus=\"bus\",\n", + " committable=True,\n", + " p_min_pu=0.3,\n", + " marginal_cost=20,\n", + " min_down_time=2,\n", + " start_up_cost=5_000,\n", + " p_nom=10_000,\n", + ")\n", + "\n", + "nu.add(\n", + " \"Generator\",\n", + " \"gas\",\n", + " bus=\"bus\",\n", + " committable=True,\n", + " marginal_cost=70,\n", + " p_min_pu=0.1,\n", + " shut_down_cost=25,\n", + " p_nom=4_000,\n", + ")\n", + "\n", + "nu.add(\"Load\", \"load\", bus=\"bus\", p_set=[3_000, 800, 3_000, 8_000])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.optimize(log_to_console=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.objective" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.generators_t.status" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.generators_t.p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Ramp Rate Limits\n", + "\n", + "Ramp rate limits can be set for ramping up and down and are given as percentage of the nominal power that can be ramped up or down per snapshot. Note that the ramp limits apply per snapshot and are **not** weighted by the time step duration (`nu.snapshot_weightings`)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu = pypsa.Network(snapshots=range(6))\n", + "\n", + "nu.add(\"Bus\", \"bus\")\n", + "\n", + "nu.add(\n", + " \"Generator\",\n", + " \"coal\",\n", + " bus=\"bus\",\n", + " marginal_cost=20,\n", + " ramp_limit_up=0.1,\n", + " ramp_limit_down=0.2,\n", + " p_nom=10_000,\n", + ")\n", + "\n", + "nu.add(\"Generator\", \"gas\", bus=\"bus\", marginal_cost=70, p_nom=4_000)\n", + "\n", + "nu.add(\"Load\", \"load\", bus=\"bus\", p_set=[4_000, 7_000, 7_000, 7_000, 7_000, 3_000])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.optimize(log_to_console=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.generators_t.p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With capacity expansion (as long as unit is not committable):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu = pypsa.Network(snapshots=range(6))\n", + "\n", + "nu.add(\"Bus\", \"bus\")\n", + "\n", + "nu.add(\n", + " \"Generator\",\n", + " \"coal\",\n", + " bus=\"bus\",\n", + " marginal_cost=20,\n", + " ramp_limit_up=0.1,\n", + " ramp_limit_down=0.2,\n", + " p_nom_extendable=True,\n", + " capital_cost=1e2,\n", + ")\n", + "\n", + "nu.add(\"Generator\", \"gas\", bus=\"bus\", marginal_cost=70, p_nom=4000)\n", + "\n", + "nu.add(\"Load\", \"load\", bus=\"bus\", p_set=[4000, 7000, 7000, 7000, 7000, 3000])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.optimize(log_to_console=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.generators.p_nom_opt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.generators_t.p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Watch out for bad interactions, for example, when the ramp limit at start up or shut down is bigger than the regular ramp limit or minimum part load, which can lead to infeasibilities." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu = pypsa.Network(snapshots=range(7))\n", + "\n", + "nu.add(\"Bus\", \"bus\")\n", + "\n", + "# Can get bad interactions if SU > RU and p_min_pu; similarly if SD > RD\n", + "nu.add(\n", + " \"Generator\",\n", + " \"coal\",\n", + " bus=\"bus\",\n", + " marginal_cost=20,\n", + " committable=True,\n", + " p_min_pu=0.05,\n", + " initial_status=0,\n", + " ramp_limit_start_up=0.1,\n", + " ramp_limit_up=0.2,\n", + " ramp_limit_down=0.25,\n", + " ramp_limit_shut_down=0.15,\n", + " p_nom=10_000,\n", + ")\n", + "\n", + "nu.add(\"Generator\", \"gas\", bus=\"bus\", marginal_cost=70, p_nom=10_000)\n", + "\n", + "nu.add(\"Load\", \"load\", bus=\"bus\", p_set=[0, 200, 7_000, 7_000, 7_000, 2_000, 0])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.optimize(log_to_console=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.generators_t.p" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.generators_t.status" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Rolling Horizon\n", + "\n", + "The unit commitment optimisation can be combined with a rolling horizon optimisation, i.e. solving the snapshots sequentially in batches. This can be done manually (as shown here) or automatically, using `nu.optimize.optimize_with_rolling_horizon()`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sets_of_snapshots = 6\n", + "p_set = [4_000, 5_000, 700, 800, 4_000]\n", + "\n", + "nu = pypsa.Network(snapshots=range(len(p_set) * sets_of_snapshots))\n", + "\n", + "nu.add(\"Bus\", \"bus\")\n", + "\n", + "nu.add(\n", + " \"Generator\",\n", + " \"coal\",\n", + " bus=\"bus\",\n", + " committable=True,\n", + " p_min_pu=0.3,\n", + " marginal_cost=20,\n", + " min_down_time=2,\n", + " min_up_time=3,\n", + " up_time_before=1,\n", + " ramp_limit_up=1,\n", + " ramp_limit_down=1,\n", + " ramp_limit_start_up=1,\n", + " ramp_limit_shut_down=1,\n", + " shut_down_cost=150,\n", + " start_up_cost=200,\n", + " p_nom=10_000,\n", + ")\n", + "\n", + "nu.add(\n", + " \"Generator\",\n", + " \"gas\",\n", + " bus=\"bus\",\n", + " committable=True,\n", + " marginal_cost=70,\n", + " p_min_pu=0.1,\n", + " up_time_before=2,\n", + " min_up_time=3,\n", + " shut_down_cost=20,\n", + " start_up_cost=50,\n", + " p_nom=1_000,\n", + ")\n", + "\n", + "nu.add(\"Load\", \"load\", bus=\"bus\", p_set=p_set * sets_of_snapshots)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "overlap = 2\n", + "for i in range(sets_of_snapshots):\n", + " snapshots = nu.snapshots[i * len(p_set) : (i + 1) * len(p_set) + overlap]\n", + " nu.optimize(snapshots=snapshots, log_to_console=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pd.concat(\n", + " {\"Active\": nu.generators_t.status.astype(bool), \"Output\": nu.generators_t.p}, axis=1\n", + ")" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/PyPSA/source/docs/home/citing.md b/PyPSA/source/docs/home/citing.md new file mode 100644 index 0000000000000000000000000000000000000000..959d78f7ee2e28b242f36d25ce7204d94467b5bf --- /dev/null +++ b/PyPSA/source/docs/home/citing.md @@ -0,0 +1,38 @@ + + +# Citing + +If you use PyPSA for your research, we would appreciate it if you would cite the following paper: + +* T. Brown, J. Hörsch, D. Schlachtberger, [PyPSA: Python for Power System Analysis](https://arxiv.org/abs/1707.09913), 2018, [Journal of Open Research Software](https://openresearchsoftware.metajnl.com/), 6(1), [arXiv:1707.09913](https://arxiv.org/abs/1707.09913), [DOI:10.5334/jors.188](https://doi.org/10.5334/jors.188) + +Please use the following BibTeX: + +``` bibtex +@article{PyPSA, + author = {T. Brown and J. H\"orsch and D. Schlachtberger}, + title = {{PyPSA: Python for Power System Analysis}}, + journal = {Journal of Open Research Software}, + volume = {6}, + issue = {1}, + number = {4}, + year = {2018}, + eprint = {1707.09913}, + url = {https://doi.org/10.5334/jors.188}, + doi = {10.5334/jors.188} +} +``` + + +If you want to cite a specific PyPSA version, each release of PyPSA is archived on [Zenodo](https://zenodo.org/) with a release-specific DOI: +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3946412.svg)](https://doi.org/10.5281/zenodo.3946412) + + +## Badge + +You can use the following badge in your README or documentation to show that you are using PyPSA, ![docs.pypsa.org](https://img.shields.io/badge/PyPSA-%23293036?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTG9nbyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA0NTAgNDUwIj4KICA8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMjkuNC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogMi4xLjAgQnVpbGQgMTUyKSAgLS0%2BCiAgPGRlZnM%2BCiAgICA8c3R5bGU%2BCiAgICAgIC5zdDAgewogICAgICAgIGZpbGw6ICMyOTkzYjU7CiAgICAgIH0KCiAgICAgIC5zdDEgewogICAgICAgIGZpbGw6ICMwYTg3NTQ7CiAgICAgIH0KCiAgICAgIC5zdDIgewogICAgICAgIGZpbGw6ICNmZmY7CiAgICAgIH0KCiAgICAgIC5zdDMgewogICAgICAgIGZpbGw6ICNkMTBhNDk7CiAgICAgIH0KCiAgICAgIC5zdDQgewogICAgICAgIGZpbGw6ICNmZmJmMDA7CiAgICAgIH0KCiAgICAgIC5zdDUgewogICAgICAgIGRpc3BsYXk6IG5vbmU7CiAgICAgIH0KICAgIDwvc3R5bGU%2BCiAgPC9kZWZzPgogIDxnIGlkPSJMZWZ0X1RyaWFuZ2xlIj4KICAgIDxwYXRoIGNsYXNzPSJzdDMiIGQ9Ik0yMTkuMzgsMTQyLjQ5bC0xMTEuNTQsMTg5LjYyYy05Ljg0LTEzLjkyLTI1LjYzLTIzLjM1LTQzLjY3LTI0LjU5bDExMi4wMi0xOTAuNDRjOS41OSwxNC4xMywyNS4yMiwyMy44Myw0My4xOCwyNS40MWgwWiIvPgogIDwvZz4KICA8ZyBpZD0iUmlnaHRfVHJpYW5nbGUiPgogICAgPHBhdGggY2xhc3M9InN0MyIgZD0iTTM4NC43MSwzMDcuMTFjLTEzLjE5LDEuMTctMjYuMDQsNi43OS0zNi4xNCwxNi44OS0yLjY2LDIuNjYtNS4wMSw1LjUxLTcuMDQsOC41MWwtMTExLjc4LTE5MC4wMmMxNy45Ni0xLjU3LDMzLjU5LTExLjI4LDQzLjE4LTI1LjQxbDExMS43NywxOTAuMDNoMFoiLz4KICA8L2c%2BCiAgPGcgaWQ9IkJvdHRvbV9UcmlhbmdsZSI%2BCiAgICA8cGF0aCBjbGFzcz0ic3QzIiBkPSJNMzM3LjA4LDM5MC4zN0gxMTMuMTZjMy40Ny03LjQ2LDUuMzktMTUuNzcsNS4zOS0yNC41NHMtMi4xMS0xNy44My01Ljg3LTI1LjU2aDIyNC4zOGMtNy41LDE1LjgxLTcuNSwzNC4yOSwwLDUwLjFoMFoiLz4KICA8L2c%2BCiAgPGcgaWQ9IlRvcF9NYXNrIiBjbGFzcz0ic3Q1Ij4KICAgIDxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0yODMuMjUsODQuMjZjMCwxMi4yMS0zLjczLDIzLjU1LTEwLjEyLDMyLjk0LTkuNjMsMTQuMTgtMjUuMzIsMjMuOTItNDMuMzUsMjUuNTEtMS43Mi4xNS0zLjQ1LjIzLTUuMjEuMjNzLTMuNDktLjA4LTUuMjEtLjIzYy0xOC4wMy0xLjU4LTMzLjcyLTExLjMyLTQzLjM1LTI1LjUxLTYuMzktOS4zOS0xMC4xMi0yMC43My0xMC4xMi0zMi45NCwwLTMyLjQsMjYuMjctNTguNjcsNTguNjctNTguNjdzNTguNjcsMjYuMjcsNTguNjcsNTguNjdoLjAyWiIvPgogIDwvZz4KICA8ZyBpZD0iTGVmdF9NYXNrIiBjbGFzcz0ic3Q1Ij4KICAgIDxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMTguNzgsMzY1LjMyYzAsOC44LTEuOTQsMTcuMTQtNS40MSwyNC42My05LjMsMjAuMS0yOS42NiwzNC4wNC01My4yNiwzNC4wNC0zMi40LDAtNTguNjgtMjYuMjctNTguNjgtNTguNjdzMjYuMjctNTguNjcsNTguNjctNTguNjdjMS4zNywwLDIuNzMuMDUsNC4wOC4xNCwxOC4xMSwxLjI0LDMzLjk2LDEwLjcsNDMuODQsMjQuNjgsMS44NCwyLjU4LDMuNDYsNS4zMiw0Ljg2LDguMiwzLjc3LDcuNzUsNS44OSwxNi40NSw1Ljg5LDI1LjY2aC4wMVoiLz4KICA8L2c%2BCiAgPGcgaWQ9IlJpZ2h0X01hc2siIGNsYXNzPSJzdDUiPgogICAgPHBhdGggY2xhc3M9InN0MiIgZD0iTTQzMS4zNyw0MDYuODFjLTIyLjkyLDIyLjkyLTYwLjA3LDIyLjkyLTgyLjk3LDAtNC45LTQuOS04Ljc0LTEwLjQ0LTExLjU0LTE2LjM1LTcuNTMtMTUuODgtNy41My0zNC40MywwLTUwLjI5LDEuMjctMi42OCwyLjc3LTUuMyw0LjQ4LTcuOCwyLjA0LTMuMDEsNC40LTUuODgsNy4wNy04LjU0LDEwLjEzLTEwLjEzLDIzLjA0LTE1Ljc5LDM2LjI4LTE2Ljk2LDE2LjctMS40OCwzMy45MSw0LjE3LDQ2LjcsMTYuOTYsMjIuOTIsMjIuOTEsMjIuOTIsNjAuMDYsMCw4Mi45N2gtLjAyWiIvPgogIDwvZz4KICA8cGF0aCBpZD0iUmlnaHRfQ2lyY2xlIiBjbGFzcz0ic3QwIiBkPSJNMzg5Ljg5LDQxNS40M2MxMy4yOSwwLDI2LjAzLTUuMjgsMzUuNDMtMTQuNjcsOS4zOS05LjM5LDE0LjY3LTIyLjEzLDE0LjY3LTM1LjQzcy01LjI4LTI2LjAzLTE0LjY3LTM1LjQzYy05LjM5LTkuMzktMjIuMTMtMTQuNjctMzUuNDMtMTQuNjdzLTI2LjAzLDUuMjgtMzUuNDMsMTQuNjdjLTkuNCw5LjM5LTE0LjY3LDIyLjEzLTE0LjY3LDM1LjQzczUuMjgsMjYuMDMsMTQuNjcsMzUuNDNjOS4zOSw5LjM5LDIyLjEzLDE0LjY3LDM1LjQzLDE0LjY3aDBaIi8%2BCiAgPHBhdGggaWQ9IlRvcF9DaXJjbGUiIGNsYXNzPSJzdDQiIGQ9Ik0yMjQuNTcsMTM0LjM3YzEzLjI5LDAsMjYuMDMtNS4yOCwzNS40My0xNC42Nyw5LjM5LTkuMzksMTQuNjctMjIuMTMsMTQuNjctMzUuNDNzLTUuMjgtMjYuMDMtMTQuNjctMzUuNDNjLTkuMzktOS4zOS0yMi4xMy0xNC42Ny0zNS40My0xNC42N3MtMjYuMDMsNS4yOC0zNS40MywxNC42N2MtOS4zOSw5LjM5LTE0LjY3LDIyLjEzLTE0LjY3LDM1LjQzczUuMjgsMjYuMDMsMTQuNjcsMzUuNDNjOS4zOSw5LjM5LDIyLjEzLDE0LjY3LDM1LjQzLDE0LjY3aDBaIi8%2BCiAgPHBhdGggaWQ9IkxlZnRfQ2lyY2xlIiBjbGFzcz0ic3QxIiBkPSJNNjAuMTEsNDE1LjQzYzEzLjI5LDAsMjYuMDMtNS4yOCwzNS40My0xNC42Nyw5LjM5LTkuMzksMTQuNjctMjIuMTMsMTQuNjctMzUuNDNzLTUuMjgtMjYuMDMtMTQuNjctMzUuNDNjLTkuMzktOS4zOS0yMi4xMy0xNC42Ny0zNS40My0xNC42N3MtMjYuMDMsNS4yOC0zNS40MywxNC42N2MtOS4zOSw5LjM5LTE0LjY3LDIyLjEzLTE0LjY3LDM1LjQzczUuMjgsMjYuMDMsMTQuNjcsMzUuNDNjOS4zOSw5LjM5LDIyLjEzLDE0LjY3LDM1LjQzLDE0LjY3WiIvPgo8L3N2Zz4%3D&link=https%3A%2F%2Fdocs.pypsa.org), or indicate a specific version, ![docs.pypsa.org](https://img.shields.io/badge/%E2%89%A51.0.0-%23d10949?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTG9nbyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA0NTAgNDUwIj4KICA8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMjkuNC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogMi4xLjAgQnVpbGQgMTUyKSAgLS0%2BCiAgPGRlZnM%2BCiAgICA8c3R5bGU%2BCiAgICAgIC5zdDAgewogICAgICAgIGZpbGw6ICMyOTkzYjU7CiAgICAgIH0KCiAgICAgIC5zdDEgewogICAgICAgIGZpbGw6ICMwYTg3NTQ7CiAgICAgIH0KCiAgICAgIC5zdDIgewogICAgICAgIGZpbGw6ICNmZmY7CiAgICAgIH0KCiAgICAgIC5zdDMgewogICAgICAgIGZpbGw6ICNkMTBhNDk7CiAgICAgIH0KCiAgICAgIC5zdDQgewogICAgICAgIGZpbGw6ICNmZmJmMDA7CiAgICAgIH0KCiAgICAgIC5zdDUgewogICAgICAgIGRpc3BsYXk6IG5vbmU7CiAgICAgIH0KICAgIDwvc3R5bGU%2BCiAgPC9kZWZzPgogIDxnIGlkPSJMZWZ0X1RyaWFuZ2xlIj4KICAgIDxwYXRoIGNsYXNzPSJzdDMiIGQ9Ik0yMTkuMzgsMTQyLjQ5bC0xMTEuNTQsMTg5LjYyYy05Ljg0LTEzLjkyLTI1LjYzLTIzLjM1LTQzLjY3LTI0LjU5bDExMi4wMi0xOTAuNDRjOS41OSwxNC4xMywyNS4yMiwyMy44Myw0My4xOCwyNS40MWgwWiIvPgogIDwvZz4KICA8ZyBpZD0iUmlnaHRfVHJpYW5nbGUiPgogICAgPHBhdGggY2xhc3M9InN0MyIgZD0iTTM4NC43MSwzMDcuMTFjLTEzLjE5LDEuMTctMjYuMDQsNi43OS0zNi4xNCwxNi44OS0yLjY2LDIuNjYtNS4wMSw1LjUxLTcuMDQsOC41MWwtMTExLjc4LTE5MC4wMmMxNy45Ni0xLjU3LDMzLjU5LTExLjI4LDQzLjE4LTI1LjQxbDExMS43NywxOTAuMDNoMFoiLz4KICA8L2c%2BCiAgPGcgaWQ9IkJvdHRvbV9UcmlhbmdsZSI%2BCiAgICA8cGF0aCBjbGFzcz0ic3QzIiBkPSJNMzM3LjA4LDM5MC4zN0gxMTMuMTZjMy40Ny03LjQ2LDUuMzktMTUuNzcsNS4zOS0yNC41NHMtMi4xMS0xNy44My01Ljg3LTI1LjU2aDIyNC4zOGMtNy41LDE1LjgxLTcuNSwzNC4yOSwwLDUwLjFoMFoiLz4KICA8L2c%2BCiAgPGcgaWQ9IlRvcF9NYXNrIiBjbGFzcz0ic3Q1Ij4KICAgIDxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0yODMuMjUsODQuMjZjMCwxMi4yMS0zLjczLDIzLjU1LTEwLjEyLDMyLjk0LTkuNjMsMTQuMTgtMjUuMzIsMjMuOTItNDMuMzUsMjUuNTEtMS43Mi4xNS0zLjQ1LjIzLTUuMjEuMjNzLTMuNDktLjA4LTUuMjEtLjIzYy0xOC4wMy0xLjU4LTMzLjcyLTExLjMyLTQzLjM1LTI1LjUxLTYuMzktOS4zOS0xMC4xMi0yMC43My0xMC4xMi0zMi45NCwwLTMyLjQsMjYuMjctNTguNjcsNTguNjctNTguNjdzNTguNjcsMjYuMjcsNTguNjcsNTguNjdoLjAyWiIvPgogIDwvZz4KICA8ZyBpZD0iTGVmdF9NYXNrIiBjbGFzcz0ic3Q1Ij4KICAgIDxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMTguNzgsMzY1LjMyYzAsOC44LTEuOTQsMTcuMTQtNS40MSwyNC42My05LjMsMjAuMS0yOS42NiwzNC4wNC01My4yNiwzNC4wNC0zMi40LDAtNTguNjgtMjYuMjctNTguNjgtNTguNjdzMjYuMjctNTguNjcsNTguNjctNTguNjdjMS4zNywwLDIuNzMuMDUsNC4wOC4xNCwxOC4xMSwxLjI0LDMzLjk2LDEwLjcsNDMuODQsMjQuNjgsMS44NCwyLjU4LDMuNDYsNS4zMiw0Ljg2LDguMiwzLjc3LDcuNzUsNS44OSwxNi40NSw1Ljg5LDI1LjY2aC4wMVoiLz4KICA8L2c%2BCiAgPGcgaWQ9IlJpZ2h0X01hc2siIGNsYXNzPSJzdDUiPgogICAgPHBhdGggY2xhc3M9InN0MiIgZD0iTTQzMS4zNyw0MDYuODFjLTIyLjkyLDIyLjkyLTYwLjA3LDIyLjkyLTgyLjk3LDAtNC45LTQuOS04Ljc0LTEwLjQ0LTExLjU0LTE2LjM1LTcuNTMtMTUuODgtNy41My0zNC40MywwLTUwLjI5LDEuMjctMi42OCwyLjc3LTUuMyw0LjQ4LTcuOCwyLjA0LTMuMDEsNC40LTUuODgsNy4wNy04LjU0LDEwLjEzLTEwLjEzLDIzLjA0LTE1Ljc5LDM2LjI4LTE2Ljk2LDE2LjctMS40OCwzMy45MSw0LjE3LDQ2LjcsMTYuOTYsMjIuOTIsMjIuOTEsMjIuOTIsNjAuMDYsMCw4Mi45N2gtLjAyWiIvPgogIDwvZz4KICA8cGF0aCBpZD0iUmlnaHRfQ2lyY2xlIiBjbGFzcz0ic3QwIiBkPSJNMzg5Ljg5LDQxNS40M2MxMy4yOSwwLDI2LjAzLTUuMjgsMzUuNDMtMTQuNjcsOS4zOS05LjM5LDE0LjY3LTIyLjEzLDE0LjY3LTM1LjQzcy01LjI4LTI2LjAzLTE0LjY3LTM1LjQzYy05LjM5LTkuMzktMjIuMTMtMTQuNjctMzUuNDMtMTQuNjdzLTI2LjAzLDUuMjgtMzUuNDMsMTQuNjdjLTkuNCw5LjM5LTE0LjY3LDIyLjEzLTE0LjY3LDM1LjQzczUuMjgsMjYuMDMsMTQuNjcsMzUuNDNjOS4zOSw5LjM5LDIyLjEzLDE0LjY3LDM1LjQzLDE0LjY3aDBaIi8%2BCiAgPHBhdGggaWQ9IlRvcF9DaXJjbGUiIGNsYXNzPSJzdDQiIGQ9Ik0yMjQuNTcsMTM0LjM3YzEzLjI5LDAsMjYuMDMtNS4yOCwzNS40My0xNC42Nyw5LjM5LTkuMzksMTQuNjctMjIuMTMsMTQuNjctMzUuNDNzLTUuMjgtMjYuMDMtMTQuNjctMzUuNDNjLTkuMzktOS4zOS0yMi4xMy0xNC42Ny0zNS40My0xNC42N3MtMjYuMDMsNS4yOC0zNS40MywxNC42N2MtOS4zOSw5LjM5LTE0LjY3LDIyLjEzLTE0LjY3LDM1LjQzczUuMjgsMjYuMDMsMTQuNjcsMzUuNDNjOS4zOSw5LjM5LDIyLjEzLDE0LjY3LDM1LjQzLDE0LjY3aDBaIi8%2BCiAgPHBhdGggaWQ9IkxlZnRfQ2lyY2xlIiBjbGFzcz0ic3QxIiBkPSJNNjAuMTEsNDE1LjQzYzEzLjI5LDAsMjYuMDMtNS4yOCwzNS40My0xNC42Nyw5LjM5LTkuMzksMTQuNjctMjIuMTMsMTQuNjctMzUuNDNzLTUuMjgtMjYuMDMtMTQuNjctMzUuNDNjLTkuMzktOS4zOS0yMi4xMy0xNC42Ny0zNS40My0xNC42N3MtMjYuMDMsNS4yOC0zNS40MywxNC42N2MtOS4zOSw5LjM5LTE0LjY3LDIyLjEzLTE0LjY3LDM1LjQzczUuMjgsMjYuMDMsMTQuNjcsMzUuNDNjOS4zOSw5LjM5LDIyLjEzLDE0LjY3LDM1LjQzLDE0LjY3WiIvPgo8L3N2Zz4%3D&label=PyPSA&labelColor=%23293036&link=https%3A%2F%2Fdocs.pypsa.org). +Please refer to [shields.io](https://shields.io/badges) and use the above as a template. diff --git a/PyPSA/source/docs/home/features.md b/PyPSA/source/docs/home/features.md new file mode 100644 index 0000000000000000000000000000000000000000..5eb0b6c647e41dc4b76c4c62b1536de7b2cfa8e0 --- /dev/null +++ b/PyPSA/source/docs/home/features.md @@ -0,0 +1,152 @@ + + +# Features + +PyPSA is a flexible framework for modelling and optimising modern energy +systems. It supports high spatial, temporal, and sectoral resolution, from +short-term dispatch to long-term planning. The following feature set outlines +its key features: + +## Optimisation Functionalities + +- **:material-power-plug-battery-outline: Economic Dispatch (ED):** Models +short-term market-based dispatch including unit commitment (either with integer +variables as MILP or in a relaxed approximation as LP), renewable availability, +short-duration and seasonal storage including hydro reservoirs with inflow and +spillage dynamics, elastic demands, load shedding and conversion between energy +carriers, using either perfect operational foresight or rolling horizon time +resolution. + +- **:material-transmission-tower: Linear Optimal Power Flow (LOPF):** Extends economic dispatch to determine +the least-cost dispatch while respecting network constraints in meshed AC-DC +networks, using a linearised representation of power flow (KVL, KCL) with +optional loss approximations. + +- **:material-security: Security-Constrained LOPF (SCLOPF):** Extends LOPF by accounting for line +outage contingencies to ensure system reliability under $N-1$ conditions. + +- **:material-crane: Capacity Expansion Planning (CEP):** Supports least-cost +long-term system planning with investment decisions for generation, storage, +conversion, and transmission infrastructure. Handles both single and multiple +investment periods. Continuous and discrete investments are supported. + +- **:material-road: Pathway Planning:** Supports co-optimisation of multiple investment periods to +plan energy system transitions over time with perfect planning foresight. + +- **:material-rollerblade: Rolling-Horizon Optimisation:** Enables sequential optimisation of operation +with myopic foresight, allowing for dynamic information updates and breaking +down large problems into manageable time slices. + +- **:material-crosshairs-question: Stochastic Optimisation:** Implements two-stage stochastic programming +framework with scenario-weighted uncertain inputs, with investments as +first-stage decisions and dispatch as recourse decisions. + +- **:material-diversify: Modelling-to-Generate-Alternatives (MGA):** Explores near-optimal decision +spaces to provide insight into the range of feasible system configurations with +similar costs. + +- **:fontawesome-solid-building-columns: Policy Constraints:** Built-in support + for policy constraints such as CO~2~ emission limits and pricing, subsidies, resource + limits, expansion limits, and growth limits. Extendable by custom constraints. + +- **:material-screwdriver: Custom Constraints:** Users can impose own objectives, variables and +constraints, such as policy constraints or technical requirements, using +[Linopy](https://linopy.readthedocs.io/). + +- **:material-dots-square: Solver Flexibility:** Supports a wide range of LP, MILP, and QP solvers from +open-source solutions (e.g. [HiGHS](https://highs.dev/), +[SCIP](https://scipopt.org)) to commercial products (e.g. +[Gurobi](https://www.gurobi.com/), [COPT](https://shanshu.ai/copt)). + +## Use Cases and Grid Modelling + +- **:material-heat-pump: Sector-Coupling:** Modelling integrated energy systems with multiple energy + carriers (electricity, heat, hydrogen, etc.) and conversion between them. + Flexible representation of technologies such as heat pumps, electrolysers, + battery electric vehicles (BEVs), direct air capture (DAC), and synthetic + fuels production. + +- **:material-lightbulb: Diverse Applications:** Supports a wide range of energy system analyses for + strategic decision support. Applications include techno-economic assessment of + technologies, capacity expansion, transmission planning, market design, + sector-coupling, integration of variable renewables such as wind and solar, flexibility needs and resource + adequacy assessments, network congestion analysis, battery scheduling, + electricity trading, planning of decarbonisation pathways, hydrogen + infrastructure planning, electrolyser siting and operation, resilience to + extreme weather, bidding zone configurations, and the design of islanded + systems for remote renewable fuel production. + +- **:material-transmission-tower: Standard Grid Components:** Includes standard types for lines and + transformers from [pandapower](https://pandapower.org). + +- **:simple-graphql: Static Power Flow Analysis:** Computes both full non-linear and linearised + load flows for meshed AC and DC grids using Newton-Raphson method with + optional distributed slack and shunt compensation. + +## Architecture and Performance + +- **:material-view-module: Modular Design:** Clean separation between data and modelling code enables +flexible scenario development. + +- **:material-car-cruise-control: Resolution Control:** Offers flexible control over temporal, spatial, and +sectoral scope and detail. + +- **:material-grid-large: Spatial Clustering:** Can reduce model size for large networks via spatial + aggregation strategies. + +- **:material-data-matrix: Data Backbone:** Uses [pandas](https://pandas.pydata.org/) for data handling + and [linopy](https://linopy.readthedocs.io/) for optimisation and interfacing + with solvers. + +- **:material-run-fast: Performance:** Using [linopy](https://linopy.readthedocs.io/), the code is +designed to scale well with high resolution networks, minimising memory usage +and time spent outside the solver. + +- **:fontawesome-solid-arrow-trend-up: Scalability:** Handles models ranging from small conceptual prototypes to +continent-scale high-resolution systems solved on high-performance compute +clusters. + +## Analysis and Usability + +- **:simple-shadow: Shadow Prices:** Outputs dual values such as nodal clearing prices (LMPs), +storage water values, scarcity and CO~2~ prices. + +- **:simple-googlesheets: Statistics:** Built-in tools for summarising and visualising results, such + as energy balances, capacities, costs, market values, curtailment, component + revenues. + +- **:octicons-paintbrush-16: Visualisations:** Built-in tools for plotting statistics, time series data + and spatial distributions of line loadings and nodal dispatch decisions. + +- **:material-notebook: Documentation:** Comprehensive user guide, API reference, and plenty of +examples are available. + +- **:fontawesome-solid-people-group: Community Support:** Active community on + [GitHub](https://github.com/pypsa/pypsa) and + [Discord](https://discord.gg/AnuJBk23FU) for user support and development + discussions. + +- **:material-lock-open: MIT License:** Fully open-source and free for commercial and academic use. + +## Illustrations + +Interactive **network visualization** of SciGRID example network clustered by federal state, showing transmission capacities, flow directions, electricity supply (upper pie charts) and demand (lower pie charts), as well as average nodal prices (color scale): + +
+ +
+ +Interactive area plot of **electricity balance time series** in a highly-renewable sector-coupled example network, showing temporal generation (positive values) and consumption (negative values) of different technologies: + +
+ +
diff --git a/PyPSA/source/docs/home/installation.md b/PyPSA/source/docs/home/installation.md new file mode 100644 index 0000000000000000000000000000000000000000..3e01e19a5db9b5f22e41e9b19b0339274fa053dd --- /dev/null +++ b/PyPSA/source/docs/home/installation.md @@ -0,0 +1,146 @@ + + +# Installation + +!!! hint + + If it is your first time using Python, we recommend [conda](https://docs.conda.io/en/latest/miniconda.html), [mamba](https://github.com/mamba-org/mamba), [pip](https://pip.pypa.io/en/stable/) or [uv](https://docs.astral.sh/uv/) as easy-to-use package managers. They are available for Windows, macOS, and GNU/Linux. It is always helpful to use dedicated environments. + +You can install PyPSA via all common package managers: + +=== "pip" + + ``` bash + pip install pypsa + ``` + +=== "conda/mamba" + + ``` bash + conda install -c conda-forge pypsa + ``` + +=== "uv" + + ``` bash + uv add pypsa + ``` + +PyPSA is written and tested to be compatible with Python 3.11 and above. We recommend to use the latest version with active support (see [endoflife.date](https://endoflife.date/python)). + +## Solvers + +PyPSA passes optimisation problems (see [overview](overview.md)) to an external solver and is deeply integrated with the optimisation framework [linopy](https://github.com/PyPSA/linopy) to do so. Some examples of available solvers: + +| Free & open source | Commercial & proprietary | +| ------------- | ------------------- | +| [HiGHS](https://highs.dev/) | [Gurobi](https://www.gurobi.com/documentation/quickstart.html) | +| [Cbc](https://projects.coin-or.org/Cbc#DownloadandInstall) | [CPLEX](https://www.ibm.com/products/ilog-cplex-optimization-studio) | +| [GLPK](https://www.gnu.org/software/glpk/) | [FICO Xpress](https://www.fico.com/en/products/fico-xpress-optimization) | +| [SCIP](https://scip.zib.de/) | [MOSEK](https://www.mosek.com/) | +| | [COPT](https://www.shanshu.ai/copt) | + +PyPSA ships with the open-source solver HiGHS by default. For installation instructions of further solvers for your operating system, follow the links above. + +!!! note + + Commercial solvers currently significantly outperform open-source solvers for large-scale problems. + It might be the case that you can only retrieve solutions by using a commercial solver. + Many commercial solvers provide free academic licenses. + + +## Upgrading + +We recommend always keeping your PyPSA installation up-to-date, since bugs get +fixed and new features are added. PyPSA is also only tested with the latest +stable versions of all the dependent packages for the respective Python +versions. + +To upgrade PyPSA, run: + +=== "pip" + + ``` bash + pip install --upgrade pypsa + # Or upgrade to a specific version: + pip install pypsa==0.35.2 + ``` + +=== "conda/mamba" + + ``` bash + conda update pypsa + # Or upgrade to a specific version: + conda install -c conda-forge pypsa==0.35.2 + ``` + +=== "uv" + + ``` bash + uv add --upgrade pypsa + # Or upgrade to a specific version: + uv add pypsa==0.35.2 + ``` + +Check the [release notes](../release-notes.md) for API changes that may require you to update your code. PyPSA releases new versions according to the [semantic versioning](https://semver.org/) scheme. Any breaking changes are always announced via deprecation warnings in the code and in the release notes, including a version when they are going to be removed (always the next major version, e.g. `v2.0.0`). That way you can be sure that your code will continue to work at least until the next major version. But this does not include bug fixes, which you only get when upgrading to the latest version. +If you are upgrading from a pre , we recommend you upgrade in small steps and fix any deprecation warnings before upgrading to `v1.0.0`. + +## Dependencies + +PyPSA relies heavily on other open-source Python packages. Some of them are: + +* [pandas](http://pandas.pydata.org/) for storing data about components and time series +* [numpy](http://www.numpy.org/) and [scipy](http://scipy.org/) for calculations, such as linear algebra and sparse matrix calculations +* [linopy](https://github.com/PyPSA/linopy) for preparing optimisation problems (LP, QP, MILP) +* [matplotlib](https://matplotlib.org/), [seaborn](https://seaborn.pydata.org/) and [plotly](https://plotly.com/python/) for static and interactive plotting +* [networkx](https://networkx.github.io/) for some network calculations +* [pytest](http://pytest.org/) for unit testing + +Find the full list of dependencies in the [`pyproject.toml`](https://github.com/PyPSA/PyPSA/blob/master/pyproject.toml) file. + +### Optional dependencies + +Besides the mandatory dependencies that are installed by default, PyPSA has a number of optional dependencies that are not installed by default and are only needed for certain features. You can install them by executing the command: + +=== "pip" + + ``` bash + pip install "pypsa[]" + ``` + +=== "conda/mamba" + + ``` bash + conda install -c conda-forge "pypsa[]" + ``` + +=== "uv" + + ``` bash + uv add "pypsa[]" + ``` + +where `` can be one of the following: + +**IO** + +- `pypsa[hdf5]`: for reading and writing HDF5 files +- `pypsa[excel]`: for reading and writing Excel files +- `pypsa[cloudpath]`: for reading and writing files from cloud storage + +**Plots** + +- `pypsa[cartopy]`: for plotting geographical maps + +**Solvers** + +- `pypsa[gurobipy]`: for installing the Gurobi Python API + +**Development** + +- `pypsa[dev]`: for installing all development dependencies, including linopy and pytest +- `pypsa[docs]`: for installing all dependencies needed to build the documentation diff --git a/PyPSA/source/docs/home/license.md b/PyPSA/source/docs/home/license.md new file mode 100644 index 0000000000000000000000000000000000000000..1e3c37316697179fb485278458fdd6f9d39baf55 --- /dev/null +++ b/PyPSA/source/docs/home/license.md @@ -0,0 +1,30 @@ + + +# License + +PyPSA is licensed under the open-source [MIT](https://opensource.org/license/mit) license: + +```text +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +``` diff --git a/PyPSA/source/docs/home/models.md b/PyPSA/source/docs/home/models.md new file mode 100644 index 0000000000000000000000000000000000000000..1657465a4171fa902be7744e0c079fe38fa045bf --- /dev/null +++ b/PyPSA/source/docs/home/models.md @@ -0,0 +1,61 @@ + + +# Open-Source Models Based on PyPSA + +PyPSA is the software framework to build open-source energy system models. Many +model implementations by different [users](users.md) already exist. Some are +open source and actively maintained. Below is a list of some known models. + +## Regional Models + +### Global +- **[:earth_africa: PyPSA-Earth](https://github.com/pypsa-meets-earth/pypsa-earth)**: Global energy system model, maintained by [pypsa-meets-earth](https://pypsa-meets-earth.github.io/) + +### Europe +- **[:flag_eu: PyPSA-Eur](https://github.com/PyPSA/pypsa-eur)**: European energy system model, maintained by [TU Berlin](https://www.tu.berlin/en/ensys) +- **[:flag_de: PyPSA-DE](https://github.com/PyPSA/pypsa-de)**: German energy system model, maintained by [TU Berlin](https://www.tu.berlin/en/ensys) +- **[:earth_africa: PyPSA-CEE](https://github.com/ember-climate/pypsa-cee)**: Central and Eastern Europe power system model, maintained by [Ember](https://ember-energy.org/) +- **[:flag_pl: PyPSA-PL](https://github.com/instrat-pl/pypsa-pl)**: Polish power system model, developed by [Instrat.pl](https://instrat.pl/en/) +- **[:flag_es: PyPSA-Spain](https://github.com/cristobal-GC/pypsa-spain)**: Spanish energy system model, maintained by Polytechnic University of Madrid +- **[:flag_gb: PyPSA-UK](https://github.com/ember-climate/pypsa-uk)**: United Kingdom power system model, maintained by [Ember](https://ember-energy.org/) +- **[:flag_gb: PyPSA-GB](https://github.com/andrewlyden/PyPSA-GB)**: Great Britain power system model, maintained by the University of Edinburgh +- **[:flag_gb: PyPSA-FES](https://github.com/centrefornetzero/pypsa-fes)**: Great Britain power system model, maintained by Octopus Energy's [Centre for Net Zero](https://www.centrefornetzero.org) + +### Asia +- **[:flag_cn: PyPSA-China-PIK](https://github.com/pik-piam/PyPSA-China-PIK)**: Chinese power and heat sector-coupling model, maintained by [PIK](https://www.pik-potsdam.de) based on a [previous version](https://github.com/Xiaowei-Z/PyPSA-China) +- **[:flag_vn: PyPSA-VN](https://github.com/fiasresna/pypsa-vn)**: Vietnamese electricity model, developed by FIAS +- **[:flag_kr: PyPSA-KR](https://energyinnovation.korea.ac.kr/research/code-and-data)**: South Korean energy system model, developed by Korea University +- **[:flag_jp: PyPSA-Japan](https://github.com/smdumlao/demandfingerprint/tree/main/papers/coaldecommissioning)**: Japanese power system model, developed by Samuel Matthew Dumlao +- **[:flag_kz: PyPSA-Kazakhstan](https://github.com/pypsa-meets-earth/pypsa-kz-data)**: Kazakhstan power system model, developed by [Open Energy Transition ](https://www.openenergytransition.org/) +- **[:earth_asia: TZ-APG](https://www.transitionzero.org/products/tz-asean-power-grid-model)**: ASEAN power system model, maintained by [TransitionZero](https://www.transitionzero.org/) +- **[:earth_asia: PyPSA-ASEAN](https://github.com/pypsa-meets-earth/pypsa-asean)**: ASEAN power system model, developed by [pypsa-meets-earth](https://pypsa-meets-earth.github.io/) + +### Americas +- **[:flag_us: PyPSA-USA](https://github.com/pypsa/pypsa-usa)**: United States energy system model, maintained by Stanford University +- **[:flag_br: PyPSA-Brazil](https://gitlab.com/dlr-ve/esy/open-brazil-energy-data/open-brazilian-energy-data)**: Brazilian power system model (see [paper](https://doi.org/10.1038/s41597-023-01992-9)), developed by the German Aerospace Center (DLR) + +### Africa +- **[:flag_za: PyPSA-RSA](https://github.com/MeridianEconomics/pypsa-rsa)**: South-African electricity model, maintained by [Meridian Economics](https://meridianeconomics.co.za/) +- **[:flag_za: PyPSA-ZA](https://github.com/PyPSA/pypsa-za)**: Previous version of PyPSA-RSA + +### Oceania +- **[:flag_au: ISPyPSA](https://github.com/Open-ISP/ISPyPSA)**: Australian capacity expansion model, maintained by [CEEM University of New South Wales](https://ceem.unsw.edu.au) +- **[:flag_nz: PyPSA-NZ](https://github.com/energyLS/pypsa-nz)**: New Zealand energy system model, developed by Leon Schumm + + +## Interactive Tools and Specialized Applications + +- Build your own global zero emission scenario [model.energy](https://model.energy), maintained by [pypsa.org](https://pypsa.org) + - Define your own sector-coupled European scenarios [scenarios](https://model.energy/scenarios/) + - Build your own green energy import supply chain [supply chain](https://model.energy/green-energy-imports/) + - See how the [future](https://model.energy/future/) German energy system might operate with today's weather +- Transport of chemical energy carriers to Germany [TRACE](https://github.com/euronion/trace) +- Transmission grid optimisation [eTraGo](https://github.com/openego/eTraGo) +- Distribution grid optimisation [dDisGo](https://github.com/openego/eDisGo) + +!!! tip "Add your model to the list" + You know another open-source model based on PyPSA or are developing one? Please reach out to us via [GitHub](https://github.com/PyPSA/PyPSA) or [Discord](https://discord.gg/AnuJBk23FU) and we will add it to the list! diff --git a/PyPSA/source/docs/home/users.md b/PyPSA/source/docs/home/users.md new file mode 100644 index 0000000000000000000000000000000000000000..089426d4cdbdf0278d315611d9fc06c2a3210260 --- /dev/null +++ b/PyPSA/source/docs/home/users.md @@ -0,0 +1,156 @@ + + +# Users + +The following universities, research institutes, companies, governmental and non-governmental organisations are known to have either used PyPSA in the past, or to continue to use PyPSA. This list is necessarily incomplete, since we cannot track who uses PyPSA and we only find out who our users are when they contact the main developers of PyPSA. + +!!! note "Research Publications" + + Research publications that cite the [PyPSA research paper](https://doi.org/10.5334/jors.188) can be found on [:fontawesome-brands-google-scholar: Google Scholar](https://scholar.google.com/scholar?oi=bibs&hl=en&cites=11241966939032736670&as_sdt=5). + +!!! note "User Meetings on YouTube" + + The PyPSA user meeting recordings on [:material-youtube: YouTube](https://www.youtube.com/@pypsa-official) give further insight into applications in the energy sector. + +## Universities + +- **[:flag_de: Technische Universität Berlin (TUB)](https://www.ensys.tu-berlin.de/)**, where PyPSA development continues since 2021, published multiple research papers, for example: +[doi:10.1038/s41467-025-60652-1](https://doi.org/10.1038/s41467-025-60652-1), [doi:10.1016/j.eneco.2025.108483](https://doi.org/10.1016/j.eneco.2025.108483), [doi:10.1038/s41560-025-01752-6](https://doi.org/10.1038/s41560-025-01752-6), [doi:10.1038/s41597-025-04550-7](https://doi.org/10.1038/s41597-025-04550-7), [doi:10.1016/j.adapen.2024.100202](https://doi.org/10.1016/j.adapen.2024.100202), [doi:10.1088/1748-9326/ad2239](https://doi.org/10.1088/1748-9326/ad2239), [doi:10.1016/j.joule.2023.10.001](https://doi.org/10.1016/j.joule.2023.10.001), [doi:10.1016/j.joule.2023.06.016](https://doi.org/10.1016/j.joule.2023.06.016). + +- **[:flag_de: Karlsruhe Institute of Technology (KIT)](https://www.kit.edu/)**, where PyPSA development took place between 2018 and 2021, published multiple research papers, for example: [doi:10.1016/j.energy.2018.06.222](https://doi.org/10.1016/j.energy.2018.06.222), [doi:10.1109/EEM.2019.8916411](https://www.doi.org/10.1109/EEM.2019.8916411), [doi:10.1016/j.esr.2018.08.012](https://doi.org/10.1016/j.esr.2018.08.012), [doi:10.1016/j.epsr.2020.106690](https://doi.org/10.1016/j.epsr.2020.106690), [doi:10.1145/3396851.3397688](https://doi.org/10.1145/3396851.3397688), [doi:10.1016/j.apenergy.2021.116726](https://doi.org/10.1016/j.apenergy.2021.116726), [doi:10.1186/s42162-022-00187-7](https://doi.org/10.1186/s42162-022-00187-7) + +- **[:flag_dk: Aarhus University](https://www.au.dk/)** in Denmark published multiple research papers, for example: [doi:10.1016/j.apenergy.2019.02.009](https://doi.org/10.1016/j.apenergy.2019.02.009), [doi:10.1016/j.apenergy.2018.12.016](https://doi.org/10.1016/j.apenergy.2018.12.016), [doi:10.1002/pip.3198](https://doi.org/10.1002/pip.3198), [doi:10.1016/j.enconman.2019.111977](https://doi.org/10.1016/j.enconman.2019.111977), [doi:10.1038/s41467-020-20015-4](https://doi.org/10.1038/s41467-020-20015-4), [doi:10.1016/j.joule.2022.04.016](https://doi.org/10.1016/j.joule.2022.04.016) + +- **[:flag_dk: Technical University of Denmark (DTU)](https://www.dtu.dk/)** in Denmark published multiple research papers, for example: [doi:10.1038/s41467-025-61492-9](https://doi.org/10.1038/s41467-025-61492-9) and [doi:10.1088/1748-9326/adc290](https://doi.org/10.1088/1748-9326/adc290). + +- **[:flag_de: Frankfurt Institute of Advanced Studies (FIAS)](https://fias.science/)** (where PyPSA was initially developed) published multiple research papers, for example: [doi:10.1016/j.energy.2017.06.004](https://doi.org/10.1016/j.energy.2017.06.004), [doi:10.1109/EEM.2017.7982024](https://doi.org/10.1109/EEM.2017.7982024), [doi:10.1016/j.energy.2018.08.070](https://doi.org/10.1016/j.energy.2018.08.070), [doi:10.1016/j.apenergy.2018.09.084](https://doi.org/10.1016/j.apenergy.2018.09.084), [doi:10.1016/j.epsr.2017.12.034](https://doi.org/10.1016/j.epsr.2017.12.034) + +- **[:flag_us: Stanford University](https://www.stanford.edu/)** in the United States published a research paper on PyPSA-USA: [doi:10.2139/ssrn.5029120](https://dx.doi.org/10.2139/ssrn.5029120) + +- **[:flag_nl: TU Delft](https://www.tudelft.nl/)** in the Netherlands published multiple research papers: [doi:10.1016/j.eneco.2018.04.037](https://doi.org/10.1016/j.eneco.2018.04.037), [doi:10.1016/j.energy.2017.02.111](https://doi.org/10.1016/j.energy.2017.02.111) + +- **[:flag_se: Chalmers University](https://www.chalmers.se/)** in Sweden published multiple research papers, for example: [doi.org:10.1016/j.apenergy.2022.120016](https://doi.org/10.1016/j.apenergy.2022.120016) + +- **[:flag_no: UiT The Arctic University of Norway (UiT)](https://uit.no/)** and the **[:flag_no: University of Oslo (UiO)](https://www.uio.no/)** in Norway used PyPSA for studying near-optimal spaces in renewable electricity system planning: [doi.org:10.1016/j.eneco.2022.106496](https://doi.org/10.1016/j.eneco.2022.106496). + +- **[:flag_jp: Kyoto University](https://www.kyoto-u.ac.jp/en)** in Japan uses PyPSA to model several high PV penetration scenarios in Kyushu, where PV capacity has been rapidly growing since 2012 and PV curtailment started in 2018: [doi:10.3390/en14092389](https://doi.org/10.3390/en14092389), [doi:10.3390/en14154496](https://doi.org/10.3390/en14154496) + +- **[:flag_it: University of Pisa](https://www.unipi.it/index.php/english/)** uses PyPSA as part of the [PyPSA-meets-Earth initiative](https://pypsa-meets-earth.github.io/) in [doi:10.1016/j.apenergy.2023.121096](https://doi.org/10.1016/j.apenergy.2023.121096) + +- **[:flag_ch: University of Geneva](https://www.unige.ch/)** in Switzerland published multiple research papers, for example: [doi:10.1038/s41467-020-18812-y](https://doi.org/10.1038/s41467-020-18812-y) + +- **[:flag_gb: University of Oxford](https://www.ox.ac.uk/)** in a [study on green ammonia imports](https://iopscience.iop.org/article/10.1088/2753-3751/ad785d) as a supplement to long-duration energy storage in the UK and with the **[:flag_es: University of Sevilla](https://www.us.es/)** in a [study on the role of regionally resolved heat pump demand](https://doi.org/10.1016/j.apenergy.2023.122331) in power system design. + +- **[:flag_de: Energiewirtschaftliches Institut zu Köln (EWI)](https://www.ewi.uni-koeln.de/de/)** pubished a working paper using PyPSA: [Working Paper, No 17/09](https://www.ewi.research-scenarios.de/cms/wp-content/uploads/2017/09/EWI_WP_17-09_Build_Wind_Capacities_at_Windy_Locations.pdf) + +- **[:flag_de: University of Flensburg](https://www.znes-flensburg.de/)** in Flensburg, Germany as part of the [open-Ego](https://github.com/openego) project with multiple publications: [doi:10.3390/en12112091](https://doi.org/10.3390/en12112091), [doi:10.1088/1742-6596/977/1/012007](https://doi.org/10.1088/1742-6596/977/1/012007), [doi:10.1016/j.apenergy.2021.116936](https://doi.org/10.1016/j.apenergy.2021.116936) + +- **[:flag_co: EIA University](https://www.eia.edu.co/)** in Medellin (Columbia) uses PyPSA for modelling the Columbian power system in a joint research project with South American transmission company [ISA](http://www.isa.co/) + +- **[:flag_ie: Dublin City University](https://dcu.ie/)** and the **[:flag_ie: SFI Insight Centre for Data Analytics](https://www.insight-centre.org/)**: use PyPSA for the [OESM project](https://sites.google.com/a/dcu.ie/dcuecrn/projects/oesm-ie) modelling the Irish energy system. + +- **[:flag_de: University of Freiburg](https://uni-freiburg.de/)** at [INATECH](https://www.inatech.uni-freiburg.de/en) + +- **[:flag_de: Hochschule Offenburg](https://www.hs-offenburg.de/)** at [INES](https://www.ines.hs-offenburg.de/) + +- **[:flag_de: Ostbayerische Technische Hochschule Regensburg](https://www.oth-regensburg.de/)** at the [Forschungsstelle für Energienetze und Energiespeicher (FENES)](https://www.fenes.net/) + +- **[:flag_de: Technische Hochschule Köln](https://www.th-koeln.de/)** used PyPSA for [a study on the role of renewables, storage and sector-coupling flexibilities in Germany at varying carbon-dioxide emission levels](https://doi.org/10.3390/su141610379). + +- **[:flag_nz: University of Canterbury](https://www.canterbury.ac.nz/)** at the [Sustainable Energy Research Group (SERG)](https://www.canterbury.ac.nz/research/about-uc-research/research-groups-and-centres/sustainable-energy-research-group) uses PyPSA for energy transition in New Zealand. + +- **[:scotland: The University of Edinburgh](https://www.ed.ac.uk/)** introduced PyPSA-GB, an open-source model of Great Britain's power system for simulating future energy scenarios: [doi.org/10.1016/j.esr.2024.101375](https://doi.org/10.1016/j.esr.2024.101375) + +- **[:flag_kr: Korea University](https://www.korea.ac.kr/sites/ko/index.do)** at the [Energy Innovation Lab](https://energyinnovation.korea.ac.kr/) uses PyPSA-Earth to support development of the country's long-term energy strategy. + +- **[:flag_th: Asian Institute of Technology](https://ait.ac.th/)** in the [South and South-East Asia Multidisciplinary Applied Research Network on Transforming Societies of Global South (SMARTS)](https://ait.ac.th/centre/smartscenter/) and the **[:flag_bd: International University of Business Agriculture and Technology (IUBAT), Dhaka](https://eee.iubat.edu/)** developed an adaptation of PyPSA-Earth, called PyPSA-BD to support the energy transition in Bangladesh [](https://doi.org/10.1016/j.ref.2024.100655) + + +## Research Institutes + +- **[:flag_eu: Joint Research Centre (JRC)](https://joint-research-centre.ec.europa.eu/index_en)** of the [European Commission](https://ec.europa.eu/info/index_en) converted METIS/PRIMES scenarios for the [Fit for 55 package to PyPSA networks](https://zenodo.org/record/7065568#.YygkDKRByMo) in 2022 and used PyPSA for further research: [doi:10.3390/en15124233](https://doi.org/10.3390/en15124233) in 2022. In 2023, JRC used PyPSA for the study ["MODECO – Modelling study on the role of energy communities in the energy transition"](https://publications.jrc.ec.europa.eu/repository/handle/JRC132896) and in 2024 for the study ["Redispatch and Congestion Management"](https://publications.jrc.ec.europa.eu/repository/handle/JRC137685) + +* **[:flag_in: The Energy and Resources Institute (TERI)](https://www.teriin.org/)** in New Delhi (India) used PyPSA for several studies. In 2020 for a government-supported study of the Indian power system in 2030, see [Renewable Power Pathways Report](https://www.teriin.org/sites/default/files/2020-07/Renewable-Power-Pathways-Report.pdf), in 2021 for a study on [A Model-Based Assessment of Variable Renewable Grid Integration Costs in India](https://www.teriin.org/sites/default/files/2021-02/A_Modal-Based_Assessment_Report_0.pdf) and in 2024 for a study on [India's Electricity Transition Pathways to 2050: Scenarios and Insights](https://teriin.org/sites/default/files/2024-02/Power_Sector_2050_Report.pdf) + +- **[:flag_za: Council for Scientific and Industrial Research (CSIR)](https://www.csir.co.za/)** in South Africa in a research publication [arXiv:1710.11199](https://arxiv.org/abs/1710.11199) + +- **[:flag_de: Forschungsstelle für Energiewirtschaft (FfE)](https://www.ffe.de/)** uses PyPSA for their [HyPTraDe](https://www.ffe.de/tools/hyptrade-global-hydrogen-modellkette/) global hydrogen model chain. + +- **[:flag_de: Reiner Lemoine Institute (RLI)](https://reiner-lemoine-institut.de/)** in Berlin, Germany within the consortial project [open-Ego](https://github.com/openego) with multiple publications: [doi:10.3390/en12112091](https://doi.org/10.3390/en12112091), [doi:10.1088/1742-6596/977/1/012007](https://doi.org/10.1088/1742-6596/977/1/012007), [doi:10.1016/j.apenergy.2021.116936](https://doi.org/10.1016/j.apenergy.2021.116936) + +- **[:flag_de: DLR Institute of Networked Energy Systems](https://www.dlr.de/ve/desktopdefault.aspx/tabid-12472/21440_read-49440/)** in Oldenburg, Germany published a research paper [doi:10.1016/j.apenergy.2020.114523](https://doi.org/10.1016/j.apenergy.2020.114523) and a [dataset publication](https://doi.org/10.1038/s41597-023-01992-9) for the Brazilian power system. + +- **[:flag_de: Fraunhofer Institute for Energy Infrastructures and Geothermal Systems (IEG)](https://www.ieg.fraunhofer.de/)**: uses PyPSA-Eur for infrastructure analysis in Europe, e.g. in the [European Hydrogen Infrastructure Planning report](https://www.wasserstoff-leitprojekte.de/lw_resource/datapool/systemfiles/elements/files/148FFEF003673B67E0637E695E8625E5/live/document/20240321_European_Hydrogen_Infrastructure_Planning.pdf) on European hydrogen infrastructure planning. + +- **[:flag_de: Fraunhofer Institute for Solar Energy Systems (ISE)](https://www.ise.fraunhofer.de/)**: uses PyPSA for grid modelling, for example in a research paper: [doi:/10.1016/j.epsr.2020.106349](https://doi.org/10.1016/j.epsr.2020.106349) + +- **[:flag_de: Fraunhofer-Institut für Energiewirtschaft und Energiesystemtechnik (IEE)](https://www.iee.fraunhofer.de/)** used PyPSA for an investigation of power-to-X in South Africa together with **[:flag_za: Stellenbosch University](http://www.sun.ac.za/english)** in [this project](https://www.iee.fraunhofer.de/en/research_projects/search/2023/ptx-south-africa.html). + +- **[:flag_se: RISE Research Institutes of Sweden AB (RISE)](https://www.ri.se/)** for energy systems research in the [RESILIENT project](https://resilient-project.github.io/). + +- **[:flag_de: Forschungszentrum Jülich (FZJ)](https://www.fz-juelich.de/)** used PyPSA for network calculations. + +## Companies + +* **[:flag_de: TransnetBW](https://www.transnetbw.de/)**, the electricity transmission system operator in southwest Germany, **[:flag_de: ONTRAS](https://www.ontras.com/)**, the gas transmission system operator in eastern Germany and **[:flag_de: d-fine](https://www.d-fine.com/)**, a consultancy firm, used PyPSA-Eur-Sec for a study in 2020 of the grid requirements in 2050 with a 90% reduction of carbon dioxide emissions in electricity, buildings and transport, see [Stromnetz 2050](https://www.transnetbw.de/de/stromnetz2050/), for an article in a 2021 issue of Energiewirtschaftliche Tagesfragen [Die Rolle von Wasserstoff in einem klimaneutralen europäischen Energiesystem – eine modellbasierte Analyse bis 2050](https://www.d-fine.com/fileadmin/user_upload/Wasserstoff_et_0102-2021.pdf), and for their 2022 study [Energy System 2050 - Towards a decarbonised Europe](https://www.energysystem2050.net/). + +* **[:flag_eu: ENTSO-E (European Network of Transmission System Operators for Electricity)](https://www.entsoe.eu/)** launched a research and innovation project to develop a complementary open-source tool in PyPSA for [Scenario Building and Cost-Benefit Analysis within the TYNDP process](https://www.linkedin.com/posts/entso-e_energytransition-opensource-innovation-activity-7293296246813851649-2ynL). + +* **[:flag_at: Austrian Power Grid (APG)](https://www.apg.at/)**, the Austrian TSO, uses PyPSA for its system vision of the Austrian energy system up to 2050, as shown in [this talk](https://www.tugraz.at/fileadmin/user_upload/tugrazExternal/738639ca-39a0-4129-b0f0-38b384c12b57/files/pr/Session_A3/132_PR_Spindler.pdf) and [this project description](https://www.apg.at/projekte/zusammen-2040/). + +- **[:flag_at: Austrian Gas Grid Management AG (AGGM)](https://www.aggm.at/)** develops and maintains a high-resolution, sector-coupled energy system model based on PyPSA-DE and PyPSA-EUR. The model is used to analyse long-term energy system projections up to 2050 and serves as a supplementary tool for AGGM’s bi-annual report, the Langfristige Integrierte Planung (LFiP). + +- **[:flag_nl: TenneT](https://www.tennet.eu/)**, the Dutch TSO, used PyPSA for [research](https://arxiv.org/abs/2104.13047) on ancillary services acquisition using agent-based modelling. + +* **[:flag_za: Meridian Economics](https://meridianeconomics.co.za/)**, a consultancy based in South Africa, uses PyPSA ecosystem tools and PyPSA-Earth for South Africa. Studies published in June 2022 were on [load shedding in 2021](https://meridianeconomics.co.za/wp-content/uploads/2022/06/Resolving-Load-Shedding-Part-A-2021-analysis-01.pdf) and [a plan to resolve load shedding](https://meridianeconomics.co.za/wp-content/uploads/2022/06/Resolving-Load-Shedding-Part-B-The-Game-Plan-01.pdf) + +* **[:flag_de: Energynautics GmbH](https://energynautics.com/)**, a grid integration consultancy in Germany, used PyPSA for a study of decentral electricity and heat integration in power grids in a study for the Rhineland-Palatinate state government in 2021: [Pilotprojekt Dezentralisierung: Stärkere Dezentralisierung des bundesdeutschen Strom-Wärme-Systems: Rechtliche und organisatorische Rahmenbedingungen sowie infrastrukturelle Folgen](https://mkuem.rlp.de/fileadmin/mulewf/Themen/Energie_und_Strahlenschutz/Energie/Endbericht_Pilotprojekt_Dezentralisierung_2021_v2.pdf) ([summary in English](https://energynautics.com/en/decentrality-study-for-rhineland-palatinate-completed/)) + +* **[:flag_nl: Witteveen+Bos](https://www.witteveenbos.com/about-us/)**, a Dutch engineering and consultancy company, used PyPSA in 2022 to consult the Ministry of Economic Affairs and Climate Policy in Netherlands: [Scenario study on nuclear energy](https://eriskgroup.com/wp-content/uploads/2022/10/Scenario-study-nuclear-energy-Conclusions-and-Summary.pdf) + +* **[:flag_sa: Saudi Aramco](https://www.aramco.com/)** used PyPSA for an assessment of renewables integration in 2019, see [arXiv:1709.03761](https://arxiv.org/abs/1709.03761). + +* **[:flag_gb: Shell](https://www.shell.com/)** uses PyPSA for electricity market simulations in Europe. + +* **[:flag_co: ISA](http://www.isa.co/)**, a South American transmission company, uses PyPSA for modelling the Columbian power system in a joint research project with [EIA University](https://www.eia.edu.co/) in Medellin, Columbia. + +* **[:flag_de: Energy Consulting Christian Meyer](http://www.energy-consulting-meyer.de/)** + +* **[:flag_us: spire](https://www.spireenergy.com/)** + + +## Governmental Organisation + +* **[:globe_with_meridians: International Energy Agency (IEA)](https://www.iea.org/)** uses PyPSA within their [Global Energy and Climate Model](https://www.iea.org/reports/global-energy-and-climate-model). A 2024 report [Managing the Seasonal Variability of Electricity Demand and Supply](https://www.iea.org/reports/managing-the-seasonal-variability-of-electricity-demand-and-supply) used PyPSA to explore how seasonal variations in both demand and supply affect electricity system operations in various world regions in 2050. + +* **[:flag_eu: The EU Agency for the Cooperation of Energy Regulators (ACER)](https://www.acer.europa.eu/)** is building a PyPSA solution for the [EU-wide flexibility assessment platform](https://www.acer.europa.eu/sites/default/files/documents/en/The_agency/Organisation/Expert_Groups/EG_Flexibility/Flexibility_EG_Open_Letter_2025.pdf). + +* **[:flag_de: GIZ](https://www.giz.de/en/html/index.html)**, the German Agency for International Cooperation (Deutsche Gesellschaft für Internationale Zusammenarbeit), published and supported several studies based on PyPSA for the [integration of renewables into the Vietnamese power system](http://gizenergy.org.vn/media/app/media/Research%20Cooperation%20Project%20Fact%20Sheet_final.pdf), [power sector analysis for Thailand](https://static.agora-energiewende.de/fileadmin/Partnerpublikationen/2021/CASE_Beyond_Net_Zero_Empowering_Climate_Mitigation/Towards_a_collective_vision_of_Thai_energy_transition_full_report.pdf) within the CASE programme, [the future of the Indonesian power system](https://energynautics.com/en/pypsa/) within the 1000 Islands programme, and the [Brazilian electricity system](https://www.energy-proceedings.org/wp-content/uploads/2022/03/Y.Deng_PyPSA-Brazil_ICAE2021_final_revised.pdf). + +* **[:flag_ca: Canada Energy Regulator (CER)](https://www.cer-rec.gc.ca/en/)**, an agency of the Government of Canada, used PyPSA for its [Canada's Energy Future 2023 report](https://www.cer-rec.gc.ca/en/data-analysis/canada-energy-future/2023/appendix-3/). + +## Non-Governmental and Non-Profit Organisations + +* **[:flag_de: Open Energy Transition (OET)](https://openenergytransition.org/)**, a non-profit software company and environmental think tank, uses, develops and provides commercial support and training for PyPSA, see [list of projects](https://openenergytransition.org/projects.html). + +* **[:flag_gb: TransitionZero](https://www.transitionzero.org)**, a non-profit climate tech company uses PyPSA for a model of the [ASEAN power grid](https://www.transitionzero.org/products/tz-asean-power-grid-model) and analysis of [24/7 CfE research in Asia](https://www.transitionzero.org/cfe). + +* **[:flag_gb: Centre for Net Zero](https://www.centrefornetzero.org/)**, a research unit founded by [Octopus Energy](https://octopus.energy), used PyPSA in a [report](https://www.centrefornetzero.org/work/whole-system-modelling-of-future-energy-scenarios-domestic-gb-demand-flexibility/) to investigate the role of demand flexibility in future UK energy systems. + +* **[:flag_gb: Ember](https://ember-climate.org/)**, a UK-based climate NGO, published a study in September 2022 on a [gas phase-out in the UK by 2030](https://ember-climate.org/insights/research/uk-gas-power-phase-out/) based on their open model [PyPSA-UK](https://github.com/ember-climate/pypsa-uk) + +* **[:flag_de: Agora Energiewende](https://www.agora-energiewende.de/)**, a think tank and policy institute in Germany, uses PyPSA for energy system integration studies, see for example this study from 2020: [Minimizing the cost of integrating wind and solar power in Japan](https://www.agora-energiewende.de/en/publications/minimizing-the-cost-of-integrating-wind-and-solar-power-in-japan/) + +* **[:flag_us: The Rocky Mountain Institute (RMI)](https://rmi.org/)**, a non-profit organization in the United States focused on a zero-carbon future, used PyPSA for analysing the levelised cost of hydrogen in different parts of the world in a study from 2021: [Fueling the Transition: Accelerating Cost-Competitive Green Hydrogen](https://rmi.org/insight/fueling-the-transition-accelerating-cost-competitive-green-hydrogen/) + +* **[:flag_de: Climate Analytics](https://climateanalytics.org/)**, a non-profit climate science and policy institute, uses PyPSA for energy system studies, see for example a study on [Employment opportunities from a coal-to-renewables transition in South Korea](https://climateanalytics.org/publications/2021/employment-opportunities-from-a-coal-to-renewables-transition-in-south-korea/) and a [study on the Philippines](https://climateanalytics.org/publications/a-15c-future-is-possible-getting-fossil-fuels-out-of-the-philippine-power-sector). + +* **[:flag_pl: Instrat](https://instrat.pl/en/)**, a think-tank focused on public policy in Poland, built PyPSA-PL and published reports in 2021 including [What's next after coal? RES potential in Poland](http://instrat.pl/wp-content/uploads/2021/06/Instrat-What-next-after-coal-v.1.2.pdf) and [Achieving the goal: Coal phase-out in the Polish power sector](https://instrat.pl/en/coal-phase-out/). + +* **[:flag_eu:/:flag_us: RAND Europe](https://www.rand.org/randeurope.html)** used PyPSA in a 2024 report [The use of AI for improving energy security](https://www.rand.org/pubs/research_reports/RRA2907-2.html) to explore the extent to which different AI applications can improve energy security in Europe. + +* **[:flag_gb: ClimateXChange](https://www.climatexchange.org.uk/)** commissioned a report for the Scottish Government on [Electricity system security of supply in Scotland](https://www.climatexchange.org.uk/projects/electricity-system-security-of-supply-in-scotland/) using PyPSA-GB. diff --git a/PyPSA/source/docs/hooks/cleanup.py b/PyPSA/source/docs/hooks/cleanup.py new file mode 100644 index 0000000000000000000000000000000000000000..1abccacf7c4b18e76881b1cd1101c2fc52b783d3 --- /dev/null +++ b/PyPSA/source/docs/hooks/cleanup.py @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import re + +try: + import griffe + + GRIFFE_AVAILABLE = True +except ImportError: + GRIFFE_AVAILABLE = False + + +def on_page_markdown(markdown, page, config, files): + # Remove # doctest: +SKIP from code blocks + pattern = r"(``` py.*?```)" + + def remove_doctest_skip(match): + code_block = match.group(0) + # Remove the doctest comments + code_block = re.sub(r"\s*# doctest: \+SKIP", "", code_block) + code_block = re.sub(r"\s*# doctest: \+ELLIPSIS", "", code_block) + # Remove entire lines ending with # docs-hide + code_block = re.sub(r"^.*# docs-hide\s*$", "", code_block, flags=re.MULTILINE) + code_block = re.sub(r"^.*\s*$", "", code_block, flags=re.MULTILINE) + return code_block + + markdown = re.sub(pattern, remove_doctest_skip, markdown, flags=re.DOTALL) + return markdown diff --git a/PyPSA/source/docs/hooks/dynamic_inspect.py b/PyPSA/source/docs/hooks/dynamic_inspect.py new file mode 100644 index 0000000000000000000000000000000000000000..c80ffb509aebafd4d8cc0d81dfd0930ce517438b --- /dev/null +++ b/PyPSA/source/docs/hooks/dynamic_inspect.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +""" +Retrieve docstrings from runtime objects instead of static analysis (default). + +See https://mkdocstrings.github.io/griffe/guide/users/how-to/selectively-inspect/ +""" + +import griffe + +logger = griffe.get_logger("griffe_inspect_specific_objects") + + +class InspectSpecificObjects(griffe.Extension): + """An extension to inspect just a few specific objects.""" + + def __init__(self, objects: list[str]) -> None: + self.objects = objects + + def on_instance(self, *, obj: griffe.Object, **kwargs) -> None: + if obj.path not in self.objects: + return + logger.info("Using InspectSpecificObjects for %s", obj.path) + + try: + runtime_obj = griffe.dynamic_import(obj.path) + except ImportError as error: + logger.warning("Could not import %s: %s", obj.path, error) + return + + if obj.docstring: + obj.docstring.value = runtime_obj.__doc__ + else: + obj.docstring = griffe.Docstring(runtime_obj.__doc__) diff --git a/PyPSA/source/docs/hooks/path_aliases.py b/PyPSA/source/docs/hooks/path_aliases.py new file mode 100644 index 0000000000000000000000000000000000000000..cf7b8596b0b7a9c7be92bd532fd3adc10805fbe7 --- /dev/null +++ b/PyPSA/source/docs/hooks/path_aliases.py @@ -0,0 +1,118 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +""" +Path aliases hook for MkDocs to enable simplified cross-references. + +This module provides MkDocs hooks to register simplified path aliases with autorefs, +allowing cross-references like [pypsa.get_option][] to work while keeping internal +paths unchanged. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from mkdocs.config.defaults import MkDocsConfig + from mkdocs.structure.pages import Page + +# Path mappings - real path to simplified display name +PATH_MAPPINGS = { + # Options API + "pypsa._options.option_context": "pypsa.option_context", + "pypsa._options.OptionsNode.get_option": "pypsa.get_option", + "pypsa._options.OptionsNode.set_option": "pypsa.set_option", + "pypsa._options.OptionsNode.reset_option": "pypsa.reset_option", + "pypsa._options.OptionsNode": "pypsa.options", + # Network Mixins + "pypsa.consistency.NetworkConsistencyMixin": "pypsa.Network", + "pypsa.network.index.NetworkIndexMixin": "pypsa.Network", + "pypsa.network.components.NetworkComponentsMixin": "pypsa.Network", + "pypsa.network.transform.NetworkTransformMixin": "pypsa.Network", + "pypsa.network.descriptors.NetworkDescriptorsMixin": "pypsa.Network", + "pypsa.network.io.NetworkIOMixin": "pypsa.Network", + "pypsa.network.power_flow.NetworkPowerFlowMixin": "pypsa.Network", + # Network Accesors + "pypsa.optimization.OptimizationAccessor": "pypsa.Network.optimize", + "pypsa.clustering.ClusteringAccessor": "pypsa.Network.cluster", + "pypsa.statistics.StatisticsAccessor": "pypsa.Network.statistics", + "pypsa.plot.accessor.PlotAccessor": "pypsa.Network.plot", + # Plot Accessors + "pypsa.plot.statistics.plotter": "pypsa.plot", + # SubNetwork + "pypsa.networks.SubNetwork": "pypsa.SubNetwork", + # Components + "pypsa.components.Components": "pypsa.Components", + # Groupers + "pypsa.statistics.grouping.Groupers": "pypsa.statistics.Groupers", +} + + +def on_post_page(output: str, *, page: Page, config: MkDocsConfig, **kwargs): + """Post-process the rendered HTML to replace path displays.""" + for original_path, simplified_path in PATH_MAPPINGS.items(): + output = output.replace(original_path, simplified_path) + return output + + +def on_env(env, *, config: MkDocsConfig, **kwargs): + """Register path aliases with autorefs for cross-references.""" + # Get the autorefs plugin + autorefs_plugin = None + for plugin_name, plugin_instance in config.plugins.items(): + if plugin_name == "autorefs": + autorefs_plugin = plugin_instance + break + + if not autorefs_plugin: + return env + + # Register aliases for commonly used methods/attributes + common_suffixes = [ + "__getattr__", + "__setattr__", + "__init__", + "__call__", + "get", + "set", + "add", + "remove", + "update", + "clear", + "load", + "save", + "export", + "import_", + "describe", + "to_dict", + "from_dict", + "copy", + "keys", + "values", + "items", + ] + + for original_path, simplified_path in PATH_MAPPINGS.items(): + # Register the base class/module alias + try: + original_url, title = autorefs_plugin.get_item_url(original_path) + autorefs_plugin.register_url(simplified_path, original_url) + print(f"Registered alias: {simplified_path} -> {original_url}") + except KeyError: + print(f"Could not find original URL for: {original_path}") + + # Register common method/attribute aliases + for suffix in common_suffixes: + full_original = f"{original_path}.{suffix}" + full_simplified = f"{simplified_path}.{suffix}" + try: + original_url, title = autorefs_plugin.get_item_url(full_original) + autorefs_plugin.register_url(full_simplified, original_url) + print(f"Registered alias: {full_simplified} -> {original_url}") + except KeyError: + # This is expected for many methods that don't exist + pass + + return env diff --git a/PyPSA/source/docs/hooks/shortcodes.py b/PyPSA/source/docs/hooks/shortcodes.py new file mode 100644 index 0000000000000000000000000000000000000000..3323f01935d3110481fe398eddee6f08365b1053 --- /dev/null +++ b/PyPSA/source/docs/hooks/shortcodes.py @@ -0,0 +1,421 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +""" +Shortcode replacements for MkDocs Material theme and Griffe extension. + +This module provides both MkDocs hooks for processing shortcodes in markdown pages +and a Griffe extension for processing shortcodes in API docstrings. +""" + +from __future__ import annotations + +import logging +import posixpath +import re +from re import Match +from typing import TYPE_CHECKING + +try: + import griffe + + GRIFFE_AVAILABLE = True +except ImportError: + GRIFFE_AVAILABLE = False + +if TYPE_CHECKING: + from mkdocs.config.defaults import MkDocsConfig + from mkdocs.structure.files import File, Files + from mkdocs.structure.pages import Page + + +# ----------------------------------------------------------------------------- +# Shared Helper Functions +# ----------------------------------------------------------------------------- + + +def clean_file_path(path: str) -> str: + """Clean file paths for display.""" + import os + + # Remove file extension + name = os.path.splitext(path)[0] + + # Get just the filename if it's a path + name = os.path.basename(name) + + # Handle special case where folder and file have same name (e.g., components/components) + parts = path.split("/") + if len(parts) > 1 and parts[-1].startswith(parts[-2]): + name = parts[-2] + + # Replace hyphens and underscores with spaces + name = name.replace("-", " ").replace("_", " ") + + # Capitalize each word + name = " ".join(word.capitalize() for word in name.split()) + + return name + + +def _badge(text: str = "", type: str = "") -> str: + """Create a badge with optional text and type.""" + classes = f"mdx-badge mdx-badge--{type}" if type else "mdx-badge" + return "".join( + [ + f'', + *([f'{text}'] if text else []), + "", + ] + ) + + +# ----------------------------------------------------------------------------- +# MkDocs Hooks +# ----------------------------------------------------------------------------- + + +def _process_shortcodes(text: str, page: Page, files: Files): + """Process shortcodes in text for MkDocs pages.""" + + def replace(match: Match): + type, args = match.groups() + args = args.strip() + + # Check if this is a badge variant + is_badge = type.startswith("badge-") + if is_badge: + type = type[6:] # Remove "badge-" prefix + + # Handle different shortcode types + if type == "version": + return ( + _badge_for_version(args, page, files) + if is_badge + else _link_for_version(args, page, files) + ) + elif type == "pr": + return _badge_pr(args) if is_badge else _link_pr(args) + elif type == "guide": + return ( + _badge_guide(args, page, files) + if is_badge + else _link_guide(args, page, files) + ) + elif type == "example": + # Example is an alias for guide + return ( + _badge_guide(args, page, files) + if is_badge + else _link_guide(args, page, files) + ) + elif type == "api": + return ( + _badge_api(args, page, files) + if is_badge + else _link_api(args, page, files) + ) + + # Otherwise, raise an error + raise RuntimeError(f"Unknown shortcode: {type}") + + return re.sub(r"", replace, text, flags=re.I | re.M) + + +def on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files: Files): + """Process shortcodes in the main markdown content.""" + return _process_shortcodes(markdown, page, files) + + +def on_env(env, config: MkDocsConfig, files: Files): + """Hook into the Jinja2 environment to add a filter for processing shortcodes.""" + + def process_docstring_shortcodes(text): + # Create a dummy page object if needed for docstring processing + # This allows shortcodes to work even when page context isn't available + try: + # Try to get the current page from context if available + page = None + # Process the shortcodes + return _process_shortcodes(text, page, files) + except: + # If processing fails, return original text + return text + + # Add filter to Jinja2 environment for use in templates + env.filters["shortcodes"] = process_docstring_shortcodes + return env + + +# ----------------------------------------------------------------------------- +# Helper functions for MkDocs +# ----------------------------------------------------------------------------- + + +def _resolve_path(path: str, page: Page, files: Files): + """Resolve path of file relative to given page.""" + original_path = path + path, anchor, *_ = f"{path}#".split("#") + file = files.get_file_from_path(path) + if file is None: + logging.warning(f"File not found: {path} (referenced in {page.file.src_uri})") + return original_path # Return original path unchanged + path = _resolve(file, page) + return "#".join([path, anchor]) if anchor else path + + +def _resolve(file: File, page: Page): + """Resolve path of file relative to given page.""" + try: + path = posixpath.relpath(file.src_uri, page.file.src_uri) + return posixpath.sep.join(path.split(posixpath.sep)[1:]) + except Exception as e: + raise Exception(f"Cannot resolve path for {file} relative to {page}") from e + + +# ----------------------------------------------------------------------------- +# Badge creation functions for MkDocs +# ----------------------------------------------------------------------------- + + +def _badge_for_version(text: str, page: Page, files: Files): + """Create badge for version.""" + path = f"release-notes.md#{text}" + icon = "material-tag-outline" + href = _resolve_path(path, page, files) + return _badge(f"[:{icon}: {text}]({href} 'Minimum Version')") + + +def _link_for_version(text: str, page: Page, files: Files): + """Create plain link for version.""" + path = f"release-notes.md#{text}" + icon = "material-tag-outline" + href = _resolve_path(path, page, files) + return f"[:{icon}: {text}]({href} 'Minimum Version')" + + +def _badge_pr(text: str): + """Create badge for pull request.""" + icon = "octicons-git-pull-request-16" + href = f"https://github.com/PyPSA/PyPSA/pull/{text}/" + return _badge(f"[:{icon}: {text}]({href} 'View Pull Request')") + + +def _link_pr(text: str): + """Create plain link for pull request.""" + icon = "octicons-git-pull-request-16" + href = f"https://github.com/PyPSA/PyPSA/pull/{text}/" + return f"[:{icon}: {text}]({href} 'View Pull Request')" + + +def _badge_guide(text: str, page: Page, files: Files): + """Create badge for user guide.""" + # Always start from user-guide directory + path = f"user-guide/{text}" + icon = "material-bookshelf" + href = _resolve_path(path, page, files) + display_text = clean_file_path(text) + return _badge(f"[:{icon}: {display_text}]({href} 'View User Guide')") + + +def _link_guide(text: str, page: Page, files: Files): + """Create plain link for user guide.""" + # Always start from user-guide directory + path = f"user-guide/{text}" + icon = "material-bookshelf" + href = _resolve_path(path, page, files) + display_text = clean_file_path(text) + return f"[:{icon}: {display_text}]({href} 'View User Guide')" + + +def _badge_example(text: str, page: Page, files: Files): + """Create badge for example.""" + path = "examples/examples.md" + icon = "material-bookshelf" + href = _resolve_path(path, page, files) + href = href.replace("examples.md", f"{text}") + display_text = clean_file_path(text) + return _badge(f"[:{icon}: {display_text}]({href} 'View Example')") + + +def _link_example(text: str, page: Page, files: Files): + """Create plain link for example.""" + path = "examples/examples.md" + icon = "material-bookshelf" + href = _resolve_path(path, page, files) + href = href.replace("examples.md", f"{text}") + display_text = clean_file_path(text) + return f"[:{icon}: {display_text}]({href} 'View Example')" + + +def _badge_api(text: str, page: Page, files: Files): + """Create badge for API reference.""" + path = "api/networks/network.md" + icon = "octicons-code-16" + href = _resolve_path(path, page, files) + href = href.replace("networks/network.md", f"{text}") + display_text = clean_file_path(text) + return _badge(f"[:{icon}: {display_text}]({href} 'View API Reference')") + + +def _link_api(text: str, page: Page, files: Files): + """Create plain link for API reference.""" + path = "api/networks/network.md" + icon = "octicons-code-16" + href = _resolve_path(path, page, files) + href = href.replace("networks/network.md", f"{text}") + display_text = clean_file_path(text) + return f"[:{icon}: {display_text}]({href} 'View API Reference')" + + +# ----------------------------------------------------------------------------- +# Griffe Extension for API Docstrings +# ----------------------------------------------------------------------------- + + +if GRIFFE_AVAILABLE: + + class ProcessShortcodes(griffe.Extension): + """Process shortcodes in docstrings.""" + + def on_module(self, *, mod: griffe.Module, **kwargs): + """Process shortcodes in module docstrings.""" + if mod.docstring and mod.docstring.value: + mod.docstring.value = self._process_shortcodes( + mod.docstring.value, mod.canonical_path + ) + + def on_class(self, *, cls: griffe.Class, **kwargs): + """Process shortcodes in class docstrings.""" + if cls.docstring and cls.docstring.value: + cls.docstring.value = self._process_shortcodes( + cls.docstring.value, cls.canonical_path + ) + + def on_function(self, *, func: griffe.Function, **kwargs): + """Process shortcodes in function docstrings.""" + if func.docstring and func.docstring.value: + func.docstring.value = self._process_shortcodes( + func.docstring.value, func.canonical_path + ) + + def on_attribute(self, *, attr: griffe.Attribute, **kwargs): + """Process shortcodes in attribute docstrings.""" + if attr.docstring and attr.docstring.value: + attr.docstring.value = self._process_shortcodes( + attr.docstring.value, attr.canonical_path + ) + + def _calculate_depth(self, canonical_path: str) -> int: + """Calculate the depth level based on the canonical path. + + For example: + - pypsa.Network -> depth 2 (api/networks/network.md) + - pypsa.components.Components -> depth 2 (api/components/components.md) + - pypsa.components.Generators -> depth 3 (api/components/types/generators.md) + """ + # Map canonical paths to their documentation structure + # Component types (with specific type names) are at depth 3 + if canonical_path.startswith("pypsa.components._types."): + # This is a component type like pypsa.components._types.generators.Generators + return 3 + # Component types from public API are also at depth 3 + elif ( + canonical_path.startswith("pypsa.components.") + and canonical_path.count(".") == 2 + ): + # Check if it's a component type (e.g., pypsa.components.Generators) + # but not the base Components class + component_name = canonical_path.split(".")[-1] + if component_name not in ( + "Components", + "components", + "common", + "array", + ): + return 3 + # Other API docs are typically at depth 2: api//.md + return 2 + + def _process_shortcodes(self, text, canonical_path: str = ""): + """Process shortcode patterns in text for API docstrings.""" + # Calculate depth for relative path construction + depth = self._calculate_depth(canonical_path) + path_prefix = "../" * depth + + def replace(match): + type, args = match.groups() + args = args.strip() if args else "" + + # Check if this is a badge variant + is_badge = type.startswith("badge-") + if is_badge: + type = type[6:] # Remove "badge-" prefix + + # For API docstrings, use relative path resolution based on depth + try: + if type == "version": + # Use relative path for version links + href = f"{path_prefix}release-notes.md#{args}" + icon = "material-tag-outline" + if is_badge: + return _badge( + f"[:{icon}: {args}]({href} 'Minimum Version')" + ) + else: + return f"[:{icon}: {args}]({href} 'Minimum Version')" + elif type == "pr": + if is_badge: + return _badge_pr(args) + else: + return _link_pr(args) + elif type == "guide": + # Use relative path for guide links + href = f"{path_prefix}user-guide/{args}" + icon = "material-bookshelf" + display_text = clean_file_path(args) + if is_badge: + return _badge( + f"[:{icon}: {display_text}]({href} 'View User Guide')" + ) + else: + return ( + f"[:{icon}: {display_text}]({href} 'View User Guide')" + ) + elif type == "example": + # Use relative path for example links + href = f"{path_prefix}examples/{args}" + icon = "material-notebook-multiple" + display_text = clean_file_path(args) + if is_badge: + return _badge( + f"[:{icon}: {display_text}]({href} 'View Example')" + ) + else: + return f"[:{icon}: {display_text}]({href} 'View Example')" + elif type == "api": + # Use relative path for API links + href = f"{path_prefix}api/{args}" + icon = "octicons-code-16" + display_text = clean_file_path(args) + if is_badge: + return _badge( + f"[:{icon}: {display_text}]({href} 'View API Reference')" + ) + else: + return f"[:{icon}: {display_text}]({href} 'View API Reference')" + except Exception as e: + # Fallback to original if anything fails + print( + f"Warning: Shortcode processing failed for {type} {args}: {e}" + ) + return match.group(0) + + # Return original if unknown type + return match.group(0) + + # Process the shortcodes - allow hyphens in shortcode type + return re.sub( + r"", replace, text, flags=re.I | re.M + ) diff --git a/PyPSA/source/docs/index.md b/PyPSA/source/docs/index.md new file mode 100644 index 0000000000000000000000000000000000000000..2aef8149f3357d68acbba667258b48b9f76ed88b --- /dev/null +++ b/PyPSA/source/docs/index.md @@ -0,0 +1,121 @@ + + +![PyPSA Header Logo](assets/logo/logo-primary-light.svg#only-light) +![PyPSA Header Logo](assets/logo/logo-primary-dark.svg#only-dark) + +# PyPSA: Python for Power System Analysis + +[![PyPI version](https://img.shields.io/pypi/v/pypsa.svg)](https://pypi.python.org/pypi/pypsa) +[![Conda version](https://img.shields.io/conda/vn/conda-forge/pypsa.svg)](https://anaconda.org/conda-forge/pypsa) +![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2FPyPSA%2FPyPSA%2Fmaster%2Fpyproject.toml) +[![REUSE status](https://api.reuse.software/badge/github.com/pypsa/pypsa)](https://api.reuse.software/info/github.com/pypsa/pypsa) +[![License](https://img.shields.io/pypi/l/pypsa.svg)](https://github.com/PyPSA/pypsa?tab=MIT-1-ov-file) +[![Zenodo](https://zenodo.org/badge/DOI/10.5281/zenodo.3946412.svg)](https://doi.org/10.5281/zenodo.3946412) +[![Discord](https://img.shields.io/discord/911692131440148490?logo=discord)](https://discord.gg/AnuJBk23FU) + +PyPSA stands for **Python for Power System Analysis**. It is pronounced "pipes-ah" /ˈpaɪpsə/. + +PyPSA is an open-source Python framework for optimising and simulating modern +power and energy systems that include features such as conventional generators +with unit commitment, variable wind and solar generation, hydro-electricity, +inter-temporal storage, coupling to other energy sectors, elastic demands, and +linearised power flow with loss approximations in DC and AC networks. PyPSA is +designed to scale well with large networks and long time series. It is made for +researchers, planners and utilities with basic coding aptitude who need a fast, +easy-to-use and transparent tool for power and energy system analysis. + +Check out the [:octicons-gear-16: Features](features.md) for more information on the functionality. + +!!! note + + PyPSA has many contributors, with the maintenance led by the [Department of Digital Transformation in + Energy Systems](https://tu.berlin/en/ensys) at the [Technical University of + Berlin](https://www.tu.berlin). The project is currently supported by the + [German Research Foundation](https://www.dfg.de/en/) (grant number [`528775426`](https://gepris.dfg.de/gepris/projekt/528775426)). + Previous versions were developed at the [Karlsruhe + Institute of Technology](http://www.kit.edu/english/index.php) funded by the + [Helmholtz Association](https://www.helmholtz.de/en/), and + at [FIAS](https://fias.uni-frankfurt.de/) funded by the [German Federal + Ministry for Education and Research (BMBF)](https://www.bmbf.de/bmbf/en/). + + +## Quick Links + +
+ +- :material-clock-fast:{ .lg .middle } **Getting started** + + --- + + [:octicons-arrow-right-24: Installation](installation.md) + + [:octicons-arrow-right-24: Quick Start](examples/example-1.ipynb) + +- :material-view-list:{ .lg .middle } **Overview** + + --- + + [:octicons-arrow-right-24: Features](features.md) + + [:octicons-arrow-right-24: Frequently Asked Questions](faq.md) + +- :material-new-box:{ .lg .middle } **Release Notes** + + --- + + Check out the latest features, bug fixes and improvements in the release notes. + + [:octicons-arrow-right-24: What's new](release-notes.md) + +- :material-scale-balance:{ .lg .middle } **Open Source, MIT** + + --- + + PyPSA is licensed under MIT and available on [GitHub](https://www.github.com/PyPSA/PyPSA). + + [:octicons-arrow-right-24: License](license.md) + +
+ +## Sections + +
+ +- :material-bookshelf:{ .lg .middle } **User Guide** + + --- + + Find a detailed description of the PyPSA **design and architecture**, how to setup different **optimization problems** and how to use the **utility functions** here. + + + [:octicons-arrow-right-24: User Guide](user-guide.md) + +- :material-notebook-multiple:{ .lg .middle } **Examples** + + --- + + Many different examples from setting up a **basic toy model** to **sector coupling** or **security-constrained optimization** can be found here. + + [:octicons-arrow-right-24: Examples](examples.md) + +- :octicons-code-16:{ .lg .middle } **API Reference** + + --- + + The API Reference is generated from the docstrings in the code. It contains a detailed description of **all classes and functions**, their parameters and how to use them. + + [:octicons-arrow-right-24: API Reference](network.md) + +- :fontawesome-solid-users:{ .lg .middle } **Contributing** + + --- + + PyPSA is an **open source project** and we welcome any contributions to **keep the project alive**. Find out how to **contribute here**. You don't need to be a developer to contribute. + + [:octicons-arrow-right-24: Contributing](contributing.md) + +
diff --git a/PyPSA/source/docs/release-notes.md b/PyPSA/source/docs/release-notes.md new file mode 100644 index 0000000000000000000000000000000000000000..4c284085e3bfde5c1d151f3605ea986147e011c3 --- /dev/null +++ b/PyPSA/source/docs/release-notes.md @@ -0,0 +1,2534 @@ + + +# Release Notes +## Upcoming Release + +!!! info "Upcoming Release" + + The features listed below have not yet been released, but will be included in the + next update! If you would like to use these features in the meantime, you will need + to install the `master` branch, e.g. `pip install git+https://github.com/pypsa/pypsa`. + +### Bug Fixes + +- Fix busmap clustering to correctly remap all bus ports in multi-port links. () +- Fix handling of inactive `StorageUnit` or `Store` components () +- Fix snapshot selection in operational limit global constraint with investment period. () + +### Documentation + +- Added new example notebook demonstrating negative electricity prices in linearized unit commitment problem. () + +## [**v1.0.3**](https://github.com/PyPSA/PyPSA/releases/tag/v1.0.3) 6th November 2025 { id="v1.0.3" } + +- Fix ramp_limit_up/down constraints: correctly detect components with ramp limits, handle rolling-horizon windows by using previous-window dispatch, and add unit tests for Link ramp limits and ramping constraints in rolling horizon mode. () + +## [**v1.0.2**](https://github.com/PyPSA/PyPSA/releases/tag/v1.0.2) 24th October 2025 { id="v1.0.2" } + +### Bug Fixes + +- Fix infinite recursion error when pickling networks. () + +## [**v1.0.1**](https://github.com/PyPSA/PyPSA/releases/tag/v1.0.1) 20th October 2025 { id="v1.0.1" } + +### Bug Fixes + +- Fix [`n.set_snapshots`][pypsa.Network.set_snapshots] to synchronize investment period weightings when snapshots with new periods are set. () + +- Fix scenario extraction methods ([pypsa.Network.get_scenario][] and [`n['scenario']`][pypsa.Network.__getitem__]) to properly handle empty components () + +- Fix bus validation when adding global constraints via [pypsa.Network.add][], eliminating false warnings for GlobalConstraints. () + +- Fix dimension name consistency when adding investment period weightings. () + +## [**v1.0.0**](https://github.com/PyPSA/PyPSA/releases/tag/v1.0.0) 14th October 2025 🎉 { id="v1.0.0" } + +### **PyPSA 1.0** is here! + +Check out [What's new in PyPSA v1.0](v1-guide.md). + +### Features + +- New optimization mode: **Stochastic optimization problems** () + + - Two-stage stochastic optimization: stage 1 investment "here-and-now"; stage 2 + operations "wait-and-see". Uncertainty via user-defined scenarios. Supports + uncertain model parameters in both static and time-series format. + + - New API: [`n.set_scenarios({name: weight, ...})`][pypsa.Network.set_scenarios] to + enable scenarios and probabilities (defaults to uniform if weights are not given). + Component tables and time series gain a new `scenario` level (MultiIndex) for + scenario-specific data. Inspect via methods [`n.has_scenarios`][pypsa.Network.has_scenarios], + [`n.scenarios`][pypsa.Network.scenarios] and + [`n.scenario_weightings`][pypsa.Network.scenario_weightings]. + +- New feature for stochastic optimization: **CVaR-based risk-averse optimization** + () + + - Risk-averse optimization mode augments expected operational costs with a Conditional + Value at Risk (CVaR) penalty, controlled by `omega` (trade-off between expectation + and risk) and `alpha` (tail level). + + - New API to enable risk preference: + [`n.set_risk_preference(alpha=..., omega=...)`][pypsa.Network.set_risk_preference]. + Inspect via [`n.has_risk_preference`][pypsa.Network.has_risk_preference] + and [`n.risk_preference`][pypsa.Network.risk_preference]. + +- New feature for **interactive map plotting**: PyDeck-based interactive maps () + + - New API: [`n.explore(...)`][pypsa.Network.explore] returns a + [`pydeck.Deck`](https://deckgl.readthedocs.io/en/latest/deck.html) object: + Extending the previous folium/geopandas-based interactive html map by all static + map plotting parameters. + + - Interactive maps can be exported to self-contained HTML files by + `n.explore(...).to_html("file.html", offline=True)`. + +- Introduces additional **MGA functionality**. Allows for solving a network in a direction + given in the coordinate space of user-specified dimensions, and also + introduces a parallelized function to solve in multiple directions at + once. (, ) + + - New API: [`n.optimize.optimize_mga_in_direction()`][pypsa.optimization.OptimizationAccessor.optimize_mga_in_direction] and [`n.optimize.optimize_mga_in_multiple_directions()`][pypsa.optimization.OptimizationAccessor.optimize_mga_in_multiple_directions] + +- The **optimization module was heavily refactored**. While the underlying `pandas`-based + data structure remains the same, the optimization module now uses an `xarray` view via + [pypsa.Components][] to write the optimization model. It allows for easier problem + formulation with a single object which contains all components data across + all dimensions and coordinates (e.g. `name`, `snapshots` with `timesteps` and + `periods` and `scenarios`). Access it via [`c.da`][pypsa.Components.da]. + () + +- **Inactive components** (see [pypsa.Components.inactive_assets][]) are now excluded from the + the optimization model entirely. This has no effect on the results, but it can + reduce the memory footprint when solving the model. + () + +- New example networks: [pypsa.examples.carbon_management][] and + [pypsa.examples.stochastic_network][] + () + +- Various new options to control PyPSA's behavior. See . + +- The option to set bus-level capacity expansion limits per carrier via [`Bus`][pypsa.components.Buses] + attributes `nom_{min/max}_{carrier}_{period}` is now deprecated. The global + constraint type `"tech_capacity_expansion_limit"` offers identical functionality. + () + +- Added utility function [pypsa.common.annuity][] to calculate the annuity + factor for a given discount rate and lifetime. Also known as capital recovery + factor, it is used to convert a capital cost into an annualized cost. + () + +- Add additional standard line types from pandapower. () + +- New network indexing methods: [`n.get_network`][pypsa.NetworkCollection.get_network], + [`n.get_scenario`][pypsa.Network.get_scenario], [`n.slice_network`][pypsa.Network.slice_network] + and enhanced [`n[...]`][pypsa.Network.__getitem__] for accessing networks. + () + +- When using certain ambiguous attribute names or typos in the [pypsa.Network.add][] method, a + warning is now raised. () + +- [pypsa.Network.add][] now returns `None` by default. Use `return_names=True` + to get the previous behavior of returning component names which have been added. + () + +- Refactored version attributes: `__version_semver__` → `__version_base__`, + `__version_short__` → `__version_major_minor__`. Removed tuple versions. + Old names raise `DeprecationWarning`. () + +- Refined statistic arguments across optimization expressions and plotting modules. + Renamed `comps` → `components`, `aggregate_groups` → `groupby_method`, + and `aggregate_time` → `groupby_time` for consistency. Old argument names + are deprecated and will be removed in v2.0.0. () + +- Improve performance of loading networks by avoiding re-ordering dataframe columns + and indices where unnecessary; especially impactful for networks with large numbers + of components. () + +### Bug Fixes + +- Fixed inconsistent period weighting application in primary energy and operational limit + global constraints for non-cyclic storage units and stores. Previously, generators correctly + applied period weightings to account for yearly repetition within investment periods, while + storage components did not. This caused inconsistent constraint behavior between generators + and storage in multi-investment period optimization. Non-cyclic storage units and stores now + require the `state_of_charge_initial_per_period` and `e_initial_per_period` flags respectively + to be set to `True` when using primary energy or operational limit constraints. () + +- The default values for `cyclic_state_of_charge_per_period` (StorageUnit) + and `e_cyclic_per_period` (Store) have been changed from `True` to `False`. This + is to be more consistent with single investment period optimization where cycling behavior + defaults to `False`. Users who work with multi-investment period optimization + and want per-period cycling behavior must now explicitly set these attributes to + `True`. () + +- Fix storage state-of-charge handling in multi-investment period optimizations. The constraint + logic incorrectly determined when to apply per-period cycling vs. continuous storage state + tracking, causing storage to behave unexpectedly regardless of flag settings. The fix + ensures storage units and stores correctly preserve or reset their state across investment + periods based on the `cyclic_state_of_charge_per_period`/`e_cyclic_per_period` and + `state_of_charge_initial_per_period`/`e_initial_per_period` flags. + (, ) + +- Fixed issue when copying a solved network after setting `n.model.solver_model` to `None`. + () + +- Correct use of snapshot weighting columns in statistics module. The + doscstring for [pypsa.Network.snapshot_weightings][] was clarified. + () + +- Resolved an issue where the network version was not correctly identified during I/O, + resulting in false update information being logged. + () + +- Fix `get_transmission_carriers()` to handle components without carrier attribute + (e.g., Transformer). () + +### Breaking Changes + +For a summary of **breaking changes**, see [What's new in PyPSA v1.0](v1-guide.md#breaking-changes). + +## [**v0.35.2**](https://github.com/PyPSA/PyPSA/releases/tag/v0.35.2) 15th August 2025 { id="v0.35.2" } + +### Bug Fixes + +- Make compatible with xarray v2025.07 + () + +## [**v0.35.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.35.1) 3rd July 2025 { id="v0.35.1" } + +### Bug Fixes + +- Fixed issue when copying a network with an unsolved model. + () + +- Fixed missing dependency issue for `typing-extensions`. + () + +- Fixed Excel import when snapshots sheet is missing. + () + +## [**v0.35.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.35.0) 22th June 2025 { id="v0.35.0" } + +### Features + +- New **interactive** plotting library () + + - [`pypsa.Network.statistics.energy_balance.iplot()`][pypsa.plot.StatisticInteractivePlotter.__call__] + to get the pre defined default plot + - [`pypsa.Network.statistics.energy_balance.iplot.bar()`][pypsa.plot.StatisticInteractivePlotter.bar] + to get a bar plot. replace `bar` with `line`, `area`, `map` or `scatter` to get the respective plot. + +- The function `n.statistics.opex()` now includes all operational cost + components: marginal costs, quadratic marginal costs, storage costs, spill + costs, start-up costs, shut-down costs, and stand-by costs. Previously, only + marginal costs were considered. A new parameter `cost_types` allows selecting + which cost components to include. () + +- New method `n.equals() ` to compare two networks for equality. + This is similar to the equality operator `==` but allows for more flexibility in the + comparison which is useful for testing and debugging. (, ) + +- The components subpackage was further restructured. The known API remains untouched. () + +- New experimental **NetworkCollection** () + + - You can now create a container for multiple `Network` objects. Use is with + `pypsa.NetworkCollection()` and pass a list of networks. The feature is + experimental and might change with the next release. Documentation and API + reference will follow with a stable version of it. + +- Add new statistics function `n.statistics.prices()` which can return + time-averaged or consumption-weighted prices by bus or bus carrier. + +### Bug Fixes + +- Bugfix: The function `n.statistics.opex()` now considers the correct + snapshot weightings `n.snapshot_weightings.objective`. + () + +- Fixed unaligned statistics index names when `groupby=False` + () + +- Fixed interactive area plots in stacked more with `facet_row` and `facet_col`. + () + +- The docstrings of the statistics function are now properly displayed again, ie. the output of `n.statistics.energy_balance?`. + () + +- Fixed various some I/O edge cases for better data preservation during import/export (, , ) + +## [**v0.34.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.34.1) 7th April 2025 { id="v0.34.1" } + +### Bug Fixes + +- The static map plots for statistics are fixed, e.g. `n.statistics.energy_balance.map()`. + () + +- The previous maps module under `pypsa/plot` is now modularized. Instead of a + monolithic module, the maps module is now split into several submodules. The + submodules are: `.maps.common`, `.maps.interactive`, and `.maps.static`. + () + + +- Added new single node capacity expansion example in style of model.energy. + It can be loaded with [`pypsa.examples.model_energy()`][pypsa.examples.model_energy]. + +- Add new example for how to run MGA ('modelling-to-generate-alternatives') optimisation. + +- Added demand elasticity example. + +## [**v0.34.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.34.0) 25th March 2025 { id="v0.34.0" } + +### Features + +- New supported file formats for import and export: **Excel** + + - Use [`n.import_from_excel`][pypsa.Network.import_from_excel] and + [`n.export_to_excel`][pypsa.Network.export_to_excel] to import and export Networks + from and to Excel files. + + - `openpyxl` and `python-calamine` are required dependencies for this feature, but + different engines can be passed. By default they are not installed, but can be + installed via `pip install pypsa[excel]`. + +- New plotting library + + - You can now create plots on any PyPSA statistic. Try them with: + + - [`n.statistics.energy_balance.plot()`][pypsa.plot.statistics.plotter.StatisticPlotter.__call__] to get the pre defined default plot + - [`n.statistics.energy_balance.plot.bar()`][pypsa.plot.statistics.plotter.StatisticPlotter.bar] to get a bar plot + - [`n.statistics.energy_balance.plot.line()`][pypsa.plot.statistics.plotter.StatisticPlotter.line] to get a line plot + - [`n.statistics.energy_balance.plot.area()`][pypsa.plot.statistics.plotter.StatisticPlotter.area] to get a area plot + - [`n.statistics.energy_balance.plot.map()`][pypsa.plot.statistics.plotter.StatisticPlotter.map] to get a map plot + + - `n.plot()` was moved to `n.plot.map()` + + - `n.explore()` was moved to `n.plot.explore()` and `n.iplot()` was moved to `n.plot.iplot()` + +- Statistics module + + - All statistics functions now interpret the bus_carrier argument as a regular + expression (regex), enabling more flexible filtering options. + () + + - All statistics functions have a new argument `carrier` to filter by carriers. + () + + - All statistics functions have two new arguments `drop_zero` and `round` to + control the output. `drop_zero` drops all rows with zero values and `round` + rounds the output to the specified number of decimal places. Those settings have been + used before already via the statistics parameters, but are deprecated now. Use the + new arguments or the module level settings instead (to set them globally). E.g. + `pypsa.options.params.statistics.nice_names = False`. List all available parameter + settings via `pypsa.options.params.describe()`. + () + +### Minor improvements + +- Ensuring that the created lp/mps file is deterministic by sorting the strongly meshed + buses. () + +- Added warning for consistent legend circle and semicirle sizes when combining plots + on a geographical axis. + +- Add new statistic `n.statistics.system_cost()` to calculate the total system cost from capital and operational expenditures. + +- Added descriptive attribute "location" to Buses. This attribute does not influence the optimisation model but can be used for aggregation in the statistics module. + +- Added descriptive attribute "location" to Buses. This attribute does not influence + the optimisation model but can be used for aggregation in the statistics module. + () + + +### Bug fixes + +- Fixed `pypsa.plot.add_legend_semicircles()` circle sizing to be consistent with + `n.plot(bus_size=..., bus_split_circle=True)` argument. + () + +## [**v0.33.2**](https://github.com/PyPSA/PyPSA/releases/tag/v0.33.2) 12th March 2025 { id="v0.33.2" } + +### Bug fixes + +- **Regression hotfix**: Fixed a critical bug in statistics functions for + multi-investment networks where built years and lifetimes were not being correctly + considered. In version `v0.32.0`, only components active in the first time period were + being included in statistics calculations. The fix ensures all components are properly + represented according to their respective built years and lifetimes across all + investment periods. This issue was patched in version `0.33.2`. We also backported the + fix to version `0.32.2`. () + +- The expressions function `n.optimize.expressions.capacity` now uses the absolute + efficiency to calculate the capacity at link ports, unless a `bus_carrier` is defined + or `at_port` is set to True. This is in line with the behavior of the statistics + functions (`statistics.installed_capacity`, `statistics.optimal_capacity`). + Before, the efficiency was allowed to be negative, which lead to inconsistent results. + +## [**v0.33.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.33.1) 3rd March 2025 { id="v0.33.1" } + +### Minor improvements + +- Added a `quotechar` parameter to [pypsa.Network.import_from_csv_folder][] and + [`n.export_to_csv_folder`][pypsa.Network.export_to_csv_folder] to handle non-standard field quoting in CSV + import/export, aligning with [`pandas.read_csv`](https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html) and + [`pandas.to_csv`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_csv.html). () + +### Bug fixes + +- `pypsa[cloudpath]` optional dependency will now only install `cloudpathlib` without + extra cloud storage provider client libraries, these will be left to the user to + install. () + +- [`n.import_from_netcdf`][pypsa.Network.import_from_netcdf] and [`n.import_from_hdf5`][pypsa.Network.import_from_hdf5] now work when a URI is + passed as a string instead of a CloudPath object. + () + +- Linearized unit commitment with equal startup and shutdown costs. + () + +- Fix pandas dtype warning. () + +## [**v0.33.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.33.0) 7th February 2025 { id="v0.33.0" } + +### Features + +- New component class structure (, ) + + - Major structural refactoring of how component data is stored and accessed. The new + structure adds an extra layer to move all component-specific data from the network + class to a new component class. + + - This is an experimental feature, will be developed further and is not yet + recommended for general use. More features, documentation and examples will + follow. Most users will not notice any changes. + + - The new additional layer makes it easy to add new features. If you wanna play around + with the new components class, see the + [Components class example](https://docs.pypsa.org/en/latest/examples/experimental-components-class.html) + in the documentation. You will find an short introduction and some simple examples + to show which other features could be added in the future. If you have any ideas, + wishes, feedback or suggestions, please let us know via the + [issue tracker](https://www.github.com/PyPSA/PyPSA/issues). + +- Breaking: Deprecation of custom components () + + - This version of PyPSA deprecates custom components. While we don't see many use + cases for them, they might be added in an improved way in future again. For a + potential reimplementation we would be happy to hear your use case and + requirements via the [issue tracker](https://www.github.com/PyPSA/PyPSA/issues). + + - If you don't know what this is or have never used the `override_components` + and `override_component_attrs` arguments during Network initialisation, you can + safely ignore this deprecation. + +- Breaking: Behavior of `n.components` + + - Iterating over `n.components` now yields the values instead of keys. Use + `n.components.keys()` to keep iterating over keys. + + - Checking if a component is in `n.components` using the 'in' operator is deprecated. + With the deprecation of custom components keys in `n.components` also ever change. + +- PyPSA `0.33` provides support for the recent Python 3.13 release and drops support + for Python 3.9. While Python 3.9 still gets security updates until October 2025, + core dependencies of PyPSA are dropping support for Python 3.9 (e.g. `numpy`) and + active support is only provided for the most recent versions + (see [endoflife.date](https://endoflife.date/python)). It is recommended to upgrade + to the latest Python version if possible. Note that there might be some issues with + Windows and Python 3.13, which are not yet resolved. + () + +- Added PyPSA options architecture via [`pypsa.get_option`][pypsa.get_option], [`pypsa.set_option`][pypsa.set_option], + and [`pypsa.option_context`][pypsa.option_context]. + This allows to set and get global options for PyPSA and + mimics the options setting behavior of pandas. Currently there are not many options + available, but this will be extended in future. + () + +- New network attributes [`n.timesteps`][pypsa.Network.timesteps], + [`n.periods`][pypsa.Network.periods] and + [`n.has_periods`][pypsa.Network.has_periods] to simplified level access + of the snapshots dimension. () + +- Consistency checks can now be run with the parameter `strict`, which will raise + them as `ConsistenyError`. Pass checks which should be strict in + [`n.consistency_check`][pypsa.Network.consistency_check] as e.g. + `strict=['unknown_buses']`. [`n.optimize`][pypsa.Network.optimize] + will run some strict checks by default now. ( ) + +- New example in the documentation showing how to implement reserve power constraints. + () + +- Doctests are now run with the unit tests. They allow to test the documentation + examples, which will improve the quality of docstrings and documentation in future + releases. () + +### Bug fixes + +- The parameter threshold in function get_strong_meshed_buses was not considered + in the function it self. A kwargs check has been added for providing a own threshold. + E.g., get_strongly_meshed_buses (network, threshold=10) + + +## [**v0.32.2**](https://github.com/PyPSA/PyPSA/releases/tag/v0.32.2) 12th March 2025 { id="v0.32.2" } + +### Bug fixes + +- Backported from version `v0.33.2`: Fixed a critical bug in statistics functions for + multi-investment networks where built years and lifetimes were not being correctly + considered. In version `v0.32.0`, only components active in the first time period were + being included in statistics calculations. The fix ensures all components are properly + represented according to their respective built years and lifetimes across all + investment periods. () + +## [**v0.32.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.32.1) 23th January 2025 { id="v0.32.1" } + +### Bug fixes + +- The expression module now correctly includes the "Load" component in the + energy balance calculation. Before the fix, the "Load" component was not + considered. () + +- The optimize/expression module now correctly assigns contributions from branch + components in the `withdrawal` and `supply` functions. Before, there was a wrong + multiplication by -1 for branch components. () + +## [**v0.32.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.32.0) 5th December 2024 { id="v0.32.0" } + +### Features + +- Improvements to groupers in the statistics module (, ) + + - The `groupby` argument now accepts keys to allow for more granular and flexible + grouping. + For example, + [`n.statistics.energy_balance(groupby=["bus_carrier", "carrier"])`][pypsa.statistics.StatisticsAccessor.energy_balance] + groups the energy balance by bus carrier and carrier. + + - Build in groupers include: + + - [`pypsa.statistics.groupers.carrier`][pypsa.statistics.grouping.Groupers.carrier] + - [`pypsa.statistics.groupers.bus_carrier`][pypsa.statistics.grouping.Groupers.bus_carrier] + - [`pypsa.statistics.groupers.name`][pypsa.statistics.grouping.Groupers.name] + - [`pypsa.statistics.groupers.bus`][pypsa.statistics.grouping.Groupers.bus] + - [`pypsa.statistics.groupers.country`][pypsa.statistics.grouping.Groupers.country] + - [`pypsa.statistics.groupers.unit`][pypsa.statistics.grouping.Groupers.unit] + - A list of registered groupers can be accessed via + [`pypsa.statistics.groupers.list_groupers`][pypsa.statistics.grouping.Groupers.list_groupers] + + - Custom groupers can be registered on module level via + [`pypsa.statistics.groupers.add_grouper`][pypsa.statistics.grouping.Groupers.add_grouper]. + The key will be used as identifier in the `groupby` argument. Check the API reference + for more information. + + - Accessing default groupers was moved to module level and an improved API was + introduced. `n.statistics.get_carrier` can now be accessed as + [`pypsa.statistics.groupers.carrier`][pypsa.statistics.grouping.Groupers.carrier] + and a combination of groupers can be accessed as + [`pypsa.statistics.groupers['bus', 'carrier']`][pypsa.statistics.Groupers] + instead of `n.statistics.groupers.get_bus_and_carrier`. + +- A new module `pypsa.optimize.expressions` was added. It contains functions to quickly + create expressions for the optimization model. The behavior of the functions is + mirroring the behavior of the `statistics` module and allows for similar complexity + in grouping and filtering. Use it with e.g. + [`n.optimize.expressions.energy_balance()`][pypsa.optimization.expressions.StatisticExpressionsAccessor.energy_balance]. + () + +- `pytables` is now an optional dependency for using the HDF5 format. Install + it via `pip install pypsa[hdf5]`. Otherwise it is not installed by default + anymore. () + +## [**v0.31.2**](https://github.com/PyPSA/PyPSA/releases/tag/v0.31.2) 27th November 2024 { id="v0.31.2" } + +### Bug fixes + +- The constraint to account for `e_sum_max`/ `e_sum_min` is now skipped if not applied to any asset (, ) + + +## [**v0.31.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.31.1) 1st November 2024 { id="v0.31.1" } + +### Bug fixes + +- Abolishing `min_units` in the post discretization. If the maximum capacity of a + component is smaller than the specified unit size, the maximum capacity is built as + soon as the threshold is passed () + +- Less verbose logging when using [`n.add`][pypsa.Network.add] + () + +## [**v0.31.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.31.0) 1st October 2024 { id="v0.31.0" } + +### Features + +- New `active` attribute () + + - A new attribute for one-port and branch components `active` was added. If set to + true (default), the asset is considered active for all functionality, including + optimization and power flow calculation. If set to false, the asset is considered + inactive and is excluded from the optimization, power flow and statistics modules. + + - The active attribute can be thought of as a global filter on the components. When + running a multi-horizon optimization, the active attribute is considered a global + condition for each horizon. Then assets are considered active only if `active` is + true and the investment period falls within the lifetime of the asset. + +- New attributes for the [generator](user-guide/components/generators.md) component + () + + - `e_sum_min` and `e_sum_max` add a new constraint and allow to set the minimum + and maximum total energy that can be generated by the generator over one + optimization horizon. + +- New [`n.add`][pypsa.Network.add] method () + + - [`n.add`][pypsa.Network.add] now handles the addition of a single or multiple + components, has more robust index alignment checks allows to overwrite existing + components using the new argument `overwrite`. Because of the more strict + alignment checks, this might be a **breaking change** for some users. + + - Therefore the methods `n.madd` and + `n.mremove` are now deprecated and will point to + their generalised counterparts. + +- New function [`n.optimize_and_run_non_linear_powerflow`][pypsa.optimization.optimize.OptimizationAccessor.optimize_and_run_non_linear_powerflow] + was added to the set of abstract optimize functions. This function optimizes the + network and runs a non-linear power flow calculation afterwards. () + +- API and structural changes: + + - The [`Component`][pypsa.Components] object is now a refactored + stand-alone class. This is ongoing work and will change further in future + releases. () + - The [`pypsa.SubNetwork`][pypsa.SubNetwork] class has new methods `df`, `pnl`, `component` + to ease the access of component data for a subnetwork. Use it with e.g. + `subnetwork.df("Generator")` and alike. () + - [`n.df`][pypsa.Network.df] and [`n.pnl`][pypsa.Network.pnl] + have been renamed to [`n.static`][pypsa.Network.static] and + [`n.dynamic`][pypsa.Network.dynamic]. But `n.df` and `n.pnl` are still available + and can be used as aliases without any deprecation warning for now. () + +## [**v0.30.3**](https://github.com/PyPSA/PyPSA/releases/tag/v0.30.3) 24th September 2024 { id="v0.30.3" } + +- Bugfix in the post discretization for `Links` with a maximum capacity. + Furthermore, giving the option to build out only multiples of the specified unit_size + or allowing to use the full maximum capacity. () + +## [**v0.30.2**](https://github.com/PyPSA/PyPSA/releases/tag/v0.30.2) 11th September 2024 { id="v0.30.2" } + +- Bugfix in operational limit global constraints, which now directly uses the + carrier of the `Store` rather than the carrier of the bus it is attached to. + () + +## [**v0.30.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.30.1) 9th September 2024 { id="v0.30.1" } + +- Added option for importing and exporting CSV, netCDF and HDF5 files in cloud + object storage. This requires the installation of the optional dependency + `cloudpathlib`, e.g. via `pip install pypsa[cloudpath]`. + +- Bugfix of `n.plot()` when single buses have no coordinates. + +## [**v0.30.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.30.0) 30th August 2024 { id="v0.30.0" } + +- Added `n.explore()` function based on `folium` and `geopandas` to + interactively explore networks. () + +- Added new `spill_cost` input parameter for storage units which penalizes + spilling excess energy. () + +- Added new `marginal_cost_storage` input parameter for stores and storage + units to represent the cost of storing energy in currency/MWh/h. + () + +- Added type annotations to all functions. () + +- Updated documentation. () + +## [**v0.29.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.29.0) 31st July 2024 { id="v0.29.0" } + +- Removed `n.lopf()` pyomo-based and nomopyomo-based optimisation modules. Use + linopy-based optimization with `n.optimize()` instead. + () + +- HiGHS becomes the new default solver for `n.optimize()`. + () + +- Changes to the `statistics` module: + + - The statistics functions `n.statistics.capex()`, + `n.statistics.installed_capex()`, and `expanded_capex` now have an + optional `cost_attribute` argument, which defaults to `capital_cost`. The + default behavior of the functions is not changed. + () + + - The functions `n.statistics.optimal_capacity()` and + `n.statistics.expanded_capacity()` now return positive and negative + capacity values if a `bus_carrier` is selected. Positive values correspond + to production capacities, negative values to consumption capacities. + () + + - The statistics module now supports the `nice_name` argument for bus + carriers. Previously, nice names were only supported for components + carriers. () + + - The statistics module now features functionality to set global style + parameters (e.g. `nice_names`, `drop_zero` and `round`) which is then + applied to all statistics methods without the need to set them individually. + To set parameters one can run + `n.statistics.set_parameters(nice_names=False, round=2)` and to view + current parameters setting `n.statistics.parameters`. + () + +- Changes to the `clustering` module: + + - Add attribute-based exemptions for clustering lines and links. With the + argument `custom_line_groupers` in the function `aggregatelines()` one + can specify additional columns besides `bus0` and `bus1` to consider as + unique criteria for clustering. This is useful, for example, to avoid the + aggregation of lines/links with different `build_year` or `carrier`. + () + +- Changes to the `plot` module: + + - Add option to add semicircle legends by running + `pypsa.plot.add_legend_semicircle(ax, sizes=[1000/scaling_factor], + labels=["1 GWh"])`. () + + - Add functionality to provide list of colors in `add_legend_lines()`. + () + +- Bugfixes: + + - The security-constrained optimization via + `n.optimize.optimize_security_constrained()` was fixed to correctly handle + multiple subnetworks. () + + - The global constraint on the total transmission costs now includes the + weight of the investment periods and persistence of investment costs of + active assets in multi-horizon optimisations. + + - Retain investment periods and weightings when clustering networks. + () + + - Removed performance regression of `statistics` module. + () + + - When adding bus ports on the fly with `add` methods, the dtype of the + freshly created column is now fixed to `string`. () + + - Using timezone information in `n.snapshots` raises an error now, since it + leads to issues with `numpy`/ `xarray`. () + +- Improvements to consistency checks and model debugging: + + - When adding components with bus ports greater than 1, e.g. `bus2`, pypsa + checks if the bus exists and prints a warning if it does not. + () + + - Also check for missing values of default attributes in the + `n.consistency_check()` function. () + + - Restructure `n.consistency_check()`. + (, , ) + + - Add option `n.optimize(compute_infeasibilities=True)` to compute Irreducible + Inconsistent Subset (IIS) in case an infeasibility was encountered and Gurobi + is installed. () + + - Improved error messages. () + +- Add functionality to compare two networks for equality via equality operator + (`==`). () + +- Add single-node electricity-only and sector-coupled capacity expansion + example. () + +- Added new line type "Al/St 490/64 4-bundle 380.0". + () + +- Use `ruff`. (, ) + +- Improve CI and auto-release process. , ) + +- Restructured API reference. () + +- Compatibility with `numpy>=2.0`. () + +## [**v0.28.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.28.0) 8th May 2024 { id="v0.28.0" } + +- When using iterative optimisation functionality + `n.optimize_transmission_expansion_iteratively()`, add option to discretize + optimised line and link capacities in the final iteration based on new keyword + arguments `line_unit_size`, `link_unit_size`, `line_threshold` and + `link_threshold`. This allows to round the optimised capacities to a + multiple of the unit size based on the threshold. + () + +- A new function `n.merge()` was added allowing the components and + time-dependent data of one network to be added to another network. The + function is also available via `n + m` with default settings. The function + requires disjunct component indices and identical snapshots and snapshot + weightings. () + +- New features in the statistics module (): + + - The statistics module introduces a new keyword argument `at_port` to all + functions. This allows considering the port of a component when calculating + statistics. Depending on the function, the default of `at_port` is set to + `True` or `False`, for example for the dispatch all ports are + considered. + + - The statistics module now supports an optional `port` argument in + `groupby` functions. This allows to group statistics while considering the + port of a component. + + - The `statistics.revenue` function introduces a new keyword argument + `kind` to optionally calculate the revenue based on the `input` + commodity or the `output` commodity of a component. + + - The `statistics.energy_balance` function introduces a new keyword argument + `kind` to optionally calculate the `supply` and `withdrawal` of a + component. + + - Deprecation warnings are added to the statistics module for the + functionalities that will be removed in the next major release. + +- Updated `environment_doc.yml` to include the latest required `pip` + dependencies for the documentation environment. () + +- Bugfix: calling `n.create_model()` or `n.optimize()` when a global + operational limit is defined will no longer set the carrier attribute of + stores to the carrier of the bus they are attached to. + () + +- Added warning to `plot.py` with instructions to handle the case where the + `requests` dependency is missing. () + +- Bugfix: calling `n.optimize.*` functions (e.g. `n.optimize.optimize_mga`) + now correctly returns each functions return values. () + + +## [**v0.27.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.27.1) 22nd March 2024 { id="v0.27.1" } + +- Fixed sometimes-faulty total budget calculation for single-horizon MGA optimisations. + +- Fixed assignment of active assets in multi-horizon optimisation with `n.optimize`. + +- Fixed setting of investment periods when copying a multi-horizon network. + +- Always use name and mask keys in variable and constraint assignment to protect against future changes in argument order. + +- Rewrite function `get_switchable_as_dense` so that it consumes less memory when calling it with large dataframes. + +- Fix of the capex description in the attribute CSV files. + +## [**v0.27.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.27.0) 18th February 2024 { id="v0.27.0" } + +- Bugfix: If plotting a network map with split buses + (`n.plot(bus_split_circle=True)`), the bus sizes are now scaled by factor 2 + to account for the fact that the bus sizes are split into half circles. This + makes the area scaling of the buses consistent with the area of non-split + buses. + +- The global constraint `define_tech_capacity_expansion_limit` now also takes + branch components into account. If defined per bus, the `bus0` of the branch + is considered as a reference bus. + +- Bugfixes in building of global constraints in multi-horizon optimisations. + +- Fixed total budget calculation for MGA on multi-horizon optimisations. + +- The `extra_functionality` argument is now also supported in `solve_model` + accessor. + +- `optimize_mga` now returns the solver termination status and condition. + +- The deprecated functions `_make_consense`, `aggregategenerators`, + `get_buses_linemap_and_lines` and `get_clustering_from_busmap` were + removed. + +- The minimum `networkx` version was bumped from `1.10` to `2`. + +- `pyomo` is no longer supported for Python 3.12 or higher. + + +## [**v0.26.3**](https://github.com/PyPSA/PyPSA/releases/tag/v0.26.3) 25th January 2024 { id="v0.26.3" } + +- Bugfix: With line transmission losses there was a sign error in the + calculation of the line capacity constraints. + +- Approximated transmission losses of lines are now stored after optimisation as + the difference between `n.lines_t.p0` and `n.lines_t.p1` so that they + appear in the energy balance (e.g. `n.statistics.energy_balance()`) and when + calculating losses with `n.lines_t.p0 + n.lines_t.p1`. + +## [**v0.26.2**](https://github.com/PyPSA/PyPSA/releases/tag/v0.26.2) 31st December 2023 { id="v0.26.2" } + +- Bugfix in the definition of spillage variables for storage units. Previously, + the spillage variable creation was skipped in some cases due to a wrong + condition check even though there was a positive inflow. + +## [**v0.26.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.26.1) 29th December 2023 { id="v0.26.1" } + +- The output attribute `n_mod` introduced in the previous version was removed + since it contains duplicate information. Calculate the number of expanded + modules with `p_nom_opt / p_nom_mod` instead. + +- Bugfix in MGA function to correctly parse the `sense` keyword argument. + +- Fix strict type compatibility issues with `pandas>=2.1` causing problems for + clustering. + +- Removed `numexpr` version constraint. + +## [**v0.26.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.26.0) 4th December 2023 { id="v0.26.0" } + +### New Features + +- The `Network` class has a **new component for geometric shapes** under + `n.shapes`. It consists of a `geopandas` dataframe which can be used to + store network related geographical data (for plotting, calculating potentials, + etc). The dataframe has the columns `geometry`, `component`, `idx` and `type`. + The columns `component`, `idx` and `type` do not require specific values, but + allow for storing information about which components the shapes belong to. The + coordinate reference system (CRS) of the shapes can be accessed and set via a + new attribute `n.crs`. For a transition period, the attribute `n.srid`, + which independently refers to the projection of the bus coordinates, is kept. + +- Improvements to the network **statistics module**: + + - The statistics module now supports the consideration of multi-port links. An + additional argument `bus_carrier` was added to the statistics functions to + select the components that are attached to buses of a certain carrier. + + - The statistics module now supports the consideration of multiple investment + periods. As soon as `n.snapshots` is a MultiIndex, the network statistics + are calculated separately for each investment period. + + - A new function `transmission` was added to the statistics accessor. This + function considers all lines and links that connect buses of the same carrier. + + - The statistics functions now support the selection of single components in + the `comps` argument. + +- The plotting function `n.plot()` now supports **plotting of only a subset of + network components** by allowing that arguments like `bus_size`, + `link_widths` or `link_color` do no longer require to contain the full + set of indices of a component. + +- Add option to specify **time-varying ramp rates** for generators and links + (`ramp_limit_up` and `ramp_limit_down`, e.g. under + `n.links_t.ramp_limit_up`). + +- Added attributes `p_nom_mod`, `e_nom_mod`, and `s_nom_mod` to components + to consider capacity modularity. When this attribute is non-zero and the + component is extendable, the component's capacity can only be extended in + multiples of the capacity modularity. The optimal number of components is + stored as `n_mod` (such that `p_nom_mod * n_mod == p_nom_opt`). The + default is kept such that extendable components can be expanded continuously. + +### Bugfixes and Compatibiliity + +- Bugfix: In rolling horizon optimisation with unit commitment constraints, the + generator status of the previously optimized time step is now considered. + +- Bugfix: Allow optimising the network for just subset of investment periods by + using `n.optimize(multi_investment_periods=True, snapshots=...)`. + +- Bugfix: The function `n.import_from_netcdf()` failed when trying to import + data from an `xarray` object. + +- Bugfix: Fix global constraints for primary energy and transmission volume + limits for networks with multiple investment periods. + +- Bugfix: Fix stand-by-costs optimization for latest `linopy` version. + +- Resolve performance regression for multi-decade optimisation in highly meshed + networks. + +- Compatibility with `pandas==2.1`. + +- Added Python 3.12 to CI and supported Python versions. + + +## [**v0.25.2**](https://github.com/PyPSA/PyPSA/releases/tag/v0.25.2) 30th September 2023 { id="v0.25.2" } + +* Add option to enable or disable nice carrier name in the statistics module, + e.g. `n.statistics(nice_name=False)``. + +* Add example in documentation for the statistics module. + +* Add example for stochastic optimization with PyPSA to the documentation. + +* Extended documentation for multi-decade optimization. + +* Bugfix: Use of ``nice_names`` keyword argument in + ``n.statistics.energy_balance()``. + +* Bugfix: Correctly handle ``p_nom`` or ``p_nom_opt`` in power flow distributed + slack. + +* Bugfix: After the optimization the right-hand side and sign of global + constraints were previously overwritten by altered values. + +* Bugfix: In netCDF export, typecasting to float32 after setting the compression + encoding led to ignored compression encodings. + +* Bugfix: Handle solver options for CBC and GLPK for ``n.lopf(pyomo=False)``. + +* Bugfix: Handle cases with multi-decade optimisation, activated transmission + limit and an empty list of lines or DC links. + +## [**v0.25.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.25.1) 27th July 2023 { id="v0.25.1" } + +**New Features** + +* The function ``get_clustering_from_busmap`` has a new argument + ``line_strategies``. + +* The ``n.optimize()`` function gets a new keyword argument + ``assign_all_duals=False`` which controls whether all dual values or only + those that already have a designated place in the network are assigned. + () + +**Changes** + +* The function ``get_buses_linemap_and_lines`` was deprecated, in favor of + direct use of ``aggregatebuses`` and ``aggregate_lines``. + +* Improve logging printout for rolling horizon optimization. + (, ) + +* The CI environment handling was migrated to ``micromamba`` + (). + +**Bugfixes** + +* The aggregation functions in the clustering module were adjusted to correctly + handle infinity values (see https://github.com/pandas-dev/pandas/issues/54161 + for more details). () + +* The unit commitment formulation with a rolling horizon horizon was fixed in + case of non-committable and committable generators with ramp limits. + () + +* The clustering functionality was fixed in case of passing a subset of carriers + that should be aggregated. () + +* When clustering, allow safe clustering of component attributes which are both + static and dynamic. () + +* When assigning a new user-defined variable to the underlying optimization + model, the assignment of the solution resulted in an error if the variable + name did not match the pattern ``{Component}-{Varname}``. This has been fixed + by ignoring variables that do not match the pattern during solution + assignment. () + +* Multilinks are now also handled automatically when importing a network from + file. () + +* Multilink default efficiencies are always set to 1.0. + () + +* For linearized unit commitment relaxation, some tightening additional + constraints are only valid if start-up and shut-down costs are equal. These + constraints are now skipped if this is not the case and a warning message is + printed. () + +* Fix division in capacity factor calculation in statistics module when not + aggregating in the time dimension. () + +## [**v0.25.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.25.0) 13th July 2023 { id="v0.25.0" } + +**New Features** + +* **Stand-by costs:** PyPSA now supports stand-by cost terms. A new column + ``stand_by_cost`` was added to generators and links. The stand-by cost is + added to the objective function when calling ``n.optimize()``. + () + +* **Rolling horizon function:** The ``n.optimize`` accessor now provides + functionality for rolling horizon optimisation using + ``n.optimize.optimize_with_rolling_horizon()`` which splits whole optimization + of the whole time span into multiple subproblems which are solved + consecutively. This is useful for operational optimizations with a high + spatial resolution. () + +* **Modelling-to-generate-alternatives (MGA) function** The ``n.optimize`` + accessor now provides functionality for running + modelling-to-generate-alternatives (MGA) on previously solved networks using + ``n.optimize.optimize_mga(slack=..., weights=...)``. This is useful for + exploring the near-optimal feasible space of the network. + () + +**Changes** + +* **Multilinks by default:** Links with multiple inputs/outputs are now + supported by default. The Link component attributes are automatically extended + if a link with ``bus2``, ``bus3``, etc. are added to the network. Overriding + component attributes at network initialisation is no longer required. + () + +* **Spatial clustering refactored:** The spatial clustering module was + refactored. The changes lead to performance improvements and a more consistent + clustering API. () + + * The network object has a new accessor ``cluster`` which allows accessing + clustering routines from the network itself. For example, + ``n.cluster.cluster_spatially_by_kmeans`` returns a spatially clustered + version of the network. + + * The default clustering strategies were refined. Per default, columns like + ``efficiency`` and ``p_max_pu`` are now aggregated by the capacity weighted + mean. + + * The clustering module now applies the custom strategies to time-dependant + data. + + * The function ``pypsa.clustering.spatial.get_clustering_from_busmap`` and + ``pypsa.clustering.spatial.aggregategenerators`` now allows the passing of a + list of buses for which aggregation of all carriers is desired. Generation + from a carrier at a bus is aggregated now if: It is either in the passed + list of aggregated carriers, or in the list of aggregated buses. + + * Take generator strategies for time-series into account. Before, time-series + would always be aggregated by summing. + () + + * The deprecated ``networkclustering`` module was removed. + () + +* A new function `get_country_and_carrier` was added to the statistics module in + order to group statistics by country and carrier. + () + +* NetCDF file compression is now disabled by default when exporting networks. + () + +**Breaking Changes** + +* The ``Clustering`` class no longer contains a positive and negative linemap. + +* Outdated examples were removed. () + +**Bugfixes** + +* In the statistics module, the calculation of operational costs of storage + units was corrected. () + + +## [**v0.24.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.24.0) 27th June 2023 { id="v0.24.0" } + +* PyPSA now supports quadratic marginal cost terms. A new column + `marginal_cost_quadratic` was added to generators, links, stores and storage + units. The quadratic marginal cost is added to the objective function when + calling ``n.optimize()``. This requires a solver that is able to solve quadratic problems, for instance, + HiGHS, Gurobi, Xpress, or CPLEX. +* The statistics function now allows calculating energy balances + ``n.statistics.energy_balance()`` and dispatch ``n.statistics.dispatch()``, as + well as time series (e.g. ``n.statistics.curtailment(aggregate_time=False)``). + The energy balance can be configured to yield energy balance time series for + each bus. +* The statistics function ``n.statistics()`` now also supports the calculation + of the market values of components. +* The function ``n.set_snapshots()`` now takes two optional keyword arguments; ``default_snapshot_weightings`` + to change the default snapshot weightings, and ``weightings_from_timedelta`` + to compute the weights if snapshots are of type ``pd.DatetimeIndex``. +* The function ``n.lopf()`` is deprecated in favour of the linopy-based + implementation ``n.optimize()`` and will be removed in PyPSA v1.0. We will + have a generous transition period, but please start migrating your + ``extra_functionality`` functions, e.g. by following our [migration guide + ](https://docs.pypsa.orgxamples/optimization-with-linopy-migrate-extra-functionalities.html). +* The module ``pypsa.networkclustering`` was moved to + ``pypsa.clustering.spatial``. The module ``pypsa.networkclustering`` is now + deprecated but all functionality will continue to be accessible until PyPSA v0.25. +* Bug fix in linearized unit commitment implementation correcting sign. +* The minimum required version of ``linopy`` is now ``0.2.1``. +* Dropped support for Python 3.8. The minimum required version of Python is now 3.9. + + +## [**v0.23.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.23.0) 10th May 2023 { id="v0.23.0" } + +* Transmission losses can now be represented during optimisation with + ``n.optimize()`` or ``n.lopf()`` using a piecewise linear approximation of the + loss parabola as presented in [this paper + ](https://doi.org/10.1016/j.apenergy.2022.118859). The number of segments can + be chosen with the argument ``n.optimize(transmission_losses=3)``. The default + remains that transmission losses are neglected with + ``n.optimize(transmission_losses=0)``, and analogously for + ``n.lopf(pyomo=True)`` and ``n.lopf(pyomo=False)``. () + +* Efficiencies and standing losses of stores, storage units and generators can + now be specified as time-varying attributes (``efficiency``, + ``efficiency_dispatch``, ``efficiency_store``, ``standing_loss``). For + example, this allows specifying temperature-dependent generator efficiencies + or evaporation in hydro reservoirs. () + +* Unit commitment constraints (ramp limits, start up and shut down costs) can + now also be applied to links in addition to generators. This is useful to + model the operational restrictions of fuel synthesis plants. () + +* Added implementation for a linearized unit commitment approximation (LP-based) + that can be activated when calling + ``n.optimize(linearized_unit_commitment=True)``. The implementation follows + Hua et al. (2017), [10.1109/TPWRS.2017.2735026 + ](https://doi.org/10.1109/TPWRS.2017.2735026). This functionality is not + implemented for ``n.lopf()``. () + +* NetCDF (``.nc``) and HDF5 (``.h5``) network files can now be read directly + from URL: + ``pypsa.Network("https://github.com/PyPSA/PyPSA/raw/master/examples/scigrid-de/scigrid-with-load-gen-trafos.nc")`` + () + +* Networks are now compressed when exporting the NetCDF + ``n.export_to_netcdf(...)`` step using the native compression feature of + netCDF files. Additionally, a typecasting option from float64 to float 32 was + added. Existing network files are not affected. To also compress existing + networks, load and save them using ``xarray`` with compression specified, see + [the xarray documentation + ](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.to_netcdf.html) + for details. The compression can be disabled with + ``n.export_to_netcdf(compression=None)``. Use + ``n.export_to_netcdf(float32=True, compression={'zlib': True, 'complevel': 9, 'least_significant_digit': 5})`` + for high compression. (, ) + +* Time aggregation for OPEX, curtailment, supply, withdrawal, and revenue now + default to 'sum' rather than 'mean'. + +* A new type of ``GlobalConstraint`` called `operational_limit` is now supported + through the ``n.optimize()`` function. It allows to limit the total + production of a carrier analogous to `primary_energy_limit` with the + difference that it applies directly to the production of a carrier rather than + to an attribute of the primary energy use. () + +* The attributes ``lifetime`` and ``build_year`` are now aggregated with a + capacity-weighted mean when clustering the network. Previously, these + attributes had to carry identical values for components that were to be + merged. () + +* To enable better backwards compatibility with the ``n.lopf()`` function, the + ``n.optimize()`` functions has now the explicit keyword argument + ``solver_options``. It takes a dictionary of options passed to the solver. + Before, these were passed as keyword arguments to the ``n.optimize()`` + function. Note that both functionalities are supported. () + +* Fixed interference of io routines with linopy optimisation (, ) + +* Fix a bug where time-dependant generator variables could be forgotten during + aggregation in a particular case. () + +* A new type of ``GlobalConstraint`` called `operational_limit` is now supported through the `Network.optimize` function. It allows to limit the total production of a carrier analogous to `primary_energy_limit` with the difference that it applies directly to the production of a carrier rather than to an attribute of the primary energy use. + +* Fix an issue appeared when processing networks which were reduced to a set of + isolated nodes in course of clustering. Previously, an empty ``Line`` + component has lead to problems when processing empty lines-related dataframes. + That has been fixed by introducing special treatment in case a lines dataframe + is empty. () + + +## [**v0.22.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.22.1) 15th February 2023 { id="v0.22.1" } + +* The model creation for large, sector-coupled models is now much quicker. +* The FICO Xpress solver interface now skips loading a basis if there is an + error associated with the basis function and continues without it. +* The colors of borders and coastlines can now be controlled with + ``n.plot(color_geomap=dict(border='b', coastline='r'))``. +* Plotting multiple legends was fixed for applying a tight layout with ``matplotlib>=3.6``. +* The plotting function now supports plotting negative and positive values + separately per bus using the argument ```n.plot(bus_split_circles=...)``. This + results in drawing separate half circles for positive and negative values. + + +## [**v0.22.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.22.0) 3rd February 2023 { id="v0.22.0" } + +* Python 3.11 is now tested. The support of Python 3.7 was dropped. The minimum supported python version is now 3.8. +* The linopy based optimization (``n.optimize()``) now allows to limit the carrier's growth by an additional linear term, so that one can limit an expansion growth by multiples of what was installed in the preceding investment period. +* The linopy based optimization now requires ``linopy`` v0.1.1 or higher. The new version eases the creation of custom constraint through a better display of linear expression and variables. +* Wrapped functions defined by the ``Network.optimize`` accessor are now wrapping meta information of the original functions more coherently. This enables better feedback in interactive sessions. +* Checking of datatypes in the ``consistency_check`` is now deactivated by default. Set ``n.consistency_check(check_dtypes=True)`` to activate it. +* The plotting functionality ``n.plot()`` now supports setting alpha values on the branch components individually. +* The plotting functionality ``n.plot()`` now allows independent control of arrow size and branch width using ``line_widths`` and ``flow`` in conjunction. +* The documentation shines in a new look using the ``sphinx-book-theme``. Limit ``sphinx`` to versions below 6. +* Address various deprecation warnings. + +## [**v0.21.3**](https://github.com/PyPSA/PyPSA/releases/tag/v0.21.3) 16th December 2022 { id="v0.21.3" } + +* Bugfix: Time-varying marginal cost of a component were removed if at least one of its value was zero. +* Bugfix: Due to xarray's ``groupby`` operation not fully supporting multi-indexes in recent version (see https://github.com/pydata/xarray/issues/6836), parts of the multi investment optimization code was adjusted. +* Update HiGHS parsing function in linopt for HiGHS version 1.4.0. Minimum version of HiGHS is v1.3.0. Older versions have not been tested. +* Update of gas boiler example to ``linopy``. +* New standard line types for DC lines. +* Included code of conduct. + +## [**v0.21.2**](https://github.com/PyPSA/PyPSA/releases/tag/v0.21.2) 30th November 2022 { id="v0.21.2" } + +* Compatibility with ``pyomo>=6.4.3``. + +## [**v0.21.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.21.1) 10th November 2022 { id="v0.21.1" } + +* Default of ``n.lopf()`` changed to ``n.lopf(pyomo=False)``. +* Bugfix in calculating statistics of curtailment. +* Bugfix in IO of netCDF network files for datetime indices. +* Bugfix for warning about imports from different PyPSA versions. +* Add linopy and statistics module to API reference. + +## [**v0.21.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.21.0) 7th November 2022 { id="v0.21.0" } + +* A new optimization module `optimization` based on [Linopy](https://github.com/PyPSA/linopy) was introduced. It aims at being as fast as the in-house optimization code and as flexible as the optimization with ``Pyomo``. A introduction to the optimization can be found at the [examples section + ](https://docs.pypsa.org/en/latest/examples/optimization-with-linopy.html) a migration guide for extra functionalities can be found at [here + ](https://docs.pypsa.org/en/latest/examples/optimization-with-linopy-migrate-extra-functionalities.html) +* A new module for a quick calculation of system relevant quantities was introduced. It is directly accessible via the new accessor `Network.statistics` which returns a table of values often calculated manually. At the same time `Network.statistics` allows to call individual functions, as `capex`, `opex`, `capacity_factor` etc. +* Add reference to [Discord server](https://discord.gg/AnuJBk23FU) for support and discussion. +* Restore import of pandapower networks. Issues regarding the transformer component and indexing as well as missing imports for shunts are fixed. () +* The import performance of networks was improved. With the changes, the import time for standard netcdf imports decreased by roughly 70%. + +## [**v0.20.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.20.1) 6th October 2022 { id="v0.20.1" } + +* The representation of networks was modified to show the number of components and snapshots. +* The performance of the consistency check function was improved. The consistency check was extended by validating the capacity expansion limits as well as global constraint attributes. +* When applying network clustering algorithms, per unit time series are now aggregated using a capacity-weighted average and default aggregation strategies were adjusted. +* The value of ``n.objective`` is now set to NaN for failed optimisation runs. +* Added example notebook on how to model redispatch with PyPSA. +* Added new network plotting example. +* Bugfix for non-pyomo version of ``n.sclopf()``. +* Accept ``pathlib.Path`` objects when importing networks with ``pypsa.Network()``. +* Addressed ``.iteritems()`` deprecations. + +## [**v0.20.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.20.0) 26th July 2022 { id="v0.20.0" } + +This release contains new features for plotting and storing metadata with Network objects. + +* A new attribute ``n.meta`` was added to the Network object. This can be an arbitrary dictionary, and is used to store meta data about the network. + +* Improved support for individually normed colorbars in ``n.plot()`` for buses, lines, links, transformers with keyword arguments ``bus_norm``, ``line_norm``, ``link_norm``, ``transformer_norm``. + + ```python title="Colorbar plotting example" + import pypsa + import matplotlib.pyplot as plt + n = pypsa.examples.ac_dc_meshed() + norm = plt.Normalize(vmin=0, vmax=10) + n.plot( + bus_colors=n.buses.x, + bus_cmap='viridis', + bus_norm=norm + ) + plt.colorbar(plt.cm.ScalarMappable(cmap='viridis', norm=norm)) + ``` + +* New utility functions to add legends for line widths (`pypsa.plot.add_legend_lines`), circles and pie chart areas (`pypsa.plot.add_legend_circles`), and patch colors (`pypsa.plot.add_legend_patches`). + See the following example: + + ```python title="Legend plotting example" + import pypsa + import matplotlib.pyplot as plt + import cartopy.crs as ccrs + from pypsa.plot import add_legend_circles + + n = pypsa.examples.ac_dc_meshed() + + fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) + n.plot(ax=ax, bus_sizes=1) + + add_legend_circles( + ax, + [1, 0.5], + ["reference size", "reference size 2"], + legend_kw=dict(frameon=False, bbox_to_anchor=(1,0.1)) + ) + ``` + +* When iterating over components of a Subnetwork, only a those assets are included in the dataframes which are included in the subnetwork. + +* In ``n.plot()``, compute boundaries in all cases for consistent circle sizes. This is realised by setting a new default margin of 0.05. + +* Compatibility with pyomo 6.4.1. + +* Removed ``pypsa.stats`` module. + +* Extended defaults for the clustering of attributes in ``pypsa.networkclustering``. + +* Removed deprecated clustering algorithms in ``pypsa.networkclustering``. + +* Improved documentation and README. + +* Fix a few deprecations. + +* Improved test coverage, e.g. when copying networks. + +* Testing: ``pypower`` is not importable with newest numpy versions. Skip test if import fails. + +Special thanks for this release to @Cellophil, +@txelldm and @rockstaedt for improving test coverage and documentation. + + +## [**v0.19.3**](https://github.com/PyPSA/PyPSA/releases/tag/v0.19.3) 22nd April 2022 { id="v0.19.3" } + +* Apply pre-commit formats to support development (incl. black formatting, + jupyter cleanup, import sorting, preventing large file uploads). This will + distort ``git blame`` functionality, which can be fixed by running ``git + config blame.ignoreRevsFile .git-blame-ignore-revs`` inside the PyPSA + repository. Run ``pre-commit install`` to set up locally. +* Change message when exporting and importing networks without a set ``network_name``. + Fixes [[#381](https://github.com/PyPSA/PyPSA/issues/381)]. +* Greedy Modularity Maximisation was introduced as new spatial + clustering method (). + +## [**v0.19.2**](https://github.com/PyPSA/PyPSA/releases/tag/v0.19.2) 7th March 2022 { id="v0.19.2" } + +* Add standard line type for 750 kV transmission line. + +## [**v0.19.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.19.1) 18th February 2022 { id="v0.19.1" } + +* When setting ramp limits for links and calling ``Network.lopf`` with ``pyomo=False``, an unexpected KeyError was raised. This was fixed by correctly accessing the data frame referring to the power dispatch of links. + +## [**v0.19.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.19.0) 11th February 2022 { id="v0.19.0" } + +This release contains new features for ramping constraints in link components, +hierarchical network clustering functionality, and an interface to the +open-source HiGHS solver. + +**New Features** + +* Ramp limits for ``Links``. The ``Link`` component has two new attributes, :code:`ramp_limit_up` and + :code:`ramp_limit_down`, which limits the marginal power increase equivalent to the + implementation for generators. The new attributes are only considered when + running ``network.lopf(pyomo=False)``. + +* Hierarchical Agglomerative Clustering (HAC) was introduced as new spatial + clustering method (). + +* Clustering networks now also supports the clustering of time-series associated + to lines. + +* Add open-source [HiGHS solver](https://github.com/ERGO-Code/HiGHS). + +* A new convenience function ``Network.get_committable_i`` was added. This returns + an index containing all committable assets of component ``c``. In case that + component ``c`` does not support committable assets, it returns an empty + dataframe. + +* A warning message is shown if a network contains one or more links with an + :code:`efficiency` smaller than 1 and a negative value for :code:`p_min_pu` + (). + +* New example for spatial clustering. + +* Speed-up of ``network.plot()`` by only plotting buses with non-zero size. + +* Increased test coverage. + +**Changes** + +* The names of the indexes in static dataframes are now set to the component + names. So, the index of ``n.generators`` has the name 'Generator'. The same + accounts for the columns of the timeseries. + +* The snapshot levels of a multi-indexed snapshot were renamed to ['period', + 'timestep'], the name of the index was set to 'snapshot'. This makes the + snapshot name coherent for single and multi-indexed snapshots. + +**Bugs and Compatibility** + +* Compatibility with ``pandas>=1.4``. + +* Drop support for Python 3.6 in accordance with its + [end-of-life](https://endoflife.date/python). + +* Use ``nx.Graph`` instead of ``nx.OrderedGraph`` which guarantees order is + preserved for Python 3.7 and above. + +* Add assert: CBC solver does not work with '>' and '<'. + +* When running ``network.lopf(pyomo=False)``, the ramp limits did not take + the time step right before the optimization horizon into account (relevant for + rolling horizon optimization). This is now fixed. + +* Fix bug when multi-links are defined but the network has no links. + +Special thanks for this release to Samuel Matthew Dumlao (@smdumlao) for +implementing the ramp limits for Links in PyPSA, Martha Frysztacki (@martacki) for +implementing the hierarchical network clustering, and Max Parzen (@pz-max) for +implementing the HiGHS solver interface. + +## [**v0.18.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.18.1) 15th October 2021 { id="v0.18.1" } + +* Compatibility with ``pyomo>=6.1``. + +* Bugfix: specifying the ``solver_logfile`` is no longer mandatory with CPLEX for + ``n.lopf(pyomo=False)``. + +* The distance measures for the network clustering functions ``busmap_by_spectral()`` + and ``busmap_by_louvain()`` were adapted to use electrical distance + (``s_nom/|r+i*x|``} before: ``num_parallel``). + +* Deprecations: The functions ``busmap_by_linemask()``, ``busmap_by_length()``, ``length_clustering()``, + ``busmap_by_spectral_clustering()``, ``spectral_clustering()``, ``busmap_by_louvain()``, + ``louvain_clustering()``, ``busmap_by_rectangular_grid()``, ``rectangular_grid_clustering()`` + and ``stubs_clustering()`` were deprecated and will be removed in v0.20. + +* Distance measures for function ``busmap_by_spectral()`` and ``busmap_by_louvain()`` + were adapted to electrical distance (``s_nom/|r+i*x|``) (before: ``num_parallel``) + +* In ``pypsa.networkclustering``, strip the string of the clustered + component name. Not doing this had caused troubles for components with an + empty carrier column. + +* Various documentation updates. + + +## [**v0.18.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.18.0) 12th August 2021 { id="v0.18.0" } + +This release contains new features for pathway optimisation, improvements of the +documentation's examples section as well as compatibility and bug fixes. + +**Licensing** + +* With this release, we have changed the licence from the copyleft GPLv3 + to the more liberal MIT licence with the consent of all contributors + (for the reasoning why, see ()). + +**New features** + +* Added support for the optimisation of multiple investment periods, also known + as pathway optimization. With this feature, snapshots can span over multiple + years or decades which are divided into investment periods. Within each + investment period, assets can be added to the network. The optimization only + works with ``pyomo=False``. For more information see the multi-horizon documentation. Endogenous learning curves can be applied as ``extra_functionality``. + +* ``n.snapshot_weightings`` is now a ``pandas.DataFrame`` rather than + a ``pandas.Series`` with weightings now subdivided into weightings + for the objective function, generators and stores/storage + units. This separation of weightings is relevant for temporal + snapshot clustering, where the weight in the objective function may + differ from the number of hours represented by each snapshot for + storage purposes. + + * Objective weightings determine the multiplier of the marginal costs in the + objective function of the LOPF. + + * Generator weightings specify the impact of generators in a + ``GlobalConstraint`` (e.g. in a carbon dioxide emission constraint). + + * Store weightings define the elapsed hours for the charge, discharge, + standing loss and spillage of storage units and stores in order to determine + the current state of charge. + + PyPSA still supports setting ``n.snapshot_weightings`` with a ``pandas.Series``. + In this case, the weightings are uniformly applied to all columns of the new + ``n.snapshot_weightings`` ``pandas.DataFrame``. + +* All functionalities except for optimisation with ``pyomo=True`` now work + with multi-indexed snapshots. + +* Many example notebooks are now also integrated in the + documentation. See [Examples](examples/examples.md). + + +* A new module ``examples`` was added which contains frontend functions for + retrieving/loading example networks provided by the PyPSA project. + +* When solving ``n.lopf(pyomo=False)``, PyPSA now supports setting lower and + upper capacity bounds per bus and carrier. These are specified in the columns + ``n.buses['nom_min_{carrier}']`` and ``n.buses['nom_max_{carrier}']`` + respectively. For example, if multiple generators of carrier ``wind`` are at bus + ``bus1``, the combined capacity is limited to 1000 MW by setting + ``n.buses.loc['bus1', 'nom_max_wind'] = 1000`` (a minimal capacity is forced by + setting ``n.buses.loc['bus1', 'nom_min_wind']``). In the same manner the + combined ``p_nom`` of components ``StorageUnit`` and ``e_nom`` of components + ``Store`` can be limited. + +* Add new attribute ``carrier`` to the components ``Line``, ``Link``, ``Store`` + and ``Load``, defining the energy carrier of the components. Its default is an + empty string. When calling ``n.calculate_dependent_values()``, empty carriers + are replaced by the carriers of the buses to which the components are attached. + +* Add new descriptive attribute ``unit`` to ``bus`` component. + +* Automated upload of code coverage reports for pull requests. + +**Changes** + +* When using iterative LOPF with ``n.ilopf()`` to consider impedance updates of + reinforced transmission lines, the attributes ``p_nom`` and ``s_nom`` of lines + and links are reset to their original values after final iteration. + +* ``n.snapshots`` are now a property, hence assigning values with + ``n.snapshots = values`` is the same as ``n.set_snapshots(values)``. + +* Remove deprecated function ``geo.area_from_lon_lat_poly``. + +**Deprecations** + +* The function ``geo.area_from_lon_lat_poly()`` was deprecated and will be removed in v0.19. + +* The deprecated argument ``csv_folder_name`` in ``pypsa.Network`` was removed. + +* The deprecated column names ``source``, ``dispatch``, ``p_max_pu_fixed``, + ``p_min_pu_fixed`` for the class ``Generator``, ``current_type`` for the class + ``Bus`` and ``s_nom`` for the class ``Link`` were removed. + +**Bugs and Compatibility** + +* Added support for ``pandas`` version 1.3. + +* Adjust log file creation for CPLEX version 12.10 and higher. + +* ``n.snapshot_weightings`` is no longer copied for ``n.copy(with_time=False)``. + +* Bugfix in ``n.ilopf()`` where previously all links were fixed in the final + iteration when it should only be the HVDC links. + +* Fix setting ``margin`` and ``boundaries`` when plotting a network with ``geomap=False``. + +Special thanks for this release to Lisa Zeyen (@lisazeyen) for implementing the +multi-horizon investment in PyPSA and to Fabian Hofmann (@FabianHofmann) for +thoroughly reviewing it and adding the example notebooks to the documentation. + + +## [**v0.17.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.17.1) 15th July 2020 { id="v0.17.1" } + +This release contains bug fixes and extensions to the features for optimization when not using Pyomo. + +* N-1 security-constrained linear optimal power flow is now also supported without pyomo by running ``network.sclopf(pyomo=False)``. + +* Added support for the FICO Xpress commercial solver for optimization without pyomo, i.e. ``pyomo=False``. + +* There was a bug in the LOPF with ``pyomo=False`` whereby if some Links + were defined with multiple outputs (i.e. bus2, bus3, etc. were + defined), but there remained some Links without multiple outputs + (bus2, bus3, etc. set to ``""``), then the Links without multiple + outputs were assigned erroneous non-zero values for p2, p3, etc. in + the LOPF with ``pyomo=False``. Now p2, p3, etc. revert to the default + value for Links where bus2, bus3, etc. are not defined, just like + for the LOPF with ``pyomo=True``. + +* Handle double-asterisk prefix in ``solution_fn`` when solving ``n.lopf(pyomo=False)`` using CBC. + +* When solving ``n.lopf(pyomo=False, store_basis=True, solver_name="cplex")`` an error raised by trying to store a non-existing basis is caught. + +* Add compatibility for Pyomo 5.7. This is also the new minimum requirement. + +* Fixed bug when saving dual variables of the line volume limit. Now using dual from the second last iteration in ``pypsa.linopf``, + because last iteration returns NaN (no optimisation of line capacities in final iteration). + +* Added tracking of iterations of global constraints in the optimisation. + +* When solving ``n.lopf(pyomo=False)``, PyPSA now constrains the dispatch variables for non extendable components with actual constraints, not with standard variable bounds. This allows retrieving shadow prices for all dispatch variables when running ``n.lopf(pyomo=False, keep_shadowprices=True)``. + +* Can now cluster lines with different static ``s_max_pu`` values. Time-varying ``s_max_pu`` are not supported in clustering. + +* Improved handling of optional dependencies for network clustering functionalities (``sklearn`` and ``community``). + +Thanks to Pietro Belotti from FICO for adding the Xpress support, to Fabian Neumann (KIT) and Fabian Hofmann (FIAS) for all their +hard work on this release, and to all those who fixed bugs and reported issues. + +## [**v0.17.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.17.0) 23rd March 2020 { id="v0.17.0" } + +This release contains some minor breaking changes to plotting, some +new features and bug fixes. + + +* For plotting geographical features ``basemap`` is not supported anymore. Please use ``cartopy`` instead. +* Changes in the plotting functions ``n.plot()`` and ``n.iplot()`` include some **breaking changes**: + + * A set of new arguments were introduced to separate style parameters of the different branch components: ``link_colors``, ``link_widths``, ``transformer_colors``, ``transformer_widths``, ``link_cmap``, ``transformer_cmap`` + * ``line_widths``, ``line_colors``, and ``line_cmap`` now only apply for lines and can no longer be used for other branch types (links and transformers). Passing a pandas.Series with a pandas.MultiIndex will raise an error. + * Additionally, the function `n.iplot()` has new arguments ``line_text``, ``link_text``, ``transformer_text`` to configure the text displayed when hovering over a branch component. + * The function ``directed_flow()`` now takes only a pandas.Series with single pandas.Index. + * The argument ``bus_colorscale`` in ``n.iplot()`` was renamed to ``bus_cmap``. + * The default colours changed. + +* If non-standard output fields in the time-dependent ``network.components_t`` (e.g. ``network.links_t.p2`` when there are multi-links) were exported, then PyPSA will now also import them automatically without requiring the use of the ``override_component_attrs`` argument. +* Deep copies of networks can now be created with a subset of + snapshots, e.g. ``network.copy(snapshots=network.snapshots[:2])``. +* When using the ``pyomo=False`` formulation of the LOPF (``network.lopf(pyomo=False)``): + + * It is now possible to alter the objective function. + Terms can be added to the objective via ``extra_functionality`` + using the function `pypsa.linopt.write_objective`. + When a pure custom objective function needs to be declared, + one can set ``skip_objective=True``. + In this case, only terms defined through ``extra_functionality`` + will be considered in the objective function. + * Shadow prices of capacity bounds for non-extendable passive branches + are parsed (similar to the ``pyomo=True`` setting) + * Fixed `pypsa.linopf.define_kirchhoff_constraints` to handle + exclusively radial network topologies. + * CPLEX is now supported as an additional solver option. Enable it by installing the [cplex](https://pypi.org/project/cplex/) package (e.g. via ``pip install cplex`` or ``conda install -c ibmdecisionoptimization cplex``) and setting ``solver_name='cplex'`` + +* When plotting, ``bus_sizes`` are now consistent when they have a ``pandas.MultiIndex`` + or a ``pandas.Index``. The default is changed to ``bus_sizes=0.01`` because the bus + sizes now relate to the axis values. +* When plotting, ``bus_alpha`` can now be used to add an alpha channel + which controls the opacity of the bus markers. +* The argument ``bus_colors`` can a now also be a pandas.Series. +* The ``carrier`` component has two new columns 'color' and 'nice_name'. + The color column is used by the plotting function if ``bus_sizes`` is + a pandas.Series with a MultiIndex and ``bus_colors`` is not explicitly defined. +* The function `pypsa.linopf.ilopf` can now track the intermediate branch capacities + and objective values for each iteration using the ``track_iterations`` keyword. +* Fixed unit commitment: + + * when ``min_up_time`` of committable generators exceeds the length of snapshots. + * when network does not feature any extendable generators. + +* Fixed import from pandapower for transformers not based on standard types. +* The various Jupyter Notebook examples are now available on the [binder](https://mybinder.org/) platform. This allows new users to interactively run and explore the examples without the need of installing anything on their computers. +* Minor adjustments for compatibility with pandas v1.0.0. +* After optimizing, the network has now an additional attribute ``objective_constant`` which reflects the capital cost of already existing infrastructure in the network referring to ``p_nom`` and ``s_nom`` values. + +Thanks to Fabian Hofmann (FIAS) and Fabian Neumann (KIT) for all their +hard work on this release, and to all those who reported issues. + + +## [**v0.16.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.16.1) 10th January 2020 { id="v0.16.1" } + +This release contains a few minor bux fixes from the introduction of +nomopyomo in the previous release, as well as a few minor features. + +* When using the ``nomopyomo`` formulation of the LOPF with + ``network.lopf(pyomo=False)``, PyPSA was not correcting the bus + marginal prices by dividing by the ``network.snapshot_weightings``, as is done + in the ``pyomo`` formulation. This correction is now applied in the + ``nomopyomo`` formulation to be consistent with the ``pyomo`` + formulation. (The reason this correction is applied is so that the + prices have a clear currency/MWh definition regardless of the + snapshot weightings. It also makes them stay roughly the same when + snapshots are aggregated: e.g. if hourly simulations are sampled + every n-hours, and the snapshot weighting is n.) +* The ``status, termination_condition`` that the ``network.lopf`` returns + is now consistent between the ``nomopyomo`` and ``pyomo`` + formulations. The possible return values are documented in the LOPF + docstring, see also the [LOPF documentation + ](https://docs.pypsa.org/en/latest/user-guide/optimal-power-flow.html#pypsa.Network.lopf). + Furthermore in the ``nomopyomo`` formulation, the solution is still + returned when gurobi finds a suboptimal solution, since this + solution is usually close to optimal. In this case the LOPF returns + a ``status`` of ``warning`` and a ``termination_condition`` of + ``suboptimal``. +* For plotting with ``network.plot()`` you can override the bus + coordinates by passing it a ``layouter`` function from ``networkx``. See + the docstring for more information. This is particularly useful for + networks with no defined coordinates. +* For plotting with ``network.iplot()`` a background from [mapbox + ](https://www.mapbox.com/) can now be integrated. + +Please note that we are still aware of one implementation difference +between ``nomopyomo`` and ``pyomo``, namely that ``nomopyomo`` doesn't read +out shadow prices for non-extendable branches, see the `github issue +`_. + + +## [**v0.16.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.16.0) 20th December 2019 { id="v0.16.0" } + +This release contains major new features. It is also the first release +to drop support for Python 2.7. Only Python 3.6 and 3.7 are supported +going forward. Python 3.8 will be supported as soon as the gurobipy +package in conda is updated. + +* A new version of the linear optimal power flow (LOPF) has been + introduced that uses a custom optimization framework rather than + Pyomo. The new framework, based on [nomoypomo + ](https://github.com/PyPSA/nomopyomo), uses barely any memory and + is much faster than Pyomo. As a result the total memory usage of + PyPSA processing and gurobi is less than a third what it is with + Pyomo for large problems with millions of variables that take + several gigabytes of memory (see this [graphical comparison + ](https://github.com/PyPSA/PyPSA/pull/99) + for a large network optimization). The new framework is not enabled + by default. To enable it, use ``network.lopf(pyomo=False)``. Almost + all features of the regular ``network.lopf`` are implemented with + the exception of minimum down/up time and start up/shut down costs + for unit commitment. If you use the ``extra_functionality`` argument + for ``network.lopf`` you will need to update your code for the new + syntax. There is [documentation + ](https://docs.pypsa.org/en/latest/user-guide/optimal-power-flow.html#pyomo-is-set-to-false) + for the new syntax as well as a [Jupyter notebook of examples + ](https://github.com/PyPSA/PyPSA/blob/master/examples/lopf_with_pyomo_False.ipynb). + +* Distributed active power slack is now implemented for the full + non-linear power flow. If you pass ``network.pf()`` the argument + ``distribute_slack=True``, it will distribute the slack power across + generators proportional to generator dispatch by default, or + according to the distribution scheme provided in the argument + ``slack_weights``. If ``distribute_slack=False`` only the slack + generator takes up the slack. There is further [documentation + ](https://docs.pypsa.org/en/latest/user-guide/power-flow.html#full-non-linear-power-flow)_. + +* Unit testing is now performed on all of GNU/Linux, Windows and MacOS. + +* NB: You may need to update your version of the package ``six``. + +Special thanks for this release to Fabian Hofmann for implementing the +nomopyomo framework in PyPSA and Fabian Neumann for providing the +customizable distributed slack. + + +## [**v0.15.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.15.0) 8th November 2019 { id="v0.15.0" } + +This release contains new improvements and bug fixes. + +* The unit commitment (UC) has been revamped to take account of + constraints at the beginning and end of the simulated ``snapshots`` + better. This is particularly useful for rolling horizon UC. UC now + accounts for up-time and down-time in the periods before the + ``snapshots``. The generator attribute ``initial_status`` has been + replaced with two attributes ``up_time_before`` and + ``down_time_before`` to give information about the status before + ``network.snapshots``. At the end of the simulated ``snapshots``, minimum + up-times and down-times are also enforced. Ramping constraints also + look before the simulation at previous results, if there are + any. See the [unit commitment documentation + ](https://docs.pypsa.org/en/latest/user-guide/optimal-power-flow.html#generator-unit-commitment-constraints) + for full details. The [UC example + ](https://docs.pypsa.org/en/latest/examples/unit-commitment.html) has been updated + with a rolling horizon example at the end. +* Documentation is now available on [readthedocs + ](https://docs.pypsa.org/), with information about functions + pulled from the docstrings. +* The dependency on cartopy is now an optional extra. +* PyPSA now works with pandas 0.25 and above, and networkx above 2.3. +* A bug was fixed that broke the Security-Constrained Linear Optimal + Power Flow (SCLOPF) constraints with extendable lines. +* Network plotting can now plot arrows to indicate the direction of flow by passing ``network.plot`` an ``flow`` argument. +* The objective sense (``minimize`` or ``maximize``) can now be set (default + remains ``minimize``). +* The ``network.snapshot_weightings`` is now carried over when the network + is clustered. +* Various other minor fixes. + +We thank colleagues at TERI for assisting with testing the new unit +commitment code, Clara Büttner for finding the SCLOPF bug, and all +others who contributed issues and pull requests. + + +## [**v0.14.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.14.1) 27th May 2019 { id="v0.14.1" } + +This minor release contains three small bug fixes: + +* Documentation parses now correctly on PyPI +* Python 2.7 and 3.6 are automatically tested using Travis +* PyPSA on Python 2.7 was fixed + +This will also be the first release to be available directly from +[conda-forge](https://conda-forge.org/). + +## [**v0.14.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.14.0) 15th May 2019 { id="v0.14.0" } + +This release contains a new feature and bug fixes. + +* Network plotting can now use the mapping library [cartopy + ](https://scitools.org.uk/cartopy/) as well as [basemap + ](https://matplotlib.org/basemap/), which was used in previous + versions of PyPSA. The basemap developers will be phasing out + basemap over the next few years in favour of cartopy (see their + [end-of-life announcement + ](https://matplotlib.org/basemap/users/intro.html#cartopy-new-management-and-eol-announcement)). PyPSA + now defaults to cartopy unless you tell it explicitly to use + basemap. Otherwise the plotting interface is the same as in previous + versions. +* Optimisation now works with the newest version of Pyomo 5.6.2 (there + was a Pyomo update that affected the opt.py expression for building + linear sums). +* A critical bug in the networkclustering sub-library has been fixed + which was preventing the capital_cost parameter of conventional + generators being handled correctly when networks are aggregated. +* Network.consistency_check() now only prints necessary columns when + reporting NaN values. +* Import from [pandapower](https://www.pandapower.org/)_ networks has + been updated to pandapower 2.0 and to include non-standard lines and + transformers. + +We thank Fons van der Plas and Fabian Hofmann for helping with the +cartopy interface, Chloe Syranidis for pointing out the problem with +the Pyomo 5.6.2 update, Hailiang Liu for the consistency check update +and Christian Brosig for the pandapower updates. + +## [**v0.13.2**](https://github.com/PyPSA/PyPSA/releases/tag/v0.13.2) 10th January 2019 { id="v0.13.2" } + +This minor release contains small new features and fixes. + +* Optimisation now works with Pyomo >= 5.6 (there was a Pyomo update + that affected the opt.py LConstraint object). +* New functional argument can be passed to Network.lopf: + extra_postprocessing(network,snapshots,duals), which is called after + solving and results are extracted. It can be used to get the values + of shadow prices for constraints that are not normally extracted by + PyPSA. +* In the lopf kirchhoff formulation, the cycle constraint is rescaled + by a factor 1e5, which improves the numerical stability of the + interior point algorithm (since the coefficients in the constraint + matrix were very small). +* Updates and fixes to networkclustering, io, plot. + +We thank Soner Candas of TUM for reporting the problem with the most +recent version of Pyomo and providing the fix. + + +## [**v0.13.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.13.1) 27th March 2018 { id="v0.13.1" } + +This release contains bug fixes for the new features introduced in +0.13.0. + +* Export network to netCDF file bug fixed (components that were all + standard except their name were ignored). +* Import/export network to HDF5 file bug fixed and now works with more + than 1000 columns; HDF5 format is no longer deprecated. +* When networks are copied or sliced, overridden components + (introduced in 0.13.0) are also copied. +* Sundry other small fixes. + +We thank Tim Kittel for pointing out the first and second bugs. We +thank Kostas Syranidis for not only pointing out the third issue with +copying overridden components, but also submitting a fix as a pull +request. + +For this release we acknowledge funding to Tom Brown from the +[RE-INVEST project](http://www.reinvestproject.eu/). + + + +## [**v0.13.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.13.0) 25th January 2018 { id="v0.13.0" } + +This release contains new features aimed at coupling power networks to +other energy sectors, fixes for library dependencies and some minor +internal API changes. + +* If you want to define your own components and override the standard + functionality of PyPSA, you can now override the standard components + by passing pypsa.Network() the arguments ``override_components`` and + ``override_component_attrs``, see the section on + [custom components](user-guide/components.md). There are examples for defining new + components in the git repository in ``examples/new_components/``, + including an example of overriding ``network.lopf()`` for + functionality for combined-heat-and-power (CHP) plants. +* The ``Link`` component can now be defined with multiple outputs in + fixed ratio to the power in the single input by defining new columns + ``bus2``, ``bus3``, etc. (``bus`` followed by an integer) in + ``network.links`` along with associated columns for the efficiencies + ``efficiency2``, ``efficiency3``, etc. The different outputs are + then proportional to the input according to the efficiency; see + the [Link component documentation](user-guide/components/links.md) and + the [LOPF documentation](user-guide/network-optimization.md) and the [example of a CHP with a fixed power-heat + ratio](https://docs.pypsa.org/en/latest/examples/chp-fixed-heat-power-ratio.html). +* Networks can now be exported to and imported from netCDF files with + ``network.export_to_netcdf()`` and + ``network.import_from_netcdf()``. This is faster than using CSV + files and the files take up less space. Import and export with HDF5 + files, introduced in v0.12.0, is now deprecated. +* The export and import code has been refactored to be more general + and abstract. This does not affect the API. +* The internally-used sets such as ``pypsa.components.all_components`` + and ``pypsa.components.one_port_components`` have been moved from + ``pypsa.components`` to ``network``, i.e. ``network.all_components`` + and ``network.one_port_components``, since these sets may change + from network to network. +* For linear power flow, PyPSA now pre-calculates the effective per + unit reactance ``x_pu_eff`` for AC lines to take account of the + transformer tap ratio, rather than doing it on the fly; this makes + some code faster, particularly the kirchhoff formulation of the + LOPF. +* PyPSA is now compatible with networkx 2.0 and 2.1. +* PyPSA now requires Pyomo version greater than 5.3. +* PyPSA now uses the [Travis CI](https://travis-ci.org/PyPSA/PyPSA) + continuous integration service to test every commit in the [PyPSA + GitHub repository](https://github.com/PyPSA/PyPSA). This will + allow us to catch library dependency issues faster. + +We thank Russell Smith of Edison Energy for the pull request for the +effective reactance that sped up the LOPF code and Tom Edwards for +pointing out the Pyomo version dependency issue. + +For this release we also acknowledge funding to Tom Brown from the +[RE-INVEST project](http://www.reinvestproject.eu/). + + + + +## [**v0.12.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.12.0) 30th November 2017 { id="v0.12.0" } + +This release contains new features and bug fixes. + +* Support for Pyomo's persistent solver interface, so if you're making + small changes to an optimisation model (e.g. tweaking a parameter), + you don't have to rebuild the model every time. To enable this, + ``network_lopf`` has been internally split into ``build_model``, + ``prepare_solver`` and ``solve`` to allow more fine-grained control of the + solving steps. Currently the new Pyomo PersistentSolver interface + is not in the main Pyomo branch, see + [#223](https://github.com/Pyomo/pyomo/pull/223); you can obtain it with + ``pip install git+https://github.com/Pyomo/pyomo@persistent_interfaces`` +* Lines and transformers (i.e. passive branches) have a new attribute + ``s_max_pu`` to restrict the flow in the OPF, just like ``p_max_pu`` + for generators and links. It works by restricting the absolute value + of the flow per unit of the nominal rating ``abs(flow) <= + s_max_pu*s_nom``. For lines this can represent an n-1 contingency + factor or it can be time-varying to represent weather-dependent + dynamic line rating. +* The ``marginal_cost`` attribute of generators, storage units, stores + and links can now be time dependent. +* When initialising the Network object, i.e. ``network = + pypsa.Network()``, the first keyword argument is now ``import_name`` + instead of ``csv_folder_name``. With ``import_name`` PyPSA + recognises whether it is a CSV folder or an HDF5 file based on the + file name ending and deals with it appropriately. Example usage: + ``nw1 = pypsa.Network("my_store.h5")`` and ``nw2 = + pypsa.Network("/my/folder")``. The keyword argument + ``csv_folder_name`` is still there but is deprecated. +* The value ``network.objective`` is now read from the Pyomo results + attribute ``Upper Bound`` instead of ``Lower Bound``. This is + because for MILP problems under certain circumstances CPLEX records + the ``Lower bound`` as the relaxed value. ``Upper bound`` is correctly + recorded as the integer objective value. +* Bug fix due to changes in pandas 0.21.0: A bug affecting various + places in the code, including causing ``network.lopf`` to fail with + GLPK, is fixed. This is because in pandas 0.21.0 the sum of an empty + Series/DataFrame returns NaN, whereas before it returned zero. This + is a subtle bug; we hope we've fixed all instances of it, but get in + touch if you notice NaNs creeping in where they shouldn't be. All + our tests run fine. +* Bug fix due to changes in scipy 1.0.0: For the new version of scipy, + ``csgraph`` has to be imported explicit. +* Bug fix: A bug whereby logging level was not always correctly being + seen by the OPF results printout is fixed. +* Bug fix: The storage unit spillage had a bug in the LOPF, whereby it + was not respecting ``network.snapshot_weightings`` properly. + +We thank René Garcia Rosas, João Gorenstein Dedecca, Marko Kolenc, +Matteo De Felice and Florian Kühnlenz for promptly notifying us about +issues. + + +## [**v0.11.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.11.0) 21st October 2017 { id="v0.11.0" } + +This release contains new features but no changes to existing APIs. + +* There is a new function ``network.iplot()`` which creates an + interactive plot in Jupyter notebooks using the [plotly + ](https://plot.ly/python/) library. This reveals bus and branch + properties when the mouse hovers over them and allows users to + easily zoom in and out on the network. See the (sparse) documentation + [Plotting documentation](api/networks/plot.md). +* There is a new function ``network.madd()`` for adding multiple new + components to the network. This is significantly faster than + repeatedly calling ``network.add()`` and uses the functions + ``network.import_components_from_dataframe()`` and + ``network.import_series_from_dataframe()`` internally. +* There are new functions ``network.export_to_hdf5()`` and + ``network.import_from_hdf5()`` for exporting and importing networks + as single files in the [Hierarchical Data Format + ](https://en.wikipedia.org/wiki/Hierarchical_Data_Format). +* In the ``network.lopf()`` function the KKT shadow prices of the + branch limit constraints are now outputted as series called + ``mu_lower`` and ``mu_upper``. + +We thank Bryn Pickering for introducing us to `plotly +`_ and helping to `hack together +`_ +the first working prototype using PyPSA. + + +## [**v0.10.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.10.0) 7th August 2017 { id="v0.10.0" } + +This release contains some minor new features and a few minor but +important API changes. + +* There is a new component [GlobalConstraint](user-guide/components/global-constraints.md) for implementing + constraints that effect many components at once (see also the + [LOPF global constraints documentation](user-guide/optimization/global-constraints.md)). Currently only + constraints related to primary energy (i.e. before conversion with + losses by generators) are supported, the canonical example being CO2 + emissions for an optimisation period. Other primary-energy-related + gas emissions also fall into this framework. Other types of global + constraints will be added in future, e.g. "final energy" (for limits + on the share of renewable or nuclear electricity after conversion), + "generation capacity" (for limits on total capacity expansion of + given carriers) and "transmission capacity" (for limits on the total + expansion of lines and links). This replaces the ad hoc + ``network.co2_limit`` attribute. If you were using this, instead of + ``network.co2_limit = my_cap`` do ``network.add("GlobalConstraint", + "co2_limit", type="primary_energy", + carrier_attribute="co2_emissions", sense="<=", + constant=my_cap)``. The shadow prices of the global constraints + are automatically saved in ``network.global_constraints.mu``. +* The LOPF output ``network.buses_t.marginal_price`` is now defined + differently if ``network.snapshot_weightings`` are not 1. Previously + if the generator at the top of the merit order had ``marginal_cost`` + c and the snapshot weighting was w, the ``marginal_price`` was + cw. Now it is c, which is more standard. See also + the [nodal power balance documentation](user-guide/optimization/energy-balance.md). +* ``network.pf()`` now returns a dictionary of pandas DataFrames, each + indexed by snapshots and sub-networks. ``converged`` is a table of + booleans indicating whether the power flow has converged; ``error`` + gives the deviation of the non-linear solution; ``n_iter`` the + number of iterations required to achieve the tolerance. +* ``network.consistency_check()`` now includes checking for + potentially infeasible values in ``generator.p_{min,max}_pu``. +* The PyPSA version number is now saved in + ``network.pypsa_version``. In future versions of PyPSA this + information will be used to upgrade data to the latest version of + PyPSA. +* ``network.sclopf()`` has an ``extra_functionality`` argument that + behaves like that for ``network.lopf()``. +* Component attributes which are strings are now better handled on + import and in the consistency checking. +* There is a new [generation investment screening curve example + ](https://docs.pypsa.org/en/latest/examples/generation-investment-screening-curve.html) + showing the long-term equilibrium of generation investment for a + given load profile and comparing it to a screening curve + analysis. +* There is a new [logging example + ](https://docs.pypsa.org/en/latest/examples/logging-demo.html) that demonstrates + how to control the level of logging that PyPSA reports back, + e.g. error/warning/info/debug messages. +* Sundry other bug fixes and improvements. +* All examples have been updated appropriately. + + +Thanks to Nis Martensen for contributing the return values of +``network.pf()`` and Konstantinos Syranidis for contributing the +improved ``network.consistency_check()``. + + + +## [**v0.9.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.9.0) 29th April 2017 { id="v0.9.0" } + +This release mostly contains new features with a few minor API +changes. + +* Unit commitment as a MILP problem is now available for generators in + the Linear Optimal Power Flow (LOPF). If you set ``committable == + True`` for the generator, an addition binary online/offline status + is created. Minimum part loads, minimum up times, minimum down + times, start up costs and shut down costs are implemented. See the + [unit commitment documentation](user-guide/optimization/unit-commitment.md) and the [unit commitment + example](https://docs.pypsa.org/en/latest/examples/unit-commitment.html). Note + that a generator cannot currently have both unit commitment and + capacity expansion optimisation. +* Generator ramping limits have also been implemented for all + generators. See the [ramping limits documentation](user-guide/optimization/unit-commitment.md) and the [unit + commitment example](https://docs.pypsa.org/en/latest/examples/unit-commitment.html). +* Different mathematically-equivalent formulations for the Linear + Optimal Power Flow (LOPF) are now documented + and the arXiv preprint paper [Linear Optimal Power Flow Using Cycle + Flows](https://arxiv.org/abs/1704.01881). The new formulations can + solve up to 20 times faster than the standard angle-based + formulation. +* You can pass the ``network.lopf`` function the ``solver_io`` + argument for pyomo. +* There are some improvements to network clustering and graphing. +* API change: The attribute ``network.now`` has been removed since it + was unnecessary. Now, if you do not pass a ``snapshots`` argument to + network.pf() or network.lpf(), these functions will default to + ``network.snapshots`` rather than ``network.now``. +* API change: When reading in network data from CSV files, PyPSA will + parse snapshot dates as proper datetimes rather than text strings. + + +João Gorenstein Dedecca has also implemented a MILP version of the +transmission expansion, see +, which properly takes +account of the impedance with a disjunctive relaxation. This will be +pulled into the main PyPSA code base soon. + + +## [**v0.8.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.8.0) 25th January 2017 { id="v0.8.0" } + +This is a major release which contains important new features and +changes to the internal API. + +* Standard types are now available for lines and transformers so that + you do not have to calculate the electrical parameters yourself. For + lines you just need to specify the type and the length, see + the [line types documentation](user-guide/components/line-types.md). For transformers you just need to specify the + type, see the [transformer types documentation](user-guide/components/transformer-types.md). The implementation of PyPSA's + standard types is based on [pandapower's standard types](https://pandapower.readthedocs.io/en/latest/std_types/basic.html). The + old interface of specifying r, x, b and g manually is still available. +* The transformer model has been substantially overhauled, see + the [transformer model documentation](user-guide/components/transformers.md). The equivalent model now defaults to the + more accurate T model rather than the PI model, which you can control + by setting the attribute ``model``. Discrete tap steps are implemented + for transformers with types. The tap changer can be defined on the + primary side or the secondary side. In the PF there was a sign error in the implementation of the transformer + ``phase_shift``, which has now been fixed. In the LPF and LOPF angle formulation the ``phase_shift`` has now been + implemented consistently. See the new [transformer example](https://docs.pypsa.org/en/latest/examples/transformer_example.html). +* There is now a rudimentary import function for pandapower networks, + but it doesn't yet work with all switches and 3-winding + transformers. +* The object interface for components has been completely + removed. Objects for each component are no longer stored in + e.g. ``network.lines["obj"]`` and the descriptor + interface for components is gone. You can only access component + attributes through the dataframes, e.g. ``network.lines``. +* Component attributes are now defined in CSV files in + ``pypsa/data/component_attrs/``. You can access these CSVs in the code + via the dictionary ``network.components``, + e.g. ``network.components["Line"]"defaults"]`` will show a pandas + DataFrame with all attributes and their types, defaults, units and + descriptions. These CSVs are also sourced for the documentation in + the [Components documentation](user-guide/components.md), so the documentation will always be up-to-date. +* All examples have been updated appropriately. + + + + +## [**v0.7.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.7.1) 26th November 2016 { id="v0.7.1" } + +This release contains bug fixes, a minor new feature and more +warnings. + +* The unix-only library ``resource`` is no longer imported by default, + which was causing errors for Windows users. +* Bugs in the setting and getting of time-varying attributes for the + object interface have been fixed. +* The ``Link`` attribute ``efficiency`` can now be make time-varying + so that e.g. heat pump Coefficient of Performance (COP) can change + over time due to ambient temperature variations (see the [heat pump + example + ](https://docs.pypsa.org/en/latest/examples/power-to-heat-water-tank.html)). +* ``network.snapshots`` is now cast to a ``pandas.Index``. +* There are new warnings, including when you attach components to + non-existent buses. + + +Thanks to Marius Vespermann for promptly pointing out the ``resource`` +bug. + + + + + +## [**v0.7.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.7.0) 20th November 2016 { id="v0.7.0" } + +This is a major release which contains changes to the API, +particularly regarding time-varying component attributes. + +* ``network.generators_t`` are no longer pandas.Panels but + dictionaries of pandas.DataFrames, with variable columns, so that + you can be flexible about which components have time-varying + attributes; please read the section on time-varying attributes carefully. Essentially + you can either set a component attribute e.g. ``p_max_pu`` of + ``Generator``, to be static by setting it in the DataFrame + ``network.generators``, or you can let it be time-varying by + defining a new column labelled by the generator name in the + DataFrame ``network.generators_t["p_max_pu"]`` as a series, which + causes the static value in ``network.generators`` for that generator + to be ignored. The DataFrame ``network.generators_t["p_max_pu"]`` + now only includes columns which are specifically defined to be + time-varying, thus saving memory. +* The following component attributes can now be time-varying: + ``Link.p_max_pu``, ``Link.p_min_pu``, ``Store.e_max_pu`` and + ``Store.e_min_pu``. This allows the demand-side management scheme of + [https://arxiv.org/abs/1401.4121](https://arxiv.org/abs/1401.4121) to be implemented in PyPSA. +* The properties ``dispatch``, ``p_max_pu_fixed`` and + ``p_min_pu_fixed`` of ``Generator`` and ``StorageUnit`` are now + removed, because the ability to make ``p_max_pu`` and + ``p_min_pu`` either static or time-varying removes the need for this + distinction. +* All messages are sent through the standard Python library + ``logging``, so you can control the level of messages to be + e.g. ``debug``, ``info``, ``warning`` or ``error``. All verbose + switches and print statements have been removed. +* There are now more warnings. +* You can call ``network.consistency_check()`` to make sure all your + components are well defined; see the [Troubleshooting documentation](user-guide/troubleshooting.md). + + +All [examples](https://docs.pypsa.org/en/latest/examples-basic.html) have been updated to +accommodate the changes listed below. + + +## [**v0.6.2**](https://github.com/PyPSA/PyPSA/releases/tag/v0.6.2) 4th November 2016 { id="v0.6.2" } + +This release fixes a single library dependency issue: + +* pf: A single line has been fixed so that it works with new pandas + versions >= 0.19.0. + +We thank Thorben Meiners for promptly pointing out this issue with the +new versions of pandas. + + +## [**v0.6.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.6.1) 25th August 2016 { id="v0.6.1" } + +This release fixes a single critical bug: + +* opf: The latest version of Pyomo (4.4.1) had a bad interaction with + pandas when a pandas.Index was used to index variables. To fix this, + the indices are now cast to lists; compatibility with less recent + versions of Pyomo is also retained. + +We thank Joao Gorenstein Dedecca for promptly notifying us of this +bug. + + + +## [**v0.6.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.6.0) 23rd August 2016 { id="v0.6.0" } + +Like the 0.5.0 release, this release contains API changes, which +complete the integration of sector coupling. You may have to update +your old code. Models for Combined Heat and Power (CHP) units, heat +pumps, resistive Power-to-Heat (P2H), Power-to-Gas (P2G), battery +electric vehicles (BEVs) and chained hydro reservoirs can now be built +(see the `sector coupling examples +`_). The +refactoring of time-dependent variable handling has been postponed +until the 0.7.0 release. In 0.7.0 the object interface to attributes +may also be removed; see below. + +All [examples](https://docs.pypsa.org/en/latest/examples-basic.html) have been updated to +accommodate the changes listed below. + +**Sector coupling** + +* components, opt: A new ``Store`` component has been introduced which + stores energy, inheriting the energy carrier from the bus to which + it is attached. The component is more fundamental than the + ``StorageUnit``, which is equivalent to a ``Store`` and two ``Link`` + for storing and dispatching. The ``Generator`` is equivalent to a + ``Store`` with a lossy ``Link``. There is an [example which shows + the equivalences + ](https://docs.pypsa.org/en/latest/examples/replace-generator-storage-units-with-store.html). + +* components, opt: The ``Source`` component and the ``Generator`` + attribute ``gen.source`` have been renamed ``Carrier`` and + ``gen.carrier``, to be consistent with the ``bus.carrier`` + attribute. Please update your old code. + +* components, opt: The ``Link`` attributes ``link.s_nom*`` have been + renamed ``link.p_nom*`` to reflect the fact that the link can only + dispatch active power. Please update your old code. + +* components, opt: The ``TransportLink`` and ``Converter`` components, + which were deprecated in 0.5.0, have been now completely + removed. Please update your old code to use ``Link`` instead. + +**Downgrading object interface** + +The intention is to have only the pandas DataFrame interface for +accessing component attributes, to make the code simpler. The +automatic generation of objects with descriptor access to attributes +may be removed altogether. + +* examples: Patterns of for loops through ``network.components.obj`` have + been removed. + +* components: The methods on ``Bus`` like ``bus.generators()`` and + ``bus.loads()`` have been removed. + +* components: ``network.add()`` no longer returns the object. + +**Other** + +* components, opf: Unlimited upper bounds for + e.g. ``generator.p_nom_max`` or ``line.s_nom_max`` were previous set + using ``np.nan``; now they are set using ``float("inf")`` which is + more logical. You may have to update your old code accordingly. + +* components: A memory leak whereby references to + ``component.network`` were not being correctly deleted has been + fixed. + + + +## [**v0.5.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.5.0) 21st July 2016 { id="v0.5.0" } + +This is a relatively major release with some API changes, primarily +aimed at allowing coupling with other energy carriers (heat, gas, +etc.). The specification for a change and refactoring to the handling +of time series has also been prepared (see the section on time-varying attributes), which will +be implemented in the next major release v0.6.0 in the late +summer of 2016. + +An example of the coupling between electric and heating sectors can be +found in the GitHub repository at +``pypsa/examples/coupling-with-heating/`` and at +. + + +* components: To allow other energy carriers, the attribute + ``current_type`` fur buses and sub-neworks (sub-networks inherit the + attribute from their buses) has been replaced by ``carrier`` which + can take generic string values (such as "heat" or "gas"). The values + "DC" and "AC" have a special meaning and PyPSA will treat lines and + transformers within these sub-networks according to the load flow + equations. Other carriers can only have single buses in sub-networks + connected by passive branches (since they have no load flow). + +* components: A new component for a controllable directed link + ``Link`` has been introduced; ``TransportLink`` and ``Converter`` + are now *deprecated* and will be removed soon in an 0.6.x + release. Please move your code over now. See + the [controllable link documentation](user-guide/components/links.md) for more details and a description of how + to update your code to work with the new ``Link`` component. All the + examples in the GitHub repository in ``pypsa/examples/`` have been + updated to us the ``Link``. + +* graph: A new sub-module ``pypsa.graph`` has been introduced to + replace most of the networkx functionality with scipy.sparse + methods, which are more performant the pure python methods of + networkx. The discovery of network connected components is now + significantly faster. + +* io: The function ``network.export_to_csv_folder()`` has been + rewritten to only export non-default values of static and series + component attributes. Static and series attributes of all components + are not exported if they are default values. The functionality to + selectively export series has been removed from the export function, + because it was clumsy and hard to use. + + +* plot: Plotting networks is now more performant (using matplotlib + LineCollections) and allows generic branches to be plotted, not just + lines. + +* test: Unit testing for Security-Constrained Linear Optimal Power + Flow (SCLOPF) has been introduced. + + +## [**v0.4.2**](https://github.com/PyPSA/PyPSA/releases/tag/v0.4.2) 17th June 2016 { id="v0.4.2" } + +This release improved the non-linear power flow performance and +included other small refactorings: + +* pf: The non-linear power flow ``network.pf()`` now accepts a list of + snapshots ``network.pf(snapshots)`` and has been refactored to be much + more performant. +* pf: Neither ``network.pf()`` nor ``network.lpf()`` accept the + ``now`` argument anymore - for the power flow on a specific + snapshot, either set ``network.now`` or pass the snapshot as an + argument. +* descriptors: The code has been refactored and unified for each + simple descriptor. +* opt: Constraints now accept both an upper and lower bound with + ``><``. +* opf: Sub-optimal solutions can also be read out of pyomo. + + +## [**v0.4.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.4.1) 3rd April 2016 { id="v0.4.1" } + +This was mostly a bug-fixing and unit-testing release: + +* pf: A bug was fixed in the full non-linear power flow, whereby the + reactive power output of PV generators was not being set correctly. +* io: When importing from PYPOWER ppc, the generators, lines, + transformers and shunt impedances are given names like G1, G2, ..., + L1, T1, S1, to help distinguish them. This change was introduced + because the above bug was not caught by the unit-testing because the + generators were named after the buses. +* opf: A Python 3 dict.keys() list/iterator bug was fixed for the + spillage. +* test: Unit-testing for the pf and opf with inflow was improved to + catch bugs better. + +We thank Joao Gorenstein Dedecca for a bug fix. + + +## [**v0.4.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.4.0) 21st March 2016 { id="v0.4.0" } + +Additional features: + +* New module ``pypsa.contingency`` for contingency analysis and + security-constrained LOPF +* New module ``pypsa.geo`` for basic manipulation of geographic data + (distances and areas) +* Re-formulation of LOPF to improve optimisation solving time +* New objects pypsa.opt.LExpression and pypsa.opt.LConstraint to make + the bypassing of pyomo for linear problem construction easier to use +* Deep copying of networks with ``network.copy()`` (i.e. all + components, time series and network attributes are copied) +* Stricter requirements for PyPI (e.g. pandas must be at least version + 0.17.1 to get all the new features) +* Updated SciGRID-based model of Germany +* Various small bug fixes + +We thank Steffen Schroedter, Bjoern Laemmerzahl and Joao Gorenstein +Dedecca for comments and bug fixes. + + +## [**v0.3.3**](https://github.com/PyPSA/PyPSA/releases/tag/v0.3.3) 29th February 2016 { id="v0.3.3" } + +Additional features: + +* ``network.lpf`` can be called on an iterable of ``snapshots`` + i.e. ``network.lpf(snapshots)``, which is more performant that + calling ``network.lpf`` on each snapshot separately. +* Bug fix on import/export of transformers and shunt impedances (which + were left out before). +* Refactoring of some internal code. +* Better network clustering. + + +## [**v0.3.2**](https://github.com/PyPSA/PyPSA/releases/tag/v0.3.2) 17th February 2016 { id="v0.3.2" } + +In this release some minor API changes were made: + +* The Newton-Raphson tolerance ``network.nr_x_tol`` was moved to being + an argument of the function ``network.pf(x_tol=1e-6)`` instead. This + makes more sense and is then available in the docstring of + ``network.pf``. +* Following similar reasoning ``network.opf_keep_files`` was moved to + being an argument of the function + ``network.lopf(keep_files=False)``. + + +## [**v0.3.1**](https://github.com/PyPSA/PyPSA/releases/tag/v0.3.1) 7th February 2016 { id="v0.3.1" } + +In this release some minor API changes were made: + + +* Optimised capacities of generators/storage units and branches are + now written to p_nom_opt and s_nom_opt respectively, instead of + over-writing p_nom and s_nom +* The p_max/min limits of controllable branches are now p_max/min_pu + per unit of s_nom, for consistency with generation and to allow + unidirectional HVDCs / transport links for the capacity + optimisation. +* network.remove() and io.import_series_from_dataframe() both take as + argument class_name instead of list_name or the object - this is now + fully consistent with network.add("Line","my line x"). +* The booleans network.topology_determined and + network.dependent_values_calculated have been totally removed - this + was causing unexpected behaviour. Instead, to avoid repeated + unnecessary calculations, the expert user can call functions with + skip_pre=True. + + + +## [**v0.3.0**](https://github.com/PyPSA/PyPSA/releases/tag/v0.3.0) 27th January 2016 { id="v0.3.0" } + +In this release the pandas.Panel interface for time-dependent +variables was introduced. This replaced the manual attachment of +pandas.DataFrames per time-dependent variable as attributes of the +main component pandas.DataFrame. + +## Release process + +- Update ``docs/release-notes.md`` + - Check if all major changes are documented + - Link PR's with badge (e.g. ) +- You don't need to update the version number anywhere, this is done automatically. +- Commit: ``git commit -m 'prepare release `vx.x.x`'`` +- Add tag: ``git tag vx.x.x`` +- Direct push on master: ``git push upstream master`` +- Push tag: ``git push upstream vx.x.x`` +- The upload to [PyPI](https://pypi.org/) is automated in the Github Action + ``deploy.yml``, which is triggered by pushing a tag. + To upload manually, run ``python setup.py sdist``, + then ``twine check dist/pypsa-0.x.0.tar.gz`` and + ``twine upload dist/pypsa-0.x.0.tar.gz`` +- The [GitHub release](https://github.com/PyPSA/PyPSA/releases) is also automated in + the Github Action. Making a GitHub release will also trigger + [zenodo](https://zenodo.org/) to archive the release with its own DOI. +- To update to conda-forge, check the pull request generated at the + [feedstock repository](https://github.com/conda-forge/pypsa-feedstock). A PR will be + generated automatically after a couple of hours by the + [conda-forge bot](https://github.com/conda-forge/conda-forge.github.io), + and needs to be merged by a maintainer. diff --git a/PyPSA/source/docs/requirements.txt b/PyPSA/source/docs/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..c3ccdaa5c1155652cc3b09f529a6fa260781b53c --- /dev/null +++ b/PyPSA/source/docs/requirements.txt @@ -0,0 +1,881 @@ +# This file was autogenerated by uv via the following command: +# uv export --format requirements-txt --all-extras --no-hashes --output-file docs/requirements.txt +-e . +anyio==4.11.0 + # via + # httpx + # jupyter-server +appnope==0.1.4 ; sys_platform == 'darwin' + # via ipykernel +argon2-cffi==25.1.0 + # via jupyter-server +argon2-cffi-bindings==25.1.0 + # via argon2-cffi +arrow==1.3.0 + # via isoduration +asttokens==3.0.0 + # via stack-data +async-lru==2.0.5 + # via jupyterlab +attrs==25.4.0 + # via + # jsonschema + # referencing + # reuse +babel==2.17.0 + # via + # jupyterlab-server + # mkdocs-git-revision-date-localized-plugin + # mkdocs-material +backrefs==5.9 + # via mkdocs-material +beautifulsoup4==4.14.2 + # via nbconvert +bleach==6.2.0 + # via nbconvert +blosc2==3.10.0 + # via tables +boolean-py==5.0 + # via license-expression +bottleneck==1.6.0 + # via linopy +cachetools==6.2.0 + # via google-auth +cairocffi==1.7.1 + # via cairosvg +cairosvg==2.8.2 + # via pypsa +cartopy==0.25.0 + # via pypsa +certifi==2025.10.5 + # via + # httpcore + # httpx + # netcdf4 + # pyogrio + # pyproj + # requests +cffi==2.0.0 + # via + # argon2-cffi-bindings + # cairocffi + # pyzmq +cfgv==3.4.0 + # via pre-commit +cftime==1.6.4.post1 + # via netcdf4 +charset-normalizer==3.4.3 + # via + # python-debian + # requests +click==8.3.0 + # via + # dask + # mkdocs + # reuse +cloudpathlib==0.23.0 + # via pypsa +cloudpickle==3.1.1 + # via dask +colorama==0.4.6 + # via + # click + # griffe + # ipython + # mkdocs + # mkdocs-material + # pytest + # tqdm +comm==0.2.3 + # via + # ipykernel + # ipywidgets +contourpy==1.3.2 ; python_full_version < '3.11' + # via matplotlib +contourpy==1.3.3 ; python_full_version >= '3.11' + # via matplotlib +coverage==7.10.7 + # via + # pypsa + # pytest-cov +cssselect2==0.8.0 + # via cairosvg +cycler==0.12.1 + # via matplotlib +dask==2025.9.1 + # via linopy +debugpy==1.8.17 + # via ipykernel +decorator==5.2.1 + # via ipython +deepdiff==8.6.1 + # via pandapower +defusedxml==0.7.1 + # via + # cairosvg + # nbconvert +deprecation==2.1.0 + # via + # linopy + # pypsa +distlib==0.4.0 + # via virtualenv +et-xmlfile==2.0.0 + # via openpyxl +exceptiongroup==1.3.0 ; python_full_version < '3.11' + # via + # anyio + # ipython + # pytest +executing==2.2.1 + # via stack-data +fastjsonschema==2.21.2 + # via nbformat +filelock==3.20.0 + # via virtualenv +fonttools==4.60.1 + # via matplotlib +fqdn==1.5.1 + # via jsonschema +fsspec==2025.9.0 + # via dask +geojson==3.2.0 + # via pandapower +geopandas==1.1.1 + # via pypsa +ghp-import==2.1.0 + # via mkdocs +gitdb==4.0.12 + # via gitpython +gitpython==3.1.45 + # via mkdocs-git-revision-date-localized-plugin +google-api-core==2.26.0 + # via + # google-cloud-core + # google-cloud-storage +google-auth==2.41.1 + # via + # google-api-core + # google-cloud-core + # google-cloud-storage +google-cloud-core==2.4.3 + # via google-cloud-storage +google-cloud-storage==3.4.1 + # via linopy +google-crc32c==1.7.1 + # via + # google-cloud-storage + # google-resumable-media +google-resumable-media==2.7.2 + # via google-cloud-storage +googleapis-common-protos==1.70.0 + # via google-api-core +griffe==1.14.0 + # via mkdocstrings-python +gurobipy==12.0.3 + # via pypsa +h11==0.16.0 + # via httpcore +highspy==1.11.0 + # via + # pypsa + # tsam +httpcore==1.0.9 + # via httpx +httpx==0.28.1 + # via jupyterlab +identify==2.6.15 + # via pre-commit +idna==3.10 + # via + # anyio + # httpx + # jsonschema + # requests +importlib-metadata==8.7.0 ; python_full_version < '3.12' + # via dask +iniconfig==2.1.0 + # via pytest +ipykernel==6.30.1 + # via + # jupyter + # jupyter-console + # jupyterlab + # mkdocs-jupyter +ipython==8.37.0 ; python_full_version < '3.11' + # via + # ipykernel + # ipywidgets + # jupyter-console +ipython==9.6.0 ; python_full_version >= '3.11' + # via + # ipykernel + # ipywidgets + # jupyter-console +ipython-pygments-lexers==1.1.1 ; python_full_version >= '3.11' + # via ipython +ipywidgets==8.1.7 + # via jupyter +isoduration==20.11.0 + # via jsonschema +jedi==0.19.2 + # via ipython +jinja2==3.1.6 + # via + # jupyter-server + # jupyterlab + # jupyterlab-server + # mkdocs + # mkdocs-material + # mkdocstrings + # nbconvert + # pydeck + # pytest-mpl + # reuse +joblib==1.5.2 + # via scikit-learn +json5==0.12.1 + # via jupyterlab-server +jsonpointer==3.0.0 + # via jsonschema +jsonschema==4.25.1 + # via + # jupyter-events + # jupyterlab-server + # nbformat +jsonschema-specifications==2025.9.1 + # via jsonschema +jupyter==1.1.1 + # via pypsa +jupyter-client==8.6.3 + # via + # ipykernel + # jupyter-console + # jupyter-server + # nbclient +jupyter-console==6.6.3 + # via jupyter +jupyter-core==5.8.1 + # via + # ipykernel + # jupyter-client + # jupyter-console + # jupyter-server + # jupyterlab + # nbclient + # nbconvert + # nbformat +jupyter-events==0.12.0 + # via jupyter-server +jupyter-lsp==2.3.0 + # via jupyterlab +jupyter-server==2.17.0 + # via + # jupyter-lsp + # jupyterlab + # jupyterlab-server + # notebook + # notebook-shim +jupyter-server-terminals==0.5.3 + # via jupyter-server +jupyterlab==4.4.9 + # via + # jupyter + # notebook +jupyterlab-pygments==0.3.0 + # via nbconvert +jupyterlab-server==2.27.3 + # via + # jupyterlab + # notebook +jupyterlab-widgets==3.0.15 + # via ipywidgets +jupytext==1.17.3 + # via mkdocs-jupyter +kiwisolver==1.4.9 + # via matplotlib +lark==1.3.0 + # via rfc3987-syntax +levenshtein==0.27.1 + # via pypsa +license-expression==30.4.4 + # via reuse +linopy==0.5.7 + # via pypsa +locket==1.0.0 + # via partd +lxml==6.0.2 + # via + # mkdocs-video + # pandapower +markdown==3.9 + # via + # mkdocs + # mkdocs-autorefs + # mkdocs-material + # mkdocstrings + # pymdown-extensions +markdown-it-py==4.0.0 + # via + # jupytext + # mdit-py-plugins +markupsafe==3.0.3 + # via + # jinja2 + # mkdocs + # mkdocs-autorefs + # mkdocstrings + # nbconvert +matplotlib==3.10.7 + # via + # cartopy + # pypsa + # pytest-mpl + # seaborn +matplotlib-inline==0.1.7 + # via + # ipykernel + # ipython +mdit-py-plugins==0.5.0 + # via jupytext +mdurl==0.1.2 + # via markdown-it-py +mergedeep==1.3.4 + # via + # mkdocs + # mkdocs-get-deps +mistune==3.1.4 + # via nbconvert +mkdocs==1.6.1 + # via + # mkdocs-autolinks-plugin + # mkdocs-autorefs + # mkdocs-ezlinks-plugin + # mkdocs-git-revision-date-localized-plugin + # mkdocs-jupyter + # mkdocs-material + # mkdocs-redirects + # mkdocs-table-reader-plugin + # mkdocs-video + # mkdocstrings +mkdocs-autolinks-plugin==0.7.1 + # via pypsa +mkdocs-autorefs==1.4.3 + # via + # mkdocstrings + # mkdocstrings-python +mkdocs-ezlinks-plugin==0.1.14 + # via pypsa +mkdocs-get-deps==0.2.0 + # via mkdocs +mkdocs-git-revision-date-localized-plugin==1.4.7 + # via pypsa +mkdocs-jupyter==0.25.1 + # via pypsa +mkdocs-material==9.6.21 + # via + # mkdocs-jupyter + # pypsa +mkdocs-material-extensions==1.3.1 + # via mkdocs-material +mkdocs-redirects==1.2.2 + # via pypsa +mkdocs-table-reader-plugin==3.1.0 + # via pypsa +mkdocs-video==1.5.0 + # via pypsa +mkdocstrings==0.30.1 + # via mkdocstrings-python +mkdocstrings-python==1.18.2 + # via pypsa +msgpack==1.1.2 + # via blosc2 +mypy==1.18.2 + # via pypsa +mypy-extensions==1.1.0 + # via mypy +narwhals==2.7.0 + # via plotly +nbclient==0.10.2 + # via nbconvert +nbconvert==7.16.6 + # via + # jupyter + # jupyter-server + # mkdocs-jupyter +nbformat==5.10.4 + # via + # jupyter-server + # jupytext + # nbclient + # nbconvert +ndindex==1.10.0 + # via blosc2 +nest-asyncio==1.6.0 + # via ipykernel +netcdf4==1.7.2 + # via pypsa +networkx==3.4.2 ; python_full_version < '3.11' + # via + # pandapower + # pypsa + # tsam +networkx==3.5 ; python_full_version >= '3.11' + # via + # pandapower + # pypsa + # tsam +nodeenv==1.9.1 + # via pre-commit +notebook==7.4.7 + # via jupyter +notebook-shim==0.2.4 + # via + # jupyterlab + # notebook +numexpr==2.13.1 + # via + # blosc2 + # linopy + # tables +numpy==1.26.4 ; python_full_version < '3.11' + # via + # blosc2 + # bottleneck + # cartopy + # cftime + # contourpy + # geopandas + # highspy + # linopy + # matplotlib + # netcdf4 + # numexpr + # pandapower + # pandas + # pydeck + # pyogrio + # pypsa + # scikit-learn + # scipy + # seaborn + # shapely + # tables + # tsam + # xarray +numpy==2.3.3 ; python_full_version >= '3.11' + # via + # blosc2 + # bottleneck + # cartopy + # cftime + # contourpy + # geopandas + # highspy + # linopy + # matplotlib + # netcdf4 + # numexpr + # pandapower + # pandas + # pydeck + # pyogrio + # pypsa + # scikit-learn + # scipy + # seaborn + # shapely + # tables + # tsam + # xarray +openpyxl==3.1.5 + # via pypsa +orderly-set==5.5.0 + # via deepdiff +overrides==7.7.0 ; python_full_version < '3.12' + # via jupyter-server +packaging==25.0 + # via + # cartopy + # dask + # deprecation + # geopandas + # ipykernel + # jupyter-events + # jupyter-server + # jupyterlab + # jupyterlab-server + # jupytext + # matplotlib + # mkdocs + # nbconvert + # pandapower + # plotly + # pyogrio + # pytest + # pytest-mpl + # tables + # xarray +paginate==0.5.7 + # via mkdocs-material +pandapower==3.1.2 ; python_full_version < '3.11' + # via pypsa +pandapower==3.2.0 ; python_full_version >= '3.11' + # via pypsa +pandas==2.3.3 + # via + # geopandas + # mkdocs-table-reader-plugin + # pandapower + # pypsa + # seaborn + # tsam + # xarray +pandocfilters==1.5.1 + # via nbconvert +parso==0.8.5 + # via jedi +partd==1.4.2 + # via dask +pathspec==0.12.1 + # via + # mkdocs + # mypy +pexpect==4.9.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' + # via ipython +pillow==11.3.0 + # via + # cairosvg + # matplotlib + # pypsa + # pytest-mpl +platformdirs==4.5.0 + # via + # blosc2 + # jupyter-core + # mkdocs-get-deps + # virtualenv +plotly==6.3.1 + # via pypsa +pluggy==1.6.0 + # via + # pytest + # pytest-cov +ply==3.11 + # via pyomo +polars==1.34.0 + # via linopy +polars-runtime-32==1.34.0 + # via polars +pre-commit==4.3.0 + # via pypsa +prometheus-client==0.23.1 + # via jupyter-server +prompt-toolkit==3.0.52 + # via + # ipython + # jupyter-console +proto-plus==1.26.1 + # via google-api-core +protobuf==6.32.1 + # via + # google-api-core + # googleapis-common-protos + # proto-plus +psutil==7.1.0 + # via ipykernel +ptyprocess==0.7.0 ; os_name != 'nt' or (sys_platform != 'emscripten' and sys_platform != 'win32') + # via + # pexpect + # terminado +pure-eval==0.2.3 + # via stack-data +py-cpuinfo==9.0.0 + # via + # blosc2 + # tables +pyasn1==0.6.1 + # via + # pyasn1-modules + # rsa +pyasn1-modules==0.4.2 + # via google-auth +pycparser==2.23 ; implementation_name != 'PyPy' + # via cffi +pydeck==0.9.1 + # via pypsa +pygments==2.19.2 + # via + # ipython + # ipython-pygments-lexers + # jupyter-console + # mkdocs-jupyter + # mkdocs-material + # nbconvert + # pytest +pygtrie==2.5.0 + # via mkdocs-ezlinks-plugin +pymdown-extensions==10.16.1 + # via + # mkdocs-material + # mkdocstrings +pyogrio==0.11.1 + # via geopandas +pyomo==6.9.4 + # via tsam +pyparsing==3.2.5 + # via matplotlib +pypower==5.1.19 + # via pypsa +pyproj==3.7.1 ; python_full_version < '3.11' + # via + # cartopy + # geopandas +pyproj==3.7.2 ; python_full_version >= '3.11' + # via + # cartopy + # geopandas +pyshp==2.3.1 + # via cartopy +pytest==8.4.2 + # via + # pypsa + # pytest-cov + # pytest-mpl +pytest-cov==7.0.0 + # via pypsa +pytest-mpl==0.17.0 + # via pypsa +python-calamine==0.5.3 + # via pypsa +python-dateutil==2.9.0.post0 + # via + # arrow + # ghp-import + # jupyter-client + # matplotlib + # pandas +python-debian==1.0.1 + # via reuse +python-json-logger==4.0.0 + # via jupyter-events +python-magic==0.4.27 + # via reuse +pytz==2025.2 + # via + # mkdocs-git-revision-date-localized-plugin + # pandas +pywin32==311 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' + # via jupyter-core +pywinpty==3.0.2 ; os_name == 'nt' + # via + # jupyter-server + # jupyter-server-terminals + # terminado +pyyaml==6.0.3 + # via + # dask + # jupyter-events + # jupytext + # mkdocs + # mkdocs-get-deps + # mkdocs-table-reader-plugin + # pre-commit + # pymdown-extensions + # pyyaml-env-tag +pyyaml-env-tag==1.1 + # via mkdocs +pyzmq==27.1.0 + # via + # ipykernel + # jupyter-client + # jupyter-console + # jupyter-server +rapidfuzz==3.14.1 + # via levenshtein +referencing==0.36.2 + # via + # jsonschema + # jsonschema-specifications + # jupyter-events +requests==2.32.5 + # via + # blosc2 + # google-api-core + # google-cloud-storage + # jupyterlab-server + # linopy + # mkdocs-material +reuse==6.1.2 + # via pypsa +rfc3339-validator==0.1.4 + # via + # jsonschema + # jupyter-events +rfc3986-validator==0.1.1 + # via + # jsonschema + # jupyter-events +rfc3987-syntax==1.1.0 + # via jsonschema +rpds-py==0.27.1 + # via + # jsonschema + # referencing +rsa==4.9.1 + # via google-auth +ruff==0.14.0 + # via pypsa +scikit-learn==1.7.2 + # via + # pypsa + # tsam +scipy==1.15.3 ; python_full_version < '3.11' + # via + # linopy + # pandapower + # pypsa + # scikit-learn +scipy==1.16.2 ; python_full_version >= '3.11' + # via + # linopy + # pandapower + # pypsa + # scikit-learn +seaborn==0.13.2 + # via pypsa +send2trash==1.8.3 + # via jupyter-server +setuptools==80.9.0 + # via jupyterlab +shapely==2.0.7 + # via + # cartopy + # geopandas + # pypsa +six==1.17.0 + # via + # python-dateutil + # rfc3339-validator +smmap==5.0.2 + # via gitdb +sniffio==1.3.1 + # via anyio +soupsieve==2.8 + # via beautifulsoup4 +stack-data==0.6.3 + # via ipython +tables==3.10.1 ; python_full_version < '3.11' + # via pypsa +tables==3.10.2 ; python_full_version >= '3.11' + # via pypsa +tabulate==0.9.0 + # via mkdocs-table-reader-plugin +terminado==0.18.1 + # via + # jupyter-server + # jupyter-server-terminals +threadpoolctl==3.6.0 + # via scikit-learn +tinycss2==1.4.0 + # via + # bleach + # cairosvg + # cssselect2 +tomli==2.3.0 ; python_full_version <= '3.11' + # via + # coverage + # jupyterlab + # jupytext + # mypy + # pytest +tomlkit==0.13.3 + # via reuse +toolz==1.0.0 + # via + # dask + # linopy + # partd +tornado==6.5.2 + # via + # ipykernel + # jupyter-client + # jupyter-server + # jupyterlab + # notebook + # terminado +tqdm==4.67.1 + # via + # linopy + # pandapower + # tsam +traitlets==5.14.3 + # via + # ipykernel + # ipython + # ipywidgets + # jupyter-client + # jupyter-console + # jupyter-core + # jupyter-events + # jupyter-server + # jupyterlab + # matplotlib-inline + # nbclient + # nbconvert + # nbformat +tsam==2.3.9 + # via pypsa +types-python-dateutil==2.9.0.20251008 + # via arrow +typing-extensions==4.15.0 + # via + # anyio + # async-lru + # beautifulsoup4 + # cloudpathlib + # exceptiongroup + # ipython + # mistune + # mkdocstrings-python + # mypy + # pandapower + # pypsa + # referencing + # tables + # virtualenv +tzdata==2025.2 + # via pandas +uri-template==1.3.0 + # via jsonschema +urllib3==2.5.0 + # via requests +validators==0.35.0 + # via pypsa +virtualenv==20.35.1 + # via pre-commit +watchdog==6.0.0 + # via mkdocs +wcwidth==0.2.14 + # via prompt-toolkit +webcolors==24.11.1 + # via jsonschema +webencodings==0.5.1 + # via + # bleach + # cssselect2 + # tinycss2 +websocket-client==1.9.0 + # via jupyter-server +widgetsnbextension==4.0.14 + # via ipywidgets +xarray==2025.6.1 ; python_full_version < '3.11' + # via + # linopy + # pypsa +xarray==2025.9.0 ; python_full_version >= '3.11' + # via + # linopy + # pypsa +zipp==3.23.0 ; python_full_version < '3.12' + # via importlib-metadata diff --git a/PyPSA/source/docs/user-guide/clustering.ipynb b/PyPSA/source/docs/user-guide/clustering.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..ff0bc2fde0684c904fe5852308d758e5dc5797a1 --- /dev/null +++ b/PyPSA/source/docs/user-guide/clustering.ipynb @@ -0,0 +1,454 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "929954c0", + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "c907dcbe", + "metadata": {}, + "source": [ + "# Network Clustering\n", + "\n", + "In this section, we show how PyPSA can deal with spatial clustering of networks. We first load the SciGRID example network and reduce it to standard attributes as otherwise the clustering does not work.\n", + "\n", + "The important information needed for spatial clustering is in the `busmap`. This `pandas.Series` contains the mapping of which buses from the unclustered network should be grouped together, similar to the `groupby` argument known from `pandas`.\n", + "\n", + "You can either calculate a `busmap` from the provided **clustering algorithms** or you can provide your **own busmap**." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ca7c4ab", + "metadata": {}, + "outputs": [], + "source": [ + "import cartopy.crs as ccrs\n", + "import geopandas as gpd\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "\n", + "import pypsa\n", + "\n", + "crs = ccrs.LambertConformal(\n", + " central_longitude=10, central_latitude=51, standard_parallels=(47, 55)\n", + ")\n", + "\n", + "kwargs = {\"boundaries\": [6, 15, 47, 55]}\n", + "\n", + "n = pypsa.examples.scigrid_de()\n", + "\n", + "n.calculate_dependent_values()\n", + "n.lines = n.lines.reindex(columns=n.components[\"Line\"][\"defaults\"].index[1:])\n", + "n.lines[\"type\"] = \"Al/St 240/40 2-bundle 220.0\"\n", + "n.buses = n.buses.reindex(columns=n.components[\"Bus\"][\"defaults\"].index[1:])\n", + "n.buses[\"frequency\"] = 50" + ] + }, + { + "cell_type": "markdown", + "id": "3d8716b3", + "metadata": {}, + "source": [ + "## Custom Clustering\n", + "\n", + "Let's start with creating an own busmap. Suppose we want to cluster by federal states.\n", + "Using `geopandas` we can achieve this by spatially joining the bus coordinates with a `geopandas.GeoDataFrame` containing the federal state boundaries.\n", + "Here, we use the function `.sjoin_nearest()` with an appropriate projection (\"EPSG:3035\") as some buses are located near the borders of federal states in neighbouring countries.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ab25c89b", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://media.githubusercontent.com/media/wmgeolab/geoBoundaries/9469f09592ced973a3448cf66b6100b741b64c0d/releaseData/gbOpen/DEU/ADM1/geoBoundaries-DEU-ADM1-all.zip\"\n", + "states = gpd.read_file(url, layer=\"geoBoundaries-DEU-ADM1_simplified\")\n", + "states.plot(edgecolor=\"k\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d4b00deb", + "metadata": {}, + "outputs": [], + "source": [ + "bus_coords = gpd.GeoDataFrame(\n", + " geometry=gpd.points_from_xy(n.buses.x, n.buses.y, crs=4326), index=n.buses.index\n", + ")\n", + "busmap = bus_coords.to_crs(3035).sjoin_nearest(states.to_crs(3035), how=\"left\").shapeISO\n", + "busmap.head(10)" + ] + }, + { + "cell_type": "markdown", + "id": "eaf3f939", + "metadata": {}, + "source": [ + "Now we cluster the network based on the busmap." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d42f0202", + "metadata": {}, + "outputs": [], + "source": [ + "cluster = n.cluster.get_clustering_from_busmap(busmap)" + ] + }, + { + "cell_type": "markdown", + "id": "d28013f7", + "metadata": {}, + "source": [ + "`cluster` is a [`Clustering`][pypsa.clustering.spatial.Clustering] object which contains the `cluster.busmap`, the derived `cluster.linemap` (mapping of parallel lines to aggregate), and the clustered network `cluster.n`.\n", + "\n", + "Let's compare the original to the clustered network topology:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47dadcaa", + "metadata": {}, + "outputs": [], + "source": [ + "fig, axs = plt.subplots(1, 2, subplot_kw={\"projection\": crs}, figsize=(10, 7))\n", + "for ax in axs:\n", + " states.to_crs(crs.proj4_init).plot(ax=ax, edgecolor=\"orange\", facecolor=\"none\")\n", + "n.plot(ax=axs[0], title=\"unclustered\", bus_size=0.005, **kwargs)\n", + "cluster.n.plot(ax=axs[1], title=\"clustered by federal state\", bus_size=0.005, **kwargs);" + ] + }, + { + "cell_type": "markdown", + "id": "c7b4503a", + "metadata": {}, + "source": [ + "With the default settings of `get_clustering_from_busmap()`, the attached components are **not** aggregated. That means, for example, for every generator, only the `bus` it attaches to is changed to the respective new clustered bus." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c1e833b", + "metadata": {}, + "outputs": [], + "source": [ + "cluster.n.generators.query(\"bus=='DE-HB'\")[[\"p_nom\", \"carrier\"]]" + ] + }, + { + "cell_type": "markdown", + "id": "d5faf412", + "metadata": {}, + "source": [ + "When clustering of attached components to one representative generator per `carrier` is desired, the `aggregate_one_ports` option can be used. For instance, to cluster generators to one representative generator per `carrier` and `bus` run:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c5a77202", + "metadata": {}, + "outputs": [], + "source": [ + "cluster = n.cluster.get_clustering_from_busmap(\n", + " busmap, aggregate_one_ports={\"Generator\"}\n", + ")\n", + "cluster.n.generators.query(\"bus=='DE-HB'\")[[\"p_nom\", \"carrier\"]]" + ] + }, + { + "cell_type": "markdown", + "id": "af8e8a4b", + "metadata": {}, + "source": [ + "If only some carriers should be aggregated, this can be controlled with the `aggregate_generators_carriers` option. For example, to aggregate wind, solar and hard coal, run:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bbf697e4", + "metadata": {}, + "outputs": [], + "source": [ + "cluster = n.cluster.get_clustering_from_busmap(\n", + " busmap,\n", + " aggregate_generators_carriers=[\"Hard Coal\", \"Wind Onshore\", \"Solar\"],\n", + " aggregate_generators_weighted=True,\n", + ")\n", + "cluster.n.generators.query(\"bus=='DE-HB'\")[[\"p_nom\", \"carrier\"]]" + ] + }, + { + "cell_type": "markdown", + "id": "9a6addc9", + "metadata": {}, + "source": [ + "For each attribute, default aggregation strategies are defined (e.g. minimum, maximum, sum, or capacity-weighted sum). These can be found under:\n", + "\n", + "```python\n", + "from pypsa.clustering.spatial import DEFAULT_ONE_PORT_STRATEGIES, DEFAULT_BUS_STRATEGIES, DEFAULT_LINE_STRATEGIES\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "22bd0ef9", + "metadata": {}, + "source": [ + "## K-means Clustering\n", + "\n", + "Let's now determine' a clustering automatically based on the [K-means algorithm](https://en.wikipedia.org/wiki/K-means_clustering) for a target number of 40 clusters using [`sklearn.cluster.KMeans`](https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html).\n", + "Initially, we will assume uniform weights for all buses (i.e. the clustering will be based on pure geographical distance). Later we will add bus-specific weights." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59b16295", + "metadata": {}, + "outputs": [], + "source": [ + "weighting = pd.Series(1, n.buses.index)\n", + "busmap = n.cluster.busmap_by_kmeans(bus_weightings=weighting, n_clusters=40)\n", + "n_clustered = n.cluster.cluster_by_busmap(busmap)" + ] + }, + { + "cell_type": "markdown", + "id": "89681fef", + "metadata": {}, + "source": [ + "We can also achieve the same in one function call without first defining the `busmap` manually:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86ad0c94", + "metadata": {}, + "outputs": [], + "source": [ + "n_clustered = n.cluster.cluster_spatially_by_kmeans(\n", + " bus_weightings=weighting, n_clusters=40\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "83610fb8", + "metadata": {}, + "source": [ + "\n", + "Again, let's plot the networks to compare (with bus sizes proportional to loads):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61da02ab", + "metadata": {}, + "outputs": [], + "source": [ + "fig, axs = plt.subplots(1, 2, subplot_kw={\"projection\": crs}, figsize=(10, 7))\n", + "n.plot(ax=axs[0], title=\"unclustered\", bus_size=0.005, **kwargs)\n", + "n_clustered.plot(ax=axs[1], title=\"clustered by kmeans\", bus_size=0.005, **kwargs);" + ] + }, + { + "cell_type": "markdown", + "id": "b817f06a", + "metadata": {}, + "source": [ + "We can also assign some weights to the buses, for example, so that low-demand buses are aggregated more strongly:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "031c8371", + "metadata": {}, + "outputs": [], + "source": [ + "loads = (\n", + " n.loads_t.p_set.sum()\n", + " .groupby(n.loads.bus)\n", + " .sum()\n", + " .reindex(index=n.buses.index)\n", + " .fillna(1)\n", + ")\n", + "n_clustered = n.cluster.cluster_spatially_by_kmeans(bus_weightings=loads, n_clusters=40)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dd251359", + "metadata": {}, + "outputs": [], + "source": [ + "loads_clustered = (\n", + " n_clustered.loads_t.p_set.sum()\n", + " .groupby(n_clustered.loads.bus)\n", + " .sum()\n", + " .reindex(index=n_clustered.buses.index)\n", + ")\n", + "fig, axs = plt.subplots(1, 2, subplot_kw={\"projection\": crs}, figsize=(10, 7))\n", + "n.plot(ax=axs[0], title=\"unclustered\", bus_size=loads.div(1e6), **kwargs)\n", + "n_clustered.plot(\n", + " ax=axs[1],\n", + " title=\"clustered by weighted kmeans\",\n", + " bus_size=loads_clustered.div(1e6),\n", + " **kwargs,\n", + ");" + ] + }, + { + "cell_type": "markdown", + "id": "a9cdfcdc", + "metadata": {}, + "source": [ + "## Hierarchical Clustering\n", + "\n", + "The Hierarchical Agglomerative Clustering (HAC) is a bottom-up approach that initially treats each node as its own cluster. In each iteration two adjacent clusters (e.g. connected by a line or link) are aggregated that have the most similar feature(s) with respect to a given similarity measure. The implementation uses [`sklearn.cluster.AgglomerativeClustering`](https://scikit-learn.org/stable/modules/generated/sklearn.cluster.AgglomerativeClustering.html).\n", + "\n", + "Let's say we want to cluster the buses based on similarity in their solar generation profiles in existing plants (note that in the example dataset there are few solar generators in the central part of Germany).\n", + "Then we would prepare the `feature` matrix as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5f99108c", + "metadata": {}, + "outputs": [], + "source": [ + "feature = (\n", + " n.generators_t.p_max_pu.filter(like=\"Solar\")\n", + " .T.groupby(n.generators.bus)\n", + " .mean()\n", + " .reindex(n.buses.index)\n", + " .ffill()\n", + ")\n", + "\n", + "n_clustered = n.cluster.cluster_spatially_by_hac(feature=feature, n_clusters=40)\n", + "\n", + "feature.iloc[:5, 8:16]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d3c11bf", + "metadata": {}, + "outputs": [], + "source": [ + "feature_clustered = (\n", + " n_clustered.generators_t.p_max_pu.filter(like=\"Solar\")\n", + " .T.groupby(n_clustered.generators.bus)\n", + " .mean()\n", + ")\n", + "fig, axs = plt.subplots(1, 2, subplot_kw={\"projection\": crs}, figsize=(10, 7))\n", + "n.plot(\n", + " ax=axs[0],\n", + " title=\"unclustered\",\n", + " bus_size=0.01,\n", + " bus_color=feature.mean(axis=1),\n", + " **kwargs,\n", + ")\n", + "n_clustered.plot(\n", + " ax=axs[1],\n", + " title=\"clustered by hac (solar)\",\n", + " bus_size=0.01,\n", + " bus_color=feature_clustered.mean(axis=1),\n", + " **kwargs,\n", + ");" + ] + }, + { + "cell_type": "markdown", + "id": "8d151921", + "metadata": {}, + "source": [ + "## Greedy Modularity Clustering\n", + "\n", + "The greedy modularity clustering approach seeks to find community structures using [`networkx.community.greedy_modularity_communities`](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.community.modularity_max.greedy_modularity_communities.html). The algorithm begins with each node in its own cluster and joins the pair of clusters that most increases [modularity](https://en.wikipedia.org/wiki/Modularity_(networks)). No weights are applied for this algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e236b1ee", + "metadata": {}, + "outputs": [], + "source": [ + "n_clustered = n.cluster.cluster_spatially_by_greedy_modularity(n_clusters=80)" + ] + }, + { + "cell_type": "markdown", + "id": "6dd85e5c", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "For more information, consult the following research papers:\n", + "\n", + "1. Frysztacki, Recht, Brown (2022), [A comparison of clustering methods for the spatial reduction of renewable electricity optimisation models of Europe](https://energyinformatics.springeropen.com/articles/10.1186/s42162-022-00187-7), **Energy Informatics**.\n", + "\n", + "2. Frysztacki, Hörsch, Hagenmeyer, Brown (2021), [The strong effect of network resolution on electricity system models with high shares of wind and solar](https://doi.org/10.1016/j.apenergy.2021.116726), **Applied Energy**." + ] + }, + { + "cell_type": "markdown", + "id": "382e519a", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/user-guide/collection.md b/PyPSA/source/docs/user-guide/collection.md new file mode 100644 index 0000000000000000000000000000000000000000..062c681d3227ac49ac1916de59366ca8c7074436 --- /dev/null +++ b/PyPSA/source/docs/user-guide/collection.md @@ -0,0 +1,178 @@ + + +As the name suggests, a [NetworkCollection][pypsa.NetworkCollection] is a collection of +multiple networks. It provides a convenient way to manage and analyse multiple networks +simultaneously, access their combined data, and generate combined statistics and plots. + +!!! example "Under Active Development" + + NetworkCollections have been introduced in and will be further extended in future releases. + +## Create a collection +A network collection is simply a container that references multiple networks and wraps +around them. + +Let's take two example networks and put them into a collection: + +``` py +>>> n = pypsa.examples.ac_dc_meshed() # docs-hide +>>> _ = n.optimize() # docs-hide +>>> n # doctest: +ELLIPSIS +PyPSA Network 'AC-DC-Meshed' +... +>>> n_shuffled_load # Same network as n but with shuffled load time series +PyPSA Network 'AC-DC-Meshed-Shuffled-Load' +------------------------------------------ +Components: + - Bus: 9 + - Carrier: 6 + - Generator: 6 + - GlobalConstraint: 1 + - Line: 7 + - Link: 4 + - Load: 6 + - SubNetwork: 3 +Snapshots: 10 + +>>> nc = pypsa.NetworkCollection([n, n_shuffled_load]) +>>> nc +NetworkCollection +----------------- +Networks: 2 +Index name: 'network' +Entries: ['AC-DC-Meshed', 'AC-DC-Meshed-Shuffled-Load'] +``` + +If no index is passed, the collection will automatically assign names based on the +network's [`name`][pypsa.Network.name] attribute or a default name if that is not set. +But it is also possible to assign a custom index. Or even a MultiIndex can be used, which +allows for custom grouping (see [below](#functionality)). + +``` py +>>> nc = pypsa.NetworkCollection( +... [n, n_shuffled_load], +... index=pd.Index(["network1", "network2"], name='custom-dim') +... ) +>>> nc +NetworkCollection +----------------- +Networks: 2 +Index name: 'custom-dim' +Entries: ['network1', 'network2'] +>>> nc.networks +custom-dim +network1 PyPSA Network 'AC-DC-Meshed' +network2 PyPSA Network 'AC-DC-Meshed-Shuffled-Load' +dtype: object +``` + +!!! warning + + It is important to note that the collection contains **a reference to the networks**, + not a copy. Therefore, any changes made to the individual networks will be reflected + in the collection and vice versa. + +### Network dimension compatibility +Currently networks with the same dimensions should be used in a collection. E.g. for +comparing different scenarios of the same system. That means all dimensions should align: +[`n.snapshots`][pypsa.Network.snapshots] with [`n.periods`][pypsa.Network.periods] and +[`n.timesteps`][pypsa.Network.timesteps], [`c.names`][pypsa.Components.names] +across all components as well as [`n.scenarios`][pypsa.Network.scenarios] for stochastic +networks. If dimensions do not align, some functionality may not work as expected. +Future releases will add more flexibility here and better error messages. It is +plausible and planned to safly compare stochastic networks with non-stochastic networks, +different clustered networks with each other as well as having single node reference +scenarios. + +## Functionality +Once created, a NetworkCollection behaves similarly to a single [Network][pypsa.Network]. +While it it is not a Subclass of Network, it "duck-types" as one. This means that +many methods and properties of a Network can be accessed directly on the collection, +with the results being aggregated or concatenated across all networks in the collection. +Currently mainly the data accessors and statistics module are supported (see below). +More functionality will be added in future releases. + +### Data Access +You can access all components data (static and dynamic) in the same way as for a single +network. They will just be concatenated across all networks in the collection. + +Accessing static data: +``` py +>>> nc.buses # doctest: +NORMALIZE_WHITESPACE + v_nom type x ... generator sub_network country +custom-dim name ... +network1 London 380.0 -0.13 ... 0 UK + Norwich 380.0 1.30 ... 0 UK + Norwich DC 200.0 1.30 ... 1 UK + Manchester 380.0 -2.20 ... Manchester Wind 0 UK + Bremen 380.0 8.80 ... 2 DE + Bremen DC 200.0 8.80 ... 1 DE + Frankfurt 380.0 8.70 ... Frankfurt Wind 2 DE + Norway 380.0 10.75 ... 3 NO + Norway DC 200.0 10.75 ... 1 NO +network2 London 380.0 -0.13 ... 0 UK + Norwich 380.0 1.30 ... 0 UK + Norwich DC 200.0 1.30 ... 1 UK + Manchester 380.0 -2.20 ... Manchester Wind 0 UK + Bremen 380.0 8.80 ... 2 DE + Bremen DC 200.0 8.80 ... 1 DE + Frankfurt 380.0 8.70 ... Frankfurt Wind 2 DE + Norway 380.0 10.75 ... 3 NO + Norway DC 200.0 10.75 ... 1 NO + +[18 rows x 14 columns] +``` + +Accessing time series data: +``` py +>>> nc.loads_t.p_set.iloc[:3, [0, 1, 6, 7]] # First 3 snapshots, London and Norwich from each network # doctest: +ELLIPSIS +custom-dim network1 network2 +name London Norwich London Norwich +snapshot +2015-01-01 00:00:00 35.796244 415.462564 ... ... +2015-01-01 01:00:00 976.824561 262.606146 ... ... +2015-01-01 02:00:00 250.587312 418.476353 ... ... +``` + +!!! info + + If a pd.MultiIndex is used for the collection, the different levels will be + preserved in the concatenated dataframes. + +### Statistics Module + +The [statistics module][pypsa.Network.statistics] is fully supported for +NetworkCollections. All statistics expressions can be accessed in the same way as for +a single network. This includes dataframes and plots. How plots are combined might +change in future releases. + +Get a combined energy balance across all networks: +``` py +>>> nc.statistics.energy_balance() # doctest: +ELLIPSIS +component custom-dim carrier bus_carrier +Generator network1 gas AC 1465.27439 + wind AC 31082.35370 + network2 gas AC ... + wind AC ... +Load network1 load AC -32547.62808 + network2 load AC -32547.62808 +dtype: float64 +``` +Since only the load time series have been shuffled, the aggregated energy balance is +the same for both networks. + +Create a plot for the same energy balance: +``` py +>>> fig = nc.statistics.energy_balance.iplot() +>>> fig.write_html("docs/assets/interactive/ac_dc_collection_energy_balance_iplot.html") # docs-hide +``` +
+ +
+For the full range of plotting options, see . diff --git a/PyPSA/source/docs/user-guide/components.md b/PyPSA/source/docs/user-guide/components.md new file mode 100644 index 0000000000000000000000000000000000000000..e104a1498f7b0db7f73eb416bcea73a2c62cb185 --- /dev/null +++ b/PyPSA/source/docs/user-guide/components.md @@ -0,0 +1,194 @@ + + +# Components Object + +[`pypsa.Components`][] are the store for all component specific data. While a [`pypsa.Network`][] bundles together functionality across components, the `Components` class is the interface for all data and processing for a specific component type. + +``` py +>>> import pypsa +>>> n = pypsa.examples.ac_dc_meshed() +>>> n.components.generators # doctest: +ELLIPSIS +'Generator' Components +---------------------- +... +``` + +!!! tip + + A short name, such as `c`, is recommended since it is used frequently to access the stored data, properties and methods. + +## Components Store + +Components can be accessed in any `Network` via the `Components` store [`n.components`][pypsa.Network.components]. The `Components` store is a dict-like object that contains all components of the network. + +!!! info + + There is also an alias [`n.c`][pypsa.Network.c] for [`n.components`][pypsa.Network.components]. + +Access a single component: +``` py +>>> c = n.components.generators +>>> c # doctest: +ELLIPSIS +'Generator' Components +---------------------- +... +>>> c = n.components["Generator"] # also subscriptable +>>> c # doctest: +ELLIPSIS +'Generator' Components +---------------------- +... +``` + +Access a list of components: +``` py +>>> comps = n.components["Generator", "Bus"] # doctest: +SKIP +>>> comps # doctest: +SKIP +[Empty 'Generator' Components, Empty 'Bus' Components] +``` + +Loop through all components: +``` py +>>> for comp in n.components: +... break +``` +!!! info + + Even if assigned to a variable, the components are not copied. They are still attached to the network. If you change any data of the components object, the changes will be reflected in the network as well. There is no need to re-assign the components object to the network. + +## Stored Data +All components data is stored in the two stores [`c.static`][pypsa.Components.static] and [`c.dynamic`][pypsa.Components.dynamic]: + +- [`c.static`][pypsa.Components.static] contains all static data of the components, i.e. data that does not change over time. It is a simple `pandas.DataFrame` with the component names as index and all attributes as columns. E.g. for generators this includes `bus`, `carrier`, `p_nom`, etc. +``` py +>>> c = n.components.generators # Set c to generators for examples +>>> c.static # doctest: +ELLIPSIS + bus control ... +name ... +Manchester Wind Manchester PQ ... +Manchester Gas Manchester PQ ... +... +``` +- [`c.dynamic`][pypsa.Components.dynamic] contains all time-varying data of the component. It is a dict-like object that contains a `pandas.DataFrame` for each time-varying attribute. E.g. `p` or `p_max_pu`. +``` py +>>> c.dynamic.p_max_pu # doctest: +ELLIPSIS +name Manchester Wind Frankfurt Wind Norway Wind +snapshot +2015-01-01 00:00:00 0.930020 0.559078 0.974583 +2015-01-01 01:00:00 0.485748 0.752910 0.481290 +... +``` + +Stochastic Networks (see ) and multi-period networks (see ) use also use the same structure, but add additional dimensions to the dataframes. + +!!! info + + `c.static` and `c.dynamic` are exactly the same than `n.generators` and `n.generators_t`, which is still the default way to access the data and used in most of this documentation and any examples previous to version `v1.0`. See [below](#new-components-class-api) on the alternative way to access components data. + + +## Features +[`pypsa.Components`][] have been introduced as experimental in and are now released as stable in . They do not change anything in the underlying data structure of static and dynamic data stored in `pandas` DataFrames. + +However, they add a lot of additional functionality that would otherwise have to be reimplemented on the underlying `pandas` DataFrames repeatedly. Using them reduces the need for boilerplate code, while still allowing users to continue using only the underlying DataFrames directly. + +For a list of features, checkout the API documentation. [pypsa.Components][] lists all functionality which is available for all component types, while each type has its own class listing type-sensitive features. E.g. [pypsa.components.Generators][]. + +!!! info + + More features will be added in future releases. If you have any suggestions or requests, please open an issue on [GitHub](https://github.com/PyPSA/PyPSA/issues). + +## New Components Class API +Prior to version components data was only available in the two data stores `n.generators` and `n.generators_t`, which were directly attached to the network and not linked. With they are still available in the same way, but now also via the newly introduced class. A new **optional** breaking API is introduced to make the usage of components more intuitive. + +The current components API works as follows: + +- `n.generators` -> reference to [`n.components.generators.static`][pypsa.Components.static] +- `n.generator_t` -> reference to [`n.components.generators.dynamic`][pypsa.Components.dynamic] + +To get access to the full functionality of [pypsa.Components][] the long namespace must be used or assigned to a variable. For example, to rename a component across all dataframes ([pypsa.Components.rename_component_names][]) or add components with attribute type hints ([pypsa.components.Generators.add][]), the following code must be run: + +``` py +>>> n.components["Generator"].rename_component_names(bus1="bus_renamed") # doctest: +SKIP +>>> c = n.components["Generator"] +>>> c.add(name="New Gen", bus="bus_renamed", p_nom=100, carrier="wind") # doctest: +SKIP +``` + +### Opt-in to new API + +Therefore, PyPSA `v1.0` now allows you to opt in to an alternative, new Components API, which changes the reference to: + +| Namespace | Current API | Opt-in API | +|-----------|--------------|------------| +| `n.generators` | [`n.components.generators.static`][pypsa.Components.static] | [`n.components.generators`][pypsa.Components] | +| `n.generators_t` | [`n.components.generators.dynamic`][pypsa.Components.dynamic] | Deprecated | + +With the new API, working with components should become more intuitive. The relationship between static and dynamic data is clearer and the numerous features of the `Components` class are faster to access with full support for auto-completion in IDEs when adding components with `n.generators.add(...)`. + +One downside is that accessing the main static dataframe with `n.generators.static` instead of `n.generators` changes and is a bit longer. However,using a variable (e.g. `c`) as reference is still possible. Additionally, using the new API requires changes in existing code bases. + +### Migrating to new API +To make the migration as easy as possible, and to also allow step-by-step migration, the module is used. + +As a quick example, let's take a snippet of the [Three-Node Capacity Expansion Example](../examples/3-node-cem.ipynb). + +``` py +# Interconnector Capacities +>>> n.links.query("carrier == 'HVDC'").p_nom_opt.round(2) +Series([], Name: p_nom_opt, dtype: float64) + +# Interconnector Flows +>>> n.links_t.p0.loc[:, n.links.carrier == "HVDC"].rolling("7d").mean() # doctest: +SKIP +``` + +To switch to the new API, we just need to set the package option `api.new_components_api` to `True`. + +``` py +pypsa.options.api.new_components_api = True + +# Interconnector Capacities +n.links.static.query("carrier == 'HVDC'").p_nom_opt.round(2) + +# Interconnector Flows +n.links.dynamic.p0.loc[:, n.links.static.carrier == "HVDC"].rolling("7d").mean() + +# Now you can also use the full functionality of the Components class, for example: +n.links.additional_ports +``` + +#### Step-by-step migration +To migrate a script step by step, we can just set the option back again. + +``` py +pypsa.options.api.new_components_api = True + +# Interconnector Capacities +n.links.static.query("carrier == 'HVDC'").p_nom_opt.round(2) + +# Switch back to old API +pypsa.options.api.new_components_api = False + +# Interconnector Flows +n.links_t.p0.loc[:, n.links.carrier == "HVDC"].rolling("7d").mean() +``` + +Another way is to use the [`option_context`][pypsa.option_context] context manager to temporarily switch the API. + +``` py +with pypsa.options_context(api.new_components_api=True): + # Interconnector Capacities + n.links.static.query("carrier == 'HVDC'").p_nom_opt.round(2) + +# Interconnector Flows +n.links_t.p0.loc[:, n.links.carrier == "HVDC"].rolling("7d").mean() +``` + +PyPSA `v1.0` will support full functionality for both APIs. The example above just shows how to immediately translate between the two APIs. It often also makes sense to assign the components object to a variable, which is then used in the rest of the script. + +!!! info + + With `v2.0` of PyPSA, there are ongoing discussions to enable the new API by default with an opt-out option to still support old implementations. + + We are happy to receive feedback on this planned change of the API. Please open an issue on [GitHub](https://github.com/PyPSA/PyPSA/issues) or join the shared [Discord server](https://discord.gg/AnuJBk23FU). diff --git a/PyPSA/source/docs/user-guide/components/buses.md b/PyPSA/source/docs/user-guide/components/buses.md new file mode 100644 index 0000000000000000000000000000000000000000..f72d3d0c7b6eba426e397baf9e65ca52635d6201 --- /dev/null +++ b/PyPSA/source/docs/user-guide/components/buses.md @@ -0,0 +1,16 @@ + + +# Bus + +The [`Bus`][pypsa.components.Buses] is the fundamental node of the network, to which all other components +attach. It enforces energy conservation for all elements feeding in and out of +it in any snapshot (e.g. Kirchhoff's current law for electric buses). A [`Bus`][pypsa.components.Buses] +can represent a grid connection point, but it can also be used for other, +non-electric energy carriers (e.g. hydrogen, heat, oil) or even non-energy +carriers (e.g. CO~2~ or steel) in different locations. + +{{ read_csv('../../../pypsa/data/component_attrs/buses.csv') }} diff --git a/PyPSA/source/docs/user-guide/components/carriers.md b/PyPSA/source/docs/user-guide/components/carriers.md new file mode 100644 index 0000000000000000000000000000000000000000..c1fd1834a4f1373b63e8ce3a1758ee832a65393e --- /dev/null +++ b/PyPSA/source/docs/user-guide/components/carriers.md @@ -0,0 +1,11 @@ + + +# Carrier + +The [`Carrier`][pypsa.components.Carriers] describes **energy carriers of buses** (e.g. "AC" for alternating current, "DC" for dicrect current, "hydrogen", or "heat") or **technologies** of other components (e.g. "wind", "gas turbine", "electrolyser", or "heat pump"). Besides descriptive names and colors for visualizations, attributes relevant for can also be stored in this component class (e.g. CO$_2$ emissions of bus carriers relevant for emission limits). + +{{ read_csv('../../../pypsa/data/component_attrs/carriers.csv') }} diff --git a/PyPSA/source/docs/user-guide/components/generators.md b/PyPSA/source/docs/user-guide/components/generators.md new file mode 100644 index 0000000000000000000000000000000000000000..f9860f946ba096fcd217b49ee113c4da1c4e1050 --- /dev/null +++ b/PyPSA/source/docs/user-guide/components/generators.md @@ -0,0 +1,22 @@ + + +# Generator + +The [`Generator`][pypsa.components.Generators] components attach to a single bus and can feed in power. They +convert energy from their carrier to the carrier of the bus to which they +attach. They can be used to represent dispatchable conventional power plants, +renewable generators with variable availability, supply of grid electricity or +biomass from an external source. With inverted `sign`, they can also be used to +represent withdrawal of power at a given price or elastic demands following a +linear demand curve. + +!!! note "When to use [`Link`][pypsa.components.Links] instead?" + + Use the [`Link`][pypsa.components.Links] component if you have the fuel of the generator represented by a [`Bus`][pypsa.components.Buses] and you want to model the conversion of that fuel to electricity, e.g. a gas-fired power plant with a gas bus. + + +{{ read_csv('../../../pypsa/data/component_attrs/generators.csv') }} diff --git a/PyPSA/source/docs/user-guide/components/global-constraints.md b/PyPSA/source/docs/user-guide/components/global-constraints.md new file mode 100644 index 0000000000000000000000000000000000000000..46528d8e794ae9e831838710f3f5233a7b5aeceb --- /dev/null +++ b/PyPSA/source/docs/user-guide/components/global-constraints.md @@ -0,0 +1,12 @@ + + +# Global Constraints + +The [`GlobalConstraint`][pypsa.components.GlobalConstraints] components describe constraints in the optimisation +problem that apply to multiple components at once. + +{{ read_csv('../../../pypsa/data/component_attrs/global_constraints.csv') }} diff --git a/PyPSA/source/docs/user-guide/components/line-types.md b/PyPSA/source/docs/user-guide/components/line-types.md new file mode 100644 index 0000000000000000000000000000000000000000..eabfbc632108958d806e2dd340d44235b6ecaf90 --- /dev/null +++ b/PyPSA/source/docs/user-guide/components/line-types.md @@ -0,0 +1,18 @@ + + +# Line Types + +The `LineType` components describe standard line types with per length values +for impedances. If for a [`Line`][pypsa.components.Lines] the attribute `type` is non-empty, it is mapped +to the `LineType`'s electrical parameters and multiplied with the `length` +attribute of the [`Line`][pypsa.components.Lines]. + +{{ read_csv('../../../pypsa/data/component_attrs/line_types.csv') }} + +The following standard line types are available, which are based on [pandapower's standard types](https://pandapower.readthedocs.io/en/latest/std_types/basic.html), whose parameterisation is in turn based on [DIgSILENT PowerFactory](http://www.digsilent.de/index.php/products-powerfactory.html). Other sources include [JAO's Static Grid Model](https://www.jao.eu/static-grid-model). + +{{ read_csv('../../../pypsa/data/standard_types/line_types.csv') }} diff --git a/PyPSA/source/docs/user-guide/components/lines.md b/PyPSA/source/docs/user-guide/components/lines.md new file mode 100644 index 0000000000000000000000000000000000000000..02b20365ad9cff4efa2202cebdef844f0461a725 --- /dev/null +++ b/PyPSA/source/docs/user-guide/components/lines.md @@ -0,0 +1,17 @@ + + +# Line + +The [`Line`][pypsa.components.Lines] components represent power transmission and distribution lines. They connect a `bus0` to a `bus1`. They can connect to buses with carrier "AC" or "DC". Power flow through lines is not directly controllable, but is determined passively by their impedances and the nodal power imbalances according to Kirchhoff's voltage law. To see how the impedances are used in the power flow, see the [line model](../power-flow.md#line-model). + +!!! note "When to use [`Link`][pypsa.components.Links] instead?" + + - Use the [`Link`][pypsa.components.Links] for power lines with controllable power flow, such as point-to-point HVDC links. + - Use the [`Link`][pypsa.components.Links] for any connection between buses with different carrier. + + +{{ read_csv('../../../pypsa/data/component_attrs/lines.csv', disable_numparse=True) }} diff --git a/PyPSA/source/docs/user-guide/components/links.md b/PyPSA/source/docs/user-guide/components/links.md new file mode 100644 index 0000000000000000000000000000000000000000..3adbfab080f0163ae172cc62a08d71287a90237b --- /dev/null +++ b/PyPSA/source/docs/user-guide/components/links.md @@ -0,0 +1,53 @@ + + +# Link + +The [`Link`][pypsa.components.Links] components are used for controllable directed flows between two or +more buses with arbitrary energy carriers (`bus0`, `bus1`, `bus2`, etc.). For +instance, they can represent point-to-point HVDC links, unidirectional lossy +HVDC links, converters between AC and DC, net transfer capacities (NTCs) of HVAC +lines (neglecting Kirchhoff's voltage law), as well as any conversion between +carriers (e.g. electricity to hydrogen in electrolysis, electricity to heat in +heat pumps, or gas to electricity and heat in a combined heat and power (CHP) +plant). + +- The [`Link`][pypsa.components.Links] component has one dispatch variable `p0` associated with the input + from `bus0` (positive if withdrawing from `bus0`) and one or more outputs + `p1`, `p2`, etc. associated with the output at `bus1`, `bus2`, etc. (negative + if supplying to these buses). The outputs at `bus1`, `bus2`, etc. are + proportional to the input at `bus0` multiplied by the corresponding efficiency + (e.g. `efficiency`, `efficiency2`, etc.). That means that the link can have + multiple outputs in fixed ratio to the input. + +- The columns `bus2`, `efficiency2`, `bus3`, `efficiency3`, etc. in `n.links` + are automatically added to the component attributes. + +- Any `marginal_cost` are related to the input `p0` at `bus0` (e.g. cost per + unit of fuel consumed rather than cost per unit of electricity produced for a + power plant). + +- For links with multiple inputs in fixed ratio to one of the inputs, you can + define the other inputs as outputs with a negative efficiency so that they + withdraw from the corresponding bus if there is a positive flow for `p0`. + +!!! note "When to use [`Line`][pypsa.components.Lines] instead?" + + Use the [`Line`][pypsa.components.Lines] component type for power lines for which their power flow is determined passively through Kirchhoff's voltage law. + +!!! example "[`Link`][pypsa.components.Links] with bidirectional lossless flow" + + Because the [`Link`][pypsa.components.Links] component can have efficiency losses and marginal costs, the default settings allow only for flow in one direction (`p_min_pu=0`), from `bus0` to `bus1`. To build a bidirectional lossless link, set `efficiency = 1`, `marginal_cost = 0` and `p_min_pu = -1`. + +!!! example "[`Link`][pypsa.components.Links] with a single input and multiple outputs" + + Suppose a link representing a combined heat and power (CHP) plant takes as input 1 unit of fuel and gives as outputs 0.3 units of electricity and 0.7 units of heat. Then `bus0` connects to the fuel, `bus1` connects to electricity with `efficiency=0.3` and `bus2` connects to heat with `efficiency2=0.7`. [This example](../../examples/chp-fixed-heat-power-ratio.ipynb) illustrates a CHP with a fixed power-heat ratio using links. + +!!! example "[`Link`][pypsa.components.Links] with multiple inputs and a single output" + + Suppose a link representing a methanation process takes as inputs one unit of hydrogen and 0.5 units of carbon dioxide, and gives as outputs 0.8 units of methane and 0.2 units of heat. Then `bus0` connects to hydrogen, `bus1` connects to carbon dioxide with `efficiency=-0.5` (since 0.5 units of carbon dioxide is taken for each unit of hydrogen), `bus2` connects to methane with `efficiency2=0.8` and `bus3` to heat with `efficiency3=0.2`. [This example](../../examples/biomass-synthetic-fuels-carbon-management.ipynb) illustrates many modelling processes with multiple inputs and outputs using links. + +{{ read_csv('../../../pypsa/data/component_attrs/links.csv', disable_numparse=True) }} diff --git a/PyPSA/source/docs/user-guide/components/loads.md b/PyPSA/source/docs/user-guide/components/loads.md new file mode 100644 index 0000000000000000000000000000000000000000..71a0e8709c5290cadbeaf8e348e4ac519ba05aec --- /dev/null +++ b/PyPSA/source/docs/user-guide/components/loads.md @@ -0,0 +1,19 @@ + + +# Load + +The [`Load`][pypsa.components.Loads] components attach to a single bus and represent a demand for the +[`Bus`][pypsa.components.Buses] carrier they are connected to. With inverted sign, they can also be used +to model an exogenous supply. For "AC" buses, they act as a PQ load. If $p>0$ +the load is consuming active power from the bus and if $q>0$ it is consuming +reactive power (i.e. behaving like an inductor). + +!!! note "When to use [`Generator`][pypsa.components.Generators] instead?" + + Use the [`Generator`][pypsa.components.Generators] component with a negative `sign` to model elastic demands following a linear demand curve or to represent a comnsumption at a given price. + +{{ read_csv('../../../pypsa/data/component_attrs/loads.csv', disable_numparse=True) }} diff --git a/PyPSA/source/docs/user-guide/components/shapes.md b/PyPSA/source/docs/user-guide/components/shapes.md new file mode 100644 index 0000000000000000000000000000000000000000..c77acbae33ec20debd5659fa785fc08f2d10096e --- /dev/null +++ b/PyPSA/source/docs/user-guide/components/shapes.md @@ -0,0 +1,11 @@ + + +# Shapes + +The [`Shape`][pypsa.components.Shapes] component type is a `geopandas.GeoDataFrame` which can be used to store network-related geographical data (e.g. for visualisations). + +{{ read_csv('../../../pypsa/data/component_attrs/shapes.csv') }} diff --git a/PyPSA/source/docs/user-guide/components/shunt-impedances.md b/PyPSA/source/docs/user-guide/components/shunt-impedances.md new file mode 100644 index 0000000000000000000000000000000000000000..719d14f6f42a760c98c955c9091797833d5ca07b --- /dev/null +++ b/PyPSA/source/docs/user-guide/components/shunt-impedances.md @@ -0,0 +1,21 @@ + + +# Shunt Impedance + +The [`ShuntImpedance`][pypsa.components.ShuntImpedances] components attach to a single bus and have a +voltage-dependent admittance. For shunt impedances the power consumption is +given by $s_i = |V_i|^2 y_i^*$ so that $p_i + j q_i = |V_i|^2 (g_i -jb_i)$. +However the `p` and `q` below are defined directly proportional to `g` and `b` +with $p = |V|^2g$ and $q = |V|^2b$, thus if $p>0$ the shunt impedance is +consuming active power from the bus and if $q>0$ it is supplying reactive power +(i.e. behaving like an capacitor). + +!!! note + + Shunt impedances are only used in power flow calculations ([`n.pf()`][pypsa.Network.pf]), not in any of the optimisation problems ([`n.optimize()`][pypsa.optimization.OptimizationAccessor.__call__]). + +{{ read_csv('../../../pypsa/data/component_attrs/shunt_impedances.csv', disable_numparse=True) }} diff --git a/PyPSA/source/docs/user-guide/components/storage-units.md b/PyPSA/source/docs/user-guide/components/storage-units.md new file mode 100644 index 0000000000000000000000000000000000000000..f7ecead4d380d2c417dc35acdbefebd67dc06130 --- /dev/null +++ b/PyPSA/source/docs/user-guide/components/storage-units.md @@ -0,0 +1,19 @@ + + +# Storage Unit + +The [`StorageUnit`][pypsa.components.StorageUnits] components connect to a single bus and enable inter-temporal energy shifting with coupled power and energy capacity modelling. This compoenent is suitable for modeling batteries, pumped hydro storage, and other storage technologies where power and energy capacities are coupled. + +- Energy capacity is defined as a fixed ratio (`max_hours`) of power capacity: `e_nom = p_nom * max_hours` (MW × h = MWh) +- An `inflow` attribute (an exogenous parameter representing an energy input from external sources) and `spill` (energy overflow/spillage) variable are supported +- For storage units, if $p>0$ the storage unit is supplying active power to the bus (discharging) and if $q>0$ it is supplying reactive power. + +!!! note "When to use [`Store`][pypsa.components.Stores] instead?" + + For independent optimization of power and energy capacities, use the [`Store`][pypsa.components.Stores] component at an auxiliary [`Bus`][pypsa.components.Buses] combined with separate [`Link`][pypsa.components.Links] components for charging and discharging. Add a [`Generator`][pypsa.components.Generators] or [`Load`][pypsa.components.Loads] at the auxiliary [`Bus`][pypsa.components.Buses] for modelling spillage and inflow from an external source. See also [this example](../../examples/replace-generator-storage-units-with-store.ipynb). + +{{ read_csv('../../../pypsa/data/component_attrs/storage_units.csv') }} diff --git a/PyPSA/source/docs/user-guide/components/stores.md b/PyPSA/source/docs/user-guide/components/stores.md new file mode 100644 index 0000000000000000000000000000000000000000..1d1dacab7d1e640694456bd188552b9b9c7990bb --- /dev/null +++ b/PyPSA/source/docs/user-guide/components/stores.md @@ -0,0 +1,47 @@ + + +# Store + +The [`Store`][pypsa.components.Stores] component connects to a single bus and provides fundamental +inter-temporal storage functionality of the carrier of the [`Bus`][pypsa.components.Buses] it attaches to. +It is not limited in charging or discharging power. + +To control charging and discharging power, separate [`Link`][pypsa.components.Links] components must be +connected to the [`Bus`][pypsa.components.Buses] to which the [`Store`][pypsa.components.Stores] attaches. This decoupled approach +enables independent optimization of power and energy capacities as well as +asymmetric power ratings for charging and discharging. + +!!! example "Hydrogen storage system with a [`Store`][pypsa.components.Stores] and two [`Link`][pypsa.components.Links] components" + + ```mermaid + graph LR + ElectricityBus["Electricity Bus"]:::bus + HydrogenBus["Hydrogen Bus"]:::bus + SteelTank["Steel Tank Store"]:::store + + ElectricityBus -->|Electrolyser Link| HydrogenBus + HydrogenBus -->|Hydrogen Turbine Link| ElectricityBus + HydrogenBus --> SteelTank + + classDef bus fill:#f9f,stroke:#333,stroke-width:2,shape:circle; + classDef store fill:#bbf,stroke:#333,stroke-width:2,shape:rect; + ``` + +The `marginal_cost` attribute applies equally to both charging and discharging +operations, representing the cost per unit of energy stored or released. This +differs from [`StorageUnit`][pypsa.components.StorageUnits] components where marginal costs apply only to the +discharging power. For instance, the `marginal_cost` of the [`Store`][pypsa.components.Stores] component +can represent trading in external energy markets where the stored carrier can be +bought or sold at fixed market prices. + +!!! note "When to use [`StorageUnit`][pypsa.components.StorageUnits] instead?" + + Use [`StorageUnit`][pypsa.components.StorageUnits] when power and energy capacities have a fixed relationship and you need integrated dispatch control within a single component. For example, this is recommended for a storage device where the power-to-energy ratio is predetermined by the manufacturer. + The [`StorageUnit`][pypsa.components.StorageUnits] also has attributes for hydro-electric `inflow` and `spillage`. + See [this example](../../examples/replace-generator-storage-units-with-store.ipynb) for implementation differences. + +{{ read_csv('../../../pypsa/data/component_attrs/stores.csv', disable_numparse=True) }} diff --git a/PyPSA/source/docs/user-guide/components/sub-networks.md b/PyPSA/source/docs/user-guide/components/sub-networks.md new file mode 100644 index 0000000000000000000000000000000000000000..13e7558eb0cdcff94f3a5008549dfdf90f8f6a6f --- /dev/null +++ b/PyPSA/source/docs/user-guide/components/sub-networks.md @@ -0,0 +1,21 @@ + + +# Sub-Network + +The [`SubNetwork`][pypsa.components.SubNetworks] components are network subsets formed by buses with the same +carrier that are connected by passive branches (i.e. [`Line`][pypsa.components.Lines] and [`Transformer`][pypsa.components.Transformers]). +Sub-networks with carrier "AC" correspond to synchronous areas, in which the +power flow is determined by the line and transformer impedances following +Kirchhoff's voltage law. Only "AC" and "DC" sub-networks can contain passive +branches. All other sub-networks must contain a single isolated bus. + +!!! warning + + Sub-networks are not entered by the user but dynamically calculated by + [`n.determine_network_topology()`][pypsa.Network.determine_network_topology]. + +{{ read_csv('../../../pypsa/data/component_attrs/sub_networks.csv') }} diff --git a/PyPSA/source/docs/user-guide/components/transformer-types.md b/PyPSA/source/docs/user-guide/components/transformer-types.md new file mode 100644 index 0000000000000000000000000000000000000000..586c9d7c51fd942c871195de00c4dc1f7545c24d --- /dev/null +++ b/PyPSA/source/docs/user-guide/components/transformer-types.md @@ -0,0 +1,17 @@ + + +# Transformer Types + +The `TransformerType` components describe standard 2-winding transformer types. +If for a [`Transformer`][pypsa.components.Transformers] the attribute `type` is non-empty, the electrical +parameters of the `TransformerType` are mapped to the [`Transformer`][pypsa.components.Transformers]. + +{{ read_csv('../../../pypsa/data/component_attrs/transformer_types.csv') }} + +The following standard transformer types are available, which are based on [pandapower's standard types](https://pandapower.readthedocs.io/en/latest/std_types/basic.html), whose parameterisation is in turn based on [DIgSILENT PowerFactory](http://www.digsilent.de/index.php/products-powerfactory.html). + +{{ read_csv('../../../pypsa/data/standard_types/transformer_types.csv') }} diff --git a/PyPSA/source/docs/user-guide/components/transformers.md b/PyPSA/source/docs/user-guide/components/transformers.md new file mode 100644 index 0000000000000000000000000000000000000000..54bdc92984866a2ccd6128a15ea02cd515feaee7 --- /dev/null +++ b/PyPSA/source/docs/user-guide/components/transformers.md @@ -0,0 +1,11 @@ + + +# Transformer + +The [`Transformer`][pypsa.components.Transformers] components represent 2-winding transformers that convert AC power from one voltage level to another. They connect a `bus0` (typically at higher voltage) to a `bus1` (typically at lower voltage). Power flow through transformers is not directly controllable, but is determined passively by their impedances and the nodal power imbalances. To see how the impedances are used in the power flow, see the [transformer model](../power-flow.md#transformer-model). + +{{ read_csv('../../../pypsa/data/component_attrs/transformers.csv') }} diff --git a/PyPSA/source/docs/user-guide/design.md b/PyPSA/source/docs/user-guide/design.md new file mode 100644 index 0000000000000000000000000000000000000000..7b53e37fe015026518c03c63d72c37af28eaa19e --- /dev/null +++ b/PyPSA/source/docs/user-guide/design.md @@ -0,0 +1,377 @@ + + +# Design + +## Network Object + +The [`pypsa.Network`][] is an overall container for all network . Components cannot exist without a network and are always +attached to one. A network also holds functions to run different types of +optimisation problems, compute power flows, read or write networks to files, +retrieve statistics and plot the network. + +``` py +>>> import pypsa +>>> n = pypsa.Network() +>>> n +Empty PyPSA Network 'Unnamed Network' +------------------------------- +Components: none +Snapshots: 1 + +``` + +!!! tip + + A short name, such as `n`, is recommended since it is used frequently to access the network's components and methods. + +## Network Components + +PyPSA represents power and energy systems using the following component types: + +{{ read_csv('../../pypsa/data/components.csv') }} + +Each component has a set of attributes with data types, default values, and +descriptions for each attribute. For instance, attributes for capacity, +efficiency, costs, and the buses to which components are attached. For the +documentation of attributes for each component, see , which can also be accessed as a +`pandas.DataFrame`, e.g. as +[`n.components.buses.defaults`][pypsa.Components.defaults]. + +Components are grouped according to their properties in sets such as +[`n.one_port_components`][pypsa.network.components.NetworkComponentsMixin.one_port_components] +(connecting to a single bus), +[`n.branch_components`][pypsa.network.components.NetworkComponentsMixin.branch_components] +(connecting two or more buses), +[`n.passive_branch_components`][pypsa.network.components.NetworkComponentsMixin.passive_branch_components] +(whose power flow is determined passively by impedances and nodal power +imbalances), and +[`n.controllable_branch_components`][pypsa.network.components.NetworkComponentsMixin.controllable_branch_components] +(whose power flow can be controlled by the optimisation). + +## Buses + +The [`Bus`][pypsa.components.Buses] is the fundamental node of the network, to which all other components +attach. It enforces conservation of flows for all elements feeding in and out of +it in any time step. A [`Bus`][pypsa.components.Buses] can represent a power substation, but it can also +be used for other, non-electric energy carriers (e.g. hydrogen, heat, oil) or +even non-energy carriers (e.g. CO~2~ or steel) in different locations. + +``` py +>>> n.add("Bus", "my_bus") +``` + +
+ ![Buses](../assets/images/buses.png){ width="600" } +
+ +## Energy Balances + +- Energy **enters** the model via [`Generator`][pypsa.components.Generators] components, [`Load`][pypsa.components.Loads] components with + negative sign, and [`StorageUnit`][pypsa.components.StorageUnits] or [`Store`][pypsa.components.Stores] components with higher energy + levels in the first than in the last time step, and any components with + efficiency values greater than 1 (e.g. heat pumps). + +- Energy **leaves** the model via [`Load`][pypsa.components.Loads] components, [`Generator`][pypsa.components.Generators] components with + negative sign, [`StorageUnit`][pypsa.components.StorageUnits] or [`Store`][pypsa.components.Stores] components with higher energy in the + last then in the first time step, and in [`Link`][pypsa.components.Links], [`Line`][pypsa.components.Lines] and [`StorageUnit`][pypsa.components.StorageUnits] + components with efficiency less than 1. + +## Snapshots + +Snapshots represent the time steps of the network, and are stored as +`pandas.Index` or `pandas.MultiIndex`. Snapshots are used to represent the +time-varying nature of the network, such as the availability of renewable energy +sources, the demand for electricity, or the state of charge of storage units. +All time-dependent series quantities are indexed by +[`n.snapshots`][pypsa.network.index.NetworkIndexMixin.snapshots]. Networks +default to a single snapshot called "now" and can be set with +[`n.set_snapshots()`][pypsa.Network.set_snapshots]. + + +``` py +>>> n.set_snapshots([0, 1, 2]) +>>> n.snapshots +Index([0, 1, 2], dtype='int64', name='snapshot') +``` + +!!! note + + For many applications, snapshots represent time intervals and are commonly defined as a `pandas.DatetimeIndex`, for example using `pd.date_range("2024-01-01", periods=168, freq="H")` to create hourly intervals for a week. + +Snapshot weightings are applied to each snapshot, so that snapshots can +represent more than one hour or fractions of one hour. Three different +categories of snapshot weightings can be set. Objective weightings are used to +weight snapshots in the objective function. Store weightings determine the state +of charge change for stores and storage units. The generator weightings are used +when calculating global constraints and energy balances. Snapshot weightings are +stored as a `pandas.DataFrame` and indexed by [`n.snapshots`][pypsa.network.index.NetworkIndexMixin.snapshots]. They default to a +uniform snapshot weighting of 1 hour. + +``` py +>>> n.snapshot_weightings + objective stores generators +snapshot +0 1.0 1.0 1.0 +1 1.0 1.0 1.0 +2 1.0 1.0 1.0 +``` + +## Investment Periods + +For long-term planning problems where the network is optimised for different +time horizons, it is possible to define multiple investment periods (e.g. 2025, +2035, 2045). Investment periods can be defined in +[`n.investment_periods`][pypsa.network.index.NetworkIndexMixin.investment_periods], +a `pandas.Index` of monotonically increasing integers of years, with +[`n.set_investment_periods()`][pypsa.Network.set_investment_periods]. + +``` py +>>> n.set_investment_periods([2025, 2035, 2045]) +>>> n.investment_periods +Index([2025, 2035, 2045], dtype='int64', name='period') +``` + +By default, there are no investment periods defined, and the network is +optimised for a single investment period (overnight scenario). + +Just like snapshots, investment periods can have weightings. These are defined +in +[`n.investment_period_weightings`][pypsa.network.index.NetworkIndexMixin.investment_period_weightings], +which is a `pandas.DataFrame` indexed by +[`n.investment_periods`][pypsa.network.index.NetworkIndexMixin.investment_periods] +with two columns: "objective" and "years". Objective weightings are multiplied +with all cost coefficients in the objective function of the respective +investment period (e.g. for including a social discount rate). Years weightings +denote the elapsed time until the subsequent investment period (e.g. for global +constraints on emissions). They default to a uniform weighting of 1 for each +investment period. + +``` py +>>> n.investment_period_weightings + objective years +period +2025 1.0 1.0 +2035 1.0 1.0 +2045 1.0 1.0 +``` + +!!! note + + When investment periods are used, [`n.snapshots`][pypsa.network.index.NetworkIndexMixin.snapshots] becomes a `pandas.MultiIndex` + with two index levels: a first level for the investment periods and a second + level for the time steps. As [`n.snapshot_weightings`][pypsa.network.index.NetworkIndexMixin.snapshot_weightings] is indexed by + [`n.snapshots`][pypsa.network.index.NetworkIndexMixin.snapshots], its index is then also a `pandas.MultiIndex`. It is possible to have different snapshots + for each investment period, since users may want a higher resolution + in later years where there are more renewables, and the weather may change due to climate change. + +!!! example "Example: Applying a social discount rate to investment period objective weightings" + + To apply a social discount rate to the objective weightings of investment + periods, consider that each investment period $a$ is associated with a set of + years $y \in Y_a$ over which costs occur. + + The discount factor $d_y$ adjusts costs from year $y$ to the base currency year $y_0$ using a social discount rate $r$: + + $$d_y = \frac{1}{(1 + r)^{y - y_0}}$$ + + The total discounted weight $v_a^o$ for investment period $a$ is the sum of discounted factors over all years $y \in Y_a$: + + $$v_a^o = \sum_{y \in Y_a} d_y$$ + + For example, let $r = 0.02$, $y_0 = 2025$, and investment period $a = 2030$ with $Y_{2030} = \{2030, 2031, 2032, 2033, 2034\}$. Then: + + $$v_{2030}^o = \sum_{y=2030}^{2034} \frac{1}{(1 + 0.02)^{y - 2025}} \approx 4.355$$ + + The year weighting $v_a^y = 5$ would remain, as it represents the number of years in the investment period. + +## Scenarios + +By default, the network is optimised for a single deterministic scenario +([`n.has_scenarios`][pypsa.Network.has_scenarios] is `False`). If scenarios are defined, the network is +optimised for multiple scenarios in form of a risk-neutral two-stage stochastic programming +framework (see and [:material-book: Stochastic Programming Example](../examples/stochastic-optimization.ipynb)). + +Scenario names are stored in `n.scenarios`, a `pandas.DataFrame`, and are set +with [`n.set_scenarios()`][pypsa.Network.set_scenarios]. + +``` py +>>> n = pypsa.Network() # docs-hide +>>> n.set_scenarios(["low", "high"]) +>>> n.scenarios +Index(['low', 'high'], dtype='object', name='scenario') +``` + +Probabilities for each scenario can also be set with +[`n.set_scenarios()`][pypsa.Network.set_scenarios], by passing a dictionary with +scenario names as keys and probabilities as values. The probabilities are stored +in `n.scenario_weightings`, a `pandas.Series` indexed by `n.scenarios`. + +``` py +>>> n = pypsa.Network() # docs-hide +>>> n.set_scenarios({"low": 0.7, "high": 0.3}) +>>> n.scenario_weightings + weight +scenario +low 0.7 +high 0.3 +``` + +If no probabilities are set, they default to a uniform distribution. + +While the default stochastic programming formulation is risk-neutral, a risk-averse +formulation using the Conditional Value at Risk (CVaR) is also supported. Two parameters `omega` and `alpha` control the risk-aversion. `omega` controls the trade-off between the expected costs and the CVaR measure, while `alpha` sets the confidence level for the CVaR measure. Both parameters can be set with [`n.set_risk_preference()`][pypsa.Network.set_risk_preference] and are stored in a dictionary in `n.risk_preference`. + +```py +>>> n.set_risk_preference(alpha=0.9, omega=0.5) +>>> n.risk_preference +{'alpha': 0.9, 'omega': 0.5} +``` + +For more details, see . + +## Data Storage + +For each class of components, the data describing the components is stored in +memory in `pandas.DataFrame` objects. + +**Static data** is stored in a `pandas.DataFrame`, which is an attribute of the +[`pypsa.Network`][], with names that follow the component names. For instance, + +``` py +>>> n = pypsa.examples.ac_dc_meshed() # docs-hide +>>> n.buses + v_nom type x y ... control generator sub_network country +name ... +London 380.0 -0.13 51.50 ... PQ UK +Norwich 380.0 1.30 52.60 ... PQ UK +Norwich DC 200.0 1.30 52.50 ... PQ UK +Manchester 380.0 -2.20 53.47 ... PQ UK +Bremen 380.0 8.80 53.08 ... PQ DE +Bremen DC 200.0 8.80 52.98 ... PQ DE +Frankfurt 380.0 8.70 50.12 ... PQ DE +Norway 380.0 10.75 60.00 ... PQ NO +Norway DC 200.0 10.75 60.00 ... PQ NO + +[9 rows x 14 columns] +``` + +In this `pandas.DataFrame`, the index corresponds to the unique +string names of the components, while the columns correspond to the components' +static attributes. + +**Time-varying data** is stored in a dictionary of `pandas.DataFrame` objects, +which is an attribute of the [`pypsa.Network`][], with names that follow the +component names with a `_t` suffix. For instance, + +``` py +>>> n = pypsa.examples.ac_dc_meshed() # docs-hide +>>> n.buses_t +{'v_mag_pu_set': Empty DataFrame +Columns: [] +Index: [2015-01-01 00:00:00, 2015-01-01 01:00:00, 2015-01-01 02:00:00, 2015-01-01 03:00:00, 2015-01-01 04:00:00, 2015-01-01 05:00:00, 2015-01-01 06:00:00, 2015-01-01 07:00:00, 2015-01-01 08:00:00, 2015-01-01 09:00:00], 'p': Empty DataFrame +Columns: [] +Index: [2015-01-01 00:00:00, 2015-01-01 01:00:00, 2015-01-01 02:00:00, 2015-01-01 03:00:00, 2015-01-01 04:00:00, 2015-01-01 05:00:00, 2015-01-01 06:00:00, 2015-01-01 07:00:00, 2015-01-01 08:00:00, 2015-01-01 09:00:00], 'q': Empty DataFrame +Columns: [] +Index: [2015-01-01 00:00:00, 2015-01-01 01:00:00, 2015-01-01 02:00:00, 2015-01-01 03:00:00, 2015-01-01 04:00:00, 2015-01-01 05:00:00, 2015-01-01 06:00:00, 2015-01-01 07:00:00, 2015-01-01 08:00:00, 2015-01-01 09:00:00], 'v_mag_pu': Empty DataFrame +Columns: [] +Index: [2015-01-01 00:00:00, 2015-01-01 01:00:00, 2015-01-01 02:00:00, 2015-01-01 03:00:00, 2015-01-01 04:00:00, 2015-01-01 05:00:00, 2015-01-01 06:00:00, 2015-01-01 07:00:00, 2015-01-01 08:00:00, 2015-01-01 09:00:00], 'v_ang': Empty DataFrame +Columns: [] +Index: [2015-01-01 00:00:00, 2015-01-01 01:00:00, 2015-01-01 02:00:00, 2015-01-01 03:00:00, 2015-01-01 04:00:00, 2015-01-01 05:00:00, 2015-01-01 06:00:00, 2015-01-01 07:00:00, 2015-01-01 08:00:00, 2015-01-01 09:00:00], 'marginal_price': Empty DataFrame +Columns: [] +Index: [2015-01-01 00:00:00, 2015-01-01 01:00:00, 2015-01-01 02:00:00, 2015-01-01 03:00:00, 2015-01-01 04:00:00, 2015-01-01 05:00:00, 2015-01-01 06:00:00, 2015-01-01 07:00:00, 2015-01-01 08:00:00, 2015-01-01 09:00:00]} +``` + +The keys of the dictionary are the names of the component attributes. The index +of the `pandas.DataFrame` corresponds to the snapshots, while the columns +correspond to the component names. For instance, this can be used to represent +the changing availability of variable renewable generators per unit of nominal +capacity (`p_max_pu`): + +``` py +>>> n = pypsa.Network() # docs-hide +>>> n.set_snapshots(pd.date_range("2015-01-01", periods=3, freq="h")) # docs-hide +>>> n.add("Bus", "my_bus") # docs-hide +>>> n.add("Generator", "Wind", bus="my_bus", p_nom=10, p_max_pu=[0.1, 0.5, 0.2]) +>>> n.generators_t.p_max_pu +name Wind +snapshot +2015-01-01 00:00:00 0.1 +2015-01-01 01:00:00 0.5 +2015-01-01 02:00:00 0.2 +``` + +**Input data**, such as the availability `p_max_pu` of a generator, can be +stored statically in `n.generators` if the value does not change over +`n.snapshots` *or* can be defined in `n.generators_t.p_max_pu`. If the name of +the generator is in the columns of `n.generators_t.p_max_pu`, the static value +in `n.generators` will be ignored. + +**Output data** related to the operation of the system, such as the optimised +dispatch `p` of a generator, is always returned as time-varying data +(`n.generators_t.p`). Results related to capacities is stored as static data, +such as the optimised nominal capacity `p_nom` of a generator in +`n.generators.p_nom`. + +Attributes that can be time-varying are marked as "series" in the listings in +. + + +## Separation of Inputs and Outputs + +Input and output data is strictly separated, such that inputs are not +overwritten by outputs. For instance, set points (`p_set`) are stored separately +from actual dispatch points (`p`). + +The listings in show +for each attribute whether it is an input (which the user specifies) or output +(which is computed by PyPSA). Inputs can be either "required" or "optional". +Optional inputs are assigned a sensible default value if the user gives no +input. + +## Unit Conventions + +The units for physical quantities follow the general rules. + +| Quantity | Units | +|----------|-------| +| Power | MW/MVA/MVar (unless per unit of nominal power, e.g. `n.generators.p_max_pu` for variable generators is per-unit of `n.generators.p_nom`) | +| Time | h | +| Energy | MWh | +| Voltage | kV phase-phase for `n.buses.v_nom`; per-unit for `n.buses.v_mag_pu` | +| Angles | radians, except `n.transformers.phase_shift` which is in degrees | +| Impedance | Ohm, except transformers which are per-unit, using `n.transformers.s_nom` for the base power | +| CO~2~ emissions | tonnes of CO~2~ per MWh~thermal~ of energy carrier | + +Per unit values of voltage and impedance are used internally for +network calculations. It is assumed that the base power is +**1 MVA**. The base voltage depends on the component. + +!!! note + + The units of buses can also refer to non-electric carriers, such as tonnes of hydrogen (t~H2~). In this case, the units + of energy would be t~H2~ with units of power of t~H2~/h. Unit conversions are not applied automatically handled, but must be encoded through the efficiencies. + +## Variable Conventions + +All nominal capacities and dispatch variables refer to `bus` for one-port components and `bus0` for branch components. + +## Sign Conventions + +* The power (`p`,`q`) of generators or storage units is positive if the + asset is injecting power into the bus, and negative if withdrawing power + from bus. +* The power (`p`,`q`) of loads is positive if withdrawing power from bus, + negative if injecting power into bus. +* The power (`p0`,`q0`) at `bus0` of a branch (line, link, or transformer) is + positive if the branch is + withdrawing power from `bus0`, i.e. `bus0` is injecting into the branch. +* Similarly the power (`p1`,`q1`) at `bus1` of a branch is positive if the + branch is withdrawing power from `bus1`, and negative if the branch is + injecting into `bus1`. +* If `p0>0` and `p1<0` for a branch then power flows from `bus0` + to `bus1`; `p0+p1 > 0` is the loss for this direction of flow. diff --git a/PyPSA/source/docs/user-guide/faq.md b/PyPSA/source/docs/user-guide/faq.md new file mode 100644 index 0000000000000000000000000000000000000000..96e109ae9d7cda7b221188eeaed1f0c71b73d46c --- /dev/null +++ b/PyPSA/source/docs/user-guide/faq.md @@ -0,0 +1,203 @@ + + +# Frequently Asked Questions + +## Does PyPSA model more than just the power system? Can methane, hydrogen, carbon dioxide networks be included? + +Yes, PyPSA can model any energy carrier or material flow. Typically this is done as a transport network with linear losses. See for example how gas networks are modelled in PyPSA-Eur in [Hofmann et al. (2025)](https://www.nature.com/articles/s41560-025-01752-6). On the demand side, PyPSA has been used to model the full energy system, including building heating (heat pumps, gas boilers, district heating), process heating in industry, process emissions in industry, electric vehicles with flexible charging, demands for transport fuels and industrial feedstocks. + +## How is demand modelled? + +Demand can be modelled with linear or convex-quadratic models as perfectly inelastic, perfectly inelastic up to a value of lost load, or with price elasticity (linear demand function modelled as quadratic program). Cross-price elasticity between different hours is also possible with custom modifications (see [Brown et al. (2025)](https://www.sciencedirect.com/science/article/pii/S014098832500307X)). Constant Elasticity of Substitution (CES) functions cannot be modelled out of the box. + +## Can investments be modelled for different years over multiple decades? + +Yes, see [Pathway Planning](../user-guide/optimization/pathway-planning.md) as +well as [Zeyen et al. (2023)](https://www.nature.com/articles/s41467-023-39397-2). Just expect some hit to solution speed, as the +model size grows with the number of investment periods. + +## Can technological learning be modelled? + +No, not yet out of the box. But it is possible to model technological learning with a piecewise linear approximation of the learning curve using SOS2 constraints, as shown in [Zeyen et al. (2023)](https://www.nature.com/articles/s41467-023-39397-2). This requires some customisation of the model. + +## Can you do Generation Adequacy Studies with Monte Carlo unplanned outages? + +No, this functionality is not offered directly in PyPSA, but it can be built in +an outer loop around PyPSA. + +## Does PyPSA have a GUI? + +There is currently no desktop application where you can build a model from scratch without programming in Python. The usual mode of interaction with PyPSA is via Python scripts or Jupyter Notebooks, where you enter code to build and inspect the model and plot inputs and outputs. There are also [online scenario generators](https://model.energy/scenarios/) for specific applications, where you can enter inputs and start simulations. + +## How easy is it to add custom constraints to PyPSA? + +PyPSA uses `linopy` in the background. Using its syntax, you can add custom +objectives, variables and constraints to the optimisation (see [Custom +Constraints](../user-guide/optimization/custom-constraints.md)). + +## Can you model market clearing in PyPSA? + +Yes, if you take care with demand side. The [`Load`][pypsa.components.Loads] component represents +perfectly inelastic demand. Use [`Generator`][pypsa.components.Generators] component with negative sign +attribute to represent elastic demand, and pair [`Load`][pypsa.components.Loads] components with [`Store`][pypsa.components.Stores] +and [`StorageUnit`][pypsa.components.StorageUnits] components for modelling shiftable loads. + +## Does PyPSA do ancillary service co-optimization (e.g. frequency control)? + +Not directly. It requires some customisation depending on what you want, but +there is an example in the documentation. + +## Can you model intra-day as well as day-ahead markets? + +Currently, this is not possible without customisation. + +## Is storage capacity optimized endogenously? + +Yes, see [Storage User Guide](../user-guide/optimization/storage.md). + +## Does PyPSA offer stochastic optimisation? + +Yes, a two-stage stochastic programming framework is available since Version `v1.0` +(see [Stochastic Optimisation](../user-guide/optimization/stochastic.md)). + +## How does PyPSA model grid load flow physics? + +PyPSA models the grid load flow physics with a standard linear DC power flow +approximation, using a cycle-based formulation for computational efficiency (see +[Power Flow](../user-guide/optimization/power-flow.md)). An optional +piecwise-linear loss approximation is also available (see [Neumann et al. +(2022)](https://www.sciencedirect.com/science/article/pii/S0306261922002938)). + +## Can PyPSA model AC power flow? + +Yes and no. PyPSA can only consider linear power flow constraints in +optimisation problems with `n.optimize()`. However, it is possible to use PyPSA +to compute AC power flow solutions after the optimisation has been solved, using +the `n.pf()` function. This is useful for post-processing and analysis of the +results, but it does not change the optimisation problem itself. + +## How are N-1 and line outages handled? + +An implementation of security constrained LOPF is offered (see +[Contingencies](../user-guide/optimization/contingencies.md)). A simplified +option would be to limit line loading to 70% of the thermal limit (see +[Gazafroudi et al. (2022)](https://doi.org/10.1016/j.ijepes.2021.107702)). + +## Can transmission line projects be endogenously selected? + +The general mode is continuous transmission expansion on existing lines. Changes +in impedance can be considered by an iterative approach with +`n.optimize.optimize_transmission_expansion_iteratively()`. This function also +allows for discretisation procedures in between iterations. PyPSA also supports +Limits](../user-guide/optimization/capacity-limits.md)). However, this option +does not yet consider changes in impedance endogenously. + + + +## Is electric vehicle charging and V2G handled endogenously? + +Yes, it can be modelled with a combination of loads and storage. + +## How do I model a concentrating solar plant? + +See, for instance, [Hampp (2024)](https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0292892). + +## How do I model retrofitting of coal plants with CCS? + +See, for instance, [Zhou et al. (2024)](https://ietresearch.onlinelibrary.wiley.com/doi/full/10.1049/ein2.12011). + +## How long does it take a PyPSA model to run? + +Depends on the size and complexity of the model, but typically from a few seconds to several hours. + +## How much computing resource do I need to run a PyPSA model? + +Depends on the size and complexity of the model, but typically a modern laptop +or desktop computer is sufficient for small to medium-sized models. For larger +models, a server with more RAM and CPU cores may be required. + +## Do I need to buy a commercial solver? + +No, but it helps for very large problems with high spatial and temporal +resolution, cross-sectoral scope, and multiple investment periods. PyPSA is +CPLEX are recommended for better performance or allow finding any solutions at +all. + +## How long does it take to learn PyPSA? + +This depends on your prior experience and the depth of knowledge you want to +achieve. If you are already familiar with Python and pandas, you can get started +with PyPSA in a few hours to a few days. If you are new to Python, it may take a +week or two to become comfortable with the basics. For more advanced usage, such +as customising the model or adding new components, it may take several weeks of +practice and learning. + +## Can I get support for using PyPSA? + +PyPSA has an active community of users and developers on Discord and elsewhere +who can help you with questions and issues (see +[Support](../user-guide/support.md)). Also, various consultancies offer paid +support, such as [OET](https://www.openenergytransition.org), +[d-fine](https://www.d-fine.com/en/), +[Energynautics](https://energynautics.com/en/), and +[CLIMACT](https://www.climact.com/). + +## Can you guarantee PyPSA will be developed further in the future? + +We cannot guarantee anything, but there is a lively community of developers, +several of whom have permanent contracts, and there is funding guaranteed until +2027 for a research software engineer. + +## Is there an automatic conversion from PLEXOS to PyPSA? + +Not yet. PyPSA relies on tabular data inputs, so if you can convert your data +into a pre-defined Excel or CSV format, you can read it into PyPSA. + +## Is there an automatic conversion from MATPOWER to PyPSA? + +No, but there is an importer from PYPOWER (the Python implementation of +MATPOWER). It is called `n.import_from_pypower_ppc()`. PyPSA relies on tabular +data inputs, so if you can convert your data into a pre-defined Excel or CSV +format, you can read it into PyPSA. + +## Is there an automatic conversion from pandapower to PyPSA? + +Yes, but not all components are supported yet (e.g. three-winding transformers). +The importer is called `n.import_from_pandapower_net()`. PyPSA relies on tabular +data inputs, so if you can convert your data into a pre-defined Excel or CSV +format, you can read it into PyPSA. + +## Can I do nodal pricing / LMP calculations? + +Yes. Dual values (shadow prices) of the nodal balance constraints can be used to infer nodal prices. These are available in `n.buses_t.marginal_price`. + +## Can PyPSA model sub-hourly time resolutions? + +Yes, PyPSA can handle sub-hourly resolution (e.g. 15-minute timesteps). Just +define them in `n.snapshots` and adjust the `n.snapshot_weightings` accordingly. + +## Can I model unit commitment in PyPSA? + +Yes, PyPSA can model ramping constraints, minimum up and down times, and other +unit commitment constraints for generators and links as a MILP and with an +LP-relaxation (see [Unit +Commitment](../user-guide/optimization/unit-commitment.md)). + +## Can PyPSA model district heating networks? + +Yes, at a high level of abstraction (e.g. via transport links with losses and +centralized heating technologies). Detailed hydraulic or temperature-dependent +physics are not represented. + +## Can I model endogenous fuel prices in PyPSA? + +Yes, the market clearing prices returned in `n.buses_t.marginal_price` also work +for non-electric buses, and depend on the supply and consumption options at that +bus (e.g. fossil gas, biogas upgrading, synthetic methane with different costs +and volume restrictions). diff --git a/PyPSA/source/docs/user-guide/import-export.md b/PyPSA/source/docs/user-guide/import-export.md new file mode 100644 index 0000000000000000000000000000000000000000..1f98151d2c1fb4acdf96c0e163c763da6a827aef --- /dev/null +++ b/PyPSA/source/docs/user-guide/import-export.md @@ -0,0 +1,169 @@ + + +# Import and Export + +PyPSA can handle several different data formats, such as CSV, netCDF and HDF5 +files. It is also possible to build a [`pypsa.Network`][] within a Python +script. There is limited functionality to import from Pypower and Pandapower. +Files can be imported from and exported to local files or cloud storage +providers. + +## Adding, Removing & Merging + +Networks can be built step-by-step for each component by calling +[`n.add()`][pypsa.network.transform.NetworkTransformMixin.remove] to **add** a single or multiple components. + +``` py +n.add("Bus", "my_bus") +n.add("Generator", "gen_1", bus="my_bus", p_nom=100, marginal_cost=100) +``` + +``` py +n.add("Load", ["load_1", "load_2"], bus="my_bus", p_set=[10, 20]) +``` + +Components can be **removed** with [`n.remove()`][pypsa.network.transform.NetworkTransformMixin.add]. + +``` py +n.remove("Load", ["load_1", "load_2"]) +n.remove("Generator", "my_generator") +``` + +Two networks with a disjunct set of component indices can be **merged** with [`n1.merge(n2)`][pypsa.network.transform.NetworkTransformMixin.merge] + +## CSV Files + +Create a folder with CSVs for each component type (e.g. `generators.csv`), then a CSV for each time-dependent variable (e.g. +`generators-p_max_pu.csv`). Then run +[`n.import_from_csv_folder()`][pypsa.network.io.NetworkIOMixin.import_from_csv_folder] to **import** the network or simply provide the path in the [`pypsa.Network`][] constructor. + +!!! note + + It is not necessary to add every single column, only those where values differ from the defaults listed in [Components](../user-guide/components.md). All empty values/columns are filled with the defaults. + +A network can be **exported** as a folder of csv files with [`n.export_to_csv_folder()`][pypsa.network.io.NetworkIOMixin.export_to_csv_folder]. + +``` py +n.export_to_csv_folder("foo/bar") +n_import = pypsa.Network("foo/bar") +``` + +## Excel + +To **import** a network from an Excel file, run [`n.import_from_excel()`][pypsa.network.io.NetworkIOMixin.import_from_excel] or simply provide the path in the [`pypsa.Network`][] constructor. To **export** a network to an Excel file, run [`n.export_to_excel()`][pypsa.network.io.NetworkIOMixin.export_to_excel]. Before using this import functionality, ensure that you install `pypsa[excel]` in your environment using `pip install pypsa[excel]`. + +``` py +n.export_to_excel("foo/bar.xlsx") +n_import = pypsa.Network("foo/bar.xlsx") +``` + +To **create** an Excel file compatible with PyPSA, structure the file with separate worksheets for each component type and their associated time-series data. + +For static component data, name each worksheet using the exact component name as it appears in PyPSA (e.g., `generators`, `buses`, `lines`, `storage_units`). For time-series data, use the format `-` (e.g., `generators-p_max_pu`, `loads-p_set`, `storage_units-inflow`). Component and attribute names are case-sensitive and must match PyPSA conventions. + +The I/O functionality will automatically ignore any worksheets that do not follow this naming pattern, allowing you to include documentation, notes, or auxiliary data in separate tabs without interfering with the import/export process. + +The snapshots worksheet must contain the time-series index using an appropriate datetime format. Other time-series worksheets should have the same number of rows, with the index column either numbered sequentially (e.g., 1-8760 for hourly annual data) or using the same datetime format as the snapshots. + +!!! note + + 1. To maintain data integrity and avoid conflicts, it is recommended to use the data validation feature in Excel: define your buses in the `Bus` worksheet and apply data validation to bus reference columns in component worksheets (e.g., `bus0`, `bus1` fields in the `Line` worksheet) and similarly apply validation for carriers and time-series dataset references where applicable. + + 2. It is not necessary to add every single column, only those where values differ from the defaults. All empty values and columns are filled with the defaults. + +!!! warning + + Excel is resource-intensive and only appropriate for smaller networks. For larger datasets or production workflows, consider using netCDF files. + + +## netCDF + +netCDF files take up less space than CSV files and are faster to load. + +To **export** network and components to a netCDF file run [`n.export_to_netcdf()`][pypsa.network.io.NetworkIOMixin.export_to_netcdf]. + +To **import** network data from netCDF file run [`n.import_from_netcdf()`][pypsa.network.io.NetworkIOMixin.import_from_netcdf] or simply provide the path in the [`pypsa.Network`][] constructor. + +!!! note + + netCDF is preferred over HDF5 because netCDF is structured more + cleanly, is easier to use from other programming languages, can limit + float precision to save space and supports lazy loading. + +``` py +n.export_to_netcdf("foo/bar.nc") +n_import = pypsa.Network("foo/bar.nc") +``` + +## HDF5 + +To **export** the network to an HDF store, run [`n.export_to_hdf5()`][pypsa.network.io.NetworkIOMixin.export_to_hdf5]. + +To **import** network data from an HDF5 store, run [`n.import_from_hdf5()`][pypsa.network.io.NetworkIOMixin.import_from_hdf5] or simply provide the path in the [`pypsa.Network`][] constructor. + +``` py +n.export_to_hdf5("foo/bar.h5") +n_import = pypsa.Network("foo/bar.h5") +``` + +## PYPOWER + +To **import** a network from the [PYPOWER](https://github.com/rwl/PYPOWER) ppc dictionary/`numpy.array` format +version 2, run the function [`n.import_from_pypower_ppc()`][pypsa.network.io.NetworkIOMixin.import_from_pypower_ppc]. + +**Exporting** to PYPOWER is not currently supported. + +``` py +from pypower.api import case30 +ppc = case30() +n.import_from_pypower_ppc(ppc) +``` + +## Pandapower + +To **import** a network from [pandapower](http://www.pandapower.org/), run the function [`n.import_from_pandapower_net()`][pypsa.network.io.NetworkIOMixin.import_from_pandapower_net]. + +**Exporting** to [pandapower](http://www.pandapower.org/) is not currently supported. + +!!! warning + + Not all pandapower data fields are supported. For instance, + three-winding transformers, switches, `in_service` status and tap positions + of transformers. + +``` py +import pandapower.networks as pn +net = pn.create_cigre_network_mv(with_der='all') +n = pypsa.Network() +n.import_from_pandapower_net(net, extra_line_data=True) +``` + +## Cloud Object Storage + +CSV, netCDF and HDF5 files in cloud object storage can be imported and exported +by installing the [`cloudpathlib`](https://cloudpathlib.drivendata.org/stable/) +package. This is available through the `[cloudpath]` optional dependency, +installable via `pip install 'pypsa[cloudpath]'`. + +`cloudpathlib` supports [AWS S3](https://aws.amazon.com/s3/) (`s3://`), [Google +Cloud Storage](https://cloud.google.com/storage) (`gs://`) and [Azure Blob +Storage](https://azure.microsoft.com/en-us/products/storage/blobs) (`az://`) as +cloud object storage providers. Users will need to additionally install the +corresponding cloud storage provider client library to interface with the cloud +storage provider via `cloudpathlib` (e.g. `boto3`, `google-cloud-storage` or +`azure-storage-blob`). + +``` py +from pypsa import Network +n = Network('examples/ac-dc-meshed/ac-dc-data') +n.export_to_csv_folder('s3://my-s3-bucket/ac-dc-data') +n = Network('s3://my-s3-bucket/ac-dc-data') +n.export_to_netcdf('gs://my-gs-bucket/ac-dc-data.nc') +n = Network('gs://my-gs-bucket/ac-dc-data.nc') +n.export_to_excel('az://my-az-bucket/ac-dc-data.xlsx') +n = Network('az://my-az-bucket/ac-dc-data.xlsx') +``` diff --git a/PyPSA/source/docs/user-guide/learning-materials.md b/PyPSA/source/docs/user-guide/learning-materials.md new file mode 100644 index 0000000000000000000000000000000000000000..757031d384821aacccd483dc8c01c367317e6d8c --- /dev/null +++ b/PyPSA/source/docs/user-guide/learning-materials.md @@ -0,0 +1,17 @@ + + +This page lists a set of resources for learning about energy system modelling with and without PyPSA. + +- **[Energy Systems](https://nworbmot.org/courses/es-24/)** course by [Tom Brown](https://nworbmot.org), TU Berlin. +- **[Energy Economics](https://nworbmot.org/courses/ee-24/)** course by [Tom Brown](https://nworbmot.org), TU Berlin. +- **[Data Science for Energy System Modelling](https://fneum.github.io/data-science-for-esm/)** course by [Fabian Neumann](https://fneum.org), TU Berlin. +- **[Integrated Energy Grids](https://github.com/martavp/integrated-energy-grids)** course by [Marta Victoria](https://www.martavictoria.org/), DTU. +- **[PyPSA(-Eur) Workshop Series](https://resilient-project.github.io/pypsa-workshop-202506)** by the [RESILIENT](https://resilient-project.github.io/) project. + +!!! question + + You have your own learning resources to share? Please let us know and we will add them to this list! diff --git a/PyPSA/source/docs/user-guide/linear-power-flow.md b/PyPSA/source/docs/user-guide/linear-power-flow.md new file mode 100644 index 0000000000000000000000000000000000000000..b5edd04f87b31cebeca25dc3abbc3e8dce975834 --- /dev/null +++ b/PyPSA/source/docs/user-guide/linear-power-flow.md @@ -0,0 +1,98 @@ + + +# Linear Power Flow + +The linear power flow [`n.lpf()`][pypsa.Network.lpf] is the linearised equivalent of [`n.pf()`][pypsa.Network.pf]. + +## AC networks + +For AC networks, it is assumed for the linear power flow that reactive power decouples, there are no voltage magnitude variations, voltage angles differences across branches are small and branch resistances are much smaller than branch reactances. + +For AC networks, the linear power flow is calculated using small voltage angle differences and the series reactances alone. + +It is assumed that the active powers $P_i$ are given for all buses except the slack bus and the task is to find the voltage angles $\theta_i$ at all buses except the slack bus, where it is assumed $\theta_0 = 0$. + +To find the voltage angles, the following linear set of equations are solved + +$$P_i = \sum_j (KBK^T)_{ij} \theta_j - \sum_l K_{il} b_l \theta_l^{\textrm{shift}}$$ + +where $K$ is the incidence matrix of the network, $B$ is the diagonal matrix of inverse branch series reactances $x_l$ multiplied by the tap ratio $\tau_l$, i.e. $B_{ll} = b_l = \frac{1}{x_l\tau_l}$ and $\theta_l^{\textrm{shift}}$ is the phase shift for a transformer. The matrix $KBK^T$ is singular with a single zero eigenvalue for a connected network, therefore the row and column corresponding to the slack bus is deleted before inverting. + +The flows `p0` in the network branches at `bus0` can then be found by multiplying by the transpose incidence matrix and inverse series reactances: + +$$F_l = \sum_i (BK^T)_{li} \theta_i - b_l \theta_l^{\textrm{shift}}$$ + +## DC networks + +For DC networks, it is assumed for the linear power flow that voltage magnitude differences across branches are all small. + +For DC networks, the linear load flow is calculated using small voltage magnitude differences and series resistances alone. + +The linear load flow for DC networks follows the same calculation as for AC networks, but replacing the voltage angles by the difference in voltage magnitude $\delta V_{n,t}$ and the series reactance by the series resistance $r_l$. + + +## Linear Power Flow Contingency Analysis + +The function [n.lpf_contingency()][pypsa.Network.lpf_contingency] computes a +base case linear power flow (LPF) with no outages, and then cycles through the +list of branches in `branch_outages` and computes the line flows after the +outage of that branch using the branch outage distribution factor (BODF, see []()). + + +## Inputs + +For the linear power flow, the following data for each component are used. For the defaults and units, see . + +- `n.buses.{v_nom}` +- `n.loads.{p_set}` +- `n.generators.{p_set}` +- `n.storage_units.{p_set}` +- `n.stores.{p_set}` +- `n.shunt_impedances.{g}` +- `n.lines.{x}` +- `n.transformers.{x}` +- `n.links.{p_set}` + +!!! note + + Note that for lines and transformers you must make sure that $x$ is non-zero, otherwise the bus admittance matrix will be singular. + +## Outputs + +- `n.buses.{v_mag_pu, v_ang, p}` +- `n.loads.{p}` +- `n.generators.{p}` +- `n.storage_units.{p}` +- `n.stores.{p}` +- `n.shunt_impedances.{p}` +- `n.lines.{p0, p1}` +- `n.transformers.{p0, p1}` +- `n.links.{p0, p1}` + + +## Examples + +
+ + +- :material-notebook:{ .lg .middle } **Newton-Raphson Power Flow** + + --- + + Solves non-linear AC power flow equations using the Newton-Raphson method to inspect voltage magnitudes and angles. + + [:octicons-arrow-right-24: Go to example](../examples/minimal-example-pf.ipynb) + +- :material-notebook:{ .lg .middle } **SciGRID Network** + + --- + + Performs linear optimal power flow on a high-resolution German grid model to analyze power flows and nodal prices. + + [:octicons-arrow-right-24: Go to example](../examples/scigrid-lopf-then-pf.ipynb) + +
diff --git a/PyPSA/source/docs/user-guide/logging.ipynb b/PyPSA/source/docs/user-guide/logging.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..5597f24f33babab87752965b490d78b75ffdf655 --- /dev/null +++ b/PyPSA/source/docs/user-guide/logging.ipynb @@ -0,0 +1,195 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Logging\n", + "\n", + "PyPSA uses the Python standard library [logging](https://docs.python.org/3/library/logging.html) module to provide informative messages during execution.\n", + "\n", + "You can set the logging level to different values like **ERROR**, **WARNING**, **INFO**, or **DEBUG**. This can be configured independently for different modules.\n", + "\n", + "We'll start by setting the basic logging level to **INFO** to see informational messages." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "\n", + "import pypsa\n", + "\n", + "logging.basicConfig(level=logging.INFO)\n", + "\n", + "network = pypsa.examples.ac_dc_meshed()\n", + "\n", + "out = network.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's turn on **INFO** level messages specifically for the optimization module to see detailed optimization progress." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pypsa.optimization.optimize.logger.setLevel(logging.INFO)\n", + "\n", + "out = network.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's reduce verbosity by setting the optimization module to only show **WARNING** level messages." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pypsa.optimization.optimize.logger.setLevel(logging.WARNING)\n", + "\n", + "out = network.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's turn on **DEBUG** level messages for the power flow module to see the most detailed output." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pypsa.network.power_flow.logger.setLevel(logging.DEBUG)\n", + "\n", + "out = network.lpf()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, let's turn off messages for the power flow module by setting it back to **ERROR** level." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pypsa.network.power_flow.logger.setLevel(logging.ERROR)\n", + "\n", + "out = network.lpf()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Python Warnings versus Logging\n", + "\n", + "PyPSA also uses Python's [warnings](https://docs.python.org/3/library/warnings.html) next to the logging module:\n", + "\n", + "- **logging**: For tracking application behavior - records events and provides debugging information\n", + "- **Python Warnings**: For developers/users - alerts about deprecated features or potential issues\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "warnings.filterwarnings(\"default\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Access a deprecated feature to see a warning\n", + "network.components.generators.component_names" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n", + "network.components.generators.component_names" + ] + } + ], + "metadata": { + "@webio": { + "lastCommId": null, + "lastKernelId": null + }, + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/PyPSA/source/docs/user-guide/network-optimization.md b/PyPSA/source/docs/user-guide/network-optimization.md new file mode 100644 index 0000000000000000000000000000000000000000..baf4cbba2758df22d72ac03dd5b4b31c8479dabc --- /dev/null +++ b/PyPSA/source/docs/user-guide/network-optimization.md @@ -0,0 +1,303 @@ + + +# Optimization + +!!! info "See Also" + + Please also refer the [optimization section](optimization/overview.md) above, which offers more information on the mathematical formulations PyPSA uses when running [`n.optimize()`][pypsa.optimization.OptimizationAccessor.__call__]. + +PyPSA uses [Linopy](https://linopy.readthedocs.io/en/latest/) as the optimization backend. Linopy is a stand-alone package and works similar to [Pyomo](https://www.pyomo.org/), but without the memory overhead and much faster. However, it has a reduced set of optimization problem types it can support (LP, MILP, QP). The core optimization functions can be called with [`n.optimize()`][pypsa.optimization.OptimizationAccessor.__call__]. This call unifies creating the problem, solving it and retrieving the optimized results. +The accessor [n.optimize][pypsa.optimization.OptimizationAccessor] additionally offers a range of different functionalities. + +## Initialize Network + +Initially, let us consider one of the canonical PyPSA examples and solve it with [`n.optimize()`][pypsa.optimization.OptimizationAccessor.__call__]. + +``` py +>>> import pypsa +>>> n = pypsa.examples.ac_dc_meshed() +``` + +In order to make the network a bit more interesting, we modify its data by setting gas generators to be non-extendable, + +``` py +>>> n.generators.loc[n.generators.carrier == "gas", "p_nom_extendable"] = False +``` + +... adding ramp limits, + +``` py +>>> n.generators.loc[n.generators.carrier == "gas", "ramp_limit_down"] = 0.2 +>>> n.generators.loc[n.generators.carrier == "gas", "ramp_limit_up"] = 0.2 +``` + +... adding additional storage units (cyclic and non-cyclic) and fixing the state of charge of one storage unit, + +``` py +>>> n.add( +... "StorageUnit", +... "su", +... bus="Manchester", +... marginal_cost=10, +... inflow=50, +... p_nom_extendable=True, +... capital_cost=10, +... p_nom=2000, +... efficiency_dispatch=0.5, +... cyclic_state_of_charge=True, +... state_of_charge_initial=1000, +... ) +>>> n.add( +... "StorageUnit", +... "su2", +... bus="Manchester", +... marginal_cost=10, +... p_nom_extendable=True, +... capital_cost=50, +... p_nom=2000, +... efficiency_dispatch=0.5, +... carrier="gas", +... cyclic_state_of_charge=False, +... state_of_charge_initial=1000, +... ) +>>> n.storage_units_t.state_of_charge_set.loc[n.snapshots[7], "su"] = 100 +``` + +...and adding an additional store. + +``` py +>>> n.add("Bus", "storebus", carrier="hydro", x=-5, y=55) +>>> n.add( +... "Link", +... ["battery_power", "battery_discharge"], +... "", +... bus0=["Manchester", "storebus"], +... bus1=["storebus", "Manchester"], +... p_nom=100, +... efficiency=0.9, +... p_nom_extendable=True, +... p_nom_max=1000, +... ) +>>> n.add( +... "Store", +... ["store"], +... bus="storebus", +... e_nom=2000, +... e_nom_extendable=True, +... marginal_cost=10, +... capital_cost=10, +... e_nom_max=5000, +... e_initial=100, +... e_cyclic=True, +... ) +``` + +## Run Optimization + +Now, let's solve the network. + +``` py +>>> n.optimize() +('ok', 'optimal') +``` + +We now have a model instance attached to our network object. It is a container of all variables, constraints and the objective function. It can be modified by directly adding or deleting variables or constraints or changing the objective function. + +``` py +>>> n.model # doctest: +ELLIPSIS +Linopy LP model +=============== + +Variables: +---------- + * Generator-p_nom (name) + * ... + +Constraints: +------------ + * Generator-ext-p_nom-lower (name) + * ... + +Status: +------- +ok +``` + +Results are written to the network components' data fields, for instance: + +``` py +n.generators_t.p +n.stores.e_nom_opt +n.buses_t.marginal_price +``` + +## Modify Model & Re-Optimize + +The function call to [`n.optimize()`][pypsa.optimization.OptimizationAccessor.__call__] already solves the model directly after creating it. +To create the model instance without solving it yet, you can use the following command: + +``` py +>>> n.optimize.create_model() # doctest: +ELLIPSIS +Linopy LP model +=============== + +Variables: +---------- +... + +Constraints: +------------ +... + +Status: +------- +initialized +``` + +With the access to the model instance we gain a lot of flexibility. Let's say, for example, we want to remove the Kirchhoff Voltage Law constraint, thus converting the model to a transport model. This can be done via + +``` py +>>> n.model.constraints.remove("Kirchhoff-Voltage-Law") +``` + +Now, we can solve the altered model and write the solution back to the network. Here again, we use the [`n.optimize`][pypsa.optimization.OptimizationAccessor] accessor: + +``` py +>>> n.optimize.solve_model() +('ok', 'optimal') +``` + +Here, we followed the recommended way to create and solve models with custom alterations: + +1. **Create the model instance** - `n.optimize.create_model()` +2. **Modify the model to your needs** - modify `n.model` +3. **Solve and write back solution** - `n.optimize.solve_model()` + +It is also possible to pass modifications as an `extra_functionality` argument to [`n.optimize()`][pypsa.optimization.OptimizationAccessor.__call__]: + +``` py +import pandas as pd + +def remove_kvl(n: pypsa.Network, sns: pd.Index) -> None: + n.model.constraints.remove("Kirchhoff-Voltage-Law") + +n.optimize(extra_functionality=remove_kvl) +``` + +## Custom Constraints + +!!! info "See Also" + + Please also refer to the user guide section on [custom constraints](optimization/custom-constraints.md). + +In the following, we present a selection of examples for additional constraints. Note, the dual values of the additional constraints will not be stored in in the network object `n`, but just under `n.model` which is accessible only in the current session. + +Again, we **first build** the optimization model, then **add our constraints** and finally **solve the network**: + +``` py +>>> m = n.optimize.create_model() # the return value is the model, let's use it directly! +``` + +### Minimum for state of charge + +Assume we want to set a minimum state of charge of 50 MWh for our storage unit. This is done by: + +``` py +>>> sus = m.variables["StorageUnit-state_of_charge"] +>>> m.add_constraints(sus >= 50, name="StorageUnit-minimum_soc") # doctest: +ELLIPSIS +Constraint `StorageUnit-minimum_soc` [snapshot: ..., name: ...]: +------------------------------------------------------------- +... +``` + +The return value of the `m.add_constraints()` function is an array containing constraint labels, which can be accessed through `m.constraints`. + +``` py +>>> m.constraints["StorageUnit-minimum_soc"] # doctest: +SKIP +``` + +and inspected via its attributes like `lhs`, `sign` and `rhs`, e.g. + +``` py +>>> m.constraints["StorageUnit-minimum_soc"].rhs # doctest: +SKIP +``` + +### Fix the ratio between incoming and outgoing capacity of the `Store` + +The battery in our system is modelled with two links and a store. We should make sure that its charging and discharging capacities, i.e. the links representing the inverter, are coupled. + +``` py +>>> capacity = m.variables["Link-p_nom"] +>>> eff = n.links.at["battery_power", "efficiency"] +>>> lhs = capacity.loc["battery_power"] - eff * capacity.loc["battery_discharge"] +>>> m.add_constraints(lhs == 0, name="Link-battery_fix_ratio") # doctest: +SKIP +``` + +### Every bus must in total produce the 20% of the total demand + +For this, we use the `linopy` function `groupby_sum` which follows the pattern from `pandas` or `xarray` `groupby` functions. + +``` py +>>> total_demand = n.loads_t.p_set.sum().sum() +>>> buses = n.generators.bus.to_xarray() +>>> prod_per_bus = m.variables["Generator-p"].groupby(buses).sum().sum("snapshot") +>>> m.add_constraints(prod_per_bus >= total_demand / 5, name="Bus-minimum_production_share") # doctest: +SKIP +``` + +``` py +>>> con = prod_per_bus >= total_demand / 5 +>>> con # doctest: +SKIP +``` + +Now, let's solve the network again: + +``` py +>>> n.optimize.solve_model() +('ok', 'optimal') +``` + +## Analysing Constraints + +Let's see if the optimized system adheres to our custom constraints. Let's first look at `n.constraints` which summarises the constraints of the model: + +``` py +>>> n.model.constraints # doctest: +SKIP +``` + +The last three entries show our constraints. Let's check whether our two custom constraints are fulfilled: + +``` py +>>> n.links.loc[["battery_power", "battery_discharge"], ["p_nom_opt"]] # doctest: +SKIP +``` + +``` py +>>> n.storage_units_t.state_of_charge # doctest: +SKIP +``` + +``` py +>>> n.generators_t.p.T.groupby(n.generators.bus).sum().sum() / n.loads_t.p.sum().sum() # doctest: +SKIP +``` + +Looks good! Now, let's see which dual values were parsed. For that, we have a look into `n.model.dual`: + +``` py +>>> n.model.dual # doctest: +SKIP +``` + +``` py +>>> n.model.dual["StorageUnit-minimum_soc"] # doctest: +SKIP +``` + +``` py +>>> n.model.dual["Link-battery_fix_ratio"] # doctest: +SKIP +``` + +``` py +>>> n.model.dual["Bus-minimum_production_share"] # doctest: +SKIP +``` + +These are the basic functionalities of the [`n.optimize`][pypsa.optimization.OptimizationAccessor] accessor. There are many more functions like extended problem formulations for security constraint optimization, iterative transmission expansion optimization, and rolling-horizon optimization alongside several helper functions (fixing optimized capacities, adding load shedding). Check them out in the [:octicons-code-16: API reference](../api/networks/optimize.md). diff --git a/PyPSA/source/docs/user-guide/optimization/capacity-limits.md b/PyPSA/source/docs/user-guide/optimization/capacity-limits.md new file mode 100644 index 0000000000000000000000000000000000000000..008387d3b94a6ba22e6385d5244c75b122a34c5d --- /dev/null +++ b/PyPSA/source/docs/user-guide/optimization/capacity-limits.md @@ -0,0 +1,147 @@ + + +# Capacity Limits + +## Upper and Lower Bounds + +If the nominal capacity of a components is also the subject of optimisation (e.g. with decision variable $G_{n,s}$ for generators), limits on the installable capacity may also be introduced (e.g. $\underline{G}_{n,s}$ and $\bar{G}_{n,s}$): + +| Constraint | Dual Variable | Name | +|-------------------|------------------|------------------| +| $G_{n,s} \geq \underline{G}_{n,s}$ | `n.generators.mu_lower` | `Generator-ext-p_nom-lower` | +| $G_{n,s} \leq \bar{G}_{n,s}$ | `n.generators.mu_upper` | `Generator-ext-p_nom-upper` | +| $F_{l} \geq \underline{F}_{l}$ | `n.links.mu_lower` | `Link-ext-p_nom-lower` | +| $F_{l} \leq \bar{F}_{l}$ | `n.links.mu_upper` | `Link-ext-p_nom-upper` | +| $P_{l} \geq \underline{P}_{l}$ | `n.{lines,transformers}.mu_lower` | `{Line,Transformer}-ext-s_nom-lower` | +| $P_{l} \leq \bar{P}_{l}$ | `n.{lines, transformers}.mu_upper` | `{Line,Transformer}-ext-s_nom-upper` | +| $E_{n,s} \geq \underline{E}_{n,s}$ | `n.stores.mu_lower` | `Store-ext-e_nom-lower` | +| $E_{n,s} \leq \bar{E}_{n,s}$ | `n.stores.mu_upper` | `Store-ext-e_nom-upper` | +| $H_{n,s} \geq \underline{H}_{n,s}$ | `n.storage_units.mu_lower` | `StorageUnit-ext-p_nom-lower` | + +These constraints are set in the function `define_nominal_constraints_for_extendables`. + +## Modularity Constraints + +The capacity expansion can be further constrained to be a multiple (e.g. $G^{\textrm{mod}}_{n,s} \in \mathbb{N}$) of a modular capacity (e.g. $\tilde{G}_{n,s}$) to represent fixed block sizes of added components (e.g. fixed block size of a nuclear power plant or a fixed capacity of a new circuit). + +If `{p,s,e}_nom_mod>0`, the nominal capacity is given by: + +| Constraint | Dual Variable | Name | +|-------------------|------------------|------------------| +| $G_{n,s} = G^{\textrm{mod}}_{n,s} \cdot \tilde{G}_{n,s}$ | N/A | `Generator-p_nom_modularity` | +| $F_{l} = F^{\textrm{mod}}_{l} \cdot \tilde{F}_{l}$ | N/A | `Link-p_nom_modularity` | +| $P_{l} = P^{\textrm{mod}}_{l} \cdot \tilde{P}_{l}$ | N/A | `{Line,Transformer}-s_nom_modularity` | +| $E_{n,s} = E^{\textrm{mod}}_{n,s} \cdot \tilde{E}_{n,s}$ | N/A | `Store-e_nom_modularity` | +| $H_{n,s} = H^{\textrm{mod}}_{n,s} \cdot \tilde{H}_{n,s}$ | N/A | `StorageUnit-p_nom_modularity` | + +These constraints are set in the function `define_modular_constraints()`. + + +## Fixed Capacity + +Additionally, the nominal capacity can be fixed to a certain value $\tilde{G}_{n,s}$ for generators, $\tilde{F}_{l}$ for links, $\tilde{P}_{l}$ for lines and transformers, and $\tilde{E}_{n,s}$ for stores, and $\tilde{H}_{n,s}$ for storage units. In this case, the nominal capacity is given by: + +| Constraint | Dual Variable | Name | +|-------------------|------------------|------------------| +| $G_{n,s} = \tilde{G}_{n,s}$ | only in `n.model` | `Generator-p_nom_set` | +| $F_{l} = \tilde{F}_{l}$ | only in `n.model` | `Link-p_nom_set` | +| $P_{l} = \tilde{P}_{l}$ | only in `n.model` | `{Line,Transformer}-s_nom_set` | +| $E_{n,s} = \tilde{E}_{n,s}$ | only in `n.model` | `Store-e_nom_set` | +| $H_{n,s} = \tilde{H}_{n,s}$ | only in `n.model` | `StorageUnit-p_nom_set` | + +These constraints are set in the function `define_fixed_nominal_constraints()`. + +!!! note "Why not just set `p_nom_extendable=False`?" + + Using `p_nom_extendable=False` means the capacity is fixed and not optimized. However, sometimes we need to fix the capacity to a specific value while still keeping track of the dual variables associated with capacity constraints. Setting `{p,s,e}_nom_set` allows for this while maintaining `p_nom_extendable=True`. + + +??? note "Mapping of symbols to component attributes" + + === "Generator" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $G_{n,s}$ | `n.generators.p_nom_opt` | Decision variable | + | $G^{\textrm{mod}}_{n,s}$ | not stored | Decision variable | + | $\underline{G}_{n,s}$ | `n.generators.p_nom_min` | Parameter | + | $\bar{G}_{n,s}$ | `n.generators.p_nom_max` | Parameter | + | $\tilde{G}_{n,s}$ | `n.generators.p_nom_mod` | Parameter | + | $\hat{G}_{n,s}$ | `n.generators.p_nom_set` | Parameter | + + + === "Link" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $F_{l}$ | `n.links.p_nom_opt` | Decision variable | + | $F^{\textrm{mod}}_{l}$ | not stored | Decision variable | + | $\underline{F}_{l}$ | `n.links.p_nom_min` | Parameter | + | $\bar{F}_{l}$ | `n.links.p_nom_max` | Parameter | + | $\tilde{F}_{l}$ | `n.links.p_nom_mod` | Parameter | + | $\hat{F}_{l}$ | `n.links.p_nom_set` | Parameter | + + === "Line" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $P_{l}$ | `n.lines.s_nom_opt` | Decision variable | + | $P^{\textrm{mod}}_{l}$ | not stored | Decision variable | + | $\underline{P}_{l}$ | `n.lines.s_nom_min` | Parameter | + | $\bar{P}_{l}$ | `n.lines.s_nom_max` | Parameter | + | $\tilde{P}_{l}$ | `n.lines.s_nom_mod` | Parameter | + | $\hat{P}_{l}$ | `n.lines.s_nom_set` | Parameter | + + === "Transformer" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $P_{l}$ | `n.transformers.s_nom_opt` | Decision variable | + | $P^{\textrm{mod}}_{l}$ | not stored | Decision variable | + | $\underline{P}_{l}$ | `n.transformers.s_nom_min` | Parameter | + | $\bar{P}_{l}$ | `n.transformers.s_nom_max` | Parameter | + | $\tilde{P}_{l}$ | `n.transformers.s_nom_mod` | Parameter | + | $\hat{P}_{l}$ | `n.transformers.s_nom_set` | Parameter | + + === "Store" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $E_{n,s}$ | `n.stores.e_nom_opt` | Decision variable | + | $E^{\textrm{mod}}_{n,s}$ | not stored | Decision variable | + | $\underline{E}_{n,s}$ | `n.stores.e_nom_min` | Parameter | + | $\bar{E}_{n,s}$ | `n.stores.e_nom_max` | Parameter | + | $\tilde{E}_{n,s}$ | `n.stores.e_nom_mod` | Parameter | + | $\hat{E}_{n,s}$ | `n.stores.e_nom_set` | Parameter | + + === "Storage Unit" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $H_{n,s}$ | `n.storage_units.p_nom_opt` | Decision variable | + | $H^{\textrm{mod}}_{n,s}$ | not stored | Decision variable | + | $\underline{H}_{n,s}$ | `n.storage_units.p_nom_min` | Parameter | + | $\bar{H}_{n,s}$ | `n.storage_units.p_nom_max` | Parameter | + | $\tilde{H}_{n,s}$ | `n.storage_units.p_nom_mod` | Parameter | + | $\hat{H}_{n,s}$ | `n.storage_units.p_nom_set` | Parameter | + + +## Examples + + +
+ +- :material-notebook:{ .lg .middle } **Modular Capacity Expansion** + + --- + + Models discrete capacity additions with integer constraints on investment + decisions considering predefined unit sizes. + + [:octicons-arrow-right-24: Go to example](../../examples/modular-expansion.ipynb) + +
diff --git a/PyPSA/source/docs/user-guide/optimization/contingencies.md b/PyPSA/source/docs/user-guide/optimization/contingencies.md new file mode 100644 index 0000000000000000000000000000000000000000..e5e08368ad6df60bf627dd6ad668c3da084fd989 --- /dev/null +++ b/PyPSA/source/docs/user-guide/optimization/contingencies.md @@ -0,0 +1,131 @@ + + +# Contingencies + +Contingency analysis is concerned with the behaviour of the power system after +contingencies, such as the outage of particular branches (i.e. transformers and +lines). Only branch outages are considered here. + +!!! tip "Tip: Modelling of generator outages" + + Generator outages can be modelled through stochastic optimisation, by setting the attribute `p_max_pu` to zero for a scenario-dependent outage period. See for more details. + +## N-1 Security Constraints for SCLOPF + +The Security-Constrained Linear Optimal Power Flow (SCLOPF) builds on the Linear +Optimal Power Flow (LOPF) (part of [`n.optimize()`][pypsa.optimization.OptimizationAccessor.__call__]) by including +additional constraints that branches may not become overloaded after the outage +of a selection of branches. The implementation follows Ronellenfitsch et al. +(2017)[^1]. + +An optimisation with such $N-1$ security-constrained power flow constraints is +executed with +[`n.optimize.optimize_security_constrained()`][pypsa.optimization.OptimizationAccessor.optimize_security_constrained], +which ensures that the optimised system is robust against the outage of a +selection of branches. A selection of branches for which the outage is +considered can be made by passing the keyword argument `branch_outages` (e.g. +`branch_outages=n.lines[:10]` for considering outages of the first 10 lines). If +no selection is made, all branches are considered. + +The effect of an outage of a branch $c$ on the flows of all other branches $b$ +in a sub-network is captured by the Branch Outage Distribution Factor (BODF) +matrix, which is built from linear power flow assumptions. For the outage of +branch $c$, let $p_b$ be the flows before the outage and $p_b^{(c)}$ be the +flows after the outage. Then, the BODF is defined by: + +$$p_b^{(c)} = p_b + BODF_{bc}p_{c}$$ + +To ensure that the flows of all other branches $b$ do not exceed their +capacity $P_b$ after the outage of branch $c$, the following constraints are +added to the optimisation problem: + +$$|p_{b,t} + BODF_{bc}p_{c,t}| \leq |P_b| \quad \forall b,c,t$$ + +These constraints are added in the function [`n.optimize_security_constrained()`][pypsa.optimization.OptimizationAccessor.optimize_security_constrained]. + +!!! tip "Tip: Avoiding the computational burden of $N-1$ security constraints" + + Running security-constrained optimisation problems can be computationally expensive due to the product of lines, outages and snapshots. + Several approaches have been proposed to approximate the $N-1$ security constraints, + the simplest of which consists of reserving a security margin + from the branch capacity for contingencies (e.g. by setting `s_max_pu=0.7` to prevent line loading above 70% of the branches' rated capacity). + See, for example, the discussion in Gazafroudi et al. (2022)[^4]. + +??? note "Mapping of symbols to component attributes" + + The following table maps the symbols used in this section to the component attributes in PyPSA: + + | Symbol | Attribute | Type | + |--------|---------------------|------| + | $p_{\{b,c\},t}$ | `n.{lines,transformers}_t.p0` | Decision Variable | + | $P_b$ | For extendable components `n.{lines,transformers}.eval("s_max_pu * s_nom_opt")` | Decision Variable | + | $P_b$ | For non-extendable components`n.{lines,transformers}.eval("s_max_pu * s_nom")` | Parameter | + | $BODF_{bc}$ | Calculated by [`sn.calculate_BODF()`][pypsa.networks.SubNetwork.calculate_BODF] | Parameter | + +## Calculating Branch Outage Distribution Factors (BODF) + +The BODF is calculated by the function +[`sn.calculate_BODF()`][pypsa.SubNetwork.calculate_BODF]. +It is determined from the Power Transfer Distribution Factors (PTDF) matrix +and the incidence matrix $K$ of the network.[^2] [^3] + +The first step consists of building the branch $BPTDF$ +from the $PTDF$ and incidence matrix $K$ + +$$BPTDF_{bc} = \sum_{i} PTDF_{bi} K_{ic}$$ + +$BPTDF_{bc}$ gives the change in flow on branch $b$ if a +unit of power is injected at `bus0` of branch $c$ and +withdrawn from the `bus1` of branch $c$. If branch $b$ is +the only branch connecting two regions, then $BPTDF_{bb} = 1$, +since the power can only flow between the two ends of the branch +through the branch itself. + +The off-diagonal entries of the $BODF$ for $b \neq c$ are given by: + +$$BODF_{bc} = \frac{BPTDF_{bc}}{1-BPTDF_{cc}}$$ + +The diagonal entries of the $BODF$ are simply: + +$$BODF_{bb} = -1$$ + +!!! warning "Warning: Singular $BODF$ matrix" + + If $c$ is the only branch connecting two regions, so that the + regions become disconnected after the outage of $c$, then + $BPTDF_{cc} = 1$ and $BODF_{bc}$ becomes singular; this + case must be treated separately since, for example, each region will + need its own slack bus. + +!!! note "Note: BODF versus LODF matrix" + + The *Branch Outage Distribution Factor (BODF)* matrix is also called *Line Outage Distribution + Factor (LODF)* matrix in the literature, but in PyPSA both lines and transformers are included. + This is where the more general name stems from. + +## Examples + + +
+ +- :material-notebook:{ .lg .middle } **Security-Constrained LOPF** + + --- + + Implements N-1 security constraints in linear optimal power flow models to ensure grid reliability under line outage events. + + [:octicons-arrow-right-24: Go to example](../../examples/scigrid-sclopf.ipynb) + +
+ +[^1]: H. Ronellenfitsch, D. Manik, J. Hörsch, T. Brown and D. Witthaut, [Dual Theory of Transmission Line Outages](https://doi.org/10.1109/TPWRS.2017.2658022), in IEEE Transactions on Power Systems, vol. 32, no. 5, pp. 4060-4068, doi:10.1109/TPWRS.2017.2658022. + +[^2]: T. Guler, G. Gross and M. Liu (2007), [Generalized Line Outage Distribution Factors](https://doi.org/10.1109/TPWRS.2006.888950), in IEEE Transactions on Power Systems, vol. 22, no. 2, pp. 879-881, doi:10.1109/TPWRS.2006.888950. + +[^3]: J. Guo, Y. Fu, Z. Li and M. Shahidehpour (2009), [Direct Calculation of Line Outage Distribution Factors](https://doi.org/10.1109/TPWRS.2009.2023273), in IEEE Transactions on Power Systems, vol. 24, no. 3, pp. 1633-1634, doi:10.1109/TPWRS.2009.2023273. + +[^4]: A. S. Gazafroudi, F. Neumann, T. Brown, [Topology-based approximations for N−1 contingency constraints in power transmission networks](https://doi.org/10.1016/j.ijepes.2021.107702), International Journal of Electrical Power & Energy Systems, 137, 107702, doi:10.1016/j.ijepes.2021.107702. diff --git a/PyPSA/source/docs/user-guide/optimization/custom-constraints.md b/PyPSA/source/docs/user-guide/optimization/custom-constraints.md new file mode 100644 index 0000000000000000000000000000000000000000..0455d52071a42722dc41f99f9a27aaeb19484415 --- /dev/null +++ b/PyPSA/source/docs/user-guide/optimization/custom-constraints.md @@ -0,0 +1,149 @@ + + +# Custom Constraints + +Custom constraints allow users to tailor optimization problems to specific +requirements or scenarios. Users can model more complex limits and interactions +that are not captured by the default optimization formulations provided by +[`n.optimize()`][pypsa.optimization.OptimizationAccessor.__call__]. To build custom constraints, users can access, modify and +amend the [Linopy](https://linopy.readthedocs.io) model instance associated with +a network object, `n.model`. + +Some key functions used in the code for working with custom constraints include: + +* [`n.optimize.create_model()`][pypsa.optimization.OptimizationAccessor.create_model]: Creates Linopy model instance for the network, `n.model`. +* [`n.model.variables`](https://linopy.readthedocs.io/en/latest/generated/linopy.model.Model.html): Accesses the decision variables. +* [`n.model.add_variables()`](https://linopy.readthedocs.io/en/latest/creating-variables.html): Adds decision variables. +* [`n.model.add_constraints()`](https://linopy.readthedocs.io/en/latest/creating-constraints.html): Adds custom constraints. +* [`n.model.add_objective(overwrite=True)`](https://linopy.readthedocs.io/en/latest/generated/linopy.model.Model.add_objective.html): Overwrites the objective function. +* [`n.optimize.solve_model()`][pypsa.optimization.OptimizationAccessor.solve_model]: Solves the current model instance and writes the solution into `n`. + +!!! note "Understanding the `linopy` library" + + Before using custom constraints, ensure that you have a good understanding of the [Linopy](https://linopy.readthedocs.io/en/latest/index.html) library and its functionalities, as it is the underlying optimization framework used by PyPSA for creating and solving optimization problems. Checkout its [documentation](https://linopy.readthedocs.io/en/latest/index.html). + +A typical workflow starts with creating a Linopy model instance for a network +using +[`n.optimize.create_model()`][pypsa.optimization.OptimizationAccessor.create_model]. +This model instance contains all variables, constraints, and the objective +function of the optimization problem. + +``` py +>>> m = n.optimize.create_model() +``` + +This will create a Linopy model instance `m` for the network `n` and is also +accessible using the [`n.model`][pypsa.Network.model] attribute. By accessing +the model instance, users can directly access, add, remove, or modify variables, +constraints, and the objective as needed. + +To get a first overview of the variables and constraints in the model, call + +``` py +>>> m # doctest: +ELLIPSIS +Linopy LP model +=============== + +Variables: +---------- + * Generator-p_nom (name) + * ... + +Constraints: +------------ + * Generator-ext-p_nom-lower (name) + * ... + +Status: +------- +initialized +``` + +Specific variables can be accessed using `m.variables`, which provides a +dictionary-like structure containing the variables associated with each +component. For example, the following call retrieves generator active power variables: + +``` py +>>> gen_p = m.variables["Generator-p"] +``` + +This will return a `linopy.Variable`, and array of variables with generators and +snapshots as dimensions. The `linopy.Variable` type is closely related to +`xarray.DataArray` and `pandas.DataFrame`, and can be used in similar ways. + +To create custom constraints, sets of variables are first combined into +`linopy.LinearExpression` objects with coefficients and operations (e.g. +addition, subtraction, multiplication, division) that represent the relationship +between variables involved in the constraint. + +``` py +>>> 2 * m.variables["Generator-p"] + 0.5 * m.variables["Link-p"] # doctest: +ELLIPSIS +LinearExpression [snapshot: 10, name: 10]: +------------------------------------------ +[2015-01-01 00:00:00, Bremen Converter]: +0.5 Link-p[2015-01-01 00:00:00, Bremen Converter] +... +``` + +The constraint can then be created using standard Python operators like `==`, +`>=`, and `<=` and right-hand side constants. For example, a constraint that +forces the total generation at a bus to be at least 80% of the total demand, +would be written as follows: + +``` py +>>> bus = n.generators.bus.to_xarray() +>>> total_generation = gen_p.groupby(bus).sum().sum("snapshot") +>>> total_demand = n.loads_t.p_set.sum().sum() +>>> constraint_expression = total_generation >= 0.8 * total_demand +``` + +After defining the constraint expression, it is added to the Linopy model instance using the +`m.add_constraints()` function, providing a name for the constraint to +facilitate further modifications or inspection: + +``` py +>>> m.add_constraints(constraint_expression, name="Bus-minimum_generation_share") # doctest: +ELLIPSIS +Constraint `Bus-minimum_generation_share` [bus: 3]: +--------------------------------------------------- +[Frankfurt]: +1 Generator-p[2015-01-01 00:00:00, Frankfurt Wind] + ... ≥ 26038.102467283523 +``` + +Once the custom constraints is registered, calling +[`n.optimize.solve_model()`][pypsa.optimization.OptimizationAccessor.solve_model] +solves the model including any modifications after +[`n.optimize.create_model()`][pypsa.optimization.OptimizationAccessor.create_model] +and writes the solution. + +``` py +>>> n.optimize.solve_model() +('warning', 'infeasible') +``` + +Generally, optimised values for custom variables are not written back to the network object `n`. They must be retrieved seperately from the Linopy model instance `n.model`. For example, if you created a custom variable `custom_var`, you can access its optimised values as follows: + +``` py +>>> custom_var_values = n.model.variables["custom_var"].solution # doctest: +SKIP +``` + + + +!!! note "Alternative approach using `n.optimize(extra_functionality=...)`" + + The workflow described above is the recommended way to add custom constraints to a PyPSA network. It allows for direct access to the Linopy model instance and provides flexibility in defining and modifying constraints. However, if you prefer a more integrated approach, you can use the `extra_functionality` argument in the [`n.optimize()`][pypsa.optimization.OptimizationAccessor.__call__] function. This allows you to pass a function that will be executed after the model is created and before it is solved, enabling you to add custom constraints or modify the model as needed: + + ``` py + >>> def custom_constraints(n: pypsa.Network, sns: pd.Index) -> None: + ... m = n.model + ... # Define and add custom constraints here + ... ... + >>> n.optimize(extra_functionality=custom_constraints) + ``` + +!!! warning "Persistence of Linopy model instances" + + The Linopy model instance, `n.model`, is not retained when exporting the network to files. It is only available in memory during the current session. If you need to retain the model instance beyond + the current session, use Linopy functionality to save it separately with [`n.model.to_netcdf()`](https://linopy.readthedocs.io/en/latest/generated/linopy.model.Model.to_netcdf.html#linopy.model.Model.to_netcdf). That means, any custom constraints added to the model will not be saved when exporting the network to files. diff --git a/PyPSA/source/docs/user-guide/optimization/dispatch-limits.md b/PyPSA/source/docs/user-guide/optimization/dispatch-limits.md new file mode 100644 index 0000000000000000000000000000000000000000..47abb26f82ccf453177b8b8bfe79c3212ee17442 --- /dev/null +++ b/PyPSA/source/docs/user-guide/optimization/dispatch-limits.md @@ -0,0 +1,118 @@ + + +# Dispatch Limits + +Each [`Generator`][pypsa.components.Generators] has a dispatch variable $g_{n,s,t}$ where $n$ labels the bus, $s$ labels the particular generator at the bus and $t$ labels the snapshot. Each [`Link`][pypsa.components.Links] has a dispatch variable $f_{l,t}$ where $l$ labels the link and $t$ labels the snapshot. Each [`Line`][pypsa.components.Lines] and [`Transformer`][pypsa.components.Transformers] has a dispatch variable $p_{l,t}$ where $l$ labels the line/transformer and $t$ labels the snapshot. + +!!! note "Dispatch limits of [`Store`][pypsa.components.Stores] and [`StorageUnit`][pypsa.components.StorageUnits]" + + Dispatch limits of stores and storage units are described together with their + storage consistency equations in the [Storage](storage.md) section. + +## Non-extendable Components + +For non-extendable components (`{p,s}_nom_extendable=False`), the dispatch is limited by: + +| Constraint | Dual Variable | | Name | +|------------|---------------|--|------| +| $g_{n,s,t} \geq \underline{g}_{n,s,t} \cdot \hat{g}_{n,s}$ | $w_t^o \underline{\mu}_{n,s,t}$ | `n.generators_t.mu_lower` | `Generator-fix-p-lower` | +| $g_{n,s,t} \leq \bar{g}_{n,s,t} \cdot \hat{g}_{n,s}$ | $w_t^o \bar{\mu}_{n,s,t}$ | `n.generators_t.mu_upper` | `Generator-fix-p-upper` | +| $f_{l,t} \geq \underline{f}_{l,t} \cdot \hat{f}_{l}$ | $w_t^o \underline{\mu}_{l,t}$ | `n.links_t.mu_lower` | `Link-fix-p-lower` | +| $f_{l,t} \leq \bar{f}_{l,t} \cdot \hat{f}_{l}$ | $w_t^o \bar{\mu}_{l,t}$ | `n.links_t.mu_upper` | `Link-fix-p-upper` | +| $p_{l,t} \geq - \bar{p}_{l,t} \cdot \hat{p}_{l}$ | $w_t^o \underline{\mu}_{l,t}$ | `n.{lines,transformers}_t.mu_lower` | `Line-fix-p-lower` | +| $p_{l,t} \leq \bar{p}_{l,t} \cdot \hat{p}_{l}$ | $w_t^o \bar{\mu}_{l,t}$ | `n.{lines,transformers}_t.mu_upper` | `Line-fix-p-upper` | + +where $\hat{g}_{n,s}$, $\hat{f}_{l}$, and $\hat{p}_{l}$ are the nominal capacities; $\underline{g}_{n,s,t}$, $\underline{f}_{l,t}$, $\bar{g}_{n,s,t}$, and $\bar{f}_{l,t}$ and $\bar{p}_{l,t}$ are time-dependent restrictions on the dispatch given per unit of nominal capacity (e.g. due to wind availability for generators or dynamic line rating and security margins for lines). + +These constraints are set in the function `define_operational_constraints_for_non_extendables()`. + +## Extendable Components + +For extendable components (`{p,s}_nom_extendable=True`), the dispatch is limited by: + +Constraint | Dual Variable | | Name | +|------------|---------------|--|------| +| $g_{n,s,t} \geq \underline{g}_{n,s,t} \cdot G_{n,s}$ | $w_t^o \underline{\mu}_{n,s,t}$ | `n.generators_t.mu_lower` | `Generator-ext-p-lower` | +| $g_{n,s,t} \leq \bar{g}_{n,s,t} \cdot G_{n,s}$ | $w_t^o \bar{\mu}_{n,s,t}$ | `n.generators_t.mu_upper` | `Generator-ext-p-upper` | +| $f_{l,t} \geq \underline{f}_{l,t} \cdot F_{l}$ | $w_t^o \underline{\mu}_{l,t}$ | `n.links_t.mu_lower` | `Link-ext-p-lower` | +| $f_{l,t} \leq \bar{f}_{l,t} \cdot F_{l}$ | $w_t^o \bar{\mu}_{l,t}$ | `n.links_t.mu_upper` | `Link-ext-p-upper` | +| $p_{l,t} \geq - \bar{p}_{l,t} \cdot P_{l}$ | $w_t^o \underline{\mu}_{l,t}$ | `n.{lines,transformers}_t.mu_lower` | `Line-ext-p-lower` | +| $p_{l,t} \leq \bar{p}_{l,t} \cdot P_{l}$ | $w_t^o \bar{\mu}_{l,t}$ | `n.{lines,transformers}_t.mu_upper` | `Line-ext-p-upper` | + +where $G_{n,s}$, $F_{l}$, and $P_{l}$ are the nominal capacities to be optimised. + +These constraints are set in the function `define_operational_constraints_for_extendables()`. + +## Fixed Dispatch + +Additionally, the dispatch can be fixed to a certain value $\tilde{g}_{n,s,t}$ for generators and $\tilde{f}_{l,t}$ for links. In this case, the dispatch is given by: + + +Constraint | Dual Variable | | Name | +|------------|---------------|--|------| +| $g_{n,s,t} = \tilde{g}_{n,s,t}$ | $w_t^o \tilde{\mu}_{n,s,t}$ | `n.generators_t.mu_p_set` | `Generator-p_set` | +| $f_{l,t} = \tilde{f}_{l,t}$ | $w_t^o \tilde{\mu}_{l,t}$ | `n.links_t.mu_p_set` | `Link-p_set` | + +These constraints are set in the function `define_fixed_operation_constraints()`. + +## Volume Limits + +Generators and links can also have volume limits, i.e. the total dispatch over all snapshots must be above a minimum $\underline{e}_{*}$ or below a maximum $\bar{e}_{*}$. + +| Constraint | Dual Variable | Name | +|-------------------|------------------|------------------| +| $\sum_t w_t^g g_{n,s,t} \geq \underline{e}_{n,s} \quad \forall n,s$ | only in `n.model` | `Generator-e_sum_min` | +| $\sum_t w_t^g g_{n,s,t} \leq \bar{e}_{n,s} \quad \forall n,s$ | only in `n.model` | `Generator-e_sum_max` | +| $\sum_t w_t^g f_{l,t} \geq \underline{e}_{l} \quad \forall l$ | only in `n.model` | `Link-e_sum_min` | +| $\sum_t w_t^g f_{l,t} \leq \bar{e}_{l} \quad \forall l$ | only in `n.model` | `Link-e_sum_max` | + +These constraints are set in the function `define_total_supply_constraints()`. + + +!!! note "Mapping of symbols to attributes" + + === "Generator" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $g_{n,s,t}$ | `n.generators_t.p` | Decision variable | + | $G_{n,s}$ | `n.generators.p_nom_opt` | Decision variable | + | $\hat{g}_{n,s}$ | `n.generators.p_nom` | Parameter | + | $\underline{g}_{n,s,t}$ | `n.generators_t.p_min_pu` | Parameter | + | $\bar{g}_{n,s,t}$ | `n.generators_t.p_max_pu` | Parameter | + | $\tilde{g}_{n,s,t}$ | `n.generators_t.p_set` | Parameter | + | $\underline{e}_{n,s}$ | `n.generators.e_sum_min` | Parameter | + | $\bar{e}_{n,s}$ | `n.generators.e_sum_max` | Parameter | + | $w_t^g$ | `n.snapshots.weightings.generators` | Parameter | + | $w_t^o$ | `n.snapshots.weightings.objective` | Parameter | + + === "Link" + + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $f_{l,t}$ | `n.links_t.p` | Decision variable | + | $F_{l}$ | `n.links.p_nom_opt` | Decision variable | + | $\hat{f}_{l}$ | `n.links.p_nom` | Parameter | + | $\underline{f}_{l,t}$ | `n.links_t.p_min_pu` | Parameter | + | $\bar{f}_{l,t}$ | `n.links_t.p_max_pu` | Parameter | + | $\tilde{f}_{l,t}$ | `n.links_t.p_set` | Parameter | + | $\underline{e}_{l}$ | `n.links.e_sum_min` | Parameter | + | $\bar{e}_{l}$ | `n.links.e_sum_max` | Parameter | + | $w_t^g$ | `n.snapshots.weightings.generators` | Parameter | + | $w_t^o$ | `n.snapshots.weightings.objective` | Parameter | + + === "Line" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $p_{l,t}$ | `n.{lines,transformers}_t.p0` | Decision variable | + | $P_{l}$ | `n.{lines,transformers}.s_nom_opt` | Decision variable | + | $\hat{p}_l$ | `n.{lines,transformers}.s_nom` | Parameter | + | $\bar{p}_{l,t}$ | `n.{lines,transformers}_t.s_max_pu` | Parameter | + | $w_t^g$ | `n.snapshots.weightings.generators` | Parameter | + | $w_t^o$ | `n.snapshots.weightings.objective` | Parameter | diff --git a/PyPSA/source/docs/user-guide/optimization/energy-balance.md b/PyPSA/source/docs/user-guide/optimization/energy-balance.md new file mode 100644 index 0000000000000000000000000000000000000000..e25835dacf881fed431fe0779c45189afeb50940 --- /dev/null +++ b/PyPSA/source/docs/user-guide/optimization/energy-balance.md @@ -0,0 +1,48 @@ + + +# Energy Balances + +The energy balance equations are the most important constraints, which enforces that incoming and outgoing energy flows balance out at each bus $n$ for each time $t$ (like Kirchhoff's current law for electrical buses, see [Linearised Power Flow](../optimization/power-flow.md)). Considering all components, the balance constraint is given by + +$$\begin{gather*}\sum_{s} g_{n,s,t} + \sum_{s} \left(h_{n,s,t}^- - h_{n,s,t}^+ \right) + \sum_{s} h_{n,s,t}\\ ++ \sum_{l} L_{n,l,t} f_{l,t} + \sum_{l} K_{n,l} p_{l,t} = \sum_{s} d_{n,s,t} \quad \leftrightarrow \quad w_t^o\lambda_{n,t}\end{gather*}$$ + +where the **decision variables** are represented by: + +- $d_{n,s,t}$ for the demand of [`Load`][pypsa.components.Loads] components +- $g_{n,s,t}$ for the dispatch of [`Generator`][pypsa.components.Generators] components +- $h_{n,s,t}^-$ for the discharging of [`StorageUnit`][pypsa.components.StorageUnits] components +- $h_{n,s,t}^+$ for the charging of [`StorageUnit`][pypsa.components.StorageUnits] components +- $f_{l,t}$ for the flow on the link [`Link`][pypsa.components.Links] at `bus0` +- $p_{l,t}$ for the power flow on the [`Line`][pypsa.components.Lines] and [`Transformer`][pypsa.components.Transformers] components at `bus0` + +!!! note + + The orientation of each dispatch variable can be inverted or rescaled through the `sign` attribute of the component. + +The **incidence matrices** $K_{n,l}$ for the [`Line`][pypsa.components.Lines] and [`Transformer`][pypsa.components.Transformers] components and $L_{n,l,t}$ for the [`Link`][pypsa.components.Links] components govern flows between buses. The incidence matrix $K_{n,l}$ takes non-zero values $-1$ if the line or transformer $l$ starts at bus $n$ and $1$ if it ends at +bus $n$. If $p_{l,t}>0$ it withdraws from the starting bus. The time-varying incidence matrix $L_{n,l,t}$ takes non-zero values $-1$ if the link $l$ starts at bus $n$ and efficiency $\eta_{n,l,t}$ if it ends at bus $n$. If $f_{l,t}>0$ it withdraws from `bus0` and feeds in $\eta_{n,l,t} f_{l,t}$ to `bus1`. For a link with more than two outputs (e.g. a combined heat and power plant), the incidence matrix $L_{n,l,t}$ has more than two non-zero entries with efficiencies $\eta_{n,l,t}$ for `bus2`, `bus3`, etc.. The entries may also be negative to denote additional inputs rather than multiple outputs. + +The dual variable $\lambda_{n,t}$ represents the shadow price of the constraint (e.g. market clearing price, dynamic locational marginal prices) and is scaled by the snapshot weighting $w_t^o$ to yield units of currency per unit of energy regardless of the time resolution. + +The energy balance constraints are set in the function `define_nodal_balance_constraints()` and is called `Bus-nodal_balance`. + +??? note "Mapping of symbols to component attributes" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $g_{n,s,t}$ | `n.generators_t.p` | Decision variable | + | $h_{n,s,t}^-$ | `n.storage_units_t.p_dispatch` | Decision variable | + | $h_{n,s,t}^+$ | `n.storage_units_t.p_store` | Decision variable | + | $h_{n,s,t}$ | `n.stores_t.p` | Decision variable | + | $f_{l,t}$ | `n.links_t.p0` | Decision variable | + | $p_{l,t}$ | `n.lines_t.p0` or `n.transformers_t.p0` | Decision variable | + | $d_{n,s,t}$ | `n.loads_t.p_set` | Decision variable | + | $\lambda_{n,t}$ | `n.buses_t.marginal_price` | Dual variable | + | $K_{n,l}$ | Calculated internally by `n.incidence_matrix()` | Parameter | + | $L_{n,l,t}$ | Calculated internally from `efficiency{i}` attributes | Parameter | + | $w_t^o$ | `n.snapshot_weightings.objective` | Parameter | diff --git a/PyPSA/source/docs/user-guide/optimization/global-constraints.md b/PyPSA/source/docs/user-guide/optimization/global-constraints.md new file mode 100644 index 0000000000000000000000000000000000000000..5d4a6f756897d9d193b1c064903dcc4287161a54 --- /dev/null +++ b/PyPSA/source/docs/user-guide/optimization/global-constraints.md @@ -0,0 +1,179 @@ + + +# Global Constraints + +Global constraints apply to more than one component at once and are stored under a unique `name` in `n.global_constraints`. Several pre-defined types of global constraints are available (`type`). +For instance, for defining emission limits, transmission expansion limits, or technology capacity expansion limits using the `sense` and `constant` attributes. + +By default, global constraints span across all investment periods. For models with multiple investment periods, global constraints can be limited to affect only single investment period (e.g. an emission limit for a specific year) by specifying the in the attribute `investment_period`. The shadow price of each global constraint is stored in the attribute `mu`. + +!!! note "Naming Convention" + + Global constraints carry the name scheme `GlobalConstraint-{name}` in the Linopy model. + +## Primary Energy + +The primary energy constraints (`type="primary_energy"`) are used to limit byproducts of primary energy consumption of a carrier (e.g. CO~2~ emissions) in generators, storage units and stores. They depend on the generator efficiency and carrier-specific attributes (e.g. `co2_emissions`). + +Suppose there is a global constraint defined for CO~2~ emissions (`carrier_attribute`) with sense "<=" (`sense`) and a limit $\Gamma$ (`constant`). Emissions can come from generators whose energy carriers have CO~2~ emissions, and from stores and storage units whose storage medium releases or absorbs CO~2~ when it is converted. Only stores and storage units with non-cyclic state of charge that is different at the start ($t=-1$) and end ($t=|T|-1$) of the optimisation period contribute. + +If the specific emissions of carrier $s$ is $\rho_s$ (`n.carriers.co2_emissions`) and the generator with carrier $s$ at node $n$ and snapshot $t$ has efficiency $\eta_{n,s,t}$ then the CO~2~ constraint is + +$$\begin{gather*}\sum_{n,s,t} w_t^g \cdot \eta_{n,s,t}^{-1} \cdot g_{n,s,t}\cdot \rho_s + \sum_{n,s}\left(e_{n,s,t=-1} - e_{n,s,t=|T|-1}\right) \cdot \rho_s\\ ++ \sum_{n,s}\left(soc_{n,s,t=-1} - soc_{n,s,t=|T|-1}\right) \cdot \rho_s \leq \Gamma \quad \leftrightarrow \quad \mu\end{gather*}$$ + +The first sum is over generators; the second sum is over stores; the third over storage units. The shadow price $\mu$ would represent the CO~2~ price in this case. + +This global constraint is added in the function `define_primary_energy_limit()`. + +??? note "Mapping symbols to component attributes" + + | Symbol | Attribute | Type | + |--------|-----------|------| + | $g_{n,s,t}$ | `n.generators_t.p` | Decision Variable | + | $e_{n,s,t}$ | `n.stores_t.e` | Decision Variable | + | $soc_{n,s,t}$ | `n.storage_units_t.soc` | Decision Variable | + | $\mu$ | `n.global_constraints.mu` | Dual Variable | + | $w_t^g$ | `n.snapshot_weightings.generators` | Parameter | + | $\eta_{n,s,t}$ | `n.generators.efficiency` | Parameter | + | $\rho_s$ | `n.carriers.co2_emissions` | Parameter | + | $\Gamma$ | `n.global_constraints.constant` | Parameter | + + +## Operational Limit + +The operational constraints can limit the net production of a carrier taking +into account generator, storage units and stores (`type="operational_limit"`). +For example, this can be used to limit the usage of gas in the system to a +certain amount $\Gamma$ (`constant` in MWh). With sense "<=" (`sense`), the +constraint would be given by + +$$\begin{gather*}\sum_{n,s,t} w_t^g \cdot g_{n,s,t}+ \sum_{n,s}\left(e_{n,s,t=-1} - e_{n,s,t=|T|-1}\right) \\ ++ \sum_{n,s}\left(soc_{n,s,t=-1} - soc_{n,s,t=|T|-1}\right) \leq \Gamma \quad \leftrightarrow \quad \mu\end{gather*}$$ + +The first sum is over generators; the second sum is over stores; the third over +storage units. Structurally, it is similar to the primary energy limit, but +without the consideration of specific emissions and efficiencies. The shadow +price $\mu$ (in currency/MWh) would represent the reduction in system costs if +the operational limit were relaxed by one unit. + +This global constraint is added in the function `define_operational_limit()`. + +??? note "Mapping symbols to component attributes" + + | Symbol | Attribute | Type | + |--------|-----------|------| + | $g_{n,s,t}$ | `n.generators_t.p` | Decision Variable | + | $e_{n,s,t}$ | `n.stores_t.e` | Decision Variable | + | $soc_{n,s,t}$ | `n.storage_units_t.soc` | Decision Variable | + | $\mu$ | `n.global_constraints.mu` | Dual Variable | + | $w_t^g$ | `n.snapshot_weightings.generators` | Parameter | + | $\Gamma$ | `n.global_constraints.constant` | Parameter | + +## Volume Limit on Transmission Expansion + +This global constraint can be used to limit the expansion volume in MWkm of transmission lines and links (`type="transmission_volume_expansion_limit"`). The `carrier_attribute` specifies the subset of carriers to consider. These can be individual carriers or concatenated by commas, e.g. "AC", "DC", "AC,DC", or a [`Link`][pypsa.components.Links] carrier such as "H2 pipeline". With `sense="<="`, the constraint is defined as + +$$\sum_{l\in L_{\textrm{carriers}}} d_{l} F_{l} \leq \Gamma \quad \leftrightarrow \quad \mu$$ + +where $L_{\textrm{carriers}}$ is the set of lines and links with the specified carriers, $\Gamma$ is the maximum allowed volume expansion in MWkm, $d_{l}$ is the distance of line or link $l$ in km and $F_{l}$ is the capacity of line or link $l$ in MW. The shadow price $\mu$ represents the marginal benefit of expanding the transmission capacity in currency/MWkm/a. + +This global constraint is added in the function `define_transmission_volume_expansion_limit()`. + +??? note "Mapping symbols to component attributes" + + | Symbol | Attribute | Type | + |--------|-----------|------| + | $F_l$ | `n.{lines,links}.p_nom_opt` | Decision Variable | + | $\mu$ | `n.global_constraints.mu` | Dual Variable | + | $d_l$ | `n.{lines,links}.length` | Parameter | + | $\Gamma$ | `n.global_constraints.constant` | Parameter | + +## Cost Limit on Transmission Expansion + +This global constraint can be used to limit the total investment cost in currency/a of transmission lines and links (`type="transmission_expansion_cost_limit"`). The `carrier_attribute` specifies the subset of carriers to consider. These can be individual carriers or concatenated by commas, e.g. "AC", "DC", "AC,DC", or a [`Link`][pypsa.components.Links] carrier such as "H2 pipeline". With `sense="<="`, the constraint is defined as + +$$\sum_{l\in L_{\textrm{carriers}}} c_{l} F_{l} \leq \Gamma \quad \leftrightarrow \quad \mu$$ + +where $L_{\textrm{carriers}}$ is the set of lines and links with the specified carriers, $c_{l}$ is the capital cost of line or link $l$ in currency/MW/a, $F_{l}$ is the capacity of line or link $l$ in MW and $\Gamma$ is the maximum allowed cost of line expansion in currency/a. The shadow price $mu$ represents how much the total system cost could be reduced if the spending limit was increased by one currency/a. + +This global constraint is added in the function `define_transmission_expansion_cost_limit()`. + +??? note "Mapping symbols to component attributes" + + | Symbol | Attribute | Type | + |--------|-----------|------| + | $F_l$ | `n.{lines,links}.p_nom_opt` | Decision Variable | + | $\mu$ | `n.global_constraints.mu` | Dual Variable | + | $c_l$ | `n.{lines,links}.capital_cost` | Parameter | + | $\Gamma$ | `n.global_constraints.constant` | Parameter | + +## Expansion Limit + +This global constraint can be used to limit the total capacity of components of a carrier (`type=tech_capacity_expansion_limit`). This global constraint can be specific to an investment period by setting the `investment_period` attribute and specific to a bus by setting the `bus` attribute. This constraint is mainly used for networks with multiple investment periods, where land usage and building rate restrictions need to be applied for a range of active components of a particular carrier (`carrier_attribute`) in a certain region. + +!!! warning + + Currently, only the capacities of extendable components are considered, i.e. generators, storage units and stores with `extendable=True`. The capacities of non-extendable components are not considered in this constraint. + +For example, the capacities of all onshore wind generators (`carrier_attribute="onshore wind"`) at a certain bus (`bus="DE"`) should be smaller (`sense="<="`) than a hypothetical technical potential of 200 GW for onshore wind in the specific region (`constant=200e3`). Then the technology capacity expansion constraint across all *active* components is given by + +$$\sum_{s | b_s<=a}.build_year` | Parameter | + | $L_s$ | `n.{}.lifetime` | Parameter | + | $a$, $A$ | `n.investment_periods` | Parameter | + | $\Gamma$ | `n.global_constraints.constant` | Parameter | + + +## Growth Limit per Carrier + +This carrier-specific constraint type implements absolute and relative growth limits per carrier which constrains new installed capacities for each investment period. It can be defined by providing the attributes `n.carriers.max_growth` and `n.carriers.max_relative_growth`. + +Suppose the absolute growth limit for a specific carrier $s$ is $\Gamma_s$ (`max_growth`) and the relative growth limit is $\gamma_s$ (`max_relative_growth`). With the growth limit constraint, for each investment period $a$, the new installed capacity $G_{a,s}$ of all components with carrier $s$ is limited by: + +$$G_{a,s} \leq \gamma_s \cdot G_{a-1,s} + \Gamma_s \quad \forall a, s \quad \leftrightarrow \quad \mu_{a,s}$$ + +where $G_{a,s}$ represents the sum of all newly built nominal capacities of extendable components with carrier $s$ in period $a$. The relative growth limit $\gamma_s$ allows the new capacity in period $a$ to be proportional to the capacity added in the previous period $a-1$. + +This constraint only applies to networks with multiple investment periods and only considers components that are newly activated in each period. For the constraint to take effect, at least one of the attributes `max_growth` or `max_relative_growth` must be set to a finite value for the carrier. + +!!! warning + + The relative and abolute growth limits are additive, i.e. the absolute growth limit applies in addition to the relative growth limit. + +In general, the constraint would iterate over all investment variables for generators $G_{n,s}$, lines and transformers $P_{l}$, links $F_{l}$, stores $E_{n,s}$ and storage units $H_{n,s}$ for the specified carrier. For components connecting two buses, the bus selection is done by `bus0`. + +This global constraint is added in the function `define_growth_limit()` and carries the name `Carrier-growth_limit`. + +??? note "Mapping symbols to component attributes" + + | Symbol | Attribute | Type | + |--------|-----------|------| + | $G_{n,s}$ | `n.generators.p_nom_opt` | Decision Variable | + | $H_{n,s}$ | `n.storage_units.p_nom_opt` | Decision Variable | + | $E_{n,s}$ | `n.stores.e_nom_opt` | Decision Variable | + | $F_l$ | `n.links.p_nom_opt` | Decision Variable | + | $P_l$ | `n.{lines,transformers}.s_nom_opt` | Decision Variable |# + | $\gamma_s$ | `n.carriers.max_relative_growth` | Parameter | + | $\Gamma_s$ | `n.carriers.max_growth` | Parameter | diff --git a/PyPSA/source/docs/user-guide/optimization/modelling-to-generate-alternatives.md b/PyPSA/source/docs/user-guide/optimization/modelling-to-generate-alternatives.md new file mode 100644 index 0000000000000000000000000000000000000000..45cc66ad9815cd0f2572dc569f1c427d27e46948 --- /dev/null +++ b/PyPSA/source/docs/user-guide/optimization/modelling-to-generate-alternatives.md @@ -0,0 +1,60 @@ + + +# Modelling to Generate Alternatives (MGA) + +## Searching for alternatives with given cost slack + +The function [`n.optimize.optimize_mga`][pypsa.optimization.OptimizationAccessor.optimize_mga] runs modelling-to-generate-alternatives (MGA) on network to find near-optimal solutions. This is a technique where a solved network is re-optimized with an alternative objective function, while adding a global constraint limiting the total system cost. + +For example, the alternative objective function may represent total installed renewable capacity; minimizing this subject to a total system cost constraint could lead to a slightly more expensive system containing fewer renewables but more storage or backup capacity. + +In `optimize_mga`, the bound on total system cost is specified as a relative increase over the cost-optimum; the relative increase is called _slack_. Denote the slack as $\varepsilon$, as is common in the literature, and let $c$ be the total system cost objective function and $c^*$ its optimal value. Then the near-optimality constraint added in `optimize_mga` is $c \leq (1 + \varepsilon) \cdot c^*$. Typical values found in the literature range from about 2% to 20%, with 5% being a common starting value for exploration. + +Numerous research articles cover the theory and application of MGA in the context of energy systems modelling; many of them have used PyPSA under the hood. See for instance [Brown et al. (2021)](https://doi.org/10.1016/j.epsr.2020.106690), [Grochowicz et al. (2023)](https://doi.org/10.1016/j.eneco.2022.106496) and [Lau et al. (2024)](https://doi.org/10.1088/2753-3751/ad7d10) (the latter containing a brief literature review) for an introduction to the topic. + +## Exploring trade-offs in the near-optimal space + +Often, it is useful to explore the trade-offs between several alternative objectives using near-optimal techniques. While this can be done using the `optimize_mga` function, described above, PyPSA includes several convenient functions facilitating such exploration. + +To start with, [`n.optimize.optimize_mga_in_direction`][pypsa.optimization.OptimizationAccessor.optimize_mga_in_direction] is similar to `optimize_mga`, but instead of a single alternative objective (the `weights` argument in `optimize_mga`), this function takes a dictionary of any number of alternative objectives (the `dimensions` argument) and a `direction` vector (given as a dictionary with the same keys as `dimensions`) telling PyPSA how to balance the objectives against each other. + +For example, supposing you pass as `dimensions` two objectives $\{\text{foo}: c_1, \text{bar}: c_2\}$ and as `direction` the vector $\{\text{foo}: 1, \text{bar}: -1\}$, the `optimize_mga_in_direction` will maximize $c_1$ and minimize $c_2$ jointly. As before, these optimizations are subject to a bound on total system cost, specified by the user as a relative cost slack $\varepsilon$. + +The alternative objectives $c_1, c_2, \dots$ effectively specify a coordinates in a low-dimensional projection of the full feasible space of the linear program defined by a network. By solving the network in many different directions in this low-dimensional space (under a cost bound), it is possible to approximate the geometry of the near-optimal space in this projection. For example, supposing that $c_1$ and $c_2$ represent total installation of wind and solar generation, respectively, the near-optimal space projected to $c_1,c_2$-coordinates represents all near-optimal and feasible combinations of wind and solar installation. + +As running `optimize_mga_in_direction` in multiple different directions is a common operation, PyPSA provides a separate function, [`n.optimize.optimize_mga_in_multiple_directions`][pypsa.optimization.OptimizationAccessor.optimize_mga_in_multiple_directions], which takes a list (or pandas `DataFrame`) of directions, and solves for them in parallel. + +Moreover, three functions ([`pypsa.optimization.mga.generate_directions_random`][pypsa.optimization.mga.generate_directions_random], [`pypsa.optimization.mga.generate_directions_evenly_spaced`][pypsa.optimization.mga.generate_directions_evenly_spaced], [`pypsa.optimization.mga.generate_directions_halton`][pypsa.optimization.mga.generate_directions_halton]) are provided which generate sets of directions in the format expected by `optimize_mga_in_multiple_directions`. Of course, you can also provide your own directions. + +The function [`n.optimize.optimize_mga`][pypsa.optimization.OptimizationAccessor.optimize_mga] runs modelling-to-generate-alternatives (MGA) on network to find near-optimal solutions. + + +## Examples + + +
+ + +- :material-notebook:{ .lg .middle } **Modelling-to-Generate Alternatives** + + --- + + Explores near-optimal solution diversity by generating alternative system + designs with similar costs. + + [:octicons-arrow-right-24: Go to example](../../examples/mga.ipynb) + +- :material-notebook:{ .lg .middle } **Exploring Near-Optimal Spaces** + + --- + + Explores near-optimal space to understand flexibility in investment + decisions while maintaining cost-effectiveness. + + [:octicons-arrow-right-24: Go to example](../../examples/near-opt-space.ipynb) + +
diff --git a/PyPSA/source/docs/user-guide/optimization/objective.md b/PyPSA/source/docs/user-guide/optimization/objective.md new file mode 100644 index 0000000000000000000000000000000000000000..792a100a7301ef699118f764f2b1dd6a2fb947a0 --- /dev/null +++ b/PyPSA/source/docs/user-guide/optimization/objective.md @@ -0,0 +1,161 @@ + + +# Objective + +The objective is to **minimise total system costs** (i.e. the sum of all costs) subject to +meeting all constraints of the optimisation problem (e.g. energy balance, dispatch limits, etc.) for the selected snapshots. + +The objective function is composed of investment costs and multiple forms of operational costs, such as marginal costs, quadratic marginal costs, marginal storage costs, spillage costs, start-up, shut-down and stand-by costs. + +Which cost components are included in the objective function depends on the data provided for the network components. For instance, if no extendable components are present (`{p,s,e}_nom_extendable=False`), the investment cost terms are skipped. Or, if no components are marked as committable (`committable=False`), the start-up, shut-down and stand-by costs are skipped. All cost coefficients default to zero, so that the objective function is empty if no costs are defined. + +## Investment Costs + +For extendable components (`{p,s,e}_nom_extendable=True`), the investment costs of expanding their capacity are given by + +$$\sum_{n,s} c_{n,s} G_{n,s} + \sum_{n,s} c_{n,s} H_{n,s} + \sum_{n,s} c_{n,s} E_{n,s} + \sum_{l} c_{l} F_l + \sum_{l} c_{l} P_l$$ + +where $c_{*}$ are the capital costs per unit of nominal capacity for generators ($G_{n,s} \in \mathbb{R}$), storage units ($H_{n,s} \in \mathbb{R}$), stores ($E_{n,s} \in \mathbb{R}$), links ($F_l \in \mathbb{R}$), and lines and transformers ($P_l \in \mathbb{R}$). The subscript $n$ labels the bus, $s$ labels the particular generator/storage type at the bus, and $l$ labels the branch. +The decision variables $G_{n,s}$, $H_{n,s}$, $F_l$, and $P_l$ are the nominal power capacities, whereas $E_{n,s}$ is the nominal energy capacity of the store. The capital costs are given in currency/MW for generators, links, lines and transformers, and in currency/MWh for stores. + +To minimise **long-run annual system costs** (currency/a), capital costs for components should be set to annualised investment costs (i.e. currency/MW/a and currency/MWh/a ), marginal costs for dispatch in currency/MWh, and the weightings (h/a) are chosen such that $\sum_t w_t^o = 8760$ hours per annum. + +If no extendable components are present, only the dispatch of the components is optimised as in a **short-run market model**. + + +??? note "Mapping of symbols to attributes" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $G_{n,s}$ | `n.generators.p_nom_opt` | Decision variable | + | $H_{n,s}$ | `n.storage_units.p_nom_opt` | Decision variable | + | $E_{n,s}$ | `n.stores.p_nom_opt` | Decision variable | + | $F_l$ | `n.links.p_nom_opt` | Decision variable | + | $P_l$ | `n.lines.s_nom_opt` | Decision variable | + | $c_{n,s}$ | `n.{generators,storage_units,stores}.capital_cost` | Parameter | + | $c_{l}$ | `n.{links,lines,transformers}.capital_cost` | Parameter | + +## Marginal Costs + +The marginal costs of dispatch are given by + +$$+ \sum_{t} w_t^o \left( \sum_{n,s} o_{n,s,t} g_{n,s,t} + \sum_{n,s} o_{n,s,t} h_{n,s,t}^- + \sum_{n,s} o_{n,s,t} h_{n,s,t} + \sum_{l} o_{l,t} f_{l,t} \right)$$ + +where $o_{*}$ are the marginal costs per unit of power for generator ($g_{n,s,t} \in \mathbb{R}$), storage unit ($h_{n,s,t}^- \in \mathbb{R}$), store ($h_{n,s,t} \in \mathbb{R}$) and link ($f_{l,t} \in \mathbb{R}$) dispatch. Note here the difference between storage unit dispatch $h_{n,s,t}^-$ (where costs are only incurred for discharging) and store dispatch $h_{n,s,t}$ (where marginal costs incur a cost for discharging and a revenue for charging). The subscript $t$ indicates the snapshot. + +??? note "Mapping of symbols to attributes" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $g_{n,s,t}$ | `n.generators_t.p` | Decision variable | + | $h_{n,s,t}^-$ | `n.storage_units_t.p_dispatch` | Decision variable | + | $h_{n,s,t}$ | `n.stores_t.p` | Decision variable | + | $f_{l,t}$ | `n.links_t.p` | Decision variable | + | $o_{n,s,t}$ | `n.{generators,storage_units,stores}_t.marginal_cost` | Parameter | + | $o_{l,t}$ | `n.links_t.marginal_cost` | Parameter | + | $w_t^o$ | `n.snapshots.weightings.objective` | Parameter | + + +## Quadratic Marginal Costs + +Quadratic marginal costs can be included, which turn the problem into a quadratic program (QP): + +$$+ \sum_{t} w_t^o \left( \sum_{n,s} qmc_{n,s,t} g_{n,s,t}^2 + \sum_{n,s} qmc_{n,s,t} {h_{n,s,t}^-}^2 + \sum_{n,s} qmc_{n,s,t} h_{n,s,t}^2 + \sum_{l} qmc_{l,t} f_{l,t}^2 \right)$$ + +where $qmc_{*}$ are the quadratic marginal costs per unit of dispatch for generators ($g_{n,s,t}$), storage units ($h_{n,s,t}^-$), stores ($h_{n,s,t}$) and links ($f_{l,t}$), weighted by the objective snapshot weightings $w_t^o$. + +??? note "Mapping of symbols to attributes" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $g_{n,s,t}$ | `n.generators_t.p` | Decision variable | + | $h_{n,s,t}^-$ | `n.storage_units_t.p_dispatch` | Decision variable | + | $h_{n,s,t}$ | `n.stores_t.p` | Decision variable | + | $f_{l,t}$ | `n.links_t.p` | Decision variable | + | $qmc_{n,s,t}$ | `n.{generators,storage_units,stores}_t.marginal_cost_quadratic` | Parameter | + | $qmc_{l,t}$ | `n.links_t.marginal_cost_quadratic` | Parameter | + | $w_t^o$ | `n.snapshots.weightings.objective` | Parameter | + +## Marginal Storage Costs + +Marginal storage costs can also be applied to storage units and stores, which represent the cost of holding energy in storage. The objective function includes these costs as follows: + +$$+ \sum_{t} w_t^o \left( \sum_{n,s} mcs_{n,s,t} soc_{n,s,t} + \sum_{n,s} mcs_{n,s,t} e_{n,s,t} \right)$$ + +where $mcs_{*}$ are the marginal storage costs per unit of energy for storage units ($soc_{n,s,t} \in \mathbb{R}$) and stores ($e_{n,s,t} \in \mathbb{R}$), weighted by the objective snapshot weightings $w_t^o$. + +??? note "Mapping of symbols to attributes" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $soc_{n,s,t}$ | `n.storage_units_t.state_of_charge` | Decision variable | + | $e_{n,s,t}$ | `n.stores_t.e` | Decision variable | + | $mcs_{n,s,t}$ | `n.storage_units_t.marginal_cost_storage` | Parameter | + | $mcs_{n,s,t}$ | `n.stores_t.marginal_cost_storage` | Parameter | + | $w_t^o$ | `n.snapshots.weightings.objective` | Parameter | + +## Spillage Costs + +Spillage costs, e.g. sending water in a hydro reservoir over a spillway without generating electricity, can be given for storage units by + +$$ ++ \sum_{n,s,t} w_t^o sc_{n,s,t} \textrm{spillage}_{n,s,t} +$$ + +where $sc_{n,s,t}$ are the spillage costs per unit of spillage ($\textrm{spillage}_{n,s,t} \in \mathbb{R}$), weighted by the objective snapshot weightings $w_t^o$. + +??? note "Mapping of symbols to attributes" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $\textrm{spillage}_{n,s,t}$ | `n.storage_units_t.spillage` | Decision variable | + | $sc_{n,s,t}$ | `n.storage_units_t.spill_cost` | Parameter | + | $w_t^o$ | `n.snapshots.weightings.objective` | Parameter | + +## Start-Up, Shut-Down, Stand-By Costs + +For generators and links with unit commitment (`committable=True`), stand-by costs $sbc_{*,t}$, start-up costs $suc_{*,t}$ and shut-down costs $sdc_{*,t}$ are given by + +=== "Generator" + + $$+ \sum_{n,s,t} w_t^o sbc_{n,s,t} u_{n,s,t} + \sum_{n,s} suc_{n,s} su_{n,s,t} + \sum_{n,s} sdc_{n,s} sd_{n,s,t}$$ + +=== "Link" + + $$+ \sum_{l,t} w_t^o sbc_{l,t} u_{l,t} + \sum_{l,t} suc_{l,t} su_{l,t} + \sum_{l,t} sdc_{l,t} sd_{l,t}$$ + +where $sbc_{*,t}$, $suc_{*,t}$, and $sdc_{*,t}$ are the stand-by, start-up, and shut-down costs linked to the status ($u_{*,t} \in \mathbb{B}$), start-up ($su_{*,t} \in \mathbb{B}$), and shut-down ($sd_{*,t} \in \mathbb{B}$) unit commitment variables. Only the stand-by costs are weighted by the objective snapshot weightings $w_t^o$. Components with unit commitment constraints turn the problem into a mixed-integer linear program (MILP). + +??? note "Mapping of symbols to attributes" + + === "Generator" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $u_{n,s,t}$ | `n.generators_t.status` | Decision variable | + | $su_{n,s,t}$ | `n.generators_t.start_up` | Decision variable | + | $sd_{n,s,t}$ | `n.generators_t.shut_down` | Decision variable | + | $sbc_{n,s,t}$ | `n.generators_t.stand_by_cost` | Parameter | + | $suc_{n,s}$ | `n.generators.start_up_cost` | Parameter | + | $sdc_{n,s}$ | `n.generators.shut_down_cost` | Parameter | + | $w_t^o$ | `n.snapshots.weightings.objective` | Parameter | + + === "Link" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $u_{l,t}$ | `n.links_t.status` | Decision variable | + | $su_{l,t}$ | `n.links_t.start_up` | Decision variable | + | $sd_{l,t}$ | `n.links_t.shut_down` | Decision variable | + | $sbc_{l,t}$ | `n.links_t.stand_by_cost` | Parameter | + | $suc_{l,t}$ | `n.links.start_up_cost` | Parameter | + | $sdc_{l,t}$ | `n.links.shut_down_cost` | Parameter | + | $w_t^o$ | `n.snapshots.weightings.objective` | Parameter | + +Some decision variables do not show up in the objective function, such as the power flow on lines and transformers ($p_{l,t} \in \mathbb{R}$) and the storage unit charging ($h_{n,s,t}^+ \in \mathbb{R}$). They are only used to enforce constraints, e.g. the power flow on lines and transformers. + +The objective function is defined in the function `define_objective()`. diff --git a/PyPSA/source/docs/user-guide/optimization/overview.md b/PyPSA/source/docs/user-guide/optimization/overview.md new file mode 100644 index 0000000000000000000000000000000000000000..d96ba304e0341760fd2266ba1c76265732f1695d --- /dev/null +++ b/PyPSA/source/docs/user-guide/optimization/overview.md @@ -0,0 +1,40 @@ + + +# Overview + +PyPSA can optimise the following types of problems: + +1. **Economic Dispatch (ED)** market model with unit commitment and storage operation with perfect foresight or rolling horizon, coupled across different energy carriers (electricity, heat, hydrogen, etc.) and with conversion between them ([:material-notebook: Example](../../examples/simple-electricity-market-examples.ipynb)), + +2. **Linear Optimal Power Flow (LOPF)** with network constraints for Kirchhoff's Voltage Law (KVL) and Kirchhoff's Current Law (KCL) ([:material-notebook: Example](../../examples/scigrid-lopf-then-pf.ipynb)), + +3. **Security-Constrained Linear Optimal Power Flow (SCLOPF)** for network contingency analysis ([:material-notebook: Example](../../examples/scigrid-sclopf.ipynb)), + +4. **Capacity Expansion Planning (CEP)** with single or multiple investment periods and system-wide constraints ([:material-notebook: Example](../../examples/capacity-expansion-planning-single-node.ipynb)), + +5. **Stochastic Optimisation (SO)** in form of a two-stage stochastic program with investments as first-stage decisions and dispatch as recourse decisions across weighted scenarios of uncertain input parameters ([:material-notebook: Example](../../examples/stochastic-optimization.ipynb)), and + +6. **Modelling-to-Generate-Alternatives (MGA)** for near-optimal space exploration ([:material-notebook: Example](../../examples/mga.ipynb)). + +These problems build on each other, e.g., capacity expansion planning models include economic dispatch and linear optimal power flow constraints. Thereby, the dispatch of generation, conversion and storage technologies, as well as the capacities of generation, storage, conversion and transmission infrastructure are co-optimised. In any case, the objective is to minimize the total system cost for the snapshots selected. + +The kind of optimisation problem is determined by the parameters provided for the network components (e.g. whether components are extendable or committable). Depending on the data input, the optimisation is then formulated as a **linear program (LP)**, **quadratic program (QP)** or **mixed-integer linear program (MILP)**. Most variables are continuous, but unit commitment constraints and block-sized investments can be modelled with binary variables. Quadratic terms are added by quadratic marginal dispatch costs. + +To solve a network with a solver of your choice, run + +``` py +n.optimize(solver_name="highs", solver_options={"solver": "ipm"}) +``` + +where `solver_name` is a string and `solver_options` is a dictionary of solver-specific flags to pass to the solver. See [`n.optimize()`][pypsa.optimization.OptimizationAccessor.__call__] for details. + +A call to this function will formulate the optimisation problem with the [Linopy](https://linopy.readthedocs.io) library, solve it by interfacing with the solver, and store the results in the network object `n`. + +!!! note "Problem Extensions" + + While most types of optimisation problems are dynamically formulated by [`n.optimize()`][pypsa.optimization.OptimizationAccessor.__call__] depending on data inputs, some features require different optimization functions to be called. For instance, to optimise dispatch in a sequential rolling horizon with myopic operational foresight, + the function [`n.optimize.optimize_with_rolling_horizon()`][pypsa.optimization.OptimizationAccessor.optimize_with_rolling_horizon] can be used. diff --git a/PyPSA/source/docs/user-guide/optimization/pathway-planning.md b/PyPSA/source/docs/user-guide/optimization/pathway-planning.md new file mode 100644 index 0000000000000000000000000000000000000000..3d99ab1a013f208d6eeabee5d7ef8280b5ed55c1 --- /dev/null +++ b/PyPSA/source/docs/user-guide/optimization/pathway-planning.md @@ -0,0 +1,207 @@ + + + +For long-term planning problems where the network is optimised for different +time horizons, PyPSA offers functionality optimise the network across multiple +investment periods (e.g. 2030, 2040, 2050) simulataneously with perfect foresight. + +## Literature + +In the literature, two different methods can be distinguished for pathway +optimisation with perfect foresight, mainly differing in the way they handle +the investment costs of assets:[^1] + +* In **Type I**, the complete overnight investment costs are applied in the investment period the asset is built. +* In **Type II**, the investment costs are annualised over the years in which an asset is active (i.e. build year plus lifetime). + +PyPSA uses **Type II**, mainly because it allows a cleaner separation of the +discounting over different years and the end-of-horizon effects are smaller +compared to **Type I**. End-of-horizon effects occur when models undervalue long-lived assets and overbuild short-term ones because they ignore system needs beyond the final period. + +## Implementation + +Optimisation with multiple investment periods can be run with + +``` py +n.optimize(multi_investment_periods=True) +``` + +!!! info "Overnight versus pathway optimisation" + + By default, there are no investment periods defined, and the network is + optimised for a single investment period (overnight scenario). + +For pathway optimisation, the `n.snapshots` have to be a `pandas.MultiIndex`, +with the first level as a subset of the investment periods (see [Investment +Periods](../design.md#investment-periods)). The investment periods are defined +in `n.investment_periods`, a `pandas.Index` of monotonically increasing integers +of years (e.g. `[2030, 2040, 2050]`). + +Investment periods weightings are stored in `n.investment_period_weightings`, a +`pandas.DataFrame` indexed by `n.investment_periods` with two columns for +objective ($v_a^o$, for social discounting of costs) and years ($v_a^y$, for +emission budgets). + +The general approach for modelling multiple investment periods is to add +components for each investment period $a$, in which their capacity should be +extendable (i.e. the investment variables $G_{s,a}$ are extended by index $a$). +For example, optimising wind capacities from 2030 to 2050 in 10-year steps +requires a separate generator for each investment period (e.g. `wind-2030`, +`wind-2040`, `wind-2050`), each with the corresponding build year and lifetime. + +This structure enables the specification of differing technological assumptions +per investment period, such as decreasing investment costs, improving +efficiencies, or better capacity factors from higher hub heights. Moreover, not +all technologies need to be available in each investment period. For instance, +to forbid new coal power plants after 2025 or to introduce new technologies like +small modular nuclear reactors (SMR) only from 2040 onwards. + + +## Objective + +!!! info + + For illustration purposes, the following outline of the optimisation problem with multiple investment periods + is reduced to the case of a single bus, only generators, and only marginal operational costs. + The generalisation to multiple nodes, other asset types and cost functions works analogously. + +With multiple investment periods, the objective function is expressed by + +$$\underset{G_s,g_{s,a,t}}{\min} \quad \sum_{a \in A} v_a^o \left[\sum_{s | b_s \leq a + +- :material-notebook:{ .lg .middle } **Pathway Planning** + + Optimizes investment decisions across multiple investment periods for a long-term transition pathway with perfect foresight. + + [:octicons-arrow-right-24: Go to example](../../examples/multi-investment-optimisation.ipynb) + +- :material-notebook:{ .lg .middle } **Myopic Pathway Planning** + + Optimizes investment decisions across multiple investment periods for a + long-term transition pathway with myopic foresight. + + [:octicons-arrow-right-24: Go to example](../../examples/myopic-pathway.ipynb) + + + +[^1]: T. Brown (2020), [Multi-Horizon Planning with Perfect Foresight](https://nworbmot.org/energy/multihorizon.pdf). + +[^2]: E. Zeyen, M. Victoria, T. Brown (2023), [Endogenous learning for green hydrogen in a sector-coupled energy model for Europe](https://doi.org/10.1038/s41467-023-39397-2). Nature Communications, 14, 3743, doi:10.1038/s41467-023-39397-2. diff --git a/PyPSA/source/docs/user-guide/optimization/power-flow.md b/PyPSA/source/docs/user-guide/optimization/power-flow.md new file mode 100644 index 0000000000000000000000000000000000000000..5150a3c573205c6989a291d6d83de25de167cc17 --- /dev/null +++ b/PyPSA/source/docs/user-guide/optimization/power-flow.md @@ -0,0 +1,165 @@ + + +# Linearised Power Flow + +## Kirchhoff's Current Law (KCL) + +The Kirchhoff's Current Law (KCL) mandates that the sum of power flows into a +bus must equal the sum of power flows out of the bus at each time step. It is +covered by the more general [Energy Balance](energy-balance.md) constraints +which is also applied to non-electric buses. For electric buses, KCL is the +specific form of the energy balance constraint. + +## Kirchhoff's Voltage Law (KVL) + +For lines and transformers, whose power flows according the impedances, the +power flow $p_{l,t}$ in AC networks is governed by the cycle-based linearised formulation +of Kirchhoff's Voltage Law (KVL) + +$$\sum_l C_{l,c} x_l p_{l,t} = 0 \quad \forall\, c,t$$ + +where $C$ is a [cycle basis +matrix](https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.cycles.cycle_basis.html) +of the network graph, where the independent cycles $c$ are expressed as directed +linear combinations of lines $l$, and $x_l$ is the series reactance. + +While there are different formulations of KVL, the cycle-based formulation was +found to be substantially faster than other formulations due to its sparsity, as +shown in Hörsch et al. (2018)[^1]. This formulation defines the same feasible +space as other standard linearised formulations based on voltage angles that is +commonly found in textbooks (B-Theta) or the formulation based on Power Transfer +Distribution Factors (PTDFs). + +These constraints are set in the function `define_kirchhoff_voltage_constraints()` and carry the name `Kirchhoff-Voltage-Law`. + +!!! tip "Tip: KVL with DC networks" + + For DC networks, replace the series reactance $x_l$ by the series resistance $r_l$. + +!!! note "Note: Retrieving the PTDF matrix" + + The PTDF matrix of sub-networks (i.e. synchronous zones) can be calculated with [`sn.calculate_PTDF()`][pypsa.networks.SubNetwork.calculate_PTDF] method. + +!!! tip "Tip: Using the [`Link`][pypsa.components.Links] component for Net Transfer Capacities (NTCs)" + + For simplified transmission representation using Net Transfer Capacities (NTCs), use the [`Link`][pypsa.components.Links] component with controllable power flow like a transport model. The [`Link`][pypsa.components.Links] component can also be used to represent a point-to-point HVDC link. + +!!! note "Note: Handling impedance changes with transmission expansion" + + If $F_l$ is also subject to optimisation (`s_nom_extendable=True`), the impedance $x$ of the line is **not** automatically changed with the capacity (e.g. to represent added parallel lines). + However, the extension [`n.optimize.optimize_transmission_expansion_iteratively()`][pypsa.optimization.OptimizationAccessor.optimize_transmission_expansion_iteratively] covers this through an + iterative process as done Hagspiel et al. (2014)[^2] . + + +## Loss Approximation + +The AC transmission losses $\psi_{l,t}$ are approximated using a tangent-based linearization of the loss parabola: + +$$ +\psi_{l,t} = r_{l} p_{l,t}^2 +$$ + +where $r_l$ is the resistance, following Neumann et al. (2022)[^3]. + +The approximation uses piecewise linear constraints: + +$$ +0 \leq \psi_{l,t} \leq r_{l} (\bar{p}_{l,t} \overline{P}_{l})^2 \quad \forall l,t +$$ + +$$ +\psi_{l,t} \geq m_k \cdot p_{l,t} + a_k \quad \forall l,t,\ k = 1, \dots, n +$$ + +$$ +\psi_{l,t} \geq -m_k \cdot p_{l,t} + a_k \quad \forall l,t,\ k = 1, \dots, n +$$ + +For each segment $k$ of the total $n$ segments, the slope $m_k$ and offset $a_k$ are derived as: + +$$ +\psi_{l,t}(k) = r_{l} \left(\frac{k}{n} \cdot \bar{p}_{l,t} \overline{P}_{l}) \right)^2 +$$ + +$$ +m_k = \frac{d \psi_{l,t}(k)}{dk} = 2 r_{l} \left(\frac{k}{n} \cdot \bar{p}_{l,t} \overline{P}_{l} \right) +$$ + +$$ +a_k = \psi_{l,t}(k) - m_k \left(\frac{k}{n} \cdot \bar{p}_{l,t} \overline{P}_{l} \right) +$$ + +The losses also modify the [power balance](energy-balance.md) by adding the term to its left-hand side + +$$ +-0.5 \cdot \sum_{l} |K_{n,l}| \cdot \psi_{l,t} \quad \forall n,t +$$ + +splitting losses equally between both connection points. + +The dispatch limits of $p_{\ell,t}$ are now subtracted by $\psi_{l,t}$. + +These constraints are set in the function `define_loss_constraints()`. + +The transmission loss approximation is not activated by default, but must be +enabled by providing a number of tangents in [`n.optimize()`][pypsa.optimization.OptimizationAccessor.__call__]. + +``` py +n.optimize(transmission_losses=3) +``` + +The higher the number of tangents, the more accurate the approximation, but also +the more constraints are added to the optimisation problem. Typically, 2-4 +tangents are sufficient for a reasonably accurate approximation. + +!!! hint "Hint: Calculating transmission losses" + + The losses can be calculated with `n.lines_t.p0 + n.lines_t.p1`. + + +??? note "Mapping of symbols to component attributes" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $p_{l,t}$ | `n.lines_t.p0` or `n.transformers_t.p0` | Decision variable | + | $\psi_{l,t}$ | `n.lines_t.losses` or `n.transformers_t.losses` | Decision variable | + | $\bar{p}_{l,t}$ | `n.lines_t.s_max_pu` or `n.transformers_t.s_max_pu` | Parameter |# + | $\bar{P}_{l}$ | `n.lines.s_nom_opt` and `n.transformers.s_nom_opt` (if extendable) or `n.lines.s_nom` or `n.transformers.s_nom` (if non-extendable) | Decision variable / Parameter | + | $x_l$ | `n.lines.x_pu_eff` or `n.transformers.x_pu_eff` | Parameter | + | $r_l$ | `n.lines.r_pu_eff` or `n.transformers.r_pu_eff` | Parameter | + | $C_{l,c}$ | Cycle matrix calculated by `find_cycles()` | Parameter | + | $K_{n,l}$ | Incidence matrix calculated by [`n.incidence_matrix()`][pypsa.networks.SubNetwork.incidence_matrix] | Parameter | + +## Examples + + +
+ +- :material-notebook:{ .lg .middle } **Meshed AC-DC Networks** + + --- + + Builds a stylized 3-node AC network coupled via AC-DC converters to a 3-node DC network. + + [:octicons-arrow-right-24: Go to example](../../examples/ac-dc-lopf.ipynb) + + +- :material-notebook:{ .lg .middle } **Negative LMPs from Line Congestion** + + --- + + Explores how Kirchhoff's Voltage Law can lead to negative locational marginal prices when lines are congested. + + [:octicons-arrow-right-24: Go to example](../../examples/negative-prices-kvl-baker.ipynb) + +
+ +[^1]: J. Hörsch, H. Ronellenfitsch, D. Witthaut, T. Brown (2018), [Linear optimal power flow using cycle flows](https://www.sciencedirect.com/science/article/abs/pii/S0378779617305138), Electric Power Systems Research, 158, 126-135, doi:10.1016/j.epsr.2017.12.034. + +[^2]: S. Hagspiel, C. Jägemann, D. Lindenberger, T. Brown, S. Cherevatskiy, E. Tröster (2014), [Cost-optimal power system extension under flow-based market coupling](https://doi.org/10.1016/j.energy.2014.01.025), Energy, 66, 654-666, doi:10.1016/j.energy.2014.01.025. + +[^3]: F. Neumann, V. Hagenmeyer, T. Brown (2022), [Assessments of linear power flow and transmission loss approximations in coordinated capacity expansion problems](https://doi.org/10.1016/j.apenergy.2022.118859), Applied Energy, 314, 118859, doi:10.1016/j.apenergy.2022.118859. diff --git a/PyPSA/source/docs/user-guide/optimization/stochastic.md b/PyPSA/source/docs/user-guide/optimization/stochastic.md new file mode 100644 index 0000000000000000000000000000000000000000..b56f50c45db5ef925dcc0999abeea6154f181967 --- /dev/null +++ b/PyPSA/source/docs/user-guide/optimization/stochastic.md @@ -0,0 +1,493 @@ + + +# Stochastic Optimization + +## Overview + +Stochastic optimization in PyPSA enables modeling and solving energy system planning problems under uncertainty. This capability addresses scenarios where parameters such as fuel prices, renewable energy availability, demand patterns, or technology costs are uncertain at the time investment decisions must be made. + +PyPSA implements a **two-stage stochastic programming framework** with scenario trees, allowing users to optimize investment decisions (first-stage) that are feasible across multiple possible future realizations (scenarios) of uncertain parameters and minimize expected system costs. + +## Risk-neutral Two-Stage Stochastic Programming + +### Mathematical Formulation + +The stochastic optimization problem in PyPSA follows the standard two-stage risk-neutral stochastic programming formulation: + +$$ +\begin{align} +\min_{x} \quad & c^T x + \sum_{s \in S} p_s Q(x, \xi_s) \\ +\text{s.t.} \quad & A x = b \\ +& x \geq 0 +\end{align} +$$ + +Where: + +- $x$: First-stage (here-and-now) decision variables (e.g., investment decisions in component capacities) +- $\xi_s$: Random parameter realizations in scenario $s$ +- $p_s$: Probability of scenario $s$ occurring +- $Q(x, \xi_s)$: Second-stage recourse function for scenario $s$ +- $S$: Set of all scenarios + +The second-stage problem $Q(x, \xi_s)$ represents: + +$$ +\begin{align} +Q(x, \xi_s) = \min_{y_s} \quad & q_s^T y_s \\ +\text{s.t.} \quad & W y_s = h_s - T x \\ +& y_s \geq 0 +\end{align} +$$ + +Where: + +- $y_s$: Second-stage (wait-and-see) decision variables for scenario $s$ (e.g., energy dispatch decisions) +- $q_s$: Second-stage cost coefficients for scenario $s$ +- $W$: Recourse matrix (coefficient matrix for second-stage variables $y_s$) +- $T$: Technology matrix (coefficient matrix for first-stage variables $x$ in second-stage constraints) +- $h_s$: Right-hand-side vector for scenario $s$ + +The second-stage constraints $W y_s = h_s - T x$ show how first-stage decisions $x$ affect the feasible region for second-stage variables $y_s$ in each scenario. + +The scenario tree in PyPSA splits into **here-and-now** investment decisions (t=0) and **wait-and-see** dispatch decisions (t=1): + +| Stage | Decision Type | Variables | Information | Examples | +|-------|---------------|-----------|-------------|-----------| +| **t=0** | **Here-and-now** | Investment decisions ($x$) - Common across scenarios | Uncertain parameters unknown, probability distribution known | Generator capacity, transmission expansion, storage capacity | +| **t=1** | **Wait-and-see** | Dispatch decisions ($y_s$) - Scenario-specific | Uncertain parameters revealed, perfect information for scenario $s$ | Dispatch, storage operation, load shedding | + + +!!! note "Limitation: Investment and dispatch stages are fixed" + + Currently, PyPSA only considers dispatch decisions as second-stage variables and investment decisions as first-stage variables. Support for second-stage investment decisions (e.g., scenario-dependent capacity additions as recourse measures) as well as first-stage dispatch decisions (e.g., for stochastic optimisation in purely operational settings with forecast uncertainty) is planned for future releases. + +!!! note "Consideration of risk preferences and robust optimisation" + + The default stochastic programming implementation in PyPSA minimizes expected costs across scenarios weighted by their probabilities. This approach considers risk-neutral decision making. + PyPSA also supports changing risk preference through Conditional Value at Risk (CVaR)-based risk-averse optimization, allowing users to account for extreme outcomes and tail risks in their optimization. + +For a comprehensive treatment of two-stage stochastic programming theory and methods, see Birge and Louveaux (2011).[^1] + +### Implementation + +Let us consider a single-node capacity expansion model in the style of [model.energy](https://model.energy). This stylized model calculates the cost of meeting an hourly electricity demand time series from a combination of wind power, solar power, battery and hydrogen storage as well as load shedding. See this [example](../../examples/capacity-expansion-planning-single-node.ipynb). We add a gas power plant as additional technology option and solve the model with given load and renewable availability profiles as a deterministic problem. Afterwards, we inspect the optimal objective value and expanded capacities. + +``` py +>>> import pypsa +>>> +>>> n = pypsa.examples.model_energy() +>>> n.add( +... "Generator", +... "gas", +... carrier="gas", +... bus="electricity", +... p_nom=5000, +... efficiency=0.5, +... marginal_cost=100, +... ) +>>> +>>> n.optimize(log_to_console=False) +('ok', 'optimal') +>>> +>>> cap_deterministic = n.statistics.optimal_capacity().div(1e3).round(1) +>>> cap_deterministic +component carrier +Generator gas 5.0 + load shedding 10.9 + solar 22.8 + wind 20.2 +Link electrolysis 0.1 + turbine 1.4 +StorageUnit battery storage 11.0 +Store hydrogen storage 245.4 +dtype: float64 +>>> +>>> obj_deterministic = n.objective / 1e9 +>>> obj_deterministic # doctest: +ELLIPSIS +5.357... +``` + +### Scenario Definition + +Now suppose we want to consider the case that with a 10% probability, a volcano erupts which reduces the solar capacity factor time series uniformly by 70%. For such a scenario, we can use PyPSA's stochastic optimization functionality. First, we need to define the scenarios with probabilities that must sum to 1: + +``` py +>>> n.set_scenarios({"volcano": 0.1, "no_volcano": 0.9}) +``` + +We can check that the scenarios have been set correctly: + +``` py +>>> n.has_scenarios +True +``` + +``` py +>>> n.scenarios +Index(['volcano', 'no_volcano'], dtype='object', name='scenario') +``` + +``` py +>>> n.scenario_weightings + weight +scenario +volcano 0.1 +no_volcano 0.9 +``` + +The key change after calling `n.set_scenarios()` is that all component data is broadcasted across all scenarios. All component `pandas.DataFrame` objects gain a "scenario" dimension as outermost index level. + +``` py +>>> n.generators[["bus", "marginal_cost", "efficiency"]] # doctest: +ELLIPSIS + bus marginal_cost efficiency +scenario name +volcano load shedding electricity 2000.0 1.0 + wind electricity 0.0 1.0 + solar electricity 0.0 1.0 + gas electricity 100.0 0.5 +no_volcano load shedding electricity 2000.0 1.0 + wi... +``` + +``` py +>>> n.generators_t.p_max_pu.head(3) +scenario volcano no_volcano +name solar wind solar wind +snapshot +2019-01-01 00:00:00 0.0 0.1846 0.0 0.1846 +2019-01-01 03:00:00 0.0 0.3146 0.0 0.3146 +2019-01-01 06:00:00 0.0 0.4957 0.0 0.4957 +``` + +!!! warning + + - Scenarios must be set **after** adding all network components. + - They are **immutable** once set. + +### Parameter Updates + +So far, all data is the same across scenarios. Now that we have a separate index level for +the scenarios, we can modify scenario-specific parameters. In our case, we want to reduce the solar capacity factor in the "volcano" scenario by 70%: + +``` py +>>> n.generators_t.p_max_pu.loc[:, ("volcano", "solar")] *= 0.3 +>>> n.generators_t.p_max_pu.loc["2019-06-21"] +scenario volcano no_volcano +name solar wind solar wind +snapshot +2019-06-21 00:00:00 0.0000 0.1240 0.000 0.1240 +2019-06-21 03:00:00 0.0051 0.0843 0.017 0.0843 +2019-06-21 06:00:00 0.0864 0.0441 0.288 0.0441 +2019-06-21 09:00:00 0.1710 0.0405 0.570 0.0405 +2019-06-21 12:00:00 0.1482 0.0432 0.494 0.0432 +2019-06-21 15:00:00 0.0720 0.0347 0.240 0.0347 +2019-06-21 18:00:00 0.0030 0.0775 0.010 0.0775 +2019-06-21 21:00:00 0.0000 0.1455 0.000 0.1455 +``` + +### Optimization + +When we now call `n.optimize()`, the network is solved as a stochastic problem considering all defined scenarios and their respective probabilities. + +``` py +>>> n.optimize() +('ok', 'optimal') +``` + +PyPSA creates stochastic optimization models by reformulating the two-stage problem as a large-scale deterministic equivalent with scenario-indexed variables and constraints: + +``` py +>>> n.model +Linopy LP model +=============== + +Variables: +---------- + * Generator-p_nom (name) + * Link-p_nom (name) + * Store-e_nom (name) + * StorageUnit-p_nom (name) + * Generator-p (scenario, name, snapshot) + * Link-p (scenario, name, snapshot) + * Store-e (scenario, name, snapshot) + * StorageUnit-p_dispatch (scenario, name, snapshot) + * StorageUnit-p_store (scenario, name, snapshot) + * StorageUnit-state_of_charge (scenario, name, snapshot) + * Store-p (scenario, name, snapshot) + +Constraints: +------------ + * Generator-ext-p_nom-lower (name, scenario) + * Link-ext-p_nom-lower (name, scenario) + * Store-ext-e_nom-lower (name, scenario) + * StorageUnit-ext-p_nom-lower (name, scenario) + * Generator-fix-p-lower (scenario, name, snapshot) + * Generator-fix-p-upper (scenario, name, snapshot) + * Generator-ext-p-lower (scenario, name, snapshot) + * Generator-ext-p-upper (scenario, name, snapshot) + * Link-ext-p-lower (scenario, name, snapshot) + * Link-ext-p-upper (scenario, name, snapshot) + * Store-ext-e-lower (scenario, name, snapshot) + * Store-ext-e-upper (scenario, name, snapshot) + * StorageUnit-ext-p_dispatch-lower (scenario, name, snapshot) + * StorageUnit-ext-p_dispatch-upper (scenario, name, snapshot) + * StorageUnit-ext-p_store-lower (scenario, name, snapshot) + * StorageUnit-ext-p_store-upper (scenario, name, snapshot) + * StorageUnit-ext-state_of_charge-lower (scenario, name, snapshot) + * StorageUnit-ext-state_of_charge-upper (scenario, name, snapshot) + * Bus-nodal_balance (name, scenario, snapshot) + * StorageUnit-energy_balance (scenario, name, snapshot) + * Store-energy_balance (scenario, name, snapshot) + +Status: +------- +ok +``` + +As investment variables (i.e. `p_nom`, `s_nom`, `e_nom`) are scenario-independent first-stage decisions, they do not have a scenario dimension (i.e. are common across all scenarios). This is also called non-anticipativity constraint. Operational variables, on the other hand, are fully scenario-indexed. All constraints are duplicated across scenarios with scenario-specific parameters and the objective function weights scenario costs by their probabilities. + +!!! note "Problem Scaling Properties" + + - **Investment variables** scale as O(components) + - **Operational variables** scale as O(scenarios × components × snapshots) + - **Constraint matrix size** grows almost linearly with the number of scenarios. + + +### Evaluation + +After solving the stochastic optimization problem, we can evaluate the results. The optimal capacities for each scenario can, among other ways, be accessed via `n.statistics.optimal_capacity()`: + +``` py +>>> cap_stochastic = ( +... n.statistics.optimal_capacity().div(1e3).round(1).unstack(level="scenario") +... ) +>>> cap_stochastic +scenario no_volcano volcano +component carrier +Generator gas 5.0 5.0 + load shedding 10.9 10.9 + solar 21.1 21.1 + wind 22.0 22.0 +Link electrolysis 0.3 0.3 + turbine 1.5 1.5 +StorageUnit battery storage 10.2 10.2 +Store hydrogen storage 221.0 221.0 +``` + +Note that the optimal capacities are the same across scenarios for investment variables, reflecting the non-anticipativity constraint. However, compared to the previous deterministic model, the optimal capacities are different: + +``` py +>>> cap_stochastic.iloc[:, 0].div(cap_deterministic).round(2) +component carrier +Generator gas 1.00 + load shedding 1.00 + solar 0.93 + wind 1.09 +Link electrolysis 3.00 + turbine 1.07 +StorageUnit battery storage 0.93 +Store hydrogen storage 0.90 +dtype: float64 +``` + +For example, we see less solar and batteries and more wind and hydrogen storage. +The system costs are also 3% larger because the model has to hedge against uncertainty to remain feasible. + +``` py +>>> n.objective / 1e9 / obj_deterministic # doctest: +ELLIPSIS +1.03... +``` + +Finally, we can also see in the energy balance that more gas is used in case the volcano erupts to compensate for the reduced solar generation. Additionally, wind curtailment is reduced: + +``` py +>>> n.statistics.energy_balance().div(1e6).round(2).unstack(level="scenario") # doctest: +ELLIPSIS +scenario no_volcano volcano +component carrier bus_carrier +Generator gas electricity 10.14 21.20 + load shedding electricity 0.06 0.28 +... +``` + +### Metrics + +When working with stochastic optimization, it can be useful to quantify the value of accounting for uncertainty compared to deterministic approaches. There are several such value of information (VOI) metrics. + +**Expected Value of Perfect Information (EVPI)**: + +$$\text{EVPI} = \mathbb{E}[\text{WS}] - \text{SP}$$ + +The EVPI measures the maximum value of perfect information about uncertain parameters. It compares the expected cost of the wait-and-see (WS) solutions—where the decision maker knows which scenario will occur before making any decisions—against the stochastic programming (SP) solution. The EVPI represents an upper bound on what should be paid for improved forecasting or information gathering systems. By definition, EVPI is always non-negative, since perfect information cannot increase expected costs. + +**Expected Cost of Ignoring Uncertainty (ECIU) / Value of Stochastic Solution (VSS)**: + +$$\text{ECIU} = \text{EEV} - \text{SP}$$ + +The ECIU quantifies the cost of ignoring uncertainty by comparing the expected cost of the expected value (EEV) solution—where decisions are made using mean parameter values—against the stochastic programming solution. This metric demonstrates the value of using stochastic optimization instead of deterministic optimization with expected parameter values. A higher ECIU suggests that stochastic modeling provides substantial improvements over deterministic approaches. The ECIU is always non-negative. + +The elements $\text{WS}$, $\text{SP}$ and $\text{EVPI}$ satisfy the following inequality chain: + +$$ +\text{WS} \leq \text{SP} \leq \text{EEV} +$$ + +For detailed treatment of these measures and their economic interpretation, see Birge and Louveaux (2011), Chapter 4.[^1] + + +## Risk Preferences with Conditional Value-at-Risk (CVaR) + +The risk-neutral stochastic optimization introduced above minimizes expected systems costs and can leave the system exposed to rare but expensive outcomes. + +PyPSA also supports risk-averse stochastic optimization using Conditional Value-at-Risk (CVaR). CVaR is a risk measure that captures the expected cost in the worst-case tail of the distribution. It adds a convex penalty term for expensive outcomes, thereby shifting investments towards solutions that hedge against extreme scenarios. Users control how much of the worst-case tail to consider and how strongly to weight it relative to the expected cost optimization. + +### Mathematical Formulation + +The risk-averse stochastic optimization extends the risk-neutral formulation by including CVaR in the objective: + +$$ +\min_x \quad c^T x \;+\; (1-\omega)\sum_{s \in S} p_s Q(x,\xi_s) \;+\; \omega \,\mathrm{CVaR}_\alpha. +$$ + +Where: + +- $\alpha \in (0,1)$ is the confidence level that determines the probability threshold for the worst-case scenarios to be considered in the CVaR calculation. A higher $\alpha$ places more emphasis on extreme outcomes. CVaR averages costs over the worst $(1-\alpha)$ share of the distribution. At $\alpha = 1$, the denominator is zero (undefined). At $\alpha = 0$, the confidence level covers the entire distribution and the formulation degenerates (equivalent to risk-neutral expectation). + +- $\omega \in [0,1]$ is the tail weighting. It balances expected cost and tail cost by controlling how strongly the identified worst-case scenarios are weighted in a convex combination. The closed interval includes meaningful endpoints: $\omega=0$ corresponds to risk-neutral optimization, while $\omega=1$ corresponds to pure CVaR minimization. + +The CVaR at confidence level $\alpha$ is defined as the expected loss conditional on being in the worst $1-\alpha$ of outcomes: + +$$ +\mathrm{CVaR}_\alpha(Q) = \mathbb{E}[\,Q(x,\xi_s)\;|\;Q(x,\xi_s)\geq \,\textrm{VaR}_\alpha(Q)]. +$$ + +It can equivalently be written as an optimization problem, see Rockafellar & Uryasev (2002)[^2]: + +$$ +\mathrm{CVaR}_\alpha(Q) = \min_{\theta \in \mathbb{R}} \Big[ \theta + \frac{1}{1-\alpha} \sum_{s \in S} p_s \max\{Q(x,\xi_s)-\theta,0\} \Big]. +$$ + +To embed CVaR in a linear program, some auxiliary variables and constraints are introduced to enforce the definition: + +Variables: + +- $\theta$ (free): the Value-at-Risk cutoff at level $\alpha$. +- $\mathrm{CVaR}_\alpha$ (free): the CVaR at level $\alpha$. +- $a_s \geq 0$: excess loss of scenario $s$ above $\theta$. + +Constraints: + +$$ +a_s \ge Q(x,\xi_s) - \theta \quad \forall s \in S, +$$ + +$$ + \theta + \frac{1}{1-\alpha} \sum_{s \in S} p_s a_s \;\le\; \mathrm{CVaR}_\alpha. +$$ + +At the optimal solution, the auxiliary variable $\theta$ takes the role of the $\alpha$-quantile of scenario costs, i.e. the Value-at-Risk at level $\alpha$. Scenarios with costs above $\theta$ have $a_s > 0$ and thus contribute to the tail. The CVaR variable then represents the expected cost conditional on being in this worst $1-\alpha$ fraction of scenarios. In this way, the optimization automatically identifies which scenarios belong to the tail and penalizes them in the objective. + +!!! note "Robust-like optimization" + A robust "optimize against the worst scenario" setting is approximated by choosing $\omega=1$ and setting $\alpha = 1 - p(\text{worst scenario})$, so that the tail includes only the worst case. + +!!! note "Auxiliary variables" + The auxiliary variables are not written back to component tables; they are internal to the optimization model. + +!!! warning "Limitations" + CVaR is not available with quadratic marginal costs, since the resulting quadratic constraints are not supported by `linopy`. + +### Implementation + +Continuing the volcano example from above, risk preferences can be explored using the `n.set_risk_preference()` method. Let's compare different risk attitudes: + +```py +# Risk-neutral baseline (expected cost) +>>> n.optimize() + +# CVaR with moderate risk aversion +>>> n.set_risk_preference(alpha=0.9, omega=0.5) +>>> n.optimize() + +# Edge case: CVaR with omega=0 equals risk-neutral +>>> n.set_risk_preference(alpha=0.9, omega=0.0) +>>> n.optimize() + +# Edge case: CVaR capturing the results of the worst-case scenario +# (omega=1, alpha so only worst scenario is in tail) +>>> p_worst = float(n.scenario_weightings.loc["volcano", "weight"]) # 0.1 +>>> n.set_risk_preference(alpha=1 - p_worst, omega=1.0) +>>> n.optimize() +``` + +If no risk preference is set, the model reverts to risk-neutral stochastic optimization. + +#### Results and Interpretation + +With moderate risk aversion (`alpha=0.9, omega=0.5`), the optimization shifts investments to hedge against expensive tail scenarios: + +```py +>>> cap_cvar = ( +... n.statistics.optimal_capacity().div(1e3).round(1).unstack(level="scenario") +... ) +>>> cap_diff = cap_cvar.iloc[:, 0] - cap_stochastic.iloc[:, 0] +>>> print("Capacity diff (omega=0.5 - neutral) [GW]:") +>>> print(cap_diff.round(2)) +component carrier +Generator load shedding 0.00 + solar 10.88 + wind 4.64 +Link electrolysis 0.59 + turbine 0.36 +StorageUnit battery storage -1.31 +Store hydrogen storage 1579.99 +``` + +Economic interpretation: CVaR penalizes costly tail operations (worst-scenario OPEX, such as load shedding or expensive peakers). The model invests more upfront to reduce exposure to these rare but severe outcomes. In this case, the cheapest hedge is to overbuild solar and complement it with long-duration hydrogen storage, even though solar itself is affected in the "volcano" scenario. This combination minimizes worst-case costs. +In other words: *risk aversion hedges against the risky scenario by shifting costs from tail OPEX to upfront CAPEX.* + +### Metrics + +The insurance premium measures the increase in expected total system costs relative to the risk-neutral solution. + +$$ +\mathrm{Premium}(\alpha, \omega) = \big( c^T x + \sum_{s \in S} p_s Q(x,\xi_s) \big)_{(\alpha,\omega)} - \big( c^T x + \sum_{s \in S} p_s Q(x,\xi_s) \big)_{(\omega=0)} +$$ + +In other words, $\mathrm{Premium}(\alpha, \omega)$ is the additional expected cost of applying a hedging strategy (or of choosing a more robust portfolio/policy) compared to a baseline. + +The tail risk reduction measures the decrease in CVaR at confidence level $\alpha$. + +$$ +\Delta \,\mathrm{CVaR}_{\alpha} = \mathrm{CVaR}_{\alpha} \big(Q(x,\xi_s)\big)_{(\omega=0)} - \mathrm{CVaR}_{\alpha} \big(Q(x,\xi_s)\big)_{(\alpha,\omega)} +$$ + +In other words, $\Delta \,\mathrm{CVaR}_{\alpha}$ the amount of tail risk that is reduced thanks to the hedging strategy. + +A useful combined indicator is the risk-hedging cost, which measures the additional expected cost per unit of tail risk avoided: + +$$ + \mathrm{RHC}(\alpha, \omega) = \frac{\text{Premium}(\alpha, \omega)}{\Delta \,\mathrm{CVaR}_\alpha} +$$ + + + + +## Examples + +
+ +- :material-notebook:{ .lg .middle } **Stochastic Optimization** + + Demonstrates investment planning under uncertainty with scenario-based two-stage stochastic optimization. + + [:octicons-arrow-right-24: Go to example](../../examples/stochastic-optimization.ipynb) + +
+ +[^1]: Birge, J. R., & Louveaux, F. (2011). [Introduction to Stochastic Programming](https://link.springer.com/book/10.1007/978-1-4614-0237-4). + +[^2]: Rockafellar, R. T., & Uryasev, S. (2002). [Conditional Value-at-Risk for General Loss Distributions](https://doi.org/10.1016/S0378-4266(02)00271-6). Journal of Banking & Finance, 26(7), 1443–1471. diff --git a/PyPSA/source/docs/user-guide/optimization/storage.md b/PyPSA/source/docs/user-guide/optimization/storage.md new file mode 100644 index 0000000000000000000000000000000000000000..dd99c0f1fe0724a480b5fdc2c86950d2503361a5 --- /dev/null +++ b/PyPSA/source/docs/user-guide/optimization/storage.md @@ -0,0 +1,204 @@ + + +Two components are available for modelling storage: [`Store`][pypsa.components.Stores] and [`StorageUnit`][pypsa.components.StorageUnits]. See +the [Store Component](../components/stores.md) and [Storage Unit Component](../components/storage-units.md) descriptions for an overview of +the conceptual differences. + + +## Stores + +Stores have two time-dependent variables, the store dispatch $h_{n,s,t}$ in MW and +the store energy level $e_{n,s,t}$ in MWh. + +The store dispatch $h_{n,s,t}$ is unconstrained, i.e. it can be positive (discharging) or negative (storing): + +$$-\infty \leq h_{n,s,t} \leq +\infty$$ + +The store energy level $e_{n,s,t}$ is constrained by maximum and minimum energy levels. + +For **non-extendable** stores (`e_nom_extendable=False`), the energy level is constrained by: + +| Constraint | Dual Variable | Name | +|------------|---------------|------| +| $e_{n,s,t} \geq \underline{e}_{n,s,t} \hat{e}_{n,s}$ | `n.stores_t.mu_lower` | `Store-fix-e-lower` | +| $e_{n,s,t} \leq \bar{e}_{n,s,t} \hat{e}_{n,s}$ | `n.stores_t.mu_upper` | `Store-fix-e-upper` | + +where $\hat{e}_{n,s}$ is the nominal energy capacity, $\underline{e}_{n,s,t}$ and $\bar{e}_{n,s,t}$ are time-dependent restrictions on the energy level given per unit of nominal capacity. + +These constraints are set in the function `define_operational_constraints_for_non_extendables()`. + +For **extendable** stores (`e_nom_extendable=True`), the energy level is constrained by: + +| Constraint | Dual Variable | Name | +|------------|---------------|------| +| $e_{n,s,t} \geq \underline{e}_{n,s,t} E_{n,s}$ | `n.stores_t.mu_lower` | `Store-ext-e-lower` | +| $e_{n,s,t} \leq \bar{e}_{n,s,t} E_{n,s}$ | `n.stores_t.mu_upper` | `Store-ext-e-upper` | + +where $E_{n,s}$ is the energy capacity to be optimised. + +These constraints are set in the function [`define_operational_constraints_for_extendables`. + +!!! note "Capacity limits of [`Store`][pypsa.components.Stores] components" + + For handling of capacity limits, see the section. + +The store energy level can also be fixed to a certain value $\tilde{e}_{n,s,t}$: + +| Constraint | Dual Variable | Name | +|------------|---------------|------| +| $e_{n,s,t} = \tilde{e}_{n,s,t}$ | only in `n.model` | `Store-e_set` | + +These constraints are set in the function `define_fixed_operation_constraints()`. + +The power and energy variables are related by the **storage consistency** equation (`Store-energy_balance`): + +$$e_{n,s,t} = \eta_{\textrm{stand},n,s}^{w_t^s} e_{n,s,t-1} - w_t^s h_{n,s,t} \quad \leftrightarrow \quad \lambda_{n,s,t}^\textrm{MSV}$$ + +where $\eta_{\textrm{stand},n,s}$ represents the storage efficiency after accounting for standing losses (e.g. thermal losses in thermal storage) per hour and $w_t^s$ is the snapshot weighting (defaulting to 1 hour). The dual variable $\lambda_{n,s,t}^\textrm{MSV}$ represents the marginal storage value (also known as water value in the hydro-electricity literature). + +These constraints are set in the function `define_store_constraints()`. + +Furthermore, there are two options for specifying the initial energy level $e_{n,s,t=-1}$: + +1. Set `e_cyclic=False` (default) and the value of `e_initial` in MWh. + +$$e_{n,s,t=-1} = e_{n,s,\textrm{initial}}$$ + +2. Set `e_cyclic=True` and the optimisation sets the initial energy to be equal to the final energy level. + +$$e_{n,s,t=-1} = e_{n,s,t=|T|-1}$$ + +!!! note "[`Store`][pypsa.components.Stores] cyclicity constraints with multiple investment periods" + + For how storage cyclicity constraints are handled with multiple investment periods, see the [Pathway Planning](pathway-planning.md) section. + +??? note "Mapping of symbols to component attributes" + + | Symbol | Attribute | Type | + |--------|-----------|------| + | $h_{n,s,t}$ | `n.stores_t.p` | Decision Variable | + | $e_{n,s,t}$ | `n.stores_t.e` | Decision Variable | + | $E_{n,s}$ | `n.stores.e_nom_opt` | Decision Variable | + | $\lambda_{n,s,t}^\textrm{MSV}$ | `n.stores_t.mu_energy_balance` | Dual Variable | + | $\hat{e}_{n,s}$ | `n.stores.e_nom` | Parameter | + | $\underline{e}_{n,s,t}$ | `n.stores_t.e_min_pu` | Parameter | + | $\bar{e}_{n,s,t}$ | `n.stores_t.e_max_pu` | Parameter | + | $\tilde{e}_{n,s,t}$ | `n.stores_t.e_set` | Parameter | + | $\eta_{\textrm{stand},n,s}$ | `n.stores.standing_loss` | Parameter | + | $w_t^s$ | `n.snapshot_weightings.stores` | Parameter | + +## Storage Units + +Storage units have three time-dependent variables, the discharge $h_{n,s,t}^-$ in MW, the charge $h_{n,s,t}^+$ in MW and the state of charge $soc_{n,s,t}$ in MWh. + +With a storage unit the nominal state of charge may not be independently optimised from the nominal power output (they are linked by the maximum hours parameter `max_hours`) and the nominal charging power is linked to the maximum discharging power. + +For **non-extendable** storage units (`p_nom_extendable=False`), the charge ($h_{n,s,t}^+$), discharge ($h_{n,s,t}^-$), and state of charge ($soc_{n,s,t}$) variables are constrained by: + +| Constraint | Dual Variable | Name | +|------------|---------------|------| +| $h_{n,s,t}^- \geq 0$ | only in `n.model` | `StorageUnit-p_dispatch-lower` | +| $h_{n,s,t}^- \leq \bar{h}_{n,s,t} \hat{h}_{n,s}$ | only in `n.model` | `StorageUnit-p_dispatch-upper` | +| $h_{n,s,t}^+ \geq 0$ | only in `n.model` | `StorageUnit-p_store-lower` | +| $h_{n,s,t}^+ \leq - \underline{h}_{n,s,t} \hat{h}_{n,s}$ | only in `n.model` | `StorageUnit-p_store-upper` | +| $soc_{n,s,t} \geq 0$ | only in `n.model` | `StorageUnit-state_of_charge-lower` | +| $soc_{n,s,t} \leq r_{n,s} \hat{h}_{n,s}$ | only in `n.model` | `StorageUnit-state_of_charge-upper` | + + +where $\hat{h}_{n,s}$ is the nominal power capacity, $\bar{h}_{n,s,t}$ is a time-dependent restriction on the discharge per unit of nominal capacity, $\underline{h}_{n,s,t}$ is a time-dependent restriction on the maximum charge per unit of nominal capacity (usually negative, -1 by default), and $r_{n,s}$ is the number of hours at nominal power that fill the state of charge. + +These constraints are set in the function `define_operational_constraints_for_non_extendables()`. + +For **extendable** storage units (`p_nom_extendable=True`), the charge and discharge variables are constrained by: + +| Constraint | Dual Variable | Name | +|------------|---------------|------| +| $h_{n,s,t}^- \geq 0$ | only in `n.model` | `StorageUnit-ext-p_dispatch-lower` | +| $h_{n,s,t}^- \leq \bar{h}_{n,s,t}H_{n,s}$ | only in `n.model` | `StorageUnit-ext-p_dispatch-upper` | +| $h_{n,s,t}^+ \geq 0$ | only in `n.model` | `StorageUnit-ext-p_store-lower` | +| $h_{n,s,t}^+ \leq - \underline{h}_{n,s,t} H_{n,s}$ | only in `n.model` | `StorageUnit-ext-p_store-upper` | + +where $H_{n,s}$ is the power capacity to be optimised. + +These constraints are set in the function `define_operational_constraints_for_extendables()`. + +!!! note "Capacity limits of [`StorageUnit`][pypsa.components.StorageUnits] components" + + For handling of capacity limits, see the section. + +All three variables can also be fixed to certain values $\tilde{h}_{n,s,t}^-$, $\tilde{h}_{n,s,t}^+$, and $\tilde{soc}_{n,s,t}$: + +| Constraint | Dual Variable | Name | +|------------|---------------|------| +| $h_{n,s,t}^- = \tilde{h}_{n,s,t}^-$ | only in `n.model` | `StorageUnit-p_dispatch_set` | +| $h_{n,s,t}^+ = \tilde{h}_{n,s,t}^+$ | only in `n.model` | `StorageUnit-p_store_set` | +| $soc_{n,s,t} = \tilde{soc}_{n,s,t}$ | `n.storage_units_t.mu_state_of_charge_set` |`StorageUnit-state_of_charge_set` | + +An example use case would be if a storage unit must be empty and/or full every day. + +These constraints are set in the function `define_fixed_operation_constraints()`. + +The charging, discharging and state of charge variables are related by (`StorageUnit-energy_balance`): + +$$\begin{gather*}soc_{n,s,t} = \eta_{\textrm{stand};n,s}^{w_t^s} soc_{n,s,t-1}\\ ++ \eta_{\textrm{store};n,s} w_t^s h_{n,s,t}^+ - \eta^{-1}_{\textrm{dispatch};n,s} w_t^s h_{n,s,t}^- \\ + w_t^s\textrm{inflow}_{n,s,t} - w_t^s\textrm{spillage}_{n,s,t} \quad \leftrightarrow \quad \lambda_{n,s,t}^\textrm{MSV} \end{gather*}$$ + +$\eta_{\textrm{stand};n,s}$ is the standing efficiency (e.g. due to thermal losses for thermal storage). $\eta_{\textrm{store};n,s}$ and $\eta_{\textrm{dispatch};n,s}$ are the efficiencies for power going into and out of the storage unit, and $w_t^s$ is the snapshot weighting for stores (e.g. 1 hour). +The dual variable $\lambda_{n,s,t}^\textrm{MSV}$ represents the marginal storage value (also known as water value in the hydro-electricity literature). + +These constraints are set in the function `define_storage_unit_constraints()`. + +Furthermore, there are two options for specifying the initial state of charge $soc_{n,s,t=-1}$: + +1. Set `cyclic_state_of_charge=False` (default) and the value of `state_of_charge_initial` in MWh. + +2. Set `cyclic_state_of_charge=True` and the optimisation set the initial state of charge to the final state of charge. + +$$soc_{n,s,t=-1} = soc_{n,s,t=|T|-1}$$ + +!!! note "[`StorageUnit`][pypsa.components.StorageUnits] cyclicity constraints with multiple investment periods" + + For how store cyclicity constraints are handled with multiple investment periods, see the [Pathway Planning](pathway-planning.md) section. + + +??? note "Mapping of symbols to component attributes" + + | Symbol | Attribute | Type | + |--------|-----------|------| + | $h_{n,s,t}^+$ | `n.stores_t.p_dispatch` | Decision Variable | + | $h_{n,s,t}^-$ | `n.stores_t.p_store` | Decision Variable | + | $soc_{n,s,t}$ | `n.storage_units_t.state_of_charge` | Decision Variable | + | $H_{n,s}$ | `n.storage_units.p_nom_opt` | Decision Variable | + | $\textrm{inflow}_{n,s,t}$ | `n.storage_units_t.inflow` | Decision Variable | + | $\textrm{spillage}_{n,s,t}$ | `n.storage_units_t.spillage` | Decision Variable | + | $\lambda_{n,s,t}^\textrm{MSV}$ | `n.storage_units_t.mu_energy_balance` | Dual Variable | + | $\bar{h}_{n,s}$ | `n.storage_units.p_nom` | Parameter | + | $\underline{h}_{n,s,t}$ | `n.storage_units_t.p_min_pu` | Parameter | + | $\bar{h}_{n,s,t}$ | `n.storage_units_t.p_max_pu` | Parameter | + | $r_{n,s}$ | `n.storage_units.max_hours` | Parameter | + | $\tilde{h}_{n,s,t}^-$ | `n.storage_units_t.p_dispatch_set` | Parameter | + | $\tilde{h}_{n,s,t}^+$ | `n.storage_units_t.p_store_set` | Parameter | + | $\tilde{soc}_{n,s,t}$ | `n.storage_units_t.state_of_charge_set` | Parameter | + | $\eta_{\textrm{stand};n,s}$ | `n.storage_units.standing_loss` | Parameter | + | $\eta_{\textrm{store};n,s}$ | `n.storage_units.store_efficiency` | Parameter | + | $\eta_{\textrm{dispatch};n,s}$ | `n.storage_units.dispatch_efficiency` | Parameter | + | $w_t^s$ | `n.snapshot_weightings.stores` | Parameter | + +## Examples + + +
+ +- :material-notebook:{ .lg .middle } **Storage Units as Links & Stores** + + --- + + Shows how storage units can be replaced by more fundamental links and stores. + + [:octicons-arrow-right-24: Go to example](../../examples/replace-generator-storage-units-with-store.ipynb) + +
diff --git a/PyPSA/source/docs/user-guide/optimization/unit-commitment.md b/PyPSA/source/docs/user-guide/optimization/unit-commitment.md new file mode 100644 index 0000000000000000000000000000000000000000..15b77f32a5daf26b2c143746f3baf14eb06f996d --- /dev/null +++ b/PyPSA/source/docs/user-guide/optimization/unit-commitment.md @@ -0,0 +1,324 @@ + + +# Unit Commitment + +Unit commitment constraints are implemented for the [`Generator`][pypsa.components.Generators] and [`Link`][pypsa.components.Links] +components. They are used to model the start-up and shut-down constraints, as +well as ramping constraints. The implementation is based on Taylor (2015)[^1], +and is supplemented with work by Hua et al. (2017)[^2] for a tightened linear +relaxation. + +## Start-Up and Shut-down + +For components marked as `committable=True`, new binary status variables +$u_{*,t} \in \{0,1\}$ are introduced, which indicate whether the component is +running (1) or not (0) in period $t$. This turns the model into a mixed-integer +linear programme (MILP). The restrictions on the dispatch now enforce that the +dispatch $g_{n,s,t}$ or $f_{l,t}$ is only non-zero if the component is running, +i.e. $u_{*,t} = 1$: + +=== "Generator" + + | Constraint | Name | + |-------------------|------------------| + | $g_{n,s,t} \geq u_{n,s,t} \cdot \underline{g}_{n,s,t} \cdot \hat{g}_{n,s}$ | `Generator-com-p-lower` | + | $g_{n,s,t} \leq u_{n,s,t} \cdot \bar{g}_{n,s,t} \cdot \hat{g}_{n,s}$ | `Generator-com-p-upper` | + +=== "Link" + + | Constraint | Name | + |-------------------|------------------| + | $f_{l,t} \geq u_{l,t} \cdot \underline{f}_{l,t} \cdot \hat{f}_{l}$ | `Link-com-p-lower` | + | $f_{l,t} \leq u_{l,t} \cdot \bar{f}_{l,t} \cdot \hat{f}_{l}$ | `Link-com-p-upper` | + +!!! warning + + Note that components cannot be both extendable (`p_nom_extendable=True`) and committable (`committable=True`) because of the non-linear coupling of status and capacity. + +If the **minimum up time** $T_{\textrm{min_up}}$ is set, status switches are constrained to ensure +that the component is running for at least $T_{\textrm{min_up}}$ snapshots after it has been started up: + +=== "Generator" + + Constraint `Generator-com-up-time`: + + $$\sum_{t'=t}^{t+T_\textrm{min_up}} u_{n,s,t'}\geq T_\textrm{min_up} (u_{n,s,t} - u_{n,s,t-1})$$ + +=== "Link" + + Constraint `Link-com-up-time`: + + $$\sum_{t'=t}^{t+T_\textrm{min_up}} u_{l,t'}\geq T_\textrm{min_up} (u_{l,t} - u_{l,t-1})$$ + +The component may have been up for some periods before the optimisation period (`n.optimize(snapshots=snapshots)`). If the up-time before `snapshots` starts is less than the minimum up-time, the component is forced remain up for the difference at the start of `snapshots`. If the start of `snapshots` is the start of `n.snapshots`, the up-time before the simulation is read from the input attribute `up_time_before`. If `snapshots` falls in the middle of `n.snapshots`, then the statuses before `snapshots` are assumed to be set by previous runs. If the start of `snapshots` is very close to the start of `n.snapshots`, it will also take account of `up_time_before` as well as the statuses in between. + +At the end of `snapshots` the minimum up-time in the constraint is only enforced for the remaining snapshots, if the number of remaining snapshots is less than $T_{\textrm{min_up}}$. + +If the **minimum down time** $T_{\textrm{min_down}}$ is set, status switches are constrained to ensure that the component is not running for at least $T_{\textrm{min_down}}$ snapshots after it has been shut down: + +=== "Generator" + + Constraint `Generator-com-down-time`: + + $$\sum_{t'=t}^{t+T_\textrm{min_down}} (1-u_{n,s,t'})\geq T_\textrm{min_down} (u_{n,s,t-1} - u_{n,s,t})$$ + +=== "Link" + + Constraint `Link-com-down-time`: + + $$\sum_{t'=t}^{t+T_\textrm{min_down}} (1-u_{l,t'})\geq T_\textrm{min_down} (u_{l,t-1} - u_{l,t})$$ + +The component may have been down for some periods before the optimisation period (`n.optimize(snapshots=snapshots)`). If the down-time before `snapshots` starts is less than the minimum down-time, the component is forced to remain down for the difference at the start of `snapshots`. If the start of `snapshots` is the start of `n.snapshots`, the down-time before the simulation is read from the input attribute `down_time_before`. If `snapshots` falls in the middle of `n.snapshots`, then the statuses before `snapshots` are assumed to be set by previous runs. If the start of `snapshots` is very close to the start of `n.snapshots`, it will also take account of `down_time_before` as well as the statuses in between. + +Furthermore, two **state transition variables** for start-up ($su_{*,t} \in \{0,1\}$) and shut-down ($sd_{*,t} \in$ \{0,1\}) are introduced to associate them with start-up and shut-down cost terms in the objective function. The constraints are set so that the start-up variable is only non-zero if the component has just started up, i.e. $u_{n,s,t} - u_{n,s,t-1} = 1$, and the shut-down variable is only non-zero if the component has just shut down, i.e. $u_{n,s,t-1} - u_{n,s,t} = 1$: + +=== "Generator" + + | Constraint | Name | + |-------------------|------------------| + | $su_{n,s,t} \geq u_{n,s,t} - u_{n,s,t-1}$ | `Generator-com-transition-start-up` | + | $sd_{n,s,t} \geq u_{n,s,t-1} - u_{n,s,t}$ | `Generator-com-transition-shut-down` | + +=== "Link" + + | Constraint | Name | + |-------------------|------------------| + | $su_{l,t} \geq u_{l,t} - u_{l,t-1}$ | `Link-com-transition-start-up` | + | $sd_{l,t} \geq u_{l,t-1} - u_{l,t}$ | `Link-com-transition-shut-down` | + + + +These constraints are defined in the function `define_operational_constraints_for_committables()`. + +??? note "Mapping of symbols to component attributes" + + === "Generator" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $g_{n,s,t}$ | `n.generators_t.p` | Decision variable | + | $u_{n,s,t}$ | `n.generators_t.status` | Decision variable | + | $su_{n,s,t}$ | `n.generators_t.start_up` | Decision variable | + | $sd_{n,s,t}$ | `n.generators_t.shut_down` | Decision variable | + | $\hat{g}_{n,s}$ | `n.generators.p_nom` | Parameter | + | $\underline{g}_{n,s,t}$ | `n.generators_t.p_min_pu` | Parameter | + | $\bar{g}_{n,s,t}$ | `n.generators_t.p_max_pu` | Parameter | + | $T_{\textrm{min_up}}$ | `n.generators.min_up_time` | Parameter | + | $T_{\textrm{min_down}}$| `n.generators.min_down_time`| Parameter | + | $T_{\textrm{up_time_before}}$ | `n.generators.up_time_before` | Parameter | + | $T_{\textrm{down_time_before}}$ | `n.generators.down_time_before` | Parameter | + + === "Link" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $f_{l,t}$ | `n.links_t.p` | Decision variable | + | $u_{l,t}$ | `n.links_t.status` | Decision variable | + | $su_{l,t}$ | `n.links_t.start_up` | Decision variable | + | $sd_{l,t}$ | `n.links_t.shut_down` | Decision variable | + | $\hat{f}_{l}$ | `n.links.p_nom` | Parameter | + | $\underline{f}_{l,t}$| `n.links_t.p_min_pu` | Parameter | + | $\bar{f}_{l,t}$ | `n.links_t.p_max_pu` | Parameter | + | $T_{\textrm{min_up}}$ | `n.links.min_up_time` | Parameter | + | $T_{\textrm{min_down}}$| `n.links.min_down_time` | Parameter | + | $T_{\textrm{up_time_before}}$ | `n.links.up_time_before` | Parameter | + | $T_{\textrm{down_time_before}}$ | `n.links.down_time_before` | Parameter | + +## Ramping + +Ramp rate limits can be defined for increasing output $ru_{n,s,t}$ and decreasing output $rd_{n,s,t}$. By default these are null and ignored. They must be provided per unit of the nominal capacity. + +!!! note + + When provided, ramping limits are also considered if they are not + committable (`committable=False`), i.e. the component is not subject to start-up and shut-down constraints. + +For **non-extendable** but **non-committable** components, the dispatch obeys for $t \in \{1,\dots |T|-1\}$: + +=== "Generator" + + | Constraint | Dual Variable | Name | + |-------------------|------------------|------------------| + | $(g_{n,s,t} - g_{n,s,t-1}) \geq -rd_{n,s,t} \cdot \hat{g}_{n,s}$ | `n.generators_t.mu_ramp_limit_down` | `Generator-fix-p-ramp_limit_down` | + | $(g_{n,s,t} - g_{n,s,t-1}) \leq ru_{n,s,t} \cdot \hat{g}_{n,s}$ | `n.generators_t.mu_ramp_limit_up` | `Generator-fix-p-ramp_limit_up` | + +=== "Link" + + | Constraint | Dual Variable | Name | + |-------------------|------------------|------------------| + | $(f_{l,t} - f_{l,t-1}) \geq -rd_{l,t} \cdot \hat{f}_{l}$ | `n.links_t.mu_ramp_limit_down` | `Link-fix-p-ramp_limit_down` | + | $(f_{l,t} - f_{l,t-1}) \leq ru_{l,t} \cdot \hat{f}_{l}$ | `n.links_t.mu_ramp_limit_up` | `Link-fix-p-ramp_limit_up` | + + + +For **extendable** and **non-committable** components, the dispatch obeys for $t \in \{1,\dots |T|-1\}$: + +=== "Generator" + + | Constraint | Dual Variable | Name | + |-------------------|------------------|------------------| + | $(g_{n,s,t} - g_{n,s,t-1}) \geq -rd_{n,s,t} \cdot G_{n,s}$ | `n.generators_t.mu_ramp_limit_down` | `Generator-ext-p-ramp_limit_down` | + | $(g_{n,s,t} - g_{n,s,t-1}) \leq ru_{n,s,t} \cdot G_{n,s}$ | `n.generators_t.mu_ramp_limit_up` | `Generator-ext-p-ramp_limit_up` | + +=== "Link" + + | Constraint | Dual Variable | Name | + |-------------------|------------------|------------------| + | $(f_{l,t} - f_{l,t-1}) \geq -rd_{l,t} \cdot F_{l}$ | `n.links_t.mu_ramp_limit_down` | `Link-ext-p-ramp_limit_down` | + | $(f_{l,t} - f_{l,t-1}) \leq ru_{l,t} \cdot F_{l}$ | `n.links_t.mu_ramp_limit_up` | `Link-ext-p-ramp_limit_up` | + + +For **committable** and **non-extendable** components, additional ramp limits at start-up $rusu_{n,s}$ and shut-down $rdsd_{n,s}$ can be specified for $t \in \{1,\dots |T|-1\}$: + +=== "Generator" + + | Constraint | Name | + |-------------------|------------------| + | $(g_{n,s,t} - g_{n,s,t-1}) \geq \left[ -rd_{n,s,t} \cdot u_{n,s,t} -rdsd_{n,s}(u_{n,s,t-1} - u_{n,s,t})\right] \hat{g}_{n,s}$ | `Generator-com-p-ramp_limit_down` | + | $(g_{n,s,t} - g_{n,s,t-1}) \leq \left[ru_{n,s,t} \cdot u_{n,s,t-1} + rusu_{n,s} (u_{n,s,t} - u_{n,s,t-1})\right] \hat{g}_{n,s}$ | `Generator-com-p-ramp_limit_up` | + +=== "Link" + + | Constraint | Name | + |-------------------|------------------| + | $(f_{l,t} - f_{l,t-1}) \geq \left[ -rd_{l,t} \cdot u_{l,t} -rdsd_{l,t}(u_{l,t-1} - u_{l,t})\right] \hat{f}_{l}$ | `Link-com-p-ramp_limit_down` | + | $(f_{l,t} - f_{l,t-1}) \leq \left[ru_{l,t} \cdot u_{l,t-1} + rusu_{l} (u_{l,t} - u_{l,t-1})\right] \hat{f}_{l}$ | `Link-com-p-ramp_limit_up` | + +These constraints are defined in the function `define_ramp_limit_constraints()`. + +??? note "Mapping of symbols to component attributes" + + === "Generator" + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $g_{n,s,t}$ | `n.generators_t.p` | Decision variable | + | $G_{n,s}$ | `n.generators.p_nom_opt` | Decision variable | + | $u_{n,s,t}$ | `n.generators_t.status` | Decision variable | + | $\hat{g}_{n,s}$ | `n.generators.p_nom` | Parameter | + | $\underline{g}_{n,s,t}$ | `n.generators_t.p_min_pu` | Parameter | + | $\bar{g}_{n,s,t}$ | `n.generators_t.p_max_pu` | Parameter | + | $ru_{n,s,t}$ | `n.generators_t.ramp_limit_up` | Parameter | + | $rd_{n,s,t}$ | `n.generators_t.ramp_limit_down` | Parameter | + | $rusu_{n,s}$ | `n.generators.ramp_limit_start_up` | Parameter | + | $rdsd_{n,s}$ | `n.generators.ramp_limit_shut_down` | Parameter | + + === "Link" + + + | Symbol | Attribute | Type | + |-------------------|-----------|-------------| + | $f_{l,t}$ | `n.links_t.p` | Decision variable | + | $F_{l}$ | `n.links.p_nom_opt` | Decision variable | + | $u_{l,t}$ | `n.links_t.status` | Decision variable | + | $\hat{f}_{l}$ | `n.links.p_nom` | Parameter | + | $\underline{f}_{l,t}$ | `n.links_t.p_min_pu` | Parameter | + | $\bar{f}_{l,t}$ | `n.links_t.p_max_pu` | Parameter | + | $ru_{l,t}$ | `n.links_t.ramp_limit_up` | Parameter | + | $rd_{l,t}$ | `n.links_t.ramp_limit_down` | Parameter | + | $rusu_{l}$ | `n.links.ramp_limit_start_up` | Parameter | + | $rdsd_{l}$ | `n.links.ramp_limit_shut_down` | Parameter | + +## Linearization + +The implementation is based on Hua et al. (2017)[^2] and relaxes the binary unit commitment variables to continuous variables: + +$$u_{*,t},\quad su_{*,t},\quad sd_{*,t}\quad \in [0,1]$$ + +This allows for partial commitment states (generators can be partially on/off), making the problem more computationally tractable while loosing some accuracy. To enable this, use: + +``` py +n.optimize(linearized_unit_commitment=True) +``` + +To tighten the relaxation, additional constraints are introduced that improve capturing the relationship between commitment status, ramping, and dispatch. This requires start up and shut down costs need to be equal. Otherwise the unit commitment variables are purely relaxed. The added constraints limit the dispatch during partial start-up and shut-down, as well as ramping during partial commitment: + +=== "Generator" + + Constraint `Generator-com-p-before`: + + $$\begin{gather*} + g_{n,s,t-1} \leq rdsd_{n,s} \hat{g}_{n,s} \cdot u_{n,s,t-1} + (\bar{g}_{n,s,t} \hat{g}_{n,s} - rdsd_{n,s} \hat{g}_{n,s}) \cdot (u_{n,s,t} - su_{n,s,t}) + \end{gather*}$$ + + Constraint `Generator-com-p-current`: + + $$\begin{gather*} + g_{n,s,t} \leq \bar{g}_{n,s,t} \hat{g}_{n,s} \cdot u_{n,s,t} - (\bar{g}_{n,s,t} \hat{g}_{n,s} - rusu_{n,s} \hat{g}_{n,s}) \cdot su_{n,s,t} + \end{gather*}$$ + + Constraint `Generator-com-partly-start-up`: + + $$\begin{gather*} + g_{n,s,t} - g_{n,s,t-1} \leq (\underline{g}_{n,s,t} \hat{g}_{n,s} + ru_{n,s,t} \hat{g}_{n,s}) \cdot u_{n,s,t} - \underline{g}_{n,s,t} \hat{g}_{n,s} \cdot u_{n,s,t-1} \\ + - (\underline{g}_{n,s,t} \hat{g}_{n,s} + ru_{n,s,t} \hat{g}_{n,s} - rusu_{n,s} \hat{g}_{n,s}) \cdot su_{n,s,t} + \end{gather*}$$ + + Constraint `Generator-com-partly-shut-down`: + + $$\begin{gather*} + g_{n,s,t-1} - g_{n,s,t} \leq rdsd_{n,s} \hat{g}_{n,s} \cdot u_{n,s,t-1} - (rdsd_{n,s} \hat{g}_{n,s} - rd_{n,s,t} \hat{g}_{n,s}) \cdot u_{n,s,t} \\ + - (\underline{g}_{n,s,t} \hat{g}_{n,s} + rd_{n,s,t} \hat{g}_{n,s} - rdsd_{n,s} \hat{g}_{n,s}) \cdot su_{n,s,t} + \end{gather*}$$ + +=== "Link" + + Constraint `Link-com-p-before`: + + $$\begin{gather*} + f_{l,t-1} \leq rdsd_{l} \hat{f}_{l} \cdot u_{l,t-1} + (\bar{f}_{l,t} \hat{f}_{l} - rdsd_{l} \hat{f}_{l}) \cdot (u_{l,t} - su_{l,t}) + \end{gather*}$$ + + Constraint `Link-com-p-current`: + + $$\begin{gather*} + f_{l,t} \leq \bar{f}_{l,t} \hat{f}_{l} \cdot u_{l,t} - (\bar{f}_{l,t} \hat{f}_{l} - rusu_{l} \hat{f}_{l}) \cdot su_{l,t} + \end{gather*}$$ + + Constraint `Link-com-partly-start-up`: + + $$\begin{gather*} + f_{l,t} - f_{l,t-1} \leq (\underline{f}_{l,t} \hat{f}_{l} + ru_{l,t} \hat{f}_{l}) \cdot u_{l,t} - \underline{f}_{l,t} \hat{f}_{l} \cdot u_{l,t-1} \\ + - (\underline{f}_{l,t} \hat{f}_{l} + ru_{l,t} \hat{f}_{l} - rusu_{l} \hat{f}_{l}) \cdot su_{l,t} + \end{gather*}$$ + + Constraint `Link-com-partly-shut-down`: + + $$\begin{gather*} + f_{l,t-1} - f_{l,t} \leq rdsd_{l} \hat{f}_{l} \cdot u_{l,t-1} - (rdsd_{l} \hat{f}_{l} - rd_{l,t} \hat{f}_{l}) \cdot u_{l,t} \\ + - (\underline{f}_{l,t} \hat{f}_{l} + rd_{l,t} \hat{f}_{l} - rdsd_{l} \hat{f}_{l}) \cdot su_{l,t} + \end{gather*}$$ + + +These constraints are defined in the function `define_operational_constraints_for_committables()`. + +## Examples + + +
+ +- :material-notebook:{ .lg .middle } **Unit Commitment** + + --- + + Models generator unit commitment with start-up and shut-down costs, ramping limits, minimum part loads, up and down times using binary variables. + + [:octicons-arrow-right-24: Go to example](../../examples/unit-commitment.ipynb) + +- :material-notebook:{ .lg .middle } **Negative Prices in Linearized Unit Commitment** + + --- + + Demonstrates how negative electricity prices emerge in linearized unit commitment when generators face high cycling costs and prefer to stay online at negative prices. + + [:octicons-arrow-right-24: Go to example](../../examples/uc-prices.ipynb) + +
+ +[^1]: J.A. Taylor (2015), [Convex Optimization of Power Systems](http://www.cambridge.org/de/academic/subjects/engineering/control-systems-and-optimization/convex-optimization-power-systems), Cambridge University Press, Chapter 4.3. + +[^2]: B. Hua, R. Baldick and J. Wang (2018), [Representing Operational Flexibility in Generation Expansion Planning Through Convex Relaxation of Unit Commitment](https://doi.org/10.1109/TPWRS.2017.2735026), IEEE Transactions on Power Systems, 33, 2, 2272-2281, doi:10.1109/TPWRS.2017.2735026, equations (21-24). diff --git a/PyPSA/source/docs/user-guide/options.md b/PyPSA/source/docs/user-guide/options.md new file mode 100644 index 0000000000000000000000000000000000000000..3ec96d3a58b57191b3e958b5acf9cef32e0b4d1c --- /dev/null +++ b/PyPSA/source/docs/user-guide/options.md @@ -0,0 +1,139 @@ + + +PyPSA has an options system that allows users to customise its global behaviour. + +## Setting options + +Options can be set by assigning values to attributes of the `pypsa.options` object. For example, to change the default solver used in optimizations to `"gurobi"` (see [Parameters options](#parameters-options)), run: +``` py +>>> pypsa.options.params.optimize.solver_name = "gurobi" # doctest: +SKIP +``` + +This will set the option **globally** for the current Python session. + +You can also use a context manager to set options temporarily within a `with` block via [`pypsa.option_context`][pypsa.option_context]. This is useful if you want to change an option for a particular section of your code without altering the rest of it. For example: +``` py +>>> with pypsa.option_context(params.optimize.solver_name="gurobi"): +... n.optimize() # "gurobi" as the solver # doctest: +SKIP +... +>>> n.optimize() # "highs" as the solver # doctest: +SKIP +``` + +To reset options to their default values, simply remove the assignment or use [pypsa.reset_option][]. + +Instead of setting the value and getting the value via attribute access, you can also use [pypsa.set_option][] and [pypsa.get_option][]. + + +## List of available options +Options are grouped into categories and sub-categories. You can run the [`describe()`][pypsa.options.describe] function on any category or sub-category to get a list of available options and their current values. To list all options just run +``` py +>>> pypsa.options.describe() # doctest: +ELLIPSIS +PyPSA Options +============= +... +``` + +!!! info + + The options system was recently introduced and more options will be added in future versions. If you have any suggestions for useful options, please open an issue on [GitHub](https://github.com/PyPSA/PyPSA/issues). + +### General options + +``` py +>>> pypsa.options.general.describe() +PyPSA Options +============= +allow_network_requests: + Default: True + Description: Allow PyPSA to make network requests. When False, all network requests + (such as checking for version updates) are disabled. This may be needed + in restricted environments, offline usage, or for security/privacy reasons. + This only controls PyPSA's own network requests, dependencies may still + make network requests independently. +``` + +### Parameters options + +The `params` category allows to change the default parameters used in some PyPSA functions. For example the default solver used in optimizations is `highs`. When running [`n.optimize()`][pypsa.optimization.OptimizationAccessor.__call__] you would need to pass `solver_name="gurobi"` to use a different solver. Instead you can also +change the default globally by setting the option `pypsa.options.params.optimize.solver_name="gurobi"` as shown above. + +``` py +>>> pypsa.options.params.describe() +PyPSA Options +============= +statistics.nice_names: + Default: True + Description: Default value for the 'nice_names' parameter in statistics module. +statistics.drop_zero: + Default: True + Description: Default value for the 'drop_zero' parameter in statistics module. +statistics.round: + Default: 5 + Description: Default value for the 'round' parameter in statistics module. +add.return_names: + Default: False + Description: Default value for the 'return_names' parameter in Network.add method. + If True, the add method returns the names of added components. + If False, it returns None. +optimize.model_kwargs: + Default: {} + Description: Default value for the 'model_kwargs' parameter in optimization module. +optimize.solver_name: + Default: highs + Description: Default value for the 'solver_name' parameter in optimization module. +optimize.solver_options: + Default: {} + Description: Default value for the 'solver_options' parameter in optimization module. +``` + +### Warnings options + +Turn off or on certain warnings. + +``` py +>>> pypsa.options.warnings.describe() +PyPSA Options +============= +components_store_iter: + Default: True + Description: If False, suppresses the deprecation warning when iterating over components. +attribute_typos: + Default: True + Description: If False, suppresses warnings about potential typos in component attribute names. Note: warnings about unintended attributes (standard attributes for other components) will still be shown. +``` + +### API + +Make changes to the PyPSA API. See . + +``` py +>>> pypsa.options.api.describe() # doctest: +NORMALIZE_WHITESPACE +PyPSA Options +============= +new_components_api: + Default: False + Description: Activate the new components API, which replaces the static components data access + with the more flexible components class. This will just change the api and not any + functionality. Components class features are always available. + See `https://go.pypsa.org/new-components-api` for more details. +``` + + +### Debug options + +Options for developers to help with debugging. + +``` py +>>> pypsa.options.debug.describe() +PyPSA Options +============= +runtime_verification: + Default: False + Description: Enable runtime verification of PyPSA's internal state. This is useful + for debugging and development purposes. This will lead to overhead in + performance and should not be used in production. +``` diff --git a/PyPSA/source/docs/user-guide/plotting/charts.md b/PyPSA/source/docs/user-guide/plotting/charts.md new file mode 100644 index 0000000000000000000000000000000000000000..80afbb418e8e68d1e0ef7f6a135b159ddd505568 --- /dev/null +++ b/PyPSA/source/docs/user-guide/plotting/charts.md @@ -0,0 +1,425 @@ + + +# Statistics + +PyPSA offers a variety of functions for plotting networks based on the statistics module. Line, bar and area charts can be plotted against any network metric, and plots can be created as either as static `matplotlib` figures or interactive `plotly` charts. + + +## Static Plots +Various key metrics that can be calculated on any PyPSA network are described in the section. The plotting module lets you create plots of any of these metrics. + +!!! info + + The examples below are based on two networks: `n_simple` is based on a minimal three-node network, which is available via [pypsa.examples.ac_dc_meshed][], and `n` is the more complex hybrid scenario network from a recently published paper on carbon management. This network is also available in PyPSA via [pypsa.examples.carbon_management][]. + + Load the networks: + + === "Simple Network" + ``` py + # Simple three-node network + >>> n_simple = pypsa.examples.ac_dc_meshed() + >>> n_simple # doctest: +SKIP + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 9 + - Carrier: 6 + - Generator: 6 + - GlobalConstraint: 1 + - Line: 7 + - Link: 4 + - Load: 6 + Snapshots: 10 + + ``` + + === "Complex Network" + ``` py + # Complex sector coupled network + >>> n = pypsa.examples.carbon_management() + >>> n # doctest: +SKIP + PyPSA Network 'Hybrid Scenario from https://www.nature.com/articles/s41560-025-01752-6' + --------------------------------------------------------------------------------------- + Components: + - Bus: 2164 + - Carrier: 89 + - Generator: 1489 + - GlobalConstraint: 4 + - Line: 157 + - Link: 6830 + - Load: 1357 + - StorageUnit: 106 + - Store: 1263 + Snapshots: 168 + + ``` + + +### Defaults + +Many different statistics are available via the [`n.statistics`][pypsa.Network.statistics] accessor: + +``` py +>>> n_simple.statistics.energy_balance() +component carrier bus_carrier +Generator gas AC 1465.27439 + wind AC 31082.35370 +Load load AC -32547.62808 +dtype: float64 +``` + +Any of these metrics can also be used to create plots straight away: + +``` py +>>> n_simple.statistics.energy_balance.plot() # doctest: +SKIP +``` +
+ ![Buses](../../assets/images/ac_dc_meshed-energy_balance-area_plot.png){ width="600" } +
+ +The default plot type varies depending on which type typically makes the most sense for a given metric. For instance, `n.statistics.energy_balance.plot()` produces an area plot with snapshots on the x-axis, whereas `n.statistics.installed_capacity.plot()` produces a simple bar chart without a time dimension. + +``` py +>>> n_simple.statistics.installed_capacity.plot() # doctest: +SKIP +``` +
+ ![Buses](../../assets/images/ac_dc_meshed-installed_capacity-bar_plot.png){ width="350" } +
+ +#### Complex Networks +In principle, these standard plots can be created from any network. However, depending on factors such as network size, number of components and snapshots, the plot may be difficult to read and may require further customisation. If the default option is not useful, there are three options to get useful plots for more complex networks: + +1. The full static plot API is also available for interactive plots. This gives you more control over data selection and data can be more easily explored. See [Interactive Plots](#interactive-plots). +2. Plots are created using [matplotlib](https://matplotlib.org/) and [seaborn](https://seaborn.pydata.org/). This means that you can use all the customization options of these libraries to further customize the plot. See [Customization with matplotlib and seaborn](#customization). +3. The parameters that can be used to filter and aggregate data using base statistics methods can also be passed to plotting methods. See [Customization based on statistics parameters](#customization). + +All options are described in the following sections. + +### Plot Types +The basic plotting method ([`n.statistics..plot()`][pypsa.Network]) is not very flexible, but useful for quick exploration. To gain more control over the plot, the according plot type method can be called directly. Any plot type can be used with any metric, although not all plots will be meaningful for all metrics. + +For instance, energy balances can also be shown as a bar chart, ignoring the time dimension: +``` py +>>> n_simple.statistics.energy_balance.plot.bar() # doctest: +SKIP +``` +
+ ![Buses](../../assets/images/ac_dc_meshed-energy_balance-bar_plot.png){ width="350" } +
+ + +#### Available Plot Types +The following plot types are available: + +- [Area Plot][pypsa.Network] +- [Bar Plot][pypsa.Network] +- [Map Plot][pypsa.Network] +- [Scatter Plot][pypsa.Network] +- [Line Plot][pypsa.Network] +- [Box Plot][pypsa.Network] +- [Violin Plot][pypsa.Network] +- [Histogram Plot][pypsa.Network] + +=== "Area Plot" + ``` py + >>> n_simple.statistics.energy_balance.plot.area() # doctest: +SKIP + ``` +
+ ![Buses](../../assets/images/ac_dc_meshed-energy_balance-area_plot.png){ width="600" } +
+ +=== "Bar Plot" + ``` py + >>> n_simple.statistics.energy_balance.plot.bar() # doctest: +SKIP + ``` +
+ ![Buses](../../assets/images/ac_dc_meshed-energy_balance-bar_plot.png){ width="350" } +
+ +=== "Map Plot" + ``` py + >>> n_simple.statistics.energy_balance.plot.map() # doctest: +SKIP + ``` +
+ ![Buses](../../assets/images/ac_dc_meshed-energy_balance-map_plot.png){ width="600" } +
+ +=== "Scatter Plot" + ``` py + >>> n_simple.statistics.energy_balance.plot.scatter() # doctest: +SKIP + ``` +
+ ![Buses](../../assets/images/ac_dc_meshed-energy_balance-scatter_plot.png){ width="350" } +
+ +=== "Line Plot" + ``` py + >>> n_simple.statistics.energy_balance.plot.line() # doctest: +SKIP + ``` +
+ ![Buses](../../assets/images/ac_dc_meshed-energy_balance-line_plot.png){ width="600" } +
+ +=== "Box Plot" + ``` py + >>> n_simple.statistics.energy_balance.plot.box() # doctest: +SKIP + ``` +
+ ![Buses](../../assets/images/ac_dc_meshed-energy_balance-box_plot.png){ width="350" } +
+ +=== "Violin Plot" + ``` py + >>> n_simple.statistics.energy_balance.plot.violin() # doctest: +SKIP + ``` +
+ ![Buses](../../assets/images/ac_dc_meshed-energy_balance-violin_plot.png){ width="350" } +
+ +=== "Histogram Plot" + ``` py + >>> n_simple.statistics.energy_balance.plot.histogram() # doctest: +SKIP + ``` +
+ ![Buses](../../assets/images/ac_dc_meshed-energy_balance-histogram_plot.png){ width="350" } +
+ + +### Customization +#### Customization with matplotlib and seaborn +All plot methods return a `matplotlib` figure object, a `matplotlib` axes object and a facet grid object. These can be used to customise the plot further. See the matplotlib documentation on [`matplotlib.figure`](https://matplotlib.org/stable/api/figure_api.html), [`matplotlib.axes`](https://matplotlib.org/stable/api/axes_api.html) and the seaborn documentation on [`seaborn.FacetGrid`](https://seaborn.pydata.org/generated/seaborn.FacetGrid.html). + +``` py +>>> fig, ax, facet_col = n_simple.statistics.energy_balance.plot.area() +>>> fig.set_size_inches(12, 3) +>>> fig.suptitle("My Scenario", fontsize=12) +>>> ax.grid(True, alpha=0.5, linestyle="--") +>>> ax.set_xlabel("Time", fontsize=9) +>>> ax.set_ylabel("Energy Balance (MW)", fontsize=9) +``` +
+ ![Buses](../../assets/images/ac_dc_meshed-energy_balance-custom_area_plot.png){ width="800" } +
+ +#### Customization with statistics parameters +Parameters which are available in the statistics methods to filter and aggregate data can also be passed to the corresponding plotting methods. The full energy balance of the carbon management example network yields too many different `carriers` to be shown in a single plot. However, if we filter it down to include only buses with the 'AC' carrier, via the `bus_carrier` parameter from the statistics method, we can produce a meaningful plot. + +=== "Buses with AC carrier" + + ``` py + >>> n.statistics.energy_balance.plot.area(bus_carrier="AC", figsize=(12, 3)) # doctest: +SKIP + ``` +
+ ![Buses](../../assets/images/carbon_management-energy_balance-area_plot-AC-bus_carrier.png){ width="800" } +
+ +=== "All Buses" + + ``` py + >>> n.statistics.energy_balance.plot.area(figsize=(10, 3)) # doctest: +SKIP + ``` +
+ ![Buses](../../assets/images/carbon_management-energy_balance-area_plot.png){ width="800" } +
+ + +Behind the scenes, each plotting method selects a different set of parameters to call the relevant statistics method. Therefore, the default value can be different for each plot type and metric. Most of these parameters can also be passed directly to the plotting method. Please refer to the for more details. + +!!! tip + + To decide which subset of data to show in your plot, it is often helpful to experiment with the relevant statistics method first to find the right parameters. Once you are happy with your selection, simply pass the parameters to the plotting method and the same selection will be applied. You can also pass them alongside all the other available plotting parameters. Check the API reference for details. + + +## Interactive Plots + +All the logic for static plots, using `n.statistics..plot.()`, is mirrored for interactive plots. While the returned object and parameters may differ, you can usually simply replace `plot` with `iplot` to get an interactive version of the same plot. Behind the scenes, [Plotly](https://plotly.com/python/) is used for the interactive plots and the returned object is a Plotly figure object. Check out the [Plotly documentation](https://plotly.com/python-api-reference/generated/plotly.graph_objects.Figure.html) for more details. + +### Examples + +#### Simple Energy Balance Area Plot + +=== "Interactive Version" + + ``` py + >>> n_simple.statistics.energy_balance.iplot.area() # doctest: +SKIP + ``` + +
+ +
+ +=== "Static Plot" + + ``` py + >>> n_simple.statistics.energy_balance.plot() # doctest: +SKIP + ``` +
+ ![Buses](../../assets/images/ac_dc_meshed-energy_balance-area_plot.png){ width="600" } +
+ +#### Carbon Network Energy Balance with bus carrier `AC` + +=== "Interactive Version" + + ``` py + >>> n.statistics.energy_balance.iplot.area(bus_carrier="AC") # doctest: +SKIP + ``` + +
+ +
+ +=== "Static Plot" + + ``` py + >>> n.statistics.energy_balance.plot(bus_carrier="AC") # doctest: +SKIP + ``` +
+ ![Buses](../../assets/images/carbon_management-energy_balance-area_plot-AC-bus_carrier.png){ width="800" } +
+ + +#### Full Carbon Network Energy Balance + +=== "Interactive Version" + + ``` py + >>> n.statistics.energy_balance.iplot.area() # doctest: +SKIP + ``` +
+ +
+ +=== "Static Plot" + + ``` py + >>> n.statistics.energy_balance.plot.area(figsize=(10, 3)) # doctest: +SKIP + ``` +
+ ![Buses](../../assets/images/carbon_management-energy_balance-area_plot.png){ width="800" } +
+ + + + + diff --git a/PyPSA/source/docs/user-guide/plotting/explore.ipynb b/PyPSA/source/docs/user-guide/plotting/explore.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..89fb8acf9b80c7358c92e86ea4c7070fe3447393 --- /dev/null +++ b/PyPSA/source/docs/user-guide/plotting/explore.ipynb @@ -0,0 +1,692 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "5c7934eb", + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "b62ec1aa", + "metadata": {}, + "source": [ + "# Maps (Interactive)" + ] + }, + { + "cell_type": "markdown", + "id": "9848f516", + "metadata": {}, + "source": [ + "Next to static map plotting, PyPSA allows for exploring networks on a map, interactively. With `n.explore()`, you can explore the location of all components, including buses, lines, links, transformers, their component attributes and map results or other properties to the bus sizes, branch widths, colors, etc. Calling the method returns a standard `pydeck.Deck` object than can be layered on top of other `pydeck.Deck` objects (see https://deckgl.readthedocs.io/en/latest/layer.html). They can also be exported in self-contained HTML files for sharing. In this notebook, we demonstrate the features of `n.explore()` using the [SciGRID example](/examples/scigrid-lopf-then-pf).\n" + ] + }, + { + "cell_type": "markdown", + "id": "aa9ad5cf", + "metadata": {}, + "source": [ + "## Input data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec9b441c", + "metadata": {}, + "outputs": [], + "source": [ + "import geopandas as gpd\n", + "\n", + "import pypsa\n", + "\n", + "n = pypsa.examples.scigrid_de()" + ] + }, + { + "cell_type": "markdown", + "id": "90f5a1c7", + "metadata": {}, + "source": [ + "## Preparation" + ] + }, + { + "cell_type": "markdown", + "id": "b6b69008", + "metadata": {}, + "source": [ + "For illustrative purposes, we cluster the network based on federal states. For a more detailed guide on clustering, please go to [Network Clustering](/examples/clustering). For the scope of this guide, you can ignore the following blocks." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35bd29ac", + "metadata": {}, + "outputs": [], + "source": [ + "n.calculate_dependent_values()\n", + "n.lines = n.lines.reindex(columns=n.components[\"Line\"][\"defaults\"].index[1:])\n", + "n.lines[\"type\"] = \"Al/St 240/40 2-bundle 220.0\"\n", + "n.buses = n.buses.reindex(columns=n.components[\"Bus\"][\"defaults\"].index[1:])\n", + "n.buses[\"frequency\"] = 50\n", + "\n", + "url = \"https://media.githubusercontent.com/media/wmgeolab/geoBoundaries/9469f09592ced973a3448cf66b6100b741b64c0d/releaseData/gbOpen/DEU/ADM1/geoBoundaries-DEU-ADM1-all.zip\"\n", + "states = gpd.read_file(url, layer=\"geoBoundaries-DEU-ADM1_simplified\")\n", + "states[\"shapeName\"] = states[\"shapeName\"].apply(\n", + " lambda x: x.encode(\"latin1\").decode(\"utf-8\")\n", + ") # fix encoding issue\n", + "\n", + "bus_coords = gpd.GeoDataFrame(\n", + " geometry=gpd.points_from_xy(n.buses.x, n.buses.y, crs=4326), index=n.buses.index\n", + ")\n", + "busmap = bus_coords.to_crs(3035).sjoin_nearest(states.to_crs(3035), how=\"left\").shapeISO\n", + "nc = n.cluster.cluster_by_busmap(busmap)" + ] + }, + { + "cell_type": "markdown", + "id": "017d976b", + "metadata": {}, + "source": [ + "Let’s take an initial look at the network. By default, `n.explore()` displays information in the tooltip for each component type, including component names and their default sizes or widths. You can hover over the elements in the map below to inspect the data interactively. Optionally, you can disable tooltip by setting `tooltip=False` - this can help reduce processing time and decrease the file size when exporting to HTML." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2cedd146", + "metadata": {}, + "outputs": [], + "source": [ + "nc.explore()" + ] + }, + { + "cell_type": "markdown", + "id": "8c0d11b2", + "metadata": {}, + "source": [ + "With `help(n.explore)`, we can check what parameters the method accepts:" + ] + }, + { + "cell_type": "markdown", + "id": "82f71917", + "metadata": {}, + "source": [ + "\n", + "\n", + "
\n", + "\n", + "```text\n", + "Docstring:\n", + "Create an interactive map of the PyPSA network using Pydeck.\n", + "\n", + "Parameters\n", + "----------\n", + "branch_width_factor : float, default 1.0\n", + " Branch widths are scaled by this factor.\n", + "bus_size : float/dict/pandas.Series\n", + " Sizes of bus points in km² (corresponds to circle area), defaults to 25 km².\n", + "bus_size_factor : float, default 1.0\n", + " Bus sizes are scaled by this factor.\n", + "bus_split_circle : bool, default False\n", + " Draw half circles if bus_size is a pandas.Series with a Multiindex.\n", + " If set to true, the upper half circle per bus then includes all positive values\n", + " of the series, the lower half circle all negative values. Defaults to False.\n", + "bus_color : str/dict/pandas.Series/None\n", + " Colors for the buses, defaults to \"cadetblue\". If bus_size is a\n", + " pandas.Series with a Multiindex, bus_color defaults to the\n", + " n.c.carriers.static['color'] column.\n", + "bus_cmap : mcolors.Colormap/str, default 'Reds'\n", + " If bus_color are floats, this color map will assign the colors.\n", + "bus_cmap_norm : mcolors.Normalize/None\n", + " Normalization for bus_cmap, defaults to None.\n", + "bus_alpha : float/dict/pandas.Series\n", + " Add alpha channel to buses, defaults to 0.9.\n", + "line_flow : float/dict/pandas.Series, default 0\n", + " Series of line flows indexed by line names, defaults to 0. If 0, no arrows will be created.\n", + " If a float is provided, it will be used as a constant flow for all lines.\n", + "line_color : str/dict/pandas.Series\n", + " Colors for the lines, defaults to 'rosybrown'.\n", + "line_alpha : float/dict/pandas.Series\n", + " Add alpha channel to lines, defaults to 0.9.\n", + "line_width : float/dict/pandas.Series, default 2\n", + " Widths of line component in km.\n", + "link_flow : float/dict/pandas.Series, default 0\n", + " Series of link flows indexed by link names, defaults to 0. If 0, no arrows will be created.\n", + " If a float is provided, it will be used as a constant flow for all links.\n", + "link_color : str/dict/pandas.Series\n", + " Colors for the links, defaults to 'darkseagreen'.\n", + "link_alpha : float/dict/pandas.Series\n", + " Add alpha channel to links, defaults to 0.9.\n", + "link_width : float/dict/pandas.Series, default 2\n", + " Widths of link component in km.\n", + "tooltip : bool, default True\n", + " Whether to add a tooltip to the bus layer.\n", + "\n", + "Other Parameters\n", + "----------------\n", + "branch_components : list, set, optional, default ['Line', 'Link', 'Transformer']\n", + " Branch components to be plotted.\n", + "branch_width_max : float, default 10\n", + " Maximum width of branch component in km when `auto_scale` is True.\n", + "bus_size_max : float, default 10000\n", + " Maximum area size of bus component in km² when `auto_scale` is True.\n", + "line_cmap : mcolors.Colormap/str, default 'viridis'\n", + " If line_color are floats, this color map will assign the colors.\n", + "line_cmap_norm : mcolors.Normalize\n", + " The norm applied to the line_cmap.\n", + "link_cmap : mcolors.Colormap/str, default 'viridis'\n", + " If link_color are floats, this color map will assign the colors.\n", + "link_cmap_norm : mcolors.Normalize|matplotlib.colors.*Norm\n", + " The norm applied to the link_cmap.\n", + "transformer_flow : float/dict/pandas.Series, default 0\n", + " Series of transformer flows indexed by transformer names, defaults to 0. If 0, no arrows will be created.\n", + " If a float is provided, it will be used as a constant flow for all transformers.\n", + "transformer_color : str/dict/pandas.Series\n", + " Colors for the transformers, defaults to 'orange'.\n", + "transformer_cmap : mcolors.Colormap/str, default 'viridis'\n", + " If transformer_color are floats, this color map will assign the colors.\n", + "transformer_cmap_norm : matplotlib.colors.Normalize|matplotlib.colors.*Norm\n", + " The norm applied to the transformer_cmap.\n", + "transformer_alpha : float/dict/pandas.Series\n", + " Add alpha channel to transformers, defaults to 0.9.\n", + "transformer_width : float/dict/pandas.Series, default 2\n", + " Widths of transformer in km.\n", + "arrow_size_factor : float, default 1.5\n", + " Multiplier on branch flows to scale the arrow size.\n", + "arrow_color : str/dict/pandas.Series | None, default None\n", + " Colors for the arrows. If not specified, defaults to the same colors as the respective branch component.\n", + "arrow_cmap : str/matplotlib.colors.Colormap, default 'viridis'\n", + " Colormap to use if arrow_color is a numeric pandas.Series.\n", + "arrow_cmap_norm : matplotlib.colors.Normalize, optional\n", + " Normalization to use if arrow_color is a numeric pandas.Series.\n", + "arrow_alpha : float/dict/pandas.Series, default 0.9\n", + " Add alpha channel to arrows, defaults to 0.9.\n", + "bus_columns : list, default None\n", + " List of bus columns to include.\n", + " Specify additional columns to include in the tooltip.\n", + "line_columns : list, default None\n", + " List of line columns to include. If None, only the bus0 and bus1 columns are used.\n", + " Specify additional columns to include in the tooltip.\n", + "link_columns : list, default None\n", + " List of link columns to include. If None, only the bus0 and bus1 columns are used.\n", + " Specify additional columns to include in the tooltip.\n", + "transformer_columns : list, default None\n", + " List of transformer columns to include. If None, only the bus0 and bus1 columns are used.\n", + " Specify additional columns to include in the tooltip.\n", + "geomap : bool, default False\n", + " Whether to add a geomap layer to the plot.\n", + "geomap_alpha : float, default 0.9\n", + " Alpha transparency for the geomap features.\n", + "geomap_color : dict | None, default None\n", + " Dictionary specifying colors for different geomap features. If None, default colors will be used: `{'land': 'whitesmoke', 'ocean': 'lightblue'}\n", + "geomap_resolution : {'110m', '50m', '10m'}, default '50m'\n", + " Resolution of the geomap features. One of '110m', '50m', or '10m'.\n", + "geometry : bool, default False\n", + " Whether to use the geometry column of the branch components.\n", + "\n", + "Returns\n", + "-------\n", + "PydeckPlotter\n", + " The PydeckPlotter instance with the created layers.\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "ef2301ab", + "metadata": {}, + "source": [ + "In the docstrings, you can see that the method allows for passing the same arguments as its static counterpart `n.plot()` and even a few more. Please be aware that due to how `pydeck` and `matplotlib.pyplot` handle numeric values, passing a value for e.g. `line_width` will achieve a different scalings in each method. In `n.explore()`, all widths and flows passed are translated into kilometers on the interactive map. Values passed for `bus_size` translate into km², accordingly. By default, all branches are rendered at a width of 2 km, buses at a size of 25 km²." + ] + }, + { + "cell_type": "markdown", + "id": "c7ebd9bf", + "metadata": {}, + "source": [ + "## Retrieving Results Data" + ] + }, + { + "cell_type": "markdown", + "id": "463c42fd", + "metadata": {}, + "source": [ + "To map result to parameters of the interactive map, we first solve the network and then use `n.statistics()` to calculate relevant metrics." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e9f80d9", + "metadata": {}, + "outputs": [], + "source": [ + "# We reduce logging output for clarity\n", + "import logging\n", + "\n", + "logging.getLogger(\"pypsa\").setLevel(logging.ERROR)\n", + "logging.getLogger(\"linopy\").setLevel(logging.ERROR)\n", + "\n", + "nc.optimize()" + ] + }, + { + "cell_type": "markdown", + "id": "2abedd89", + "metadata": {}, + "source": [ + "From above we learned that `bus_size` accepts parameters of type `float`, `dict`, and `pd.Series`. When passing a multi-index `pd.Series`, its values will be mapped to pie chart slices." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b7c3000", + "metadata": {}, + "outputs": [], + "source": [ + "eb = (\n", + " nc.statistics.energy_balance(\n", + " groupby=[\"bus\", \"carrier\"],\n", + " components=[\"Generator\", \"Load\", \"StorageUnit\"],\n", + " )\n", + " .groupby([\"bus\", \"carrier\"])\n", + " .sum()\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "5e85a5f3", + "metadata": {}, + "source": [ + "We also extract branch results, e.g., line and link flows in this example." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c6c904c4", + "metadata": {}, + "outputs": [], + "source": [ + "line_flow = nc.lines_t.p0.sum(axis=0)\n", + "link_flow = nc.links_t.p0.sum(axis=0)" + ] + }, + { + "cell_type": "markdown", + "id": "d0187703", + "metadata": {}, + "source": [ + "Note that for the pie slices to be plotted and colored correctly, passing a multi-index `pd.Series` requires all carrier colors to exist. Colors can be specified by their hex code representation or from the list of [matplotlib names](https://matplotlib.org/stable/gallery/color/named_colors.html). In `n.statistics.energy_balance()` load is also included, so we also need to include a color for the load carrier." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68810aa5", + "metadata": {}, + "outputs": [], + "source": [ + "colors = {\n", + " \"Multiple\": \"pink\",\n", + " \"AC\": \"black\",\n", + " \"Brown Coal\": \"saddlebrown\",\n", + " \"Gas\": \"darkorange\",\n", + " \"Geothermal\": \"firebrick\",\n", + " \"Hard Coal\": \"darkslategray\",\n", + " \"Nuclear\": \"mediumorchid\",\n", + " \"Oil\": \"peru\",\n", + " \"Other\": \"dimgray\",\n", + " \"Pumped Hydro\": \"cornflowerblue\",\n", + " \"Run of River\": \"royalblue\",\n", + " \"Solar\": \"gold\",\n", + " \"Storage Hydro\": \"navy\",\n", + " \"Waste\": \"olive\",\n", + " \"Wind Offshore\": \"teal\",\n", + " \"Wind Onshore\": \"turquoise\",\n", + "}\n", + "\n", + "nc.carriers.color = nc.carriers.index.map(colors)" + ] + }, + { + "cell_type": "markdown", + "id": "cc62f8bd", + "metadata": {}, + "source": [ + "As the carriers for loads are missing, we need to add them, manually." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "95717b7e", + "metadata": {}, + "outputs": [], + "source": [ + "nc.carriers.loc[\"\", \"color\"] = \"darkred\"\n", + "nc.carriers.loc[\"-\", \"color\"] = \"darkred\"" + ] + }, + { + "cell_type": "markdown", + "id": "5d199d08", + "metadata": {}, + "source": [ + "## Applying n.explore()" + ] + }, + { + "cell_type": "markdown", + "id": "9e89ce50", + "metadata": {}, + "source": [ + "Finally, let's pass the results to `n.explore()`. By default the `map_style='road'` is used, we pass `dark` for illustrative purposes. Setting `bus_split_circle=True` maps negative values to the bottom half and positive values to the positive half. If set to `False`, bottom half circles are not used and negative values will automatically be omitted. As the values scale proportionally to the bus area, they are directly translated into km². This may not achieve the outcome we want, this is why we set `auto_scale=True`. This scales the maximum value to `bus_size_max`. The same applies to `branch_width` and `branch_flow` (`branch_width_max`). By defaults, arrows are scaled by 1.5, so that arrowheads are visible. If you want those to be less or more prominent, scale accordingly. We can pass additional columns for each component type that we want to include in the tooltip. Note that we disable the tooltip in the documentation due to file size limitations of our documentation." + ] + }, + { + "cell_type": "markdown", + "id": "b36f0240", + "metadata": {}, + "source": [ + "Optionally, you can pass a `pdk.ViewState` object or a `dict`. By default, PyPSA will set zoom level of `4` and calculate the initial view based on all coordinates in n.buses. For details on how to use `view_state`, we refer to the [pydeck documentation](https://deckgl.readthedocs.io/en/latest/view_state.html)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c43a4f4e", + "metadata": {}, + "outputs": [], + "source": [ + "view_state = {}\n", + "view_state[\"zoom\"] = 6\n", + "view_state[\"pitch\"] = 35 # Up/down angle relative to the map's plane\n", + "\n", + "map = nc.explore(\n", + " view_state=view_state,\n", + " map_style=\"dark\",\n", + " bus_size=eb, # MWh -> km²\n", + " bus_split_circle=True,\n", + " bus_size_max=7000, # km²\n", + " line_color=\"yellow\",\n", + " line_width=line_flow, # MWh -> km\n", + " link_width=link_flow, # MWh -> km\n", + " line_flow=line_flow, # MWh -> km\n", + " link_flow=link_flow, # MWh -> km\n", + " branch_width_max=16, # km\n", + " auto_scale=True,\n", + " bus_columns=[\"v_nom\"],\n", + " line_columns=[\"s_nom\"],\n", + " link_columns=[\"p_nom\"],\n", + " arrow_size_factor=2,\n", + " tooltip=True, # disabled here for technical limits of mkdocs-jupyter plugin\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "2252087c", + "metadata": {}, + "source": [ + "## Map Export" + ] + }, + { + "cell_type": "markdown", + "id": "7ccd46af", + "metadata": {}, + "source": [ + "To export the interactive map, we use `pydeck`'s built-in features (`pdk.to_html()`). Passing `offline=True` embeds deck.gls JavaScript library. Note that you require an internet connection if you want to be able to view the map tiles in the background. If you want country shapes also be included in the self-contained HTML, set `geomap=True`. This will however increase the file size noticeably." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "81fa17e1", + "metadata": {}, + "outputs": [], + "source": [ + "map.to_html(\"exploring.html\")" + ] + }, + { + "cell_type": "markdown", + "id": "3096f620", + "metadata": {}, + "source": [ + "## Static Equivalent" + ] + }, + { + "cell_type": "markdown", + "id": "58cc0099", + "metadata": {}, + "source": [ + "This would be its static equivalent. As mentioned above, scaling is handled differently in `pydeck` and `matplotlib`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c12cf8d2", + "metadata": {}, + "outputs": [], + "source": [ + "nc.plot(\n", + " bus_size=eb / 3e6,\n", + " bus_split_circle=True,\n", + " line_width=line_flow / 1e4,\n", + " link_width=link_flow / 1e4,\n", + " line_flow=line_flow / 5e4,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "49b1cfc4", + "metadata": {}, + "source": [ + "## Stacking Pydeck Layers" + ] + }, + { + "cell_type": "markdown", + "id": "be7bd5af", + "metadata": {}, + "source": [ + "We can use `pydeck`s built-in layer functionalities to extend the interactive map with features that are completely unrelated to `pypsa`. For example, we can use the `GeoDataFrame` containing `Polygon` and `MultiPolygon` geometries to color them by average prices (€/MWh)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0b98ef48", + "metadata": {}, + "outputs": [], + "source": [ + "avg_prices = nc.statistics.prices()\n", + "avg_prices.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87c92c69", + "metadata": {}, + "outputs": [], + "source": [ + "# Map average prices by shapeISO\n", + "states[\"avg_price\"] = states[\"shapeISO\"].map(avg_prices).round(2)" + ] + }, + { + "cell_type": "markdown", + "id": "d99582b4", + "metadata": {}, + "source": [ + "Now we can map colors to the shapes using a colormap and store them as in RGBA formatted lists in the states `GeoDataFrame`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cea75c46", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.colors as mcolors\n", + "import matplotlib.pyplot as plt\n", + "\n", + "values = states[\"avg_price\"]\n", + "cmap = plt.get_cmap(\"Reds\")\n", + "norm = mcolors.Normalize(vmin=values.min(), vmax=values.max())" + ] + }, + { + "cell_type": "markdown", + "id": "acf368ba", + "metadata": {}, + "source": [ + "We need a small helper to convert `mcolors` to `pydeck` ready RGBA lists" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2354a30c", + "metadata": {}, + "outputs": [], + "source": [ + "def price_to_color(price, alpha=0.7):\n", + " color = cmap(norm(price)) # RGBA in 0-1\n", + " rgb = [round(c * 255) for c in color[:3]] # only RGB\n", + " a = round(alpha * 255)\n", + " return rgb + [a]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "439d30f0", + "metadata": {}, + "outputs": [], + "source": [ + "states[\"color\"] = states[\"avg_price\"].apply(price_to_color)\n", + "states.head()" + ] + }, + { + "cell_type": "markdown", + "id": "410a36c1", + "metadata": {}, + "source": [ + "We now create a new layer based on our states `GeoDataFrame` and insert it to `map.layers`. If we append it, the states layer would lie above the pie charts, which would obtrude our previous map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b59f5097", + "metadata": {}, + "outputs": [], + "source": [ + "import pydeck as pdk\n", + "\n", + "# Add a custom tooltip column (HTML or plain text)\n", + "states[\"tooltip_html\"] = (\n", + " \"State: \"\n", + " + states[\"shapeName\"]\n", + " + \"
\"\n", + " + \"GID: \"\n", + " + states[\"shapeISO\"]\n", + " + \"
Avg. Price: \"\n", + " + states[\"avg_price\"].astype(str)\n", + " + \" €/MWh\"\n", + ")\n", + "\n", + "# Create layer\n", + "states_layer = pdk.Layer(\n", + " \"GeoJsonLayer\",\n", + " states,\n", + " stroked=True,\n", + " filled=True,\n", + " get_fill_color=\"color\",\n", + " get_line_color=[255, 255, 255, 255],\n", + " line_width_min_pixels=1,\n", + " pickable=True,\n", + " auto_highlight=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a319f27e", + "metadata": {}, + "outputs": [], + "source": [ + "map.layers.insert(0, states_layer)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e4aaa3f", + "metadata": {}, + "outputs": [], + "source": [ + "map.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/user-guide/plotting/static-map.ipynb b/PyPSA/source/docs/user-guide/plotting/static-map.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..6a818b1b521e66a0ea575efc6ee446ef2a10f126 --- /dev/null +++ b/PyPSA/source/docs/user-guide/plotting/static-map.ipynb @@ -0,0 +1,491 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "injected-warnings", + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# General notebook settings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"error\", category=DeprecationWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "fc823344", + "metadata": {}, + "source": [ + "# Maps (Static)" + ] + }, + { + "cell_type": "markdown", + "id": "2cd0b43d", + "metadata": {}, + "source": [ + "PyPSA has a built-in method to map parameters and results in a static plot using `n.plot()`. For interactive maps, please see [Interactive Maps](/user-guide/plotting/plotting-maps-interactive)" + ] + }, + { + "cell_type": "markdown", + "id": "bf33add3", + "metadata": {}, + "source": [ + "## Input data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e6eecbc9", + "metadata": {}, + "outputs": [], + "source": [ + "import geopandas as gpd\n", + "\n", + "import pypsa\n", + "\n", + "n = pypsa.examples.scigrid_de()" + ] + }, + { + "cell_type": "markdown", + "id": "78e4f4ff", + "metadata": {}, + "source": [ + "## Preparation" + ] + }, + { + "cell_type": "markdown", + "id": "8a625075", + "metadata": {}, + "source": [ + "For illustrative purposes and a simpler postprocessing workflow, we cluster the network based on federal states. For a more detailed guide on clustering, please go to [Network Clustering](/examples/clustering). Within the scope of this guide, you can ignore the following blocks." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d51cc221", + "metadata": {}, + "outputs": [], + "source": [ + "n.calculate_dependent_values()\n", + "n.lines = n.lines.reindex(columns=n.components[\"Line\"][\"defaults\"].index[1:])\n", + "n.lines[\"type\"] = \"Al/St 240/40 2-bundle 220.0\"\n", + "n.buses = n.buses.reindex(columns=n.components[\"Bus\"][\"defaults\"].index[1:])\n", + "n.buses[\"frequency\"] = 50\n", + "\n", + "url = \"https://media.githubusercontent.com/media/wmgeolab/geoBoundaries/9469f09592ced973a3448cf66b6100b741b64c0d/releaseData/gbOpen/DEU/ADM1/geoBoundaries-DEU-ADM1-all.zip\"\n", + "states = gpd.read_file(url, layer=\"geoBoundaries-DEU-ADM1_simplified\")\n", + "states[\"shapeName\"] = states[\"shapeName\"].apply(\n", + " lambda x: x.encode(\"latin1\").decode(\"utf-8\")\n", + ") # fix encoding issue\n", + "\n", + "bus_coords = gpd.GeoDataFrame(\n", + " geometry=gpd.points_from_xy(n.buses.x, n.buses.y, crs=4326), index=n.buses.index\n", + ")\n", + "busmap = bus_coords.to_crs(3035).sjoin_nearest(states.to_crs(3035), how=\"left\").shapeISO\n", + "nc = n.cluster.cluster_by_busmap(busmap)" + ] + }, + { + "cell_type": "markdown", + "id": "ec137b09", + "metadata": {}, + "source": [ + "By default, calling `n.plot()` will render all network components based on the `x` and `y` coordinates defined in `n.buses`. This allows us to get a first visual overview on the two networks, before and after clustering." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "773bb70e", + "metadata": {}, + "outputs": [], + "source": [ + "n.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c11c716", + "metadata": {}, + "outputs": [], + "source": [ + "nc.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "90c33ba9", + "metadata": {}, + "source": [ + "## Retrieving Results Data" + ] + }, + { + "cell_type": "markdown", + "id": "fe56d158", + "metadata": {}, + "source": [ + "To map result to parameters of `n.plot()`, we first solve the network and then use `n.statistics()` to calculate relevant metrics." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb9e905e", + "metadata": {}, + "outputs": [], + "source": [ + "# We reduce logging output for clarity\n", + "import logging\n", + "\n", + "logging.getLogger(\"pypsa\").setLevel(logging.ERROR)\n", + "logging.getLogger(\"linopy\").setLevel(logging.ERROR)\n", + "\n", + "nc.optimize()" + ] + }, + { + "cell_type": "markdown", + "id": "39c862fc", + "metadata": {}, + "source": [ + "From above we learned that `bus_size` accepts parameters of type `float`, `dict`, and `pd.Series`. When passing a multi-index `pd.Series`, its values will be mapped to pie chart slices." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "469ea126", + "metadata": {}, + "outputs": [], + "source": [ + "eb = (\n", + " nc.statistics.energy_balance(\n", + " groupby=[\"bus\", \"carrier\"],\n", + " components=[\"Generator\", \"Load\", \"StorageUnit\"],\n", + " )\n", + " .groupby([\"bus\", \"carrier\"])\n", + " .sum()\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "824d6d1d", + "metadata": {}, + "source": [ + "We also extract branch results, e.g., line and link flows in this example." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f34b341", + "metadata": {}, + "outputs": [], + "source": [ + "line_flow = nc.lines_t.p0.sum(axis=0)\n", + "link_flow = nc.links_t.p0.sum(axis=0)" + ] + }, + { + "cell_type": "markdown", + "id": "c6a1dc9f", + "metadata": {}, + "source": [ + "Note that for the pie slices to be plotted and colored correctly, passing a multi-index `pd.Series` requires all carrier colors to exist. Colors can be specified by their hex code representation or from the list of [matplotlib names](https://matplotlib.org/stable/gallery/color/named_colors.html). In `n.statistics.energy_balance()` load is also included, so we also need to include a color for the load carrier." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ac7c878", + "metadata": {}, + "outputs": [], + "source": [ + "colors = {\n", + " \"Multiple\": \"pink\",\n", + " \"AC\": \"black\",\n", + " \"Brown Coal\": \"saddlebrown\",\n", + " \"Gas\": \"darkorange\",\n", + " \"Geothermal\": \"firebrick\",\n", + " \"Hard Coal\": \"darkslategray\",\n", + " \"Nuclear\": \"mediumorchid\",\n", + " \"Oil\": \"peru\",\n", + " \"Other\": \"dimgray\",\n", + " \"Pumped Hydro\": \"cornflowerblue\",\n", + " \"Run of River\": \"royalblue\",\n", + " \"Solar\": \"gold\",\n", + " \"Storage Hydro\": \"navy\",\n", + " \"Waste\": \"olive\",\n", + " \"Wind Offshore\": \"teal\",\n", + " \"Wind Onshore\": \"turquoise\",\n", + "}\n", + "\n", + "nc.carriers.color = nc.carriers.index.map(colors)" + ] + }, + { + "cell_type": "markdown", + "id": "573072cd", + "metadata": {}, + "source": [ + "As the carriers for loads are missing, we need to add them, manually." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3cd2b6f9", + "metadata": {}, + "outputs": [], + "source": [ + "nc.carriers.loc[\"\", \"color\"] = \"darkred\"\n", + "nc.carriers.loc[\"-\", \"color\"] = \"darkred\"" + ] + }, + { + "cell_type": "markdown", + "id": "5a528be6", + "metadata": {}, + "source": [ + "## Balances and flow" + ] + }, + { + "cell_type": "markdown", + "id": "798c82b7", + "metadata": {}, + "source": [ + "We first choose a suitable projection for the plot by importing `cartopy` and `matplotlib`. Commonly used projections include `ccrs.Mercator()` or `ccrs.EqualEarth()`. We can also pass `geomap_color=True` to get default colorings for land and water bodies. Note that this requires cartopy to be installed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a41cddad", + "metadata": {}, + "outputs": [], + "source": [ + "import cartopy.crs as ccrs\n", + "import matplotlib.pyplot as plt\n", + "\n", + "fig, ax = plt.subplots(figsize=(8, 7), subplot_kw={\"projection\": ccrs.EqualEarth()})\n", + "nc.plot(ax=ax, geomap_color=True)" + ] + }, + { + "cell_type": "markdown", + "id": "634c7c53", + "metadata": {}, + "source": [ + "Next, let's pass the results to `n.explore()`. Setting `bus_split_circle=True` maps negative values to the bottom half and positive values to the positive half. If set to `False`, bottom half circles are not used and negative values will automatically be omitted. Note that the area of `bus_size` scales proportionally to the values passed. To get a useful map, we need to scale the values, according to personal preference." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46b56145", + "metadata": {}, + "outputs": [], + "source": [ + "bus_size_factor = 3e6\n", + "branch_width_factor = 2e4\n", + "branch_flow_factor = 5e4\n", + "\n", + "nc.plot(\n", + " ax=ax,\n", + " bus_size=eb / bus_size_factor,\n", + " bus_split_circle=True,\n", + " line_width=line_flow / branch_width_factor,\n", + " link_width=link_flow / branch_width_factor,\n", + " line_flow=line_flow / branch_flow_factor,\n", + ")\n", + "\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "id": "233494ca", + "metadata": {}, + "source": [ + "## Legends" + ] + }, + { + "cell_type": "markdown", + "id": "8243b676", + "metadata": {}, + "source": [ + "We import additional functions from `pypsa.plot` to add legends to our figure. It makes sense to select values that are close to values represented in the figure, e.g. the maximum, minimum values and something in between." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c56c7d24", + "metadata": {}, + "outputs": [], + "source": [ + "from pypsa.plot import add_legend_lines, add_legend_patches, add_legend_semicircles\n", + "\n", + "print(f\"Max flow: {line_flow.abs().max()}\")\n", + "print(f\"Min flow: {line_flow.abs().min()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "02a02bc0", + "metadata": {}, + "source": [ + "Based on the values, we choose the following. Not that the values need to be scaled with the same factors determined before." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22498550", + "metadata": {}, + "outputs": [], + "source": [ + "add_legend_lines(\n", + " ax,\n", + " sizes=[branch / branch_width_factor for branch in [150000, 100000, 10000]],\n", + " labels=[\"150\", \"100\", \"10\"],\n", + " legend_kw={\"loc\": \"lower right\", \"frameon\": False, \"title\": \"Line flow (GWh)\"},\n", + ")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "id": "40033a57", + "metadata": {}, + "source": [ + "Now we apply the same process for bus sizes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a3877af", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"Max gen./load: {eb.groupby('bus').sum().abs().max()}\")\n", + "print(f\"Max gen./load: {eb.groupby('bus').sum().abs().min()}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b67ad1d", + "metadata": {}, + "outputs": [], + "source": [ + "add_legend_semicircles(\n", + " ax,\n", + " sizes=[bus / bus_size_factor for bus in [120000, -120000]],\n", + " labels=[\"+120 GWh\", \"-120 GWh\"],\n", + " legend_kw={\n", + " \"loc\": \"upper left\",\n", + " \"frameon\": False,\n", + " \"bbox_to_anchor\": (0.02, 0.98),\n", + " },\n", + ")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "id": "aad793e5", + "metadata": {}, + "source": [ + "To add legend entries for each bus carrier, we use `add_legend_patches`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0ae3a279", + "metadata": {}, + "outputs": [], + "source": [ + "add_legend_patches(\n", + " ax,\n", + " colors=list(nc.carriers.color), # colors\n", + " labels=list(nc.carriers.index), # labels\n", + " legend_kw={\n", + " \"loc\": \"lower center\",\n", + " \"bbox_to_anchor\": (0.5, -0.25), # For offsetting\n", + " \"ncol\": 4,\n", + " \"frameon\": False,\n", + " },\n", + ")\n", + "\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "id": "55de92c9", + "metadata": {}, + "source": [ + "## Export" + ] + }, + { + "cell_type": "markdown", + "id": "92d18bff", + "metadata": {}, + "source": [ + "We can export the figure to any desired format, i.e., `.png`, `.jpg`, `.pdf` etc." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7b65239c", + "metadata": {}, + "outputs": [], + "source": [ + "fig.savefig(\"static-plot.jpg\", dpi=150)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PyPSA/source/docs/user-guide/power-flow.md b/PyPSA/source/docs/user-guide/power-flow.md new file mode 100644 index 0000000000000000000000000000000000000000..36cbea98a480d69f544b325fc1ecd137f0ac977c --- /dev/null +++ b/PyPSA/source/docs/user-guide/power-flow.md @@ -0,0 +1,239 @@ + + +# Non-linear Power Flow + + +The non-linear power flow calculation [`n.pf()`][pypsa.Network.pf] works for AC +networks and by extension for DC networks, too. It can be called for a +particular snapshot as `n.pf(snapshots=n.snapshots[0])` or on an iterable of +snapshots as `n.pf(snapshots=n.snapshots[:24])`. + +The power flow calculation is independent of the optimisation. A common workflow +is to first optimise the network with the linearised approximations of +[`n.optimize()`][pypsa.optimization.OptimizationAccessor.__call__] and then to run the non-linear power +flow calculation on the optimised network with [`n.pf()`][pypsa.Network.pf] for +validation, using the power imbalances from the optimisation as setpoints. There +is a function that does this for you, +[`n.optimize.optimize_and_run_non_linear_powerflow()`][pypsa.optimization.OptimizationAccessor.optimize_and_run_non_linear_powerflow]. + +## AC networks (single slack) + +The power flow ensures for given inputs (load and power plant dispatch) that the following equation is satisfied for each bus $i$: + +$$S_i = P_i + j Q_i = V_i I_i^* = V_i \left(\sum_j Y_{ij} V_j\right)^*$$ + +where $V_i = |V_i|e^{j\theta_i}$ is the complex voltage, whose rotating angle is taken relative to the slack bus. + +$Y_{ij}$ is the bus admittance matrix, based on the branch impedances and any shunt admittances attached to the buses. + +For the slack bus $i=0$ it is assumed $|V_0|$ is given and that $\theta_0 = 0$; $P$ and $Q$ are to be found. + +For the PV buses, $P$ and $|V|$ are given; $Q$ and $\theta$ are to be found. + +For the PQ buses, $P$ and $Q$ are given; $|V|$ and $\theta$ are to be found. + +If PV and PQ are the sets of buses, then there are $|PV| + 2|PQ|$ real equations to solve: + +$$\textrm{Re}\left[ V_i \left(\sum_j Y_{ij} V_j\right)^* \right] - P_i = 0 \hspace{.7cm}\forall\hspace{.1cm} i \in PV \cup PQ$$ + +$$\textrm{Im}\left[ V_i \left(\sum_j Y_{ij} V_j\right)^* \right] - Q_i = 0 \hspace{.7cm}\forall\hspace{.1cm} i \in PQ$$ + +To be found: $\theta_i \forall i \in PV \cup PQ$ and $|V_i| \forall i \in PQ$. + +These equations $f(x) = 0$ are solved using the [Newton-Raphson method](https://en.wikipedia.org/wiki/Newton%27s_method#k_variables.2C_k_functions), with the Jacobian: + +$$\frac{\partial f}{\partial x} = \left( \begin{array}{cc} +\frac{\partial P}{\partial \theta} & \frac{\partial P}{\partial |V|} \\ +\frac{\partial Q}{\partial \theta} & \frac{\partial Q}{\partial |V|} +\end{array} \right)$$ + +and the initial flat guess of $\theta_i = 0$ and $|V_i| = 1$ for unknown quantities. + +## AC networks (distributed slack) + +If the slack is to be distributed to all generators in proportion to their +dispatch with `n.pf(distribute_slack=True)`, instead of being allocated fully to +the slack bus, the active power balance is altered to + +$$\textrm{Re}\left[ V_i \left(\sum_j Y_{ij} V_j\right)^* \right] - P_i - P_{slack}\gamma_i = 0 \hspace{.7cm}\forall\hspace{.1cm} i \in PV \cup PQ \cup slack$$ + +where $P_{slack}$ is the total slack power and $\gamma_{i}$ is the share of bus $i$ of the total generation that is used to distribute the slack power. Note that also an additional active power balance is included for the slack bus since it is now part of the distribution scheme. + +This adds an additional row to the Jacobian for the derivatives of the slack bus active power balance and an additional column for the partial derivatives with respect to $\gamma_i$. + +## DC networks + +For meshed DC networks the equations are a special case of those for AC networks, with the difference that all quantities are real. + +To solve the non-linear equations for a DC network, ensure that the series reactance $x$ and shunt susceptance $b$ are zero for all branches, pick a slack bus (where $V_0 = 1$) and set all other buses to be 'PQ' buses. Then execute [`n.pf()`][pypsa.Network.pf]. + +The voltage magnitudes then satisfy at each bus $i$: + +$$P_i = V_i I_i = V_i \sum_j G_{ij} V_j$$ + +where all quantities are real. + +$G_{ij}$ is based only on the branch resistances and any shunt conductances attached to the buses. + +## Line model + +Lines are modelled with the standard equivalent PI model. + +If the series impedance is given by + +$$z = r+jx$$ + +and the shunt admittance is given by + +$$y = g + jb$$ + +then the currents and voltages at buses 0 and 1 for a line: + +
+ ![Line equivalent model](../assets/images/line-equivalent.png){ width="600" } +
Line equivalent model
+
+ +are related by + +$$\left( \begin{array}{c} +i_0 \\ i_1 +\end{array} +\right) = \left( \begin{array}{cc} \frac{1}{z} + \frac{y}{2} & -\frac{1}{z} \\ +-\frac{1}{z} & \frac{1}{z} + \frac{y}{2} +\end{array} +\right) \left( \begin{array}{c} +v_0 \\ v_1 +\end{array} +\right)$$ + +## Transformer model + +The transformer models here are largely based on the implementation in [pandapower](https://github.com/panda-power/pandapower), which is loosely based on [DIgSILENT PowerFactory](http://www.digsilent.de/index.php/products-powerfactory.html). + +Transformers are modelled either with the equivalent T model (the default, since this represents the physics better) or with the equivalent PI model. The can be controlled by setting transformer attribute `model` to either "t" or "pi". + +The tap changer can either be modelled on the primary, high voltage side 0 (the default) or on the secondary, low voltage side 1. This is set with attribute `tap_side`. + +If the transformer `type` is not given, then `tap_ratio` is defined by the user, defaulting to `1.`. If the `type` is given, then the user can specify the `tap_position` which results in a `tap ratio` $\tau$ given by: + +$$\tau = 1 + (\textrm{tap_position} - \textrm{tap_neutral})\cdot \frac{\textrm{tap_step}}{100}$$ + +For a transformer with tap ratio $\tau$ on the primary side `tap_side=0` and phase shift $\theta_{\textrm{shift}}$, the equivalent T model is given by: + +
+ ![Transformer T equivalent model (tap HV)](../assets/images/transformer-t-equivalent-tap-hv.png){ width="600" } +
Transformer T equivalent model (tap HV)
+
+ +For a transformer with tap ratio $\tau$ on the secondary side `tap_side=1` and phase shift $\theta_{\textrm{shift}}$, the equivalent T model is given by: + +
+ ![Transformer T equivalent model (tap LV)](../assets/images/transformer-t-equivalent-tap-lv.png){ width="600" } +
Transformer T equivalent model (tap LV)
+
+ +For the admittance matrix, the T model is transformed into a PI model with the wye-delta transformation. + +For a transformer with tap ratio $\tau$ on the primary side `tap_side=0` and phase shift $\theta_{\textrm{shift}}$, the equivalent PI model is given by: + +
+ ![Transformer PI equivalent model (tap HV)](../assets/images/transformer-pi-equivalent-tap-hv.png){ width="600" } +
Transformer PI equivalent model (tap HV)
+
+ +for which the currents and voltages are related by: + +$$\left( \begin{array}{c} +i_0 \\ i_1 +\end{array} +\right) = \left( \begin{array}{cc} \frac{1}{z} + \frac{y}{2} & -\frac{1}{z}\frac{1}{\tau e^{-j\theta}} \\ +-\frac{1}{z}\frac{1}{\tau e^{j\theta}} & \left(\frac{1}{z} + \frac{y}{2} \right) \frac{1}{\tau^2} +\end{array} +\right) \left( \begin{array}{c} +v_0 \\ v_1 +\end{array} +\right)$$ + +For a transformer with tap ratio $\tau$ on the secondary side `tap_side=1` and phase shift $\theta_{\textrm{shift}}$, the equivalent PI model is given by: + +
+ ![Transformer PI equivalent model (tap LV)](../assets/images/transformer-pi-equivalent-tap-lv.png){ width="600" } +
Transformer PI equivalent model (tap LV)
+
+ +for which the currents and voltages are related by: + +$$\left( \begin{array}{c} +i_0 \\ i_1 +\end{array} +\right) = \left( \begin{array}{cc} \left(\frac{1}{z} + \frac{y}{2} \right) \frac{1}{\tau^2} & -\frac{1}{z}\frac{1}{\tau e^{-j\theta}} \\ +-\frac{1}{z}\frac{1}{\tau e^{j\theta}} & \frac{1}{z} + \frac{y}{2} +\end{array} +\right) \left( \begin{array}{c} +v_0 \\ v_1 +\end{array} +\right)$$ + +## Inputs + +For the non-linear power flow, the following data for each component are used. For the defaults and units, see . + +- `n.buses.{v_nom, v_mag_pu_set}` +- `n.loads.{p_set, q_set}` +- `n.generators.{control, p_set, q_set}` +- `n.storage_units.{control, p_set, q_set}` +- `n.stores.{p_set, q_set}` +- `n.shunt_impedances.{b, g}` +- `n.lines.{x, r, b, g}` +- `n.transformers.{x, r, b, g}` +- `n.links.{p_set}` + +!!! note + + Note that the control strategy for active and reactive power PQ/PV/Slack is set on the generators not on the buses. Buses then inherit the control strategy from the generators attached at the bus, defaulting to PQ if there is no generator attached. Any PV generator will make the whole bus a PV bus. For PV buses, the voltage magnitude set point is set on the bus, not the generator, with `n.buses.v_mag_pu_set`. + +!!! note + + Note that for lines and transformers you **must** make sure that $r+jx$ is non-zero, otherwise the bus admittance matrix will be singular. + +## Outputs + +- `n.buses.{v_mag_pu, v_ang, p, q}` +- `n.loads.{p, q}` +- `n.generators.{p, q}` +- `n.storage_units.{p, q}` +- `n.stores.{p, q}` +- `n.shunt_impedances.{p, q}` +- `n.lines.{p0, q0, p1, q1}` +- `n.transformers.{p0, q0, p1, q1}` +- `n.links.{p0, p1}` + + +## Examples + + +
+ +- :material-notebook:{ .lg .middle } **Newton-Raphson Power Flow** + + --- + + Solves non-linear AC power flow equations using the Newton-Raphson method to inspect voltage magnitudes and angles. + + [:octicons-arrow-right-24: Go to example](../examples/minimal-example-pf.ipynb) + +- :material-notebook:{ .lg .middle } **Transformers** + + --- + + Shows how transformers can be considered with varying tap ratios and phase + shifts. + + [:octicons-arrow-right-24: Go to example](../examples/transformer-example.ipynb) + +
diff --git a/PyPSA/source/docs/user-guide/statistics.md b/PyPSA/source/docs/user-guide/statistics.md new file mode 100644 index 0000000000000000000000000000000000000000..adf3275e5cd1e417a22a75f273f33550dcfe68a6 --- /dev/null +++ b/PyPSA/source/docs/user-guide/statistics.md @@ -0,0 +1,158 @@ + + +The `statistics` module is used to extract and calculate common metrics from a [Network][pypsa.Network]. This module is intended to simplify the process of inspecting networks and creating first visualizations of your results. + +It is accessed via the [`n.statistics`][pypsa.Network.statistics] property of any [pypsa.Network][] object. + +## Metrics + + +Currently supported metrics are: + +- [Capital expenditure][pypsa.statistics.StatisticsAccessor.capex]: The capital expenditure of all components. +- [Installed capital expenditure][pypsa.statistics.StatisticsAccessor.installed_capex]: The capital expenditure of all components before optimization. +- [Expanded capital expenditure][pypsa.statistics.StatisticsAccessor.expanded_capex]: The capital expenditure of all components added during optimization. +- [Operational expenditure][pypsa.statistics.StatisticsAccessor.opex]: The operational expenditure of all components. +- [Installed capacities][pypsa.statistics.StatisticsAccessor.installed_capacity]: The capacities of all components before optimization. +- [Expanded capacities][pypsa.statistics.StatisticsAccessor.expanded_capacity]: The capacities of all components added during optimization. +- [Optimal capacities][pypsa.statistics.StatisticsAccessor.optimal_capacity]: The total capacities of all components after optimization. +- [Supply][pypsa.statistics.StatisticsAccessor.supply]: The energy supplied by all components. +- [Withdrawal][pypsa.statistics.StatisticsAccessor.withdrawal]: The energy withdrawn by all components. +- [Curtailment][pypsa.statistics.StatisticsAccessor.curtailment]: The energy curtailed in all components. +- [Capacity Factor][pypsa.statistics.StatisticsAccessor.capacity_factor]: The capacity factor / utilization rate of all components. +- [Revenue][pypsa.statistics.StatisticsAccessor.revenue]: The revenue received by all components. +- [Market value][pypsa.statistics.StatisticsAccessor.market_value]: The market value of all components. +- [Energy balance][pypsa.statistics.StatisticsAccessor.energy_balance]: The energy balance of the network across all carriers and snapshots. +- [System costs][pypsa.statistics.StatisticsAccessor.system_cost]: The total system costs after optimization, including capital and operational expenditure. +- [Marginal prices][pypsa.statistics.StatisticsAccessor.prices]: The marginal prices at the buses for all snapshots. +- [Transmission][pypsa.statistics.StatisticsAccessor.transmission]: The energy transmitted through transmission components (links, lines, transformers connecting to buses of the same carrier). + +These metrics can be calculated using the `n.statistics` accessor, for instance: + +``` py +>>> installed_capacity = n.statistics.installed_capacity() +>>> installed_capacity # doctest: +ELLIPSIS +component carrier +Generator gas 150000.0 +... +dtype: float64 + +>>> opex = n.statistics.opex() +>>> opex # doctest: +SKIP +Component carrier +Generator gas 1000.0 + wind 0.0 + solar 0.0 +Name: opex, dtype: float64 +``` + +## Parameters + +Most statistics methods accept common parameters to control filtering, grouping, and output formatting. + +!!! warning + + Not all metrics support all parameters. Please check the function docstring for details. + +### Filtering + +Select which components to include: + +- **`components`**: List of component types to include (e.g., `["Generator", "StorageUnit"]`) +- **`carrier`**: Filter by component carrier (e.g., `"wind"` or `["wind", "solar"]`) +- **`bus_carrier`**: Filter by connected bus carrier (e.g., `"AC"`) + +!!! tip + + All filtering can also be done via pandas indexing after calling the statistic method. But using the parameters is more efficient, as it avoids unnecessary calculations. + +``` py +# Only generators with wind carrier on AC buses +>>> n.statistics.supply(components=["Generator"], carrier="wind", bus_carrier="AC") # doctest: +SKIP +``` + +### Grouping + +Control how results are grouped and aggregated: + +**`groupby`**: Group by attributes or custom functions. + +Built-in groupers include: + +- `"carrier"` - Group by component carrier +- `"bus_carrier"` - Group by carrier of connected bus +- `"bus"` - Group by connected bus +- `"country"` - Group by country of bus location +- `"location"` - Group by location of bus +- `"name"` - Group by component name +- `"unit"` - Group by unit + +You can also use component attributes directly (e.g., `"type"`, `"p_nom"`), provide custom functions, or combine multiple groupers for multi-index grouping. + +**`groupby_method`**: Aggregation method like `"sum"`, `"mean"`, `"max"`. Default is `"sum"` + +#### Registering custom groupers +You can register custom groupers to use them by name in the `groupby` argument: + +``` py +# Define a custom grouper function +def group_by_voltage(n, c, port=""): + """Group components by voltage level of connected bus.""" + bus = f"bus{port}" + buses = n.c[c].static[bus] + voltage = n.c.buses.static.v_nom.rename("voltage") + return buses.map(voltage) + +# Register the grouper on module level +pypsa.statistics.groupers.add_grouper("voltage", group_by_voltage) + +# Use it by name in any statistics method +n.statistics.installed_capacity(groupby="voltage") + +# Or access it as attribute +n.statistics.supply(groupby=pypsa.statistics.groupers.voltage) +``` + +Custom grouper functions must: + +- Accept arguments: `n` (Network), `c` (component name), `port` (optional), `nice_names` (optional) +- Return a `pd.Series` with the same length as the component index + +### Time Aggregation + +For time-varying metrics: + +- **`groupby_time`**: Method to aggregate over time (`"sum"`, `"mean"`, etc.) or `False` to keep time series + +``` py +>>> # Average supply per time step (empty before solving the network) +>>> n.statistics.supply(groupby_time="mean") +Series([], dtype: float64) + +>>> # Get full time series (NaN until dispatch is solved) +>>> n.statistics.supply(groupby_time=False).head() # doctest: +NORMALIZE_WHITESPACE +snapshot 2015-01-01 00:00:00 ... 2015-01-01 09:00:00 +component carrier ... +Generator gas NaN ... NaN + wind NaN ... NaN +Load load NaN ... NaN + +[3 rows x 10 columns] +``` + +!!! note + Time aggregation automatically accounts for snapshot weightings. + +### Output Formatting + +- **`nice_names`**: Use nice names for carriers +- **`drop_zero`**: Remove zero-value rows +- **`round`**: Round to decimal places + +## Expressions + +Next to the statistics module under [`n.statistics`][pypsa.Network.statistics], there is also an experimental optimization expressions module under `n.optimize.expressions`. It provides similar functionality, but creates `linopy` expressions for the optimization model instead of calculating values from the network data. diff --git a/PyPSA/source/docs/user-guide/support.md b/PyPSA/source/docs/user-guide/support.md new file mode 100644 index 0000000000000000000000000000000000000000..9b40c097abd338235cb141c2b93556e0b192b238 --- /dev/null +++ b/PyPSA/source/docs/user-guide/support.md @@ -0,0 +1,17 @@ + + +# Support + +Please consider the following ways to reach out to the community and the developers: + +* To **discuss** with other PyPSA users, organise projects, share news, and get in touch with the community you can use the [Discord server](https://discord.gg/AnuJBk23FU). +* For **bugs and feature requests**, please use the [issue tracker](https://github.com/PyPSA/PyPSA/issues). +* We strongly welcome anyone interested in providing **contributions** to this project. If you have any ideas, suggestions or encounter problems, feel invited to file issues or make pull requests on [Github](https://github.com/PyPSA/PyPSA). + +!!! note + + All questions benefit from being accompanied by a minimal working example (MWE) that illustrates the problem or question, and by sharing which approaches you have already tried. See [troubleshooting](troubleshooting.md) for more information on MWEs. diff --git a/PyPSA/source/docs/user-guide/troubleshooting.md b/PyPSA/source/docs/user-guide/troubleshooting.md new file mode 100644 index 0000000000000000000000000000000000000000..a51c3ef9310f8b56c6b96b43513707dfb0a7864a --- /dev/null +++ b/PyPSA/source/docs/user-guide/troubleshooting.md @@ -0,0 +1,166 @@ + + +# Troubleshooting + +## Minimum Working Examples + +For your own debugging and when asking for help, it is very useful to create a +Minimum Working Example (MWE). An MWE is a small example that reproduces the +problem you are experiencing. It should be as small as possible, but still +reproduce the problem. Often, when creating a MWE, you will find the problem +yourself, and if not it will be much easier for others to help you. + +For details on what a MWE is and how to create one, see this blog on how [Craft Minimal Bug Reports](https://matthewrocklin.com/minimal-bug-reports). + +## Library dependencies + +If you are experiencing problems with PyPSA or with the importing of the +libraries on which PyPSA depends, please first check that you are working with +the latest versions of all packages. See [Upgrading PyPSA](installation.md#upgrading). + +## Consistency check + +A consistency check can be performed using the function +[`n.consistency_check()`][pypsa.Network.consistency_check], which can point to +potential issues in the network. + +## Optimisation convergence & infeasibility + +If your [`n.optimize()`]() is not converging, +here are some suggestions to try out: + +* Very small non-zero values, for example in `n.generators_t.p_max_pu` can + confuse the solver. Consider e.g. removing values smaller than 0.001 with + [`pandas.DataFrame.clip`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.clip.html). + +* Open source solvers like HiGHS can struggle with large problems; consider + switching to a commercial solver like Gurobi or Xpress. Alternatively, scale + down the model size to make it easier to debug the problem (e.g. reducing + spatial or temporal resolution while keeping the same structure). + +* Use the interior point or barrier method, and stop it from crossing over to + the simplex algorithm once it is close to the solution. This will provide a + good approximate solution. Also set a random seed for reproducibility. Note + that solver parameters may differ between solvers and have varying effect on + different types of problems. + +=== "HiGHS" + + ``` py + n.optimize(solver_name='highs', solver="ipm", run_crossover="off", random_seed=123) + +=== "SCIP" + + ``` py + n.optimize(solver_name='scip', solver_options={"lp/initalgorithm": "b"}) + ``` + +=== "Gurobi" + + ``` py + n.optimize(solver_name='gurobi', method=2, crossover=0, Seed=123) + ``` + +=== "CPLEX" + + ``` py + n.optimize(solver_name='cplex', lpmethod=4, solutiontype=2) + ``` + +=== "COPT" + + ``` py + n.optimize(solver_name='copt', LpMethod=2, Crossover=0) + ``` + +=== "Xpress" + + ``` py + n.optimize(solver_name='xpress', LPFLAGS=4, CROSSOVER=0, BARALG=2) + ``` + +* Your problem may be infeasible, i.e. there is no solution that satisfies all + constraints. If you are using Gurobi, you can check which constraints cause an + infeasibility by adding the keyword argument `compute_infeasibilities` to + [`n.optimize()`][pypsa.optimization.OptimizationAccessor.__call__] to compute an [Irreducible + Inconsistent Subset + (IIS)](https://support.gurobi.com/hc/en-us/articles/360029969391-How-do-I-determine-why-my-model-is-infeasible): + + ``` py + n.optimize(solver_name='gurobi', compute_infeasibilities=True) + ``` + +* Add a load shedding generator with high marginal cost to all buses, which can + be used to shed load that cannot be met. This will often allow the + optimisation to find a solution even if the original problem is infeasible. + Then, based on where the load shedding generator is used, you can identify + which constraints are causing the infeasibility. + + ``` py + n.add( + "Generator", + n.buses.index, + suffix="load-shedding", + bus=n.buses.index, + marginal_cost=10_000, # high marginal cost + p_nom=1e9, # non-binding capacity + carrier="load_shedding", + ) + ``` + +## Power flow convergence + +If your [`n.pf()`][pypsa.Network.pf] is not converging there are two possible reasons: + +* The problem you have defined is not solvable (e.g. because in + reality you would have a voltage collapse). + +* The problem is solvable, but there are numerical instabilities in the solving + algorithm (e.g. Newton-Raphson is known not to converge even for + ill-conditioned solvable problems; or the flat solution PyPSA uses as an + initial guess is too far from the correction solution because of transformer + phase-shifts) + +There are some steps you can take to distinguish these two cases: + +* Check the units you have used to define the problem are correct. If your units + are out by a factor 1000 (e.g. using kW instead of MW) do not be surprised if + your problem is no longer solvable. + +* Check with a linear power flow [`n.lpf()`][pypsa.Network.lpf] that all voltage + angles differences across branches are less than 40 degrees. You can do this with the following code: + + ``` py + >>> import pandas as pd + >>> import numpy as np + >>> now = n.snapshots[0] # + >>> angle_diff = pd.Series( + ... n.buses_t.v_ang.loc[now,n.lines.bus0].values - + ... n.buses_t.v_ang.loc[now,n.lines.bus1].values, + ... index=n.lines.index + ... ) + >>> (angle_diff * 180 / np.pi).describe() #D doctest: +SKIP + ``` + +* You can seed the non-linear power flow initial guess with the + voltage angles from the linear power flow. This is advisable if you + have transformers with phase shifts in the network, which lead to + solutions far away from the flat initial guess of all voltage angles + being zero. To seed the problem activate the `use_seed` switch: + + ``` py + n.lpf() + n.pf(use_seed=True) + ``` + +* Reduce all power values `p_set` and `q_set` of generators and + loads to a fraction, e.g. 10%, solve the load flow and use it as a + seed for the power at 20%, iteratively up to 100%. + +## Reporting bugs and issues + +See the [Support](support.md) page for how to report bugs and issues. diff --git a/PyPSA/source/docs/user-guide/user-guide.md b/PyPSA/source/docs/user-guide/user-guide.md new file mode 100644 index 0000000000000000000000000000000000000000..1989960d0ef09c57b0e783f70b0929a1a847e641 --- /dev/null +++ b/PyPSA/source/docs/user-guide/user-guide.md @@ -0,0 +1,53 @@ + + +This section provides an overview of PyPSA's, design principles, optimisation +problem formulations, power flow calculation features and usage tips for +specific functions and components. + +
+ +- :simple-instructure:{ .lg .middle } **Core Design** + + --- + + Description of design choices, conventions and components of PyPSA. + + [:material-notebook: Core Design](../user-guide/design.md) + + --- + +- :material-hub:{ .lg .middle } **Optimisation** + + --- + + Description of optimisation problems that can be solved with PyPSA. + + [:material-notebook: Optimisation](../user-guide/optimization/overview.md) + + --- + +- :material-cog:{ .lg .middle } **Simulation** + + --- + + Description of power flow calculations PyPSA can run on solved networks. + + [:material-notebook: Simulation](../user-guide/power-flow.md) + + --- + +- :material-run:{ .lg .middle } **Usage** + + --- + + Description of useful statistics, plotting and comparison functions. + + [:material-notebook: Usage](../user-guide/statistics.md) + + --- + +
diff --git a/PyPSA/source/docs/user-guide/v1-guide.md b/PyPSA/source/docs/user-guide/v1-guide.md new file mode 100644 index 0000000000000000000000000000000000000000..6a14de40348b0b527ebf7458337aa3a3f1f7731e --- /dev/null +++ b/PyPSA/source/docs/user-guide/v1-guide.md @@ -0,0 +1,148 @@ + + +# What's new in PyPSA v1.0 + +**PyPSA v1.0** is here and brings a range of new features. Alongside this release, the documentation has also been completely updated, redesigned and moved to a new URL: [`https://docs.pypsa.org`](https://docs.pypsa.org). Take a look at the new documentation to find out about the latest features, as well as those that have not yet been well documented. + +!!! warning + While breaking changes have been kept to a minimum with this release, there are still some [changes to be aware of](#breaking-changes). Especially, when deprecation warnings from previous releases have not been addressed yet. + +## Features + +### Stochastic Optimization + +PyPSA now supports **two-stage stochastic programming** with scenario trees out of the box, which allows users to optimize investment decisions (first-stage) that are robust across multiple possible future realizations (scenarios) of uncertain parameters. In addition to the default risk-neutral formulation, a risk-averse formulation using the Conditional Value at Risk (CVaR) measure is also supported. See and [:material-notebook-multiple: Example Notebook](../examples/stochastic-optimization.ipynb). + +``` py +>>> n_stoch = pypsa.examples.ac_dc_meshed() +>>> n_stoch.set_scenarios({"low": 0.4, "med": 0.3, "high": 0.3}) +>>> n_stoch +Stochastic PyPSA Network 'AC-DC-Meshed' +--------------------------------------- +Components: + - Bus: 27 + - Carrier: 18 + - Generator: 18 + - GlobalConstraint: 3 + - Line: 21 + - Link: 12 + - Load: 18 +Snapshots: 10 +Scenarios: 3 +``` + +### Plotting Module +#### Statistics Plotting +Any **network metric** like `n.statistics.energy_balance()` or `n.statistics.optimal_capacity()` can now be plotted as a line, bar, area charts or maps. The equivalent plotting methods extend parameters from the statistics module and therefore use the same logic. Also **interactive plots** can be created for all metrics and plot types. See [:material-bookshelf: User Guide](plotting/charts.md) and [:octicons-code-16: API Reference](../api/networks/plot.md). + +``` py +>>> n_stoch.statistics.energy_balance.iplot.area() # doctest: +SKIP +``` + +
+ +
+ +#### Interactive Maps +Next to static map plotting with `n.plot()`, networks can now be rendered on a map, interactively. With `n.explore()`, you can explore the location of all components, including buses, lines, links, transformers, their component attributes and map results or other properties to the bus sizes, branch widths, colors, etc. Calling the method returns a standard `pydeck.Deck` object than can be layered on top of other `pydeck.Deck` objects (see https://deckgl.readthedocs.io/en/latest/layer.html). They can also be exported in self-contained HTML files for sharing. See [:material-bookshelf: User Guide](plotting/explore.ipynb) and [:octicons-code-16: API Reference](../api/networks/plot.md) + +``` py +>>> n.explore() # doctest: +SKIP +``` + +
+ +
+ + +### Network Collection +A new object called [`NetworkCollection`][pypsa.NetworkCollection] has been added to the library. It allows users to store multiple networks in a single object and perform operations on them. See [:material-bookshelf: User Guide](../user-guide/collection.md) and[:octicons-code-16: API Reference](../api/networks/collection.md). + +``` py +>>> n_base = pypsa.examples.ac_dc_meshed() # docs-hide +>>> n_base.name = 'Base Case' # docs-hide +>>> n_reference = pypsa.examples.ac_dc_meshed() # docs-hide +>>> n_reference.name = 'Reference Case' # docs-hide +>>> pypsa.NetworkCollection([n_base, n_reference]) +NetworkCollection +----------------- +Networks: 2 +Index name: 'network' +Entries: ['Base Case', 'Reference Case'] +``` + + +### Components Class +PyPSA [`Components`][pypsa.Components] are a new intermediate layer between the network object and the components data. They provide various auxiliary functions to reduce the need for boilerplate code when working with PyPSA networks without changing the underlying `pandas`-based structure. With the release of PyPSA `v1.0`, they are officially released and complemented by a new optional breaking API. See [:material-bookshelf: User Guide](../user-guide/components.md) and [:octicons-code-16: API Reference](../api/components/components.md). + +``` py +>>> n = pypsa.examples.ac_dc_meshed() +>>> n.generators + bus control ... weight p_nom_opt +name ... +Manchester Wind Manchester PQ ... 1.0 0.0 +Manchester Gas Manchester PQ ... 1.0 0.0 +Norway Wind Norway PQ ... 1.0 0.0 +Norway Gas Norway PQ ... 1.0 0.0 +Frankfurt Wind Frankfurt PQ ... 1.0 0.0 +Frankfurt Gas Frankfurt PQ ... 1.0 0.0 + +[6 rows x 38 columns] + +# Opt-in to new components API +>>> pypsa.options.api.new_components_api = True + +# n.generators will now return a Components object +>>> n.generators +'Generator' Components +---------------------- +Attached to PyPSA Network 'AC-DC-Meshed' +Components: 6 + +# Static data and more is still available +>>> n.generators.static + bus control ... weight p_nom_opt +name ... +Manchester Wind Manchester PQ ... 1.0 0.0 +Manchester Gas Manchester PQ ... 1.0 0.0 +Norway Wind Norway PQ ... 1.0 0.0 +Norway Gas Norway PQ ... 1.0 0.0 +Frankfurt Wind Frankfurt PQ ... 1.0 0.0 +Frankfurt Gas Frankfurt PQ ... 1.0 0.0 + +[6 rows x 38 columns] + +>>> pypsa.options.api.new_components_api = False +``` + +## Breaking Changes +While PyPSA has been stable for a while now, version `v1.0` is the first stable release. This means that future versions will introduce new features and improvements, but we will maintain full backward compatibility up until version `v2.0`. For more details check the [upgrade guide](../home/installation.md#upgrading). + +- Inactive components (e.g. when using the `active` attribute or when `build_time` and `lifetime` never match) are now excluded from the optimization model entirely. You can list them via [pypsa.Components.inactive_assets][]. This has no effect on any of the results, but if you access the `linopy` model directly, you will see that the number of variables and constraints might be reduced. + +- Index names of all pandas dataframes used to store components data (e.g. `n.generators` and `n.generators_t`) have been changed. The axis which lists the components is now called `name` across all component types, instead of the previous component type name (e.g. `Generator`), to align with the newly introduced xarray view ([pypsa.Components.da][]). + +- When retrieving a list of extendable, fixed or committable components, no suffix (e.g. `"-ext"` or `"-fix"`) is added anymore. This was previously be accessible via `n.get_extendable_i()` and now via [pypsa.Components.extendables][]. + +- The default values for `cyclic_state_of_charge_per_period` (StorageUnit) and `e_cyclic_per_period` (Store) have been changed from `True` to `False`. This is to be more consistent with single investment period optimization where cycling behavior defaults to `False`. Users who work with multi-investment period optimizations and want per-period cycling behavior must now explicitly set these attributes to `True`. + +- All features that were announced as deprecated in previous versions have now been removed. If those warnings have not been addressed yet, you will not be able to use PyPSA `v1.0`. + +!!! warning + + If you are unsure if you are still using any deprecated features, first install the latest version `v0.35.2` (also see [upgrade guide](../home/installation.md#upgrading)) and resolve all warnings. You can catch them all by raising them as errors. Just add the following to the top of your script: + ``` py + >>> import warnings + >>> warnings.filterwarnings('error', category=DeprecationWarning, module='pypsa') + ``` +- The new Components Class API is a major breaking change. It is opt-in with version `v1.0` and both the old and the new API are fully supported. A detailed introduction can be found in the dedicated [user guide section](../user-guide/components.md#new-components-class-api). Please have a look and provide feedback. The new API is planned to be the default in version `v2.0`. diff --git a/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed.nc b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed.nc new file mode 100644 index 0000000000000000000000000000000000000000..eecbc08ce63468710a772eac02ed2ddb445ea326 --- /dev/null +++ b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e2acbcaeb6c594ab605e58fe25809f62c64202721e0d600713244d47c732c57 +size 110163 diff --git a/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/buses.csv b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/buses.csv new file mode 100644 index 0000000000000000000000000000000000000000..d083bb2ab5c213ad42ee2c759bf9a78255c1e43d --- /dev/null +++ b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/buses.csv @@ -0,0 +1,10 @@ +name,v_nom,x,y,carrier,country +London,380.0,-0.13,51.5,AC,UK +Norwich,380.0,1.3,52.6,AC,UK +Norwich DC,200.0,1.3,52.5,DC,UK +Manchester,380.0,-2.2,53.47,AC,UK +Bremen,380.0,8.8,53.08,AC,DE +Bremen DC,200.0,8.8,52.98,DC,DE +Frankfurt,380.0,8.7,50.12,AC,DE +Norway,380.0,10.75,60.0,AC,NO +Norway DC,200.0,10.75,60.0,DC,NO diff --git a/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/carriers.csv b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/carriers.csv new file mode 100644 index 0000000000000000000000000000000000000000..8528563482e274fb08a566181c5a7e72071ad194 --- /dev/null +++ b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/carriers.csv @@ -0,0 +1,7 @@ +name,co2_emissions,color,marginal_cost,efficiency,capital_cost +gas,0.24,red,0.0,1.0,0.0 +wind,0.0,blue,0.0,1.0,0.0 +battery,0.0,green,0.0,1.0,0.0 +load,0.0,black,,, +AC,0.0,orange,,, +DC,0.0,purple,,, diff --git a/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/crs.json b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/crs.json new file mode 100644 index 0000000000000000000000000000000000000000..49edf3439c1f2d009aeeae1606c27c86fcd50f3f --- /dev/null +++ b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/crs.json @@ -0,0 +1 @@ +{"_crs": "GEOGCRS[\"WGS 84\",ENSEMBLE[\"World Geodetic System 1984 ensemble\",MEMBER[\"World Geodetic System 1984 (Transit)\"],MEMBER[\"World Geodetic System 1984 (G730)\"],MEMBER[\"World Geodetic System 1984 (G873)\"],MEMBER[\"World Geodetic System 1984 (G1150)\"],MEMBER[\"World Geodetic System 1984 (G1674)\"],MEMBER[\"World Geodetic System 1984 (G1762)\"],MEMBER[\"World Geodetic System 1984 (G2139)\"],MEMBER[\"World Geodetic System 1984 (G2296)\"],ELLIPSOID[\"WGS 84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]],ENSEMBLEACCURACY[2.0]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],CS[ellipsoidal,2],AXIS[\"geodetic latitude (Lat)\",north,ORDER[1],ANGLEUNIT[\"degree\",0.0174532925199433]],AXIS[\"geodetic longitude (Lon)\",east,ORDER[2],ANGLEUNIT[\"degree\",0.0174532925199433]],USAGE[SCOPE[\"Horizontal component of 3D system.\"],AREA[\"World.\"],BBOX[-90,-180,90,180]],ID[\"EPSG\",4326]]"} \ No newline at end of file diff --git a/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/generators-p_max_pu.csv b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/generators-p_max_pu.csv new file mode 100644 index 0000000000000000000000000000000000000000..3126d895dbbc44a955d31565faa848e82a34da12 --- /dev/null +++ b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/generators-p_max_pu.csv @@ -0,0 +1,11 @@ +,Manchester Wind,Frankfurt Wind,Norway Wind +0,0.930019875,0.5590784039,0.9745832033 +1,0.4857475804,0.7529103711,0.4812903778 +2,0.2336917351,0.1234650887,0.4072258018 +3,0.2576042221,0.9666766524,0.5999649628 +4,0.6269055694,0.8590078044,0.524468219 +5,0.6035984088,0.5261537924,0.0096927054 +6,0.6789075462,0.077893008,0.2204533621 +7,0.3613026112,0.0590234716,0.8239185004 +8,0.6216040549,0.2485544952,0.5562297265 +9,0.5215183715,0.1080601728,0.4394160378 diff --git a/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/generators.csv b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/generators.csv new file mode 100644 index 0000000000000000000000000000000000000000..966e5043ed997575364a1484dd0f08bc366a0add --- /dev/null +++ b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/generators.csv @@ -0,0 +1,7 @@ +name,bus,p_nom,p_nom_extendable,p_nom_min,carrier,marginal_cost,capital_cost,efficiency +Manchester Wind,Manchester,80.0,True,100.0,wind,0.11,2793.6516029328,1.0 +Manchester Gas,Manchester,50000.0,True,0.0,gas,4.5323676307,196.6151679691,0.3500264336 +Norway Wind,Norway,100.0,True,100.0,wind,0.09,2184.3747960912,1.0 +Norway Gas,Norway,20000.0,True,0.0,gas,5.8928445406,158.2512497168,0.3568363832 +Frankfurt Wind,Frankfurt,110.0,True,100.0,wind,0.1,2129.4561224763,1.0 +Frankfurt Gas,Frankfurt,80000.0,True,0.0,gas,4.0863219899,102.6769530076,0.3516658529 diff --git a/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/global_constraints.csv b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/global_constraints.csv new file mode 100644 index 0000000000000000000000000000000000000000..4794cc6f661bd664ad55b861a8a38e638e20bd9e --- /dev/null +++ b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/global_constraints.csv @@ -0,0 +1,2 @@ +name,sense,constant +co2_limit,<=,1000.0 diff --git a/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/investment_periods.csv b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/investment_periods.csv new file mode 100644 index 0000000000000000000000000000000000000000..34dd42197768b92fdb696fcfe70396544f01bb62 --- /dev/null +++ b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/investment_periods.csv @@ -0,0 +1 @@ +period,objective,years diff --git a/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/lines.csv b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/lines.csv new file mode 100644 index 0000000000000000000000000000000000000000..8a40a60f42347ffe625bf9554a8e55bda0ae5aad --- /dev/null +++ b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/lines.csv @@ -0,0 +1,8 @@ +name,bus0,bus1,x,r,s_nom,s_nom_extendable,capital_cost,carrier +0,London,Manchester,0.7968782824,0.0,40000.0,True,0.1367157553,AC +1,Manchester,Norwich,0.3915599178,0.0,40000.0,True,0.1334916779,AC +2,Bremen DC,Norwich DC,0.0,0.2126041927,40000.0,True,0.0086734246,AC +3,Norwich DC,Norway DC,0.0,0.4861637504,40000.0,True,0.1291260515,AC +4,Norway DC,Bremen DC,0.0,0.4287266497,40000.0,True,0.0624298729,AC +5,Norwich,London,0.2388003463,0.0,40000.0,True,0.0218524519,AC +6,Bremen,Frankfurt,0.4,0.0,40000.0,True,0.2,AC diff --git a/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/links.csv b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/links.csv new file mode 100644 index 0000000000000000000000000000000000000000..fde36517012ad01b0a22a50dc26a091b46371b8e --- /dev/null +++ b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/links.csv @@ -0,0 +1,5 @@ +name,bus0,bus1,carrier,p_nom,p_nom_extendable,p_min_pu,p_max_pu,capital_cost +Norwich Converter,Norwich,Norwich DC,DC,1000.0,True,-0.9,0.9,0.21 +Norway Converter,Norway,Norway DC,DC,1000.0,True,-0.9,0.9,0.2 +Bremen Converter,Bremen,Bremen DC,DC,1000.0,True,-0.9,0.9,0.19 +DC link,London,Bremen,DC,1000.0,True,-0.9,0.9,0.8765342 diff --git a/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/loads-p_set.csv b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/loads-p_set.csv new file mode 100644 index 0000000000000000000000000000000000000000..5aefc6a2b002d3d53c807d09498937bfb2c144e0 --- /dev/null +++ b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/loads-p_set.csv @@ -0,0 +1,11 @@ +,London,Norwich,Frankfurt,Bremen,Norway,Manchester +0,35.7962441027,415.4625642653,398.0478469638,640.0863775411,820.035835936,857.5514402011 +1,976.8245614698,262.6061464526,432.4361062425,703.554333706,854.8340468618,750.5996237166 +2,250.5873120464,418.4763531902,379.8039282662,440.8361303183,42.550744351,156.5648760141 +3,130.7531445827,552.9595393098,868.3617642835,612.5763056818,647.5482327851,527.8708221189 +4,151.1001686,218.159858091,548.7707546221,803.4367808051,884.0738733306,83.8977589634 +5,931.857051942,791.9762655836,828.6652426012,605.4006873582,509.0624485516,676.6233193474 +6,289.8482871447,531.8706808219,449.2907519075,641.0905902397,595.6079648147,731.1371004827 +7,864.3433217147,23.5134667186,699.1637663734,408.0085411725,291.6424496984,553.3448891847 +8,689.5772637703,970.0590684572,915.8667802518,912.2477761646,2.1534925491,298.338082262 +9,627.8789859434,0.9248336907,414.8876464034,898.0530916423,760.7401765038,768.2905859888 diff --git a/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/loads.csv b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/loads.csv new file mode 100644 index 0000000000000000000000000000000000000000..89d320822f6c273b6d70c9e14bca53a9f3521bf0 --- /dev/null +++ b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/loads.csv @@ -0,0 +1,7 @@ +name,bus,carrier +London,London,load +Frankfurt,Frankfurt,load +Norway,Norway,load +Norwich,Norwich,load +Bremen,Bremen,load +Manchester,Manchester,load diff --git a/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/meta.json b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/meta.json new file mode 100644 index 0000000000000000000000000000000000000000..9e26dfeeb6e641a33dae4961196235bdb965b21b --- /dev/null +++ b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/meta.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/network.csv b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/network.csv new file mode 100644 index 0000000000000000000000000000000000000000..e7a56ba2745890488c6f494c9859aeed4065e522 --- /dev/null +++ b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/network.csv @@ -0,0 +1,2 @@ +name,_multi_invest,now,pypsa_version,srid +AC-DC-Meshed,0,now,1.0.3,4326 diff --git a/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/snapshots.csv b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/snapshots.csv new file mode 100644 index 0000000000000000000000000000000000000000..7b8f5c7e78467d987cfe85e910b3ab6b6db0acbe --- /dev/null +++ b/PyPSA/source/examples/networks/ac-dc-meshed/ac-dc-meshed/snapshots.csv @@ -0,0 +1,11 @@ +,snapshot,objective,stores,generators +0,2015-01-01 00:00:00,1.0,1.0,1.0 +1,2015-01-01 01:00:00,1.0,1.0,1.0 +2,2015-01-01 02:00:00,1.0,1.0,1.0 +3,2015-01-01 03:00:00,1.0,1.0,1.0 +4,2015-01-01 04:00:00,1.0,1.0,1.0 +5,2015-01-01 05:00:00,1.0,1.0,1.0 +6,2015-01-01 06:00:00,1.0,1.0,1.0 +7,2015-01-01 07:00:00,1.0,1.0,1.0 +8,2015-01-01 08:00:00,1.0,1.0,1.0 +9,2015-01-01 09:00:00,1.0,1.0,1.0 diff --git a/PyPSA/source/examples/networks/model-energy/model-energy.nc b/PyPSA/source/examples/networks/model-energy/model-energy.nc new file mode 100644 index 0000000000000000000000000000000000000000..439a26035918dba251b388d37d903d96c8d07e70 --- /dev/null +++ b/PyPSA/source/examples/networks/model-energy/model-energy.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c7d33c1c6d1ab56c4fa7069e90cf748a483048032d66eae06c890140fe27d94 +size 155533 diff --git a/PyPSA/source/examples/networks/model-energy/model-energy/buses.csv b/PyPSA/source/examples/networks/model-energy/model-energy/buses.csv new file mode 100644 index 0000000000000000000000000000000000000000..9d197aa440ad3440751a0eb10866692d4c230867 --- /dev/null +++ b/PyPSA/source/examples/networks/model-energy/model-energy/buses.csv @@ -0,0 +1,3 @@ +name,carrier +electricity,electricity +hydrogen,hydrogen diff --git a/PyPSA/source/examples/networks/model-energy/model-energy/carriers.csv b/PyPSA/source/examples/networks/model-energy/model-energy/carriers.csv new file mode 100644 index 0000000000000000000000000000000000000000..0021b94c4907346770076b6529037c7dd6428b09 --- /dev/null +++ b/PyPSA/source/examples/networks/model-energy/model-energy/carriers.csv @@ -0,0 +1,10 @@ +name,color +wind,dodgerblue +solar,gold +hydrogen storage,black +battery storage,yellowgreen +load shedding,darkorange +electrolysis,magenta +turbine,red +electricity,grey +hydrogen,grey diff --git a/PyPSA/source/examples/networks/model-energy/model-energy/crs.json b/PyPSA/source/examples/networks/model-energy/model-energy/crs.json new file mode 100644 index 0000000000000000000000000000000000000000..49edf3439c1f2d009aeeae1606c27c86fcd50f3f --- /dev/null +++ b/PyPSA/source/examples/networks/model-energy/model-energy/crs.json @@ -0,0 +1 @@ +{"_crs": "GEOGCRS[\"WGS 84\",ENSEMBLE[\"World Geodetic System 1984 ensemble\",MEMBER[\"World Geodetic System 1984 (Transit)\"],MEMBER[\"World Geodetic System 1984 (G730)\"],MEMBER[\"World Geodetic System 1984 (G873)\"],MEMBER[\"World Geodetic System 1984 (G1150)\"],MEMBER[\"World Geodetic System 1984 (G1674)\"],MEMBER[\"World Geodetic System 1984 (G1762)\"],MEMBER[\"World Geodetic System 1984 (G2139)\"],MEMBER[\"World Geodetic System 1984 (G2296)\"],ELLIPSOID[\"WGS 84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]],ENSEMBLEACCURACY[2.0]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],CS[ellipsoidal,2],AXIS[\"geodetic latitude (Lat)\",north,ORDER[1],ANGLEUNIT[\"degree\",0.0174532925199433]],AXIS[\"geodetic longitude (Lon)\",east,ORDER[2],ANGLEUNIT[\"degree\",0.0174532925199433]],USAGE[SCOPE[\"Horizontal component of 3D system.\"],AREA[\"World.\"],BBOX[-90,-180,90,180]],ID[\"EPSG\",4326]]"} \ No newline at end of file diff --git a/PyPSA/source/examples/networks/model-energy/model-energy/generators-p_max_pu.csv b/PyPSA/source/examples/networks/model-energy/model-energy/generators-p_max_pu.csv new file mode 100644 index 0000000000000000000000000000000000000000..7a892b2ba53d649172f4ad7f5357f6dce0319d85 --- /dev/null +++ b/PyPSA/source/examples/networks/model-energy/model-energy/generators-p_max_pu.csv @@ -0,0 +1,2921 @@ +,solar,wind +0,0.0,0.1846 +1,0.0,0.3146 +2,0.0,0.4957 +3,0.059,0.6824 +4,0.063,0.7519 +5,0.0,0.7663 +6,0.0,0.811 +7,0.0,0.8764 +8,0.0,0.8771 +9,0.0,0.8463 +10,0.0,0.852 +11,0.108,0.8246 +12,0.079,0.7899 +13,0.0,0.7035 +14,0.0,0.672 +15,0.0,0.555 +16,0.0,0.5014 +17,0.0,0.4475 +18,0.0,0.4309 +19,0.131,0.5387 +20,0.083,0.6009 +21,0.0,0.4565 +22,0.0,0.3712 +23,0.0,0.2591 +24,0.0,0.2592 +25,0.0,0.1739 +26,0.0,0.1598 +27,0.075,0.1903 +28,0.035,0.3394 +29,0.0,0.4495 +30,0.0,0.4593 +31,0.0,0.3633 +32,0.0,0.3842 +33,0.0,0.5215 +34,0.0,0.6503 +35,0.039,0.6313 +36,0.064,0.5509 +37,0.0,0.4557 +38,0.0,0.4113 +39,0.0,0.3607 +40,0.0,0.3511 +41,0.0,0.3805 +42,0.0,0.331 +43,0.233,0.3239 +44,0.165,0.332 +45,0.0,0.24 +46,0.0,0.1937 +47,0.0,0.1012 +48,0.0,0.0789 +49,0.0,0.1023 +50,0.0,0.1084 +51,0.125,0.0919 +52,0.152,0.118 +53,0.0,0.1636 +54,0.0,0.249 +55,0.0,0.3547 +56,0.0,0.4996 +57,0.0,0.5996 +58,0.0,0.5694 +59,0.114,0.6112 +60,0.105,0.7685 +61,0.0,0.7992 +62,0.0,0.7674 +63,0.0,0.6876 +64,0.0,0.6607 +65,0.0,0.6128 +66,0.0,0.5576 +67,0.083,0.5028 +68,0.063,0.4954 +69,0.0,0.4481 +70,0.0,0.4195 +71,0.0,0.3647 +72,0.0,0.3498 +73,0.0,0.3338 +74,0.0,0.35 +75,0.126,0.3224 +76,0.135,0.3328 +77,0.0,0.3119 +78,0.0,0.2605 +79,0.0,0.1885 +80,0.0,0.1571 +81,0.0,0.1493 +82,0.0,0.1635 +83,0.237,0.2048 +84,0.209,0.3433 +85,0.0,0.4075 +86,0.0,0.4682 +87,0.0,0.5146 +88,0.0,0.5116 +89,0.0,0.4842 +90,0.0,0.4335 +91,0.127,0.4288 +92,0.128,0.4547 +93,0.0,0.6102 +94,0.0,0.7119 +95,0.0,0.6079 +96,0.0,0.45 +97,0.0,0.4464 +98,0.0,0.4712 +99,0.104,0.5271 +100,0.098,0.6504 +101,0.0,0.7477 +102,0.0,0.7655 +103,0.0,0.7495 +104,0.0,0.7693 +105,0.0,0.8362 +106,0.0,0.8618 +107,0.298,0.8073 +108,0.16,0.8033 +109,0.0,0.7739 +110,0.0,0.7361 +111,0.0,0.636 +112,0.0,0.6908 +113,0.0,0.7142 +114,0.0,0.7045 +115,0.129,0.7385 +116,0.139,0.8117 +117,0.0,0.7824 +118,0.0,0.8401 +119,0.0,0.8477 +120,0.0,0.783 +121,0.0,0.6789 +122,0.0,0.5269 +123,0.213,0.3655 +124,0.16,0.3394 +125,0.0,0.3422 +126,0.0,0.3592 +127,0.0,0.4201 +128,0.0,0.5008 +129,0.0,0.5439 +130,0.0,0.5953 +131,0.213,0.4385 +132,0.159,0.3684 +133,0.0,0.5397 +134,0.0,0.6434 +135,0.0,0.529 +136,0.0,0.4977 +137,0.0,0.4842 +138,0.0,0.3858 +139,0.292,0.4326 +140,0.362,0.2834 +141,0.001,0.1041 +142,0.0,0.0635 +143,0.0,0.0332 +144,0.0,0.016 +145,0.0,0.0243 +146,0.001,0.0534 +147,0.379,0.0321 +148,0.381,0.0601 +149,0.001,0.1157 +150,0.0,0.163 +151,0.0,0.1389 +152,0.0,0.1041 +153,0.0,0.0768 +154,0.0,0.0535 +155,0.289,0.0436 +156,0.321,0.0385 +157,0.001,0.0581 +158,0.0,0.0794 +159,0.0,0.0637 +160,0.0,0.047 +161,0.0,0.0348 +162,0.002,0.0273 +163,0.439,0.0167 +164,0.459,0.0127 +165,0.002,0.0046 +166,0.0,0.0247 +167,0.0,0.0477 +168,0.0,0.0595 +169,0.0,0.0457 +170,0.001,0.0402 +171,0.414,0.0324 +172,0.444,0.0314 +173,0.003,0.0696 +174,0.0,0.1437 +175,0.0,0.1254 +176,0.0,0.1144 +177,0.0,0.1019 +178,0.002,0.152 +179,0.408,0.1843 +180,0.353,0.2291 +181,0.0,0.2393 +182,0.0,0.2594 +183,0.0,0.1958 +184,0.0,0.1832 +185,0.0,0.1724 +186,0.001,0.1506 +187,0.31,0.153 +188,0.251,0.1119 +189,0.0,0.0989 +190,0.0,0.1034 +191,0.0,0.0826 +192,0.0,0.0575 +193,0.0,0.0691 +194,0.001,0.0819 +195,0.205,0.078 +196,0.185,0.1208 +197,0.0,0.1046 +198,0.0,0.1341 +199,0.0,0.2254 +200,0.0,0.3214 +201,0.0,0.4108 +202,0.0,0.4097 +203,0.054,0.3473 +204,0.046,0.4015 +205,0.0,0.4326 +206,0.0,0.4072 +207,0.0,0.3541 +208,0.0,0.3264 +209,0.0,0.2803 +210,0.0,0.3703 +211,0.164,0.4139 +212,0.153,0.4941 +213,0.002,0.4516 +214,0.0,0.4523 +215,0.0,0.4178 +216,0.0,0.3198 +217,0.0,0.2818 +218,0.0,0.2214 +219,0.102,0.2745 +220,0.133,0.3528 +221,0.001,0.415 +222,0.0,0.3882 +223,0.0,0.3957 +224,0.0,0.477 +225,0.0,0.4865 +226,0.004,0.4277 +227,0.343,0.3477 +228,0.324,0.1717 +229,0.003,0.0656 +230,0.0,0.0536 +231,0.0,0.0917 +232,0.0,0.1682 +233,0.0,0.2179 +234,0.008,0.2045 +235,0.46,0.1898 +236,0.431,0.2009 +237,0.006,0.2419 +238,0.0,0.3162 +239,0.0,0.2466 +240,0.0,0.1917 +241,0.0,0.1296 +242,0.003,0.0869 +243,0.389,0.0479 +244,0.348,0.0722 +245,0.005,0.1214 +246,0.0,0.272 +247,0.0,0.229 +248,0.0,0.2659 +249,0.0,0.3 +250,0.003,0.3699 +251,0.189,0.3383 +252,0.085,0.2674 +253,0.001,0.3493 +254,0.0,0.4288 +255,0.0,0.395 +256,0.0,0.3425 +257,0.0,0.3288 +258,0.003,0.3251 +259,0.124,0.2615 +260,0.177,0.2529 +261,0.003,0.1759 +262,0.0,0.1001 +263,0.0,0.0716 +264,0.0,0.0865 +265,0.0,0.2296 +266,0.0,0.348 +267,0.063,0.401 +268,0.085,0.35 +269,0.001,0.3045 +270,0.0,0.3628 +271,0.0,0.3518 +272,0.0,0.3201 +273,0.0,0.2944 +274,0.006,0.195 +275,0.344,0.1157 +276,0.436,0.0925 +277,0.026,0.0661 +278,0.0,0.1605 +279,0.0,0.1901 +280,0.0,0.164 +281,0.0,0.1636 +282,0.019,0.173 +283,0.505,0.0827 +284,0.489,0.0931 +285,0.025,0.1265 +286,0.0,0.1634 +287,0.0,0.1196 +288,0.0,0.1079 +289,0.0,0.0843 +290,0.021,0.0886 +291,0.461,0.0817 +292,0.481,0.1235 +293,0.029,0.1731 +294,0.0,0.2435 +295,0.0,0.2269 +296,0.0,0.2653 +297,0.0,0.3047 +298,0.026,0.3403 +299,0.45,0.3961 +300,0.353,0.4167 +301,0.009,0.3653 +302,0.0,0.3726 +303,0.0,0.3209 +304,0.0,0.2236 +305,0.0,0.2155 +306,0.005,0.2384 +307,0.344,0.2241 +308,0.444,0.2252 +309,0.031,0.2515 +310,0.0,0.3545 +311,0.0,0.4376 +312,0.0,0.4437 +313,0.0,0.5327 +314,0.009,0.6322 +315,0.33,0.6967 +316,0.427,0.6832 +317,0.018,0.664 +318,0.0,0.6314 +319,0.0,0.5308 +320,0.0,0.4139 +321,0.0,0.3 +322,0.007,0.3506 +323,0.208,0.5538 +324,0.264,0.7339 +325,0.003,0.742 +326,0.0,0.7184 +327,0.0,0.6858 +328,0.0,0.7094 +329,0.0,0.7816 +330,0.012,0.7474 +331,0.234,0.7714 +332,0.224,0.7844 +333,0.016,0.7571 +334,0.0,0.6945 +335,0.0,0.6265 +336,0.0,0.6333 +337,0.0,0.6697 +338,0.013,0.665 +339,0.239,0.6781 +340,0.273,0.6738 +341,0.019,0.428 +342,0.0,0.3924 +343,0.0,0.2918 +344,0.0,0.2576 +345,0.0,0.2347 +346,0.015,0.314 +347,0.344,0.4513 +348,0.234,0.3772 +349,0.017,0.363 +350,0.0,0.3602 +351,0.0,0.3036 +352,0.0,0.2305 +353,0.0,0.21 +354,0.006,0.1759 +355,0.201,0.0745 +356,0.372,0.0663 +357,0.043,0.0672 +358,0.0,0.1058 +359,0.0,0.1525 +360,0.0,0.1038 +361,0.0,0.0863 +362,0.07,0.0866 +363,0.534,0.0159 +364,0.524,0.0117 +365,0.062,0.0148 +366,0.0,0.0608 +367,0.0,0.1491 +368,0.0,0.1552 +369,0.0,0.1209 +370,0.081,0.1916 +371,0.549,0.0967 +372,0.535,0.118 +373,0.071,0.2016 +374,0.0,0.3791 +375,0.0,0.4165 +376,0.0,0.3197 +377,0.0,0.1845 +378,0.111,0.1005 +379,0.563,0.042 +380,0.541,0.0574 +381,0.079,0.1027 +382,0.0,0.2416 +383,0.0,0.317 +384,0.0,0.308 +385,0.0,0.2657 +386,0.077,0.2205 +387,0.554,0.0523 +388,0.55,0.0627 +389,0.088,0.1507 +390,0.0,0.3337 +391,0.0,0.3868 +392,0.0,0.3989 +393,0.0,0.3445 +394,0.071,0.2934 +395,0.525,0.1675 +396,0.486,0.3162 +397,0.04,0.2723 +398,0.0,0.4367 +399,0.0,0.394 +400,0.0,0.2857 +401,0.0,0.2085 +402,0.031,0.1734 +403,0.442,0.2262 +404,0.357,0.257 +405,0.028,0.1517 +406,0.0,0.1796 +407,0.0,0.198 +408,0.0,0.2128 +409,0.0,0.2225 +410,0.054,0.2093 +411,0.32,0.1945 +412,0.212,0.2186 +413,0.01,0.2638 +414,0.0,0.3216 +415,0.0,0.4153 +416,0.0,0.434 +417,0.0,0.5274 +418,0.022,0.5516 +419,0.383,0.5326 +420,0.507,0.4172 +421,0.102,0.3434 +422,0.0,0.2799 +423,0.0,0.1848 +424,0.0,0.1416 +425,0.0,0.1112 +426,0.151,0.0837 +427,0.571,0.1119 +428,0.536,0.0754 +429,0.107,0.0665 +430,0.0,0.1167 +431,0.0,0.0809 +432,0.0,0.0498 +433,0.0,0.0298 +434,0.068,0.0316 +435,0.405,0.0055 +436,0.434,0.0237 +437,0.077,0.0386 +438,0.0,0.0765 +439,0.0,0.1106 +440,0.0,0.145 +441,0.0,0.1742 +442,0.117,0.1894 +443,0.534,0.0753 +444,0.482,0.1435 +445,0.109,0.2292 +446,0.0,0.3768 +447,0.0,0.3891 +448,0.0,0.3121 +449,0.0,0.3729 +450,0.113,0.4321 +451,0.529,0.3268 +452,0.486,0.3407 +453,0.08,0.3174 +454,0.0,0.3948 +455,0.0,0.245 +456,0.0,0.1103 +457,0.0,0.0558 +458,0.118,0.0347 +459,0.582,0.0311 +460,0.559,0.0372 +461,0.12,0.0747 +462,0.0,0.2057 +463,0.0,0.4411 +464,0.0,0.5345 +465,0.0,0.5522 +466,0.158,0.4655 +467,0.577,0.5793 +468,0.548,0.6849 +469,0.072,0.5579 +470,0.0,0.4775 +471,0.0,0.4381 +472,0.0,0.3493 +473,0.0,0.3024 +474,0.023,0.2693 +475,0.204,0.2531 +476,0.221,0.2601 +477,0.027,0.1951 +478,0.0,0.2066 +479,0.0,0.1632 +480,0.0,0.0993 +481,0.0,0.0856 +482,0.033,0.0824 +483,0.275,0.1467 +484,0.3,0.1958 +485,0.048,0.0878 +486,0.0,0.1713 +487,0.0,0.2243 +488,0.0,0.356 +489,0.0,0.4075 +490,0.035,0.4859 +491,0.328,0.6848 +492,0.331,0.7147 +493,0.026,0.5833 +494,0.0,0.6207 +495,0.0,0.5792 +496,0.0,0.4966 +497,0.0,0.464 +498,0.087,0.337 +499,0.382,0.6267 +500,0.343,0.8596 +501,0.029,0.825 +502,0.0,0.7611 +503,0.0,0.7072 +504,0.0,0.7492 +505,0.0,0.7522 +506,0.099,0.731 +507,0.477,0.8457 +508,0.293,0.7639 +509,0.039,0.3651 +510,0.0,0.3419 +511,0.0,0.3256 +512,0.0,0.2185 +513,0.0,0.1257 +514,0.06,0.117 +515,0.583,0.2546 +516,0.59,0.4046 +517,0.143,0.4703 +518,0.0,0.5733 +519,0.0,0.4872 +520,0.0,0.4454 +521,0.0,0.3134 +522,0.068,0.3403 +523,0.498,0.6233 +524,0.467,0.6907 +525,0.058,0.6264 +526,0.0,0.5699 +527,0.0,0.5963 +528,0.0,0.6054 +529,0.0,0.6567 +530,0.094,0.6886 +531,0.516,0.8732 +532,0.463,0.8101 +533,0.094,0.5058 +534,0.0,0.376 +535,0.0,0.3396 +536,0.0,0.2927 +537,0.0,0.3408 +538,0.065,0.6028 +539,0.203,0.8695 +540,0.305,0.8795 +541,0.052,0.7947 +542,0.0,0.8438 +543,0.0,0.9378 +544,0.0,0.7689 +545,0.0,0.4075 +546,0.039,0.2097 +547,0.078,0.2176 +548,0.079,0.5454 +549,0.06,0.8525 +550,0.0,0.9693 +551,0.0,0.9298 +552,0.0,0.8905 +553,0.0,0.778 +554,0.123,0.3908 +555,0.231,0.5216 +556,0.181,0.6047 +557,0.043,0.5078 +558,0.0,0.6108 +559,0.0,0.518 +560,0.0,0.3897 +561,0.0,0.2861 +562,0.09,0.1882 +563,0.504,0.253 +564,0.506,0.2621 +565,0.169,0.2922 +566,0.0,0.398 +567,0.0,0.53 +568,0.0,0.6879 +569,0.0,0.5932 +570,0.092,0.5267 +571,0.531,0.688 +572,0.469,0.6467 +573,0.089,0.5637 +574,0.0,0.6175 +575,0.0,0.5772 +576,0.0,0.5795 +577,0.0,0.58 +578,0.101,0.5313 +579,0.384,0.7216 +580,0.256,0.681 +581,0.025,0.6896 +582,0.0,0.775 +583,0.0,0.8188 +584,0.0,0.7542 +585,0.0,0.7316 +586,0.118,0.5951 +587,0.379,0.7432 +588,0.289,0.6047 +589,0.027,0.6213 +590,0.0,0.7963 +591,0.0,0.8324 +592,0.0,0.7531 +593,0.0,0.6908 +594,0.044,0.5166 +595,0.282,0.5021 +596,0.24,0.5751 +597,0.051,0.4497 +598,0.0,0.567 +599,0.0,0.5125 +600,0.0,0.409 +601,0.0,0.3481 +602,0.106,0.3638 +603,0.614,0.6685 +604,0.589,0.7418 +605,0.142,0.5001 +606,0.0,0.3088 +607,0.0,0.4021 +608,0.0,0.3909 +609,0.0,0.3142 +610,0.081,0.375 +611,0.408,0.5775 +612,0.347,0.4961 +613,0.12,0.3873 +614,0.0,0.3107 +615,0.0,0.3382 +616,0.0,0.2518 +617,0.0,0.1646 +618,0.175,0.1005 +619,0.477,0.1447 +620,0.484,0.145 +621,0.145,0.1416 +622,0.0,0.0971 +623,0.0,0.0888 +624,0.0,0.0788 +625,0.0,0.0793 +626,0.217,0.0343 +627,0.594,0.0185 +628,0.63,0.0203 +629,0.225,0.025 +630,0.0,0.0144 +631,0.0,0.0529 +632,0.0,0.0806 +633,0.0,0.0872 +634,0.225,0.0316 +635,0.555,0.0052 +636,0.572,0.0096 +637,0.204,0.0103 +638,0.0,0.0136 +639,0.0,0.0142 +640,0.0,0.0125 +641,0.0,0.0213 +642,0.18,0.0204 +643,0.549,0.0314 +644,0.578,0.057 +645,0.21,0.0555 +646,0.0,0.1426 +647,0.0,0.2051 +648,0.0,0.1593 +649,0.0,0.106 +650,0.117,0.0694 +651,0.605,0.0325 +652,0.611,0.0305 +653,0.219,0.0572 +654,0.0,0.2025 +655,0.0,0.1305 +656,0.0,0.1708 +657,0.0,0.1264 +658,0.085,0.0685 +659,0.385,0.0199 +660,0.372,0.0014 +661,0.081,0.0449 +662,0.0,0.0916 +663,0.0,0.1052 +664,0.0,0.1268 +665,0.0,0.1514 +666,0.171,0.1999 +667,0.473,0.6226 +668,0.286,0.6386 +669,0.078,0.3814 +670,0.0,0.2198 +671,0.0,0.237 +672,0.0,0.1817 +673,0.0,0.1595 +674,0.143,0.2037 +675,0.588,0.4222 +676,0.59,0.6212 +677,0.184,0.6198 +678,0.0,0.3923 +679,0.0,0.2641 +680,0.0,0.1359 +681,0.0,0.0823 +682,0.159,0.0751 +683,0.579,0.1053 +684,0.592,0.1022 +685,0.195,0.1314 +686,0.0,0.1322 +687,0.0,0.178 +688,0.0,0.1568 +689,0.0,0.1872 +690,0.109,0.1416 +691,0.228,0.1431 +692,0.193,0.1168 +693,0.08,0.0862 +694,0.0,0.107 +695,0.0,0.0747 +696,0.0,0.0232 +697,0.0,0.0146 +698,0.15,0.005 +699,0.593,0.0117 +700,0.623,0.0096 +701,0.206,0.0171 +702,0.0,0.0622 +703,0.0,0.0663 +704,0.0,0.0509 +705,0.0,0.0532 +706,0.259,0.0419 +707,0.655,0.0475 +708,0.634,0.05 +709,0.213,0.0457 +710,0.0,0.0752 +711,0.0,0.1177 +712,0.0,0.1519 +713,0.0,0.1585 +714,0.238,0.0608 +715,0.612,0.0604 +716,0.56,0.1032 +717,0.167,0.1858 +718,0.0,0.3084 +719,0.0,0.2623 +720,0.0,0.2414 +721,0.0,0.246 +722,0.251,0.2192 +723,0.666,0.2888 +724,0.654,0.2868 +725,0.255,0.3311 +726,0.0,0.4605 +727,0.0,0.4171 +728,0.0,0.34 +729,0.0,0.2692 +730,0.26,0.114 +731,0.59,0.2373 +732,0.552,0.2587 +733,0.189,0.2437 +734,0.0,0.4347 +735,0.0,0.4507 +736,0.0,0.3724 +737,0.0,0.275 +738,0.137,0.2368 +739,0.492,0.3181 +740,0.495,0.3144 +741,0.189,0.3016 +742,0.0,0.4494 +743,0.0,0.5286 +744,0.0,0.3813 +745,0.0,0.2798 +746,0.208,0.2894 +747,0.641,0.5134 +748,0.573,0.5629 +749,0.221,0.4714 +750,0.0,0.3884 +751,0.0,0.3179 +752,0.0,0.2439 +753,0.0,0.1768 +754,0.187,0.0666 +755,0.54,0.1472 +756,0.467,0.1655 +757,0.12,0.1099 +758,0.0,0.2013 +759,0.0,0.1374 +760,0.0,0.1055 +761,0.0,0.0923 +762,0.128,0.0467 +763,0.47,0.121 +764,0.464,0.1895 +765,0.22,0.1609 +766,0.0,0.2121 +767,0.0,0.1867 +768,0.0,0.167 +769,0.0,0.1565 +770,0.213,0.0771 +771,0.588,0.2391 +772,0.522,0.1837 +773,0.182,0.1425 +774,0.0,0.1531 +775,0.0,0.1384 +776,0.0,0.102 +777,0.0,0.0675 +778,0.184,0.0277 +779,0.531,0.0411 +780,0.463,0.0341 +781,0.144,0.0508 +782,0.0,0.1032 +783,0.0,0.1086 +784,0.0,0.1033 +785,0.0,0.1325 +786,0.239,0.084 +787,0.574,0.1177 +788,0.523,0.125 +789,0.177,0.2349 +790,0.0,0.364 +791,0.0,0.3066 +792,0.0,0.2094 +793,0.0,0.1706 +794,0.163,0.2177 +795,0.434,0.2624 +796,0.455,0.2432 +797,0.146,0.3171 +798,0.0,0.374 +799,0.0,0.3181 +800,0.0,0.2904 +801,0.0,0.3032 +802,0.217,0.3787 +803,0.558,0.3795 +804,0.559,0.3354 +805,0.211,0.3193 +806,0.0,0.3074 +807,0.0,0.2469 +808,0.0,0.213 +809,0.0,0.1859 +810,0.186,0.2391 +811,0.485,0.2256 +812,0.517,0.1809 +813,0.201,0.1774 +814,0.0,0.2011 +815,0.0,0.1587 +816,0.0,0.1367 +817,0.0,0.1158 +818,0.191,0.1317 +819,0.396,0.1165 +820,0.404,0.0917 +821,0.161,0.1024 +822,0.0,0.1915 +823,0.0,0.1625 +824,0.0,0.107 +825,0.0,0.104 +826,0.085,0.1436 +827,0.288,0.2144 +828,0.335,0.2626 +829,0.144,0.266 +830,0.0,0.322 +831,0.0,0.2882 +832,0.0,0.2415 +833,0.0,0.2128 +834,0.307,0.0699 +835,0.691,0.2255 +836,0.679,0.1599 +837,0.298,0.149 +838,0.0,0.3114 +839,0.0,0.2898 +840,0.0,0.262 +841,0.0,0.2179 +842,0.315,0.0625 +843,0.693,0.1309 +844,0.679,0.1042 +845,0.301,0.1184 +846,0.0,0.3412 +847,0.0,0.3967 +848,0.0,0.2919 +849,0.0,0.2077 +850,0.287,0.1086 +851,0.671,0.2367 +852,0.623,0.2157 +853,0.249,0.2134 +854,0.0,0.2682 +855,0.0,0.2488 +856,0.0,0.1945 +857,0.0,0.194 +858,0.304,0.084 +859,0.674,0.3846 +860,0.655,0.332 +861,0.287,0.2833 +862,0.0,0.3729 +863,0.0,0.3156 +864,0.0,0.2247 +865,0.0,0.2138 +866,0.314,0.0653 +867,0.678,0.2012 +868,0.666,0.1687 +869,0.301,0.1861 +870,0.0,0.3408 +871,0.0,0.3169 +872,0.0,0.1755 +873,0.0,0.079 +874,0.319,0.0093 +875,0.68,0.0404 +876,0.663,0.0322 +877,0.275,0.047 +878,0.0,0.213 +879,0.0,0.1844 +880,0.0,0.122 +881,0.0,0.1042 +882,0.317,0.0306 +883,0.68,0.0507 +884,0.669,0.0319 +885,0.306,0.0342 +886,0.0,0.1348 +887,0.0,0.143 +888,0.0,0.1024 +889,0.0,0.1178 +890,0.314,0.0933 +891,0.679,0.2084 +892,0.655,0.3137 +893,0.298,0.5281 +894,0.0,0.6132 +895,0.0,0.5777 +896,0.0,0.5893 +897,0.0,0.546 +898,0.255,0.6851 +899,0.589,0.7938 +900,0.463,0.8211 +901,0.117,0.7451 +902,0.0,0.685 +903,0.0,0.594 +904,0.0,0.4928 +905,0.0,0.4718 +906,0.182,0.4403 +907,0.524,0.47 +908,0.554,0.4455 +909,0.26,0.4314 +910,0.0,0.591 +911,0.0,0.5285 +912,0.0,0.4097 +913,0.0,0.1553 +914,0.298,0.0269 +915,0.626,0.0634 +916,0.626,0.1851 +917,0.294,0.2171 +918,0.0,0.4083 +919,0.0,0.4194 +920,0.0,0.3112 +921,0.0,0.2663 +922,0.275,0.0837 +923,0.562,0.0933 +924,0.528,0.1552 +925,0.17,0.2801 +926,0.0,0.3989 +927,0.0,0.3472 +928,0.0,0.2569 +929,0.0,0.2212 +930,0.15,0.1955 +931,0.407,0.1298 +932,0.37,0.1315 +933,0.16,0.1457 +934,0.0,0.1182 +935,0.0,0.1813 +936,0.0,0.2542 +937,0.0,0.2335 +938,0.114,0.2607 +939,0.456,0.1897 +940,0.467,0.1338 +941,0.206,0.1391 +942,0.0,0.1513 +943,0.0,0.1887 +944,0.0,0.2022 +945,0.0,0.2147 +946,0.079,0.3209 +947,0.196,0.3586 +948,0.163,0.334 +949,0.049,0.175 +950,0.0,0.1907 +951,0.0,0.305 +952,0.0,0.2237 +953,0.0,0.222 +954,0.128,0.254 +955,0.473,0.31 +956,0.499,0.3267 +957,0.215,0.299 +958,0.0,0.2594 +959,0.0,0.2764 +960,0.0,0.2505 +961,0.0,0.2594 +962,0.296,0.1647 +963,0.486,0.2863 +964,0.411,0.2633 +965,0.186,0.28 +966,0.0,0.2478 +967,0.0,0.2908 +968,0.0,0.2582 +969,0.0,0.2406 +970,0.279,0.193 +971,0.644,0.441 +972,0.632,0.4569 +973,0.279,0.3175 +974,0.0,0.1325 +975,0.0,0.0746 +976,0.0,0.1302 +977,0.0,0.1292 +978,0.135,0.1522 +979,0.423,0.1687 +980,0.479,0.2468 +981,0.251,0.2387 +982,0.0,0.0908 +983,0.0,0.0271 +984,0.0,0.0848 +985,0.0,0.102 +986,0.21,0.0457 +987,0.364,0.0989 +988,0.222,0.1787 +989,0.077,0.1686 +990,0.0,0.0744 +991,0.0,0.2066 +992,0.0,0.2276 +993,0.0,0.1803 +994,0.215,0.2955 +995,0.501,0.3144 +996,0.477,0.3032 +997,0.169,0.2698 +998,0.0,0.1691 +999,0.0,0.1882 +1000,0.0,0.2146 +1001,0.0,0.178 +1002,0.258,0.1911 +1003,0.573,0.2574 +1004,0.55,0.2794 +1005,0.251,0.3133 +1006,0.0,0.2189 +1007,0.0,0.2107 +1008,0.0,0.2089 +1009,0.001,0.1845 +1010,0.313,0.1124 +1011,0.577,0.1023 +1012,0.586,0.066 +1013,0.264,0.0308 +1014,0.0,0.0026 +1015,0.0,0.0621 +1016,0.0,0.1778 +1017,0.001,0.2141 +1018,0.31,0.1932 +1019,0.551,0.3345 +1020,0.582,0.4217 +1021,0.204,0.4042 +1022,0.0,0.404 +1023,0.0,0.4069 +1024,0.0,0.416 +1025,0.0,0.2527 +1026,0.076,0.131 +1027,0.411,0.1021 +1028,0.419,0.1145 +1029,0.203,0.117 +1030,0.0,0.1277 +1031,0.0,0.2788 +1032,0.0,0.3049 +1033,0.001,0.2778 +1034,0.233,0.3087 +1035,0.594,0.3792 +1036,0.526,0.3765 +1037,0.263,0.2421 +1038,0.001,0.0926 +1039,0.0,0.0499 +1040,0.0,0.0326 +1041,0.002,0.0672 +1042,0.276,0.0456 +1043,0.393,0.197 +1044,0.286,0.1886 +1045,0.109,0.146 +1046,0.0,0.088 +1047,0.0,0.2098 +1048,0.0,0.3562 +1049,0.0,0.3132 +1050,0.136,0.3645 +1051,0.315,0.4536 +1052,0.339,0.5161 +1053,0.163,0.4977 +1054,0.0,0.3354 +1055,0.0,0.2769 +1056,0.0,0.2482 +1057,0.0,0.2391 +1058,0.267,0.3697 +1059,0.567,0.3953 +1060,0.533,0.3776 +1061,0.277,0.3359 +1062,0.002,0.1999 +1063,0.0,0.2021 +1064,0.0,0.2305 +1065,0.004,0.2564 +1066,0.32,0.3378 +1067,0.455,0.4329 +1068,0.377,0.5383 +1069,0.148,0.4813 +1070,0.001,0.304 +1071,0.0,0.308 +1072,0.0,0.2841 +1073,0.0,0.2362 +1074,0.138,0.3252 +1075,0.227,0.4203 +1076,0.187,0.3711 +1077,0.082,0.287 +1078,0.0,0.1345 +1079,0.0,0.1381 +1080,0.0,0.1358 +1081,0.001,0.0801 +1082,0.099,0.0549 +1083,0.265,0.0088 +1084,0.345,0.0122 +1085,0.183,0.0113 +1086,0.001,0.0235 +1087,0.0,0.0468 +1088,0.0,0.0447 +1089,0.002,0.0518 +1090,0.253,0.0745 +1091,0.627,0.1439 +1092,0.639,0.1735 +1093,0.331,0.2133 +1094,0.004,0.2371 +1095,0.0,0.2804 +1096,0.0,0.2081 +1097,0.005,0.1344 +1098,0.286,0.1725 +1099,0.688,0.1854 +1100,0.662,0.1681 +1101,0.305,0.1228 +1102,0.002,0.1774 +1103,0.0,0.1794 +1104,0.0,0.1241 +1105,0.006,0.1167 +1106,0.328,0.0659 +1107,0.583,0.1464 +1108,0.56,0.1049 +1109,0.234,0.0984 +1110,0.002,0.1224 +1111,0.0,0.1396 +1112,0.0,0.084 +1113,0.001,0.0547 +1114,0.125,0.0349 +1115,0.347,0.1463 +1116,0.301,0.2033 +1117,0.17,0.1752 +1118,0.002,0.1653 +1119,0.0,0.1749 +1120,0.0,0.1093 +1121,0.004,0.0562 +1122,0.266,0.0211 +1123,0.501,0.0058 +1124,0.383,0.0171 +1125,0.15,0.0563 +1126,0.001,0.1683 +1127,0.0,0.3072 +1128,0.0,0.3559 +1129,0.001,0.3915 +1130,0.129,0.4943 +1131,0.255,0.6166 +1132,0.235,0.6217 +1133,0.104,0.5576 +1134,0.001,0.4418 +1135,0.0,0.3868 +1136,0.0,0.3326 +1137,0.002,0.2367 +1138,0.213,0.2766 +1139,0.462,0.2863 +1140,0.51,0.2446 +1141,0.287,0.1922 +1142,0.005,0.0943 +1143,0.0,0.0639 +1144,0.0,0.0664 +1145,0.009,0.0633 +1146,0.338,0.021 +1147,0.649,0.0323 +1148,0.647,0.0368 +1149,0.286,0.0551 +1150,0.004,0.0428 +1151,0.0,0.0155 +1152,0.0,0.0455 +1153,0.009,0.0711 +1154,0.319,0.0091 +1155,0.563,0.0202 +1156,0.395,0.0331 +1157,0.173,0.0355 +1158,0.003,0.0545 +1159,0.0,0.0787 +1160,0.0,0.0336 +1161,0.005,0.0152 +1162,0.307,0.0252 +1163,0.607,0.1143 +1164,0.56,0.1939 +1165,0.297,0.1787 +1166,0.008,0.0932 +1167,0.0,0.1674 +1168,0.0,0.19 +1169,0.009,0.2491 +1170,0.32,0.1324 +1171,0.622,0.2034 +1172,0.496,0.1993 +1173,0.164,0.1214 +1174,0.001,0.0984 +1175,0.0,0.1172 +1176,0.0,0.0876 +1177,0.004,0.1278 +1178,0.169,0.1637 +1179,0.214,0.1745 +1180,0.192,0.1343 +1181,0.092,0.1095 +1182,0.002,0.1194 +1183,0.0,0.1552 +1184,0.0,0.1674 +1185,0.003,0.1774 +1186,0.15,0.3159 +1187,0.376,0.3268 +1188,0.479,0.2723 +1189,0.232,0.2551 +1190,0.006,0.1726 +1191,0.0,0.1815 +1192,0.0,0.1692 +1193,0.011,0.1142 +1194,0.341,0.1157 +1195,0.639,0.1418 +1196,0.584,0.0962 +1197,0.287,0.0599 +1198,0.01,0.0477 +1199,0.0,0.0718 +1200,0.0,0.067 +1201,0.013,0.0532 +1202,0.321,0.0262 +1203,0.624,0.0452 +1204,0.531,0.0421 +1205,0.221,0.0432 +1206,0.007,0.0408 +1207,0.0,0.0351 +1208,0.0,0.0318 +1209,0.013,0.0524 +1210,0.315,0.0212 +1211,0.598,0.024 +1212,0.598,0.0382 +1213,0.31,0.0526 +1214,0.01,0.0425 +1215,0.0,0.0252 +1216,0.0,0.0127 +1217,0.014,0.0248 +1218,0.326,0.0088 +1219,0.62,0.0075 +1220,0.604,0.0065 +1221,0.311,0.0145 +1222,0.012,0.0909 +1223,0.0,0.2258 +1224,0.0,0.2013 +1225,0.018,0.1464 +1226,0.346,0.0341 +1227,0.654,0.0719 +1228,0.645,0.1026 +1229,0.333,0.0802 +1230,0.012,0.0707 +1231,0.0,0.1201 +1232,0.0,0.0807 +1233,0.017,0.0386 +1234,0.337,0.0022 +1235,0.638,0.0018 +1236,0.614,0.0044 +1237,0.306,0.0186 +1238,0.011,0.0882 +1239,0.0,0.1819 +1240,0.0,0.1577 +1241,0.018,0.0824 +1242,0.341,0.0345 +1243,0.634,0.0549 +1244,0.592,0.0579 +1245,0.291,0.1034 +1246,0.012,0.2428 +1247,0.0,0.3021 +1248,0.0,0.2511 +1249,0.017,0.2263 +1250,0.321,0.2626 +1251,0.516,0.2516 +1252,0.405,0.2171 +1253,0.185,0.2032 +1254,0.002,0.1736 +1255,0.0,0.1327 +1256,0.0,0.0577 +1257,0.005,0.0205 +1258,0.263,0.0241 +1259,0.581,0.0481 +1260,0.571,0.0844 +1261,0.304,0.1398 +1262,0.014,0.1958 +1263,0.0,0.2788 +1264,0.0,0.3655 +1265,0.02,0.2787 +1266,0.322,0.459 +1267,0.604,0.4625 +1268,0.593,0.4192 +1269,0.345,0.2925 +1270,0.016,0.113 +1271,0.0,0.038 +1272,0.0,0.0317 +1273,0.018,0.0622 +1274,0.337,0.1114 +1275,0.653,0.1343 +1276,0.639,0.1264 +1277,0.318,0.1584 +1278,0.007,0.1867 +1279,0.0,0.2464 +1280,0.0,0.1841 +1281,0.013,0.1529 +1282,0.283,0.1642 +1283,0.542,0.1973 +1284,0.486,0.1761 +1285,0.221,0.1298 +1286,0.007,0.2119 +1287,0.0,0.2711 +1288,0.0,0.2214 +1289,0.016,0.1823 +1290,0.317,0.1502 +1291,0.563,0.1945 +1292,0.471,0.208 +1293,0.238,0.2564 +1294,0.01,0.3124 +1295,0.0,0.33 +1296,0.0,0.2821 +1297,0.019,0.1901 +1298,0.331,0.2335 +1299,0.615,0.5039 +1300,0.608,0.5566 +1301,0.294,0.4276 +1302,0.009,0.2669 +1303,0.0,0.2056 +1304,0.0,0.1233 +1305,0.015,0.0963 +1306,0.285,0.0511 +1307,0.569,0.0458 +1308,0.525,0.0545 +1309,0.282,0.0336 +1310,0.015,0.0265 +1311,0.0,0.0702 +1312,0.0,0.0607 +1313,0.019,0.0653 +1314,0.335,0.0875 +1315,0.629,0.1463 +1316,0.621,0.1236 +1317,0.281,0.077 +1318,0.011,0.1886 +1319,0.0,0.3392 +1320,0.0,0.2943 +1321,0.017,0.3268 +1322,0.289,0.1938 +1323,0.442,0.3047 +1324,0.439,0.2561 +1325,0.222,0.1777 +1326,0.004,0.1515 +1327,0.0,0.173 +1328,0.0,0.1364 +1329,0.007,0.1182 +1330,0.136,0.0791 +1331,0.336,0.11 +1332,0.303,0.0866 +1333,0.197,0.1019 +1334,0.011,0.0748 +1335,0.0,0.0977 +1336,0.0,0.1096 +1337,0.02,0.0936 +1338,0.34,0.0974 +1339,0.635,0.0952 +1340,0.583,0.0914 +1341,0.263,0.1055 +1342,0.013,0.1291 +1343,0.0,0.174 +1344,0.0,0.182 +1345,0.016,0.1797 +1346,0.284,0.1154 +1347,0.556,0.1073 +1348,0.559,0.0657 +1349,0.301,0.0422 +1350,0.014,0.0936 +1351,0.0,0.2263 +1352,0.0,0.1565 +1353,0.02,0.1072 +1354,0.281,0.0384 +1355,0.494,0.0564 +1356,0.446,0.0698 +1357,0.213,0.0571 +1358,0.012,0.1237 +1359,0.0,0.2211 +1360,0.0,0.2231 +1361,0.017,0.1686 +1362,0.309,0.1268 +1363,0.551,0.0983 +1364,0.454,0.1099 +1365,0.194,0.0691 +1366,0.01,0.0354 +1367,0.0,0.0843 +1368,0.0,0.124 +1369,0.017,0.0843 +1370,0.288,0.0441 +1371,0.57,0.0405 +1372,0.494,0.0432 +1373,0.24,0.0347 +1374,0.01,0.0775 +1375,0.0,0.1455 +1376,0.0,0.1728 +1377,0.014,0.1699 +1378,0.249,0.234 +1379,0.331,0.2568 +1380,0.262,0.2648 +1381,0.145,0.2315 +1382,0.004,0.2681 +1383,0.0,0.3207 +1384,0.0,0.2859 +1385,0.007,0.2567 +1386,0.168,0.3123 +1387,0.412,0.4076 +1388,0.402,0.3444 +1389,0.217,0.2347 +1390,0.014,0.1986 +1391,0.0,0.2484 +1392,0.0,0.2118 +1393,0.021,0.1991 +1394,0.339,0.1214 +1395,0.645,0.1461 +1396,0.632,0.1441 +1397,0.326,0.138 +1398,0.017,0.1762 +1399,0.0,0.2645 +1400,0.0,0.2451 +1401,0.023,0.1521 +1402,0.341,0.1308 +1403,0.64,0.1509 +1404,0.628,0.1034 +1405,0.329,0.1018 +1406,0.016,0.1617 +1407,0.0,0.3124 +1408,0.0,0.248 +1409,0.024,0.1795 +1410,0.335,0.1048 +1411,0.624,0.1157 +1412,0.597,0.1172 +1413,0.314,0.1268 +1414,0.015,0.1521 +1415,0.0,0.2569 +1416,0.0,0.2161 +1417,0.019,0.1995 +1418,0.317,0.1811 +1419,0.631,0.2382 +1420,0.642,0.3431 +1421,0.346,0.3797 +1422,0.017,0.3023 +1423,0.0,0.2625 +1424,0.0,0.1841 +1425,0.025,0.1018 +1426,0.351,0.0679 +1427,0.67,0.0857 +1428,0.659,0.0775 +1429,0.344,0.11 +1430,0.016,0.0994 +1431,0.0,0.1129 +1432,0.0,0.0597 +1433,0.024,0.0376 +1434,0.348,0.0131 +1435,0.659,0.0053 +1436,0.646,0.0057 +1437,0.34,0.0125 +1438,0.016,0.0635 +1439,0.0,0.2001 +1440,0.0,0.202 +1441,0.023,0.1885 +1442,0.345,0.14 +1443,0.645,0.243 +1444,0.632,0.2682 +1445,0.333,0.29 +1446,0.014,0.3432 +1447,0.0,0.2528 +1448,0.0,0.1983 +1449,0.017,0.1216 +1450,0.253,0.0293 +1451,0.393,0.1766 +1452,0.373,0.3249 +1453,0.165,0.3562 +1454,0.003,0.2398 +1455,0.0,0.1772 +1456,0.0,0.1507 +1457,0.009,0.1282 +1458,0.274,0.11 +1459,0.552,0.1301 +1460,0.647,0.1982 +1461,0.342,0.2048 +1462,0.016,0.2252 +1463,0.0,0.1909 +1464,0.0,0.1458 +1465,0.024,0.0966 +1466,0.355,0.0667 +1467,0.677,0.0699 +1468,0.668,0.1065 +1469,0.352,0.1214 +1470,0.015,0.1403 +1471,0.0,0.1408 +1472,0.0,0.1034 +1473,0.024,0.0606 +1474,0.353,0.0209 +1475,0.672,0.0223 +1476,0.66,0.0435 +1477,0.345,0.0563 +1478,0.014,0.0678 +1479,0.0,0.0533 +1480,0.0,0.0318 +1481,0.008,0.0854 +1482,0.218,0.1118 +1483,0.556,0.2551 +1484,0.605,0.4188 +1485,0.328,0.411 +1486,0.015,0.1638 +1487,0.0,0.0439 +1488,0.0,0.0017 +1489,0.019,0.0543 +1490,0.318,0.1557 +1491,0.624,0.3331 +1492,0.607,0.4272 +1493,0.307,0.432 +1494,0.011,0.2572 +1495,0.0,0.2007 +1496,0.0,0.1307 +1497,0.015,0.1086 +1498,0.243,0.1238 +1499,0.45,0.231 +1500,0.428,0.33 +1501,0.241,0.3281 +1502,0.013,0.2558 +1503,0.0,0.1824 +1504,0.0,0.1082 +1505,0.023,0.1179 +1506,0.357,0.1626 +1507,0.681,0.3095 +1508,0.666,0.3141 +1509,0.332,0.2321 +1510,0.008,0.1316 +1511,0.0,0.1064 +1512,0.0,0.0767 +1513,0.019,0.0776 +1514,0.319,0.086 +1515,0.615,0.1362 +1516,0.603,0.1609 +1517,0.316,0.1875 +1518,0.012,0.1823 +1519,0.0,0.145 +1520,0.0,0.1625 +1521,0.019,0.1361 +1522,0.341,0.151 +1523,0.662,0.1617 +1524,0.647,0.1607 +1525,0.332,0.1766 +1526,0.011,0.1296 +1527,0.0,0.0812 +1528,0.0,0.0484 +1529,0.015,0.0577 +1530,0.313,0.0595 +1531,0.576,0.0863 +1532,0.535,0.0768 +1533,0.232,0.0821 +1534,0.004,0.0511 +1535,0.0,0.0433 +1536,0.0,0.0937 +1537,0.007,0.0992 +1538,0.186,0.0937 +1539,0.401,0.106 +1540,0.417,0.0949 +1541,0.177,0.1004 +1542,0.004,0.0392 +1543,0.0,0.0427 +1544,0.0,0.068 +1545,0.006,0.1091 +1546,0.2,0.1534 +1547,0.388,0.2329 +1548,0.319,0.2673 +1549,0.169,0.2179 +1550,0.006,0.1049 +1551,0.0,0.1729 +1552,0.0,0.1713 +1553,0.011,0.1171 +1554,0.28,0.1002 +1555,0.452,0.0988 +1556,0.378,0.1188 +1557,0.222,0.1289 +1558,0.006,0.1224 +1559,0.0,0.1687 +1560,0.0,0.1361 +1561,0.013,0.088 +1562,0.291,0.0468 +1563,0.573,0.0435 +1564,0.57,0.052 +1565,0.296,0.0621 +1566,0.008,0.0828 +1567,0.0,0.1025 +1568,0.0,0.0712 +1569,0.013,0.0469 +1570,0.316,0.1272 +1571,0.628,0.2126 +1572,0.642,0.2366 +1573,0.34,0.2293 +1574,0.009,0.1687 +1575,0.0,0.1076 +1576,0.0,0.0679 +1577,0.013,0.0846 +1578,0.297,0.0561 +1579,0.605,0.0663 +1580,0.558,0.0691 +1581,0.244,0.0576 +1582,0.005,0.0607 +1583,0.0,0.091 +1584,0.0,0.0532 +1585,0.01,0.0474 +1586,0.318,0.0029 +1587,0.585,0.0143 +1588,0.546,0.0482 +1589,0.265,0.0588 +1590,0.005,0.0457 +1591,0.0,0.0747 +1592,0.0,0.1179 +1593,0.007,0.138 +1594,0.214,0.0515 +1595,0.41,0.1133 +1596,0.385,0.1302 +1597,0.166,0.0832 +1598,0.003,0.0248 +1599,0.0,0.0098 +1600,0.0,0.0306 +1601,0.006,0.0273 +1602,0.262,0.0095 +1603,0.546,0.0489 +1604,0.478,0.0812 +1605,0.185,0.0659 +1606,0.003,0.082 +1607,0.0,0.1913 +1608,0.0,0.2201 +1609,0.004,0.1682 +1610,0.268,0.0715 +1611,0.263,0.1164 +1612,0.301,0.1646 +1613,0.228,0.1181 +1614,0.004,0.0702 +1615,0.0,0.0896 +1616,0.0,0.0708 +1617,0.007,0.0449 +1618,0.265,0.0127 +1619,0.569,0.0654 +1620,0.396,0.11 +1621,0.2,0.0942 +1622,0.003,0.0593 +1623,0.0,0.0436 +1624,0.0,0.0409 +1625,0.004,0.0537 +1626,0.305,0.121 +1627,0.607,0.143 +1628,0.604,0.1493 +1629,0.314,0.1478 +1630,0.005,0.1383 +1631,0.0,0.1191 +1632,0.0,0.0747 +1633,0.008,0.0558 +1634,0.317,0.0135 +1635,0.632,0.0129 +1636,0.623,0.0218 +1637,0.318,0.038 +1638,0.005,0.1444 +1639,0.0,0.2208 +1640,0.0,0.1396 +1641,0.007,0.0643 +1642,0.31,0.0022 +1643,0.622,0.0002 +1644,0.614,0.0053 +1645,0.311,0.0435 +1646,0.004,0.1589 +1647,0.0,0.1675 +1648,0.0,0.0914 +1649,0.006,0.0601 +1650,0.312,0.0352 +1651,0.615,0.044 +1652,0.607,0.0511 +1653,0.302,0.0904 +1654,0.002,0.283 +1655,0.0,0.2921 +1656,0.0,0.2126 +1657,0.004,0.1519 +1658,0.214,0.1466 +1659,0.475,0.1238 +1660,0.365,0.0985 +1661,0.092,0.0925 +1662,0.0,0.0842 +1663,0.0,0.1132 +1664,0.0,0.1176 +1665,0.002,0.137 +1666,0.214,0.2091 +1667,0.532,0.2047 +1668,0.401,0.174 +1669,0.141,0.1337 +1670,0.0,0.0399 +1671,0.0,0.0406 +1672,0.0,0.0503 +1673,0.002,0.0439 +1674,0.28,0.0254 +1675,0.553,0.0049 +1676,0.516,0.0056 +1677,0.208,0.0616 +1678,0.001,0.096 +1679,0.0,0.1238 +1680,0.0,0.1246 +1681,0.002,0.12 +1682,0.237,0.107 +1683,0.498,0.0979 +1684,0.429,0.0979 +1685,0.219,0.1019 +1686,0.001,0.0992 +1687,0.0,0.0807 +1688,0.0,0.0766 +1689,0.001,0.1126 +1690,0.235,0.0615 +1691,0.494,0.0939 +1692,0.428,0.0816 +1693,0.156,0.0947 +1694,0.001,0.0818 +1695,0.0,0.1001 +1696,0.0,0.1227 +1697,0.002,0.0879 +1698,0.308,0.0464 +1699,0.621,0.0985 +1700,0.596,0.0906 +1701,0.286,0.0713 +1702,0.001,0.0814 +1703,0.0,0.0757 +1704,0.0,0.0289 +1705,0.001,0.0249 +1706,0.271,0.0146 +1707,0.489,0.0165 +1708,0.341,0.0118 +1709,0.13,0.0278 +1710,0.0,0.0415 +1711,0.0,0.0556 +1712,0.0,0.0559 +1713,0.001,0.0401 +1714,0.18,0.0267 +1715,0.311,0.0501 +1716,0.219,0.0736 +1717,0.144,0.1022 +1718,0.0,0.0817 +1719,0.0,0.1216 +1720,0.0,0.1212 +1721,0.002,0.1035 +1722,0.308,0.0398 +1723,0.564,0.0376 +1724,0.508,0.0488 +1725,0.228,0.0612 +1726,0.0,0.0503 +1727,0.0,0.0276 +1728,0.0,0.0235 +1729,0.001,0.0586 +1730,0.266,0.0341 +1731,0.488,0.0997 +1732,0.36,0.1184 +1733,0.119,0.081 +1734,0.0,0.0347 +1735,0.0,0.0828 +1736,0.0,0.1455 +1737,0.001,0.1252 +1738,0.264,0.0362 +1739,0.54,0.1225 +1740,0.437,0.1941 +1741,0.158,0.159 +1742,0.0,0.0974 +1743,0.0,0.1408 +1744,0.0,0.1229 +1745,0.0,0.0997 +1746,0.12,0.0704 +1747,0.34,0.1301 +1748,0.392,0.2699 +1749,0.1,0.1459 +1750,0.0,0.1328 +1751,0.0,0.1841 +1752,0.0,0.2338 +1753,0.0,0.1647 +1754,0.306,0.1113 +1755,0.591,0.1414 +1756,0.48,0.1891 +1757,0.167,0.1587 +1758,0.0,0.1201 +1759,0.0,0.1287 +1760,0.0,0.0974 +1761,0.001,0.1141 +1762,0.299,0.0411 +1763,0.575,0.0588 +1764,0.517,0.0656 +1765,0.241,0.0484 +1766,0.0,0.1206 +1767,0.0,0.2441 +1768,0.0,0.2046 +1769,0.0,0.1599 +1770,0.129,0.2106 +1771,0.355,0.4 +1772,0.365,0.3507 +1773,0.178,0.4012 +1774,0.0,0.2324 +1775,0.0,0.2273 +1776,0.0,0.2056 +1777,0.0,0.1563 +1778,0.299,0.039 +1779,0.575,0.0836 +1780,0.577,0.0664 +1781,0.291,0.0546 +1782,0.0,0.0933 +1783,0.0,0.1541 +1784,0.0,0.1685 +1785,0.0,0.0866 +1786,0.213,0.0958 +1787,0.445,0.0617 +1788,0.32,0.0573 +1789,0.122,0.0484 +1790,0.0,0.0772 +1791,0.0,0.1034 +1792,0.0,0.1385 +1793,0.0,0.1561 +1794,0.236,0.1678 +1795,0.573,0.1847 +1796,0.571,0.2187 +1797,0.184,0.1792 +1798,0.0,0.1193 +1799,0.0,0.1071 +1800,0.0,0.0785 +1801,0.0,0.0654 +1802,0.195,0.0445 +1803,0.51,0.0992 +1804,0.535,0.1325 +1805,0.247,0.1243 +1806,0.0,0.0498 +1807,0.0,0.0201 +1808,0.0,0.0622 +1809,0.0,0.1371 +1810,0.299,0.0878 +1811,0.55,0.1792 +1812,0.441,0.2347 +1813,0.153,0.1928 +1814,0.0,0.1648 +1815,0.0,0.1815 +1816,0.0,0.1424 +1817,0.0,0.1343 +1818,0.203,0.1144 +1819,0.467,0.1137 +1820,0.412,0.108 +1821,0.213,0.07 +1822,0.0,0.0242 +1823,0.0,0.043 +1824,0.0,0.1341 +1825,0.0,0.1246 +1826,0.19,0.0773 +1827,0.433,0.2173 +1828,0.454,0.2552 +1829,0.127,0.2434 +1830,0.0,0.1593 +1831,0.0,0.1824 +1832,0.0,0.1434 +1833,0.0,0.1277 +1834,0.176,0.1514 +1835,0.586,0.4454 +1836,0.598,0.467 +1837,0.282,0.4107 +1838,0.0,0.3047 +1839,0.0,0.2216 +1840,0.0,0.1402 +1841,0.0,0.1342 +1842,0.189,0.0668 +1843,0.425,0.1434 +1844,0.435,0.1112 +1845,0.195,0.0766 +1846,0.0,0.0645 +1847,0.0,0.0513 +1848,0.0,0.0406 +1849,0.0,0.0545 +1850,0.159,0.0271 +1851,0.416,0.018 +1852,0.321,0.0082 +1853,0.105,0.0853 +1854,0.0,0.1961 +1855,0.0,0.1834 +1856,0.0,0.2373 +1857,0.0,0.2701 +1858,0.192,0.2171 +1859,0.484,0.1062 +1860,0.487,0.0548 +1861,0.235,0.0603 +1862,0.0,0.0851 +1863,0.0,0.1051 +1864,0.0,0.1073 +1865,0.0,0.1472 +1866,0.223,0.212 +1867,0.471,0.2156 +1868,0.489,0.1082 +1869,0.221,0.0667 +1870,0.0,0.1152 +1871,0.0,0.109 +1872,0.0,0.0705 +1873,0.0,0.0645 +1874,0.267,0.0265 +1875,0.615,0.0224 +1876,0.602,0.021 +1877,0.251,0.041 +1878,0.0,0.1453 +1879,0.0,0.1605 +1880,0.0,0.1365 +1881,0.0,0.1376 +1882,0.2,0.0615 +1883,0.385,0.1015 +1884,0.209,0.0726 +1885,0.057,0.0665 +1886,0.0,0.1855 +1887,0.0,0.199 +1888,0.0,0.1542 +1889,0.0,0.1178 +1890,0.191,0.0444 +1891,0.36,0.1095 +1892,0.335,0.1032 +1893,0.146,0.081 +1894,0.0,0.1462 +1895,0.0,0.138 +1896,0.0,0.0829 +1897,0.0,0.0566 +1898,0.203,0.0208 +1899,0.482,0.0842 +1900,0.39,0.0797 +1901,0.139,0.0681 +1902,0.0,0.1168 +1903,0.0,0.1521 +1904,0.0,0.1252 +1905,0.0,0.1125 +1906,0.211,0.052 +1907,0.425,0.1174 +1908,0.332,0.085 +1909,0.143,0.0724 +1910,0.0,0.1522 +1911,0.0,0.1978 +1912,0.0,0.1715 +1913,0.0,0.1471 +1914,0.244,0.0669 +1915,0.5,0.1319 +1916,0.379,0.1136 +1917,0.135,0.0622 +1918,0.0,0.0882 +1919,0.0,0.1407 +1920,0.0,0.1608 +1921,0.0,0.1615 +1922,0.229,0.02 +1923,0.412,0.0183 +1924,0.308,0.0186 +1925,0.073,0.0246 +1926,0.0,0.0721 +1927,0.0,0.0903 +1928,0.0,0.069 +1929,0.0,0.0379 +1930,0.182,0.0049 +1931,0.433,0.011 +1932,0.368,0.0043 +1933,0.14,0.0063 +1934,0.0,0.0392 +1935,0.0,0.0852 +1936,0.0,0.1039 +1937,0.0,0.104 +1938,0.238,0.0381 +1939,0.585,0.1029 +1940,0.574,0.1137 +1941,0.239,0.1194 +1942,0.0,0.1972 +1943,0.0,0.2283 +1944,0.0,0.2323 +1945,0.0,0.2333 +1946,0.251,0.0871 +1947,0.537,0.158 +1948,0.431,0.1432 +1949,0.131,0.1152 +1950,0.0,0.1255 +1951,0.0,0.1546 +1952,0.0,0.1226 +1953,0.0,0.148 +1954,0.057,0.1741 +1955,0.183,0.1658 +1956,0.21,0.2209 +1957,0.084,0.1707 +1958,0.0,0.0877 +1959,0.0,0.0729 +1960,0.0,0.043 +1961,0.0,0.0381 +1962,0.261,0.0327 +1963,0.637,0.0838 +1964,0.624,0.1067 +1965,0.264,0.11 +1966,0.0,0.0292 +1967,0.0,0.0131 +1968,0.0,0.023 +1969,0.0,0.0342 +1970,0.272,0.0231 +1971,0.626,0.0961 +1972,0.615,0.1294 +1973,0.26,0.1471 +1974,0.0,0.205 +1975,0.0,0.2449 +1976,0.0,0.2263 +1977,0.0,0.1749 +1978,0.254,0.0468 +1979,0.541,0.1078 +1980,0.479,0.154 +1981,0.145,0.2073 +1982,0.0,0.2608 +1983,0.0,0.1824 +1984,0.0,0.1661 +1985,0.0,0.112 +1986,0.163,0.0814 +1987,0.473,0.0413 +1988,0.468,0.0209 +1989,0.108,0.0249 +1990,0.0,0.0817 +1991,0.0,0.0889 +1992,0.0,0.0615 +1993,0.0,0.0186 +1994,0.068,0.005 +1995,0.205,0.0228 +1996,0.171,0.0185 +1997,0.059,0.0171 +1998,0.0,0.0193 +1999,0.0,0.023 +2000,0.0,0.0193 +2001,0.0,0.0223 +2002,0.105,0.012 +2003,0.417,0.0262 +2004,0.404,0.0274 +2005,0.085,0.0537 +2006,0.0,0.1328 +2007,0.0,0.1812 +2008,0.0,0.1511 +2009,0.0,0.1329 +2010,0.078,0.1092 +2011,0.226,0.1956 +2012,0.245,0.2662 +2013,0.084,0.2966 +2014,0.0,0.321 +2015,0.0,0.2722 +2016,0.0,0.2375 +2017,0.0,0.1629 +2018,0.183,0.0831 +2019,0.601,0.0958 +2020,0.542,0.1117 +2021,0.193,0.0695 +2022,0.0,0.0482 +2023,0.0,0.0673 +2024,0.0,0.0622 +2025,0.0,0.043 +2026,0.181,0.0081 +2027,0.523,0.0326 +2028,0.546,0.0671 +2029,0.212,0.0831 +2030,0.0,0.0871 +2031,0.0,0.1608 +2032,0.0,0.2233 +2033,0.0,0.2076 +2034,0.207,0.0982 +2035,0.493,0.1556 +2036,0.525,0.1605 +2037,0.191,0.1428 +2038,0.0,0.1195 +2039,0.0,0.1415 +2040,0.0,0.1816 +2041,0.0,0.1888 +2042,0.225,0.0667 +2043,0.57,0.2211 +2044,0.488,0.2509 +2045,0.112,0.1756 +2046,0.0,0.1832 +2047,0.0,0.1811 +2048,0.0,0.1636 +2049,0.0,0.1148 +2050,0.217,0.0688 +2051,0.594,0.0242 +2052,0.599,0.0107 +2053,0.228,0.012 +2054,0.0,0.0493 +2055,0.0,0.1008 +2056,0.0,0.1638 +2057,0.0,0.2234 +2058,0.24,0.1106 +2059,0.606,0.2438 +2060,0.592,0.2621 +2061,0.224,0.224 +2062,0.0,0.3109 +2063,0.0,0.3115 +2064,0.0,0.2223 +2065,0.0,0.1576 +2066,0.109,0.0345 +2067,0.3,0.0726 +2068,0.35,0.2455 +2069,0.118,0.1921 +2070,0.0,0.0601 +2071,0.0,0.0523 +2072,0.0,0.0835 +2073,0.0,0.0749 +2074,0.121,0.0684 +2075,0.396,0.2803 +2076,0.511,0.5727 +2077,0.193,0.6853 +2078,0.0,0.5884 +2079,0.0,0.6029 +2080,0.0,0.5158 +2081,0.0,0.3835 +2082,0.217,0.3065 +2083,0.64,0.2848 +2084,0.625,0.3009 +2085,0.236,0.2992 +2086,0.0,0.181 +2087,0.0,0.1718 +2088,0.0,0.2063 +2089,0.0,0.1892 +2090,0.217,0.1255 +2091,0.596,0.1597 +2092,0.579,0.1767 +2093,0.213,0.1794 +2094,0.0,0.1086 +2095,0.0,0.0813 +2096,0.0,0.0784 +2097,0.0,0.1003 +2098,0.228,0.032 +2099,0.588,0.0505 +2100,0.594,0.0616 +2101,0.21,0.0504 +2102,0.0,0.0341 +2103,0.0,0.0408 +2104,0.0,0.0793 +2105,0.0,0.0922 +2106,0.222,0.0331 +2107,0.61,0.0514 +2108,0.594,0.0718 +2109,0.216,0.0909 +2110,0.0,0.2894 +2111,0.0,0.335 +2112,0.0,0.313 +2113,0.0,0.3138 +2114,0.234,0.1512 +2115,0.61,0.3057 +2116,0.548,0.2606 +2117,0.158,0.1922 +2118,0.0,0.294 +2119,0.0,0.2375 +2120,0.0,0.1639 +2121,0.0,0.1456 +2122,0.147,0.0494 +2123,0.46,0.0577 +2124,0.443,0.0462 +2125,0.1,0.0478 +2126,0.0,0.0696 +2127,0.0,0.0699 +2128,0.0,0.0596 +2129,0.0,0.0677 +2130,0.111,0.0221 +2131,0.427,0.008 +2132,0.427,0.013 +2133,0.082,0.0373 +2134,0.0,0.0732 +2135,0.0,0.0873 +2136,0.0,0.0948 +2137,0.0,0.1054 +2138,0.049,0.1295 +2139,0.221,0.2147 +2140,0.237,0.2775 +2141,0.075,0.1786 +2142,0.0,0.2094 +2143,0.0,0.2301 +2144,0.0,0.1985 +2145,0.0,0.1364 +2146,0.144,0.0865 +2147,0.545,0.1334 +2148,0.494,0.1267 +2149,0.12,0.091 +2150,0.0,0.1038 +2151,0.0,0.1379 +2152,0.0,0.1682 +2153,0.0,0.1579 +2154,0.077,0.14 +2155,0.233,0.2607 +2156,0.192,0.3473 +2157,0.04,0.1944 +2158,0.0,0.2306 +2159,0.0,0.2568 +2160,0.0,0.2375 +2161,0.0,0.231 +2162,0.113,0.2284 +2163,0.425,0.4203 +2164,0.453,0.4356 +2165,0.138,0.3415 +2166,0.0,0.368 +2167,0.0,0.3683 +2168,0.0,0.3375 +2169,0.0,0.2625 +2170,0.06,0.1984 +2171,0.44,0.4807 +2172,0.489,0.548 +2173,0.102,0.5953 +2174,0.0,0.6906 +2175,0.0,0.717 +2176,0.0,0.7913 +2177,0.0,0.8624 +2178,0.125,0.9194 +2179,0.517,0.9542 +2180,0.504,0.9189 +2181,0.144,0.7727 +2182,0.0,0.5317 +2183,0.0,0.311 +2184,0.0,0.2635 +2185,0.0,0.2304 +2186,0.163,0.2577 +2187,0.532,0.6737 +2188,0.424,0.6493 +2189,0.086,0.4515 +2190,0.0,0.3913 +2191,0.0,0.353 +2192,0.0,0.3416 +2193,0.0,0.2375 +2194,0.038,0.224 +2195,0.149,0.2617 +2196,0.151,0.3681 +2197,0.027,0.2078 +2198,0.0,0.2232 +2199,0.0,0.2796 +2200,0.0,0.28 +2201,0.0,0.2988 +2202,0.092,0.3773 +2203,0.316,0.4789 +2204,0.395,0.4464 +2205,0.092,0.2136 +2206,0.0,0.1868 +2207,0.0,0.1448 +2208,0.0,0.091 +2209,0.0,0.0576 +2210,0.062,0.0355 +2211,0.325,0.0747 +2212,0.304,0.1581 +2213,0.037,0.0924 +2214,0.0,0.1463 +2215,0.0,0.2027 +2216,0.0,0.2078 +2217,0.0,0.2382 +2218,0.022,0.233 +2219,0.175,0.4089 +2220,0.145,0.4557 +2221,0.035,0.3527 +2222,0.0,0.306 +2223,0.0,0.2218 +2224,0.0,0.1081 +2225,0.0,0.0635 +2226,0.066,0.0529 +2227,0.476,0.088 +2228,0.39,0.0633 +2229,0.043,0.0729 +2230,0.0,0.1705 +2231,0.0,0.1651 +2232,0.0,0.166 +2233,0.0,0.1251 +2234,0.151,0.0524 +2235,0.599,0.0184 +2236,0.594,0.0013 +2237,0.188,0.0058 +2238,0.0,0.0246 +2239,0.0,0.1527 +2240,0.0,0.2232 +2241,0.0,0.246 +2242,0.038,0.2439 +2243,0.275,0.5269 +2244,0.238,0.6378 +2245,0.041,0.4542 +2246,0.0,0.327 +2247,0.0,0.2781 +2248,0.0,0.2663 +2249,0.0,0.2356 +2250,0.054,0.1365 +2251,0.33,0.1203 +2252,0.167,0.1802 +2253,0.019,0.1872 +2254,0.0,0.3749 +2255,0.0,0.4921 +2256,0.0,0.4923 +2257,0.0,0.4646 +2258,0.047,0.3853 +2259,0.311,0.471 +2260,0.282,0.5093 +2261,0.084,0.3917 +2262,0.0,0.3946 +2263,0.0,0.3408 +2264,0.0,0.2867 +2265,0.0,0.2024 +2266,0.035,0.23 +2267,0.443,0.4889 +2268,0.294,0.4787 +2269,0.049,0.3598 +2270,0.0,0.4324 +2271,0.0,0.4074 +2272,0.0,0.378 +2273,0.0,0.3548 +2274,0.092,0.3189 +2275,0.527,0.4585 +2276,0.529,0.5087 +2277,0.14,0.3764 +2278,0.0,0.3815 +2279,0.0,0.3125 +2280,0.0,0.2812 +2281,0.0,0.2766 +2282,0.078,0.2095 +2283,0.512,0.1955 +2284,0.51,0.1891 +2285,0.119,0.1937 +2286,0.0,0.3916 +2287,0.0,0.3926 +2288,0.0,0.3306 +2289,0.0,0.2399 +2290,0.073,0.1397 +2291,0.499,0.0766 +2292,0.515,0.0599 +2293,0.118,0.1385 +2294,0.0,0.3424 +2295,0.0,0.2732 +2296,0.0,0.2612 +2297,0.0,0.2449 +2298,0.046,0.1803 +2299,0.427,0.2579 +2300,0.479,0.3019 +2301,0.097,0.2817 +2302,0.0,0.3025 +2303,0.0,0.3373 +2304,0.0,0.3945 +2305,0.0,0.4802 +2306,0.04,0.3942 +2307,0.349,0.5512 +2308,0.498,0.528 +2309,0.112,0.3448 +2310,0.0,0.2786 +2311,0.0,0.2764 +2312,0.0,0.2497 +2313,0.0,0.2331 +2314,0.055,0.2055 +2315,0.414,0.21 +2316,0.446,0.1962 +2317,0.11,0.108 +2318,0.0,0.1887 +2319,0.0,0.2215 +2320,0.0,0.1825 +2321,0.0,0.1796 +2322,0.046,0.1431 +2323,0.456,0.1581 +2324,0.408,0.2468 +2325,0.067,0.2582 +2326,0.0,0.2645 +2327,0.0,0.1585 +2328,0.0,0.0725 +2329,0.0,0.0624 +2330,0.038,0.0637 +2331,0.29,0.0772 +2332,0.288,0.1954 +2333,0.06,0.1296 +2334,0.0,0.1913 +2335,0.0,0.138 +2336,0.0,0.1685 +2337,0.0,0.159 +2338,0.026,0.1327 +2339,0.398,0.1302 +2340,0.476,0.1269 +2341,0.083,0.1531 +2342,0.0,0.2917 +2343,0.0,0.2592 +2344,0.0,0.2397 +2345,0.0,0.1926 +2346,0.041,0.1237 +2347,0.444,0.0496 +2348,0.448,0.0398 +2349,0.06,0.018 +2350,0.0,0.0249 +2351,0.0,0.0275 +2352,0.0,0.0395 +2353,0.0,0.0533 +2354,0.048,0.0068 +2355,0.459,0.0011 +2356,0.449,0.0117 +2357,0.057,0.0222 +2358,0.0,0.0546 +2359,0.0,0.0527 +2360,0.0,0.0563 +2361,0.0,0.0873 +2362,0.039,0.102 +2363,0.39,0.1214 +2364,0.457,0.1114 +2365,0.077,0.0967 +2366,0.0,0.2783 +2367,0.0,0.2425 +2368,0.0,0.2221 +2369,0.0,0.2058 +2370,0.039,0.191 +2371,0.469,0.2144 +2372,0.481,0.279 +2373,0.083,0.2019 +2374,0.0,0.2849 +2375,0.0,0.2268 +2376,0.0,0.178 +2377,0.0,0.1433 +2378,0.028,0.1158 +2379,0.415,0.1428 +2380,0.416,0.1721 +2381,0.055,0.1037 +2382,0.0,0.0821 +2383,0.0,0.126 +2384,0.0,0.1689 +2385,0.0,0.1818 +2386,0.056,0.182 +2387,0.489,0.1526 +2388,0.49,0.188 +2389,0.087,0.2082 +2390,0.0,0.392 +2391,0.0,0.3615 +2392,0.0,0.3393 +2393,0.0,0.3519 +2394,0.046,0.354 +2395,0.475,0.3814 +2396,0.437,0.3458 +2397,0.035,0.1861 +2398,0.0,0.1125 +2399,0.0,0.0989 +2400,0.0,0.1015 +2401,0.0,0.0807 +2402,0.008,0.0495 +2403,0.335,0.0547 +2404,0.464,0.054 +2405,0.071,0.0523 +2406,0.0,0.0604 +2407,0.0,0.0342 +2408,0.0,0.0236 +2409,0.0,0.0227 +2410,0.008,0.0283 +2411,0.165,0.0454 +2412,0.22,0.0725 +2413,0.026,0.1139 +2414,0.0,0.1669 +2415,0.0,0.1211 +2416,0.0,0.0833 +2417,0.0,0.0597 +2418,0.012,0.0636 +2419,0.454,0.1015 +2420,0.474,0.0875 +2421,0.052,0.1087 +2422,0.0,0.208 +2423,0.0,0.1914 +2424,0.0,0.1708 +2425,0.0,0.1477 +2426,0.047,0.1038 +2427,0.485,0.0873 +2428,0.498,0.0605 +2429,0.062,0.0711 +2430,0.0,0.144 +2431,0.0,0.1565 +2432,0.0,0.1394 +2433,0.0,0.1441 +2434,0.045,0.2081 +2435,0.504,0.3875 +2436,0.451,0.4008 +2437,0.017,0.3826 +2438,0.0,0.4314 +2439,0.0,0.4416 +2440,0.0,0.4846 +2441,0.0,0.5098 +2442,0.004,0.4802 +2443,0.114,0.4417 +2444,0.17,0.4104 +2445,0.012,0.3584 +2446,0.0,0.3635 +2447,0.0,0.2648 +2448,0.0,0.2373 +2449,0.0,0.3073 +2450,0.003,0.2743 +2451,0.121,0.2218 +2452,0.199,0.1809 +2453,0.005,0.2894 +2454,0.0,0.3198 +2455,0.0,0.2948 +2456,0.0,0.3549 +2457,0.0,0.4073 +2458,0.003,0.3571 +2459,0.263,0.3987 +2460,0.412,0.2986 +2461,0.018,0.2406 +2462,0.0,0.3809 +2463,0.0,0.3514 +2464,0.0,0.2782 +2465,0.0,0.2683 +2466,0.001,0.2915 +2467,0.093,0.1737 +2468,0.101,0.1459 +2469,0.002,0.1072 +2470,0.0,0.1395 +2471,0.0,0.1313 +2472,0.0,0.1257 +2473,0.0,0.1195 +2474,0.008,0.1189 +2475,0.362,0.1539 +2476,0.371,0.2592 +2477,0.01,0.1823 +2478,0.0,0.2351 +2479,0.0,0.1655 +2480,0.0,0.1085 +2481,0.0,0.1098 +2482,0.002,0.0822 +2483,0.255,0.0807 +2484,0.411,0.127 +2485,0.016,0.1302 +2486,0.0,0.281 +2487,0.0,0.1917 +2488,0.0,0.0749 +2489,0.0,0.0759 +2490,0.002,0.18 +2491,0.099,0.227 +2492,0.121,0.2367 +2493,0.007,0.1893 +2494,0.0,0.2134 +2495,0.0,0.1749 +2496,0.0,0.151 +2497,0.0,0.1238 +2498,0.001,0.1393 +2499,0.049,0.1296 +2500,0.089,0.1896 +2501,0.003,0.1644 +2502,0.0,0.1806 +2503,0.0,0.1085 +2504,0.0,0.0472 +2505,0.0,0.0221 +2506,0.004,0.0411 +2507,0.277,0.0396 +2508,0.313,0.0575 +2509,0.015,0.0632 +2510,0.0,0.0564 +2511,0.0,0.0958 +2512,0.0,0.0804 +2513,0.0,0.0415 +2514,0.007,0.0644 +2515,0.428,0.1541 +2516,0.449,0.2644 +2517,0.01,0.2361 +2518,0.0,0.311 +2519,0.0,0.3083 +2520,0.0,0.3355 +2521,0.0,0.2591 +2522,0.0,0.2195 +2523,0.073,0.0979 +2524,0.101,0.075 +2525,0.0,0.0518 +2526,0.0,0.0884 +2527,0.0,0.0766 +2528,0.0,0.0775 +2529,0.0,0.0353 +2530,0.0,0.0369 +2531,0.064,0.0236 +2532,0.049,0.0524 +2533,0.0,0.1017 +2534,0.0,0.1712 +2535,0.0,0.1864 +2536,0.0,0.1297 +2537,0.0,0.1029 +2538,0.005,0.0917 +2539,0.455,0.1031 +2540,0.459,0.2093 +2541,0.01,0.3444 +2542,0.0,0.4622 +2543,0.0,0.3524 +2544,0.0,0.3372 +2545,0.0,0.3701 +2546,0.002,0.4157 +2547,0.145,0.4794 +2548,0.267,0.4789 +2549,0.001,0.3684 +2550,0.0,0.4388 +2551,0.0,0.4231 +2552,0.0,0.4798 +2553,0.0,0.4882 +2554,0.001,0.4521 +2555,0.275,0.4072 +2556,0.34,0.3001 +2557,0.004,0.1871 +2558,0.0,0.1124 +2559,0.0,0.0638 +2560,0.0,0.1386 +2561,0.0,0.1667 +2562,0.001,0.249 +2563,0.164,0.2731 +2564,0.207,0.4319 +2565,0.0,0.5854 +2566,0.0,0.6277 +2567,0.0,0.5811 +2568,0.0,0.3774 +2569,0.0,0.2995 +2570,0.0,0.3194 +2571,0.075,0.2939 +2572,0.215,0.3375 +2573,0.003,0.3285 +2574,0.0,0.2782 +2575,0.0,0.293 +2576,0.0,0.329 +2577,0.0,0.298 +2578,0.0,0.1782 +2579,0.268,0.1688 +2580,0.253,0.1818 +2581,0.0,0.1373 +2582,0.0,0.1575 +2583,0.0,0.121 +2584,0.0,0.077 +2585,0.0,0.0581 +2586,0.0,0.0452 +2587,0.1,0.0247 +2588,0.142,0.0274 +2589,0.0,0.0797 +2590,0.0,0.0988 +2591,0.0,0.063 +2592,0.0,0.0694 +2593,0.0,0.0994 +2594,0.0,0.1379 +2595,0.091,0.0915 +2596,0.088,0.1056 +2597,0.0,0.1446 +2598,0.0,0.2455 +2599,0.0,0.2622 +2600,0.0,0.2029 +2601,0.0,0.1775 +2602,0.0,0.2081 +2603,0.13,0.2748 +2604,0.202,0.2922 +2605,0.0,0.3247 +2606,0.0,0.3919 +2607,0.0,0.3693 +2608,0.0,0.3754 +2609,0.0,0.3832 +2610,0.001,0.4058 +2611,0.332,0.5272 +2612,0.364,0.5549 +2613,0.002,0.5682 +2614,0.0,0.5072 +2615,0.0,0.4121 +2616,0.0,0.3533 +2617,0.0,0.3222 +2618,0.001,0.2579 +2619,0.186,0.2382 +2620,0.247,0.1915 +2621,0.001,0.1957 +2622,0.0,0.1505 +2623,0.0,0.0977 +2624,0.0,0.0808 +2625,0.0,0.0709 +2626,0.0,0.0548 +2627,0.068,0.0582 +2628,0.105,0.0416 +2629,0.0,0.0316 +2630,0.0,0.0722 +2631,0.0,0.0919 +2632,0.0,0.0859 +2633,0.0,0.094 +2634,0.0,0.0993 +2635,0.053,0.114 +2636,0.052,0.1238 +2637,0.0,0.098 +2638,0.0,0.1297 +2639,0.0,0.141 +2640,0.0,0.1695 +2641,0.0,0.2325 +2642,0.0,0.3137 +2643,0.061,0.3735 +2644,0.056,0.2476 +2645,0.0,0.1851 +2646,0.0,0.22 +2647,0.0,0.2714 +2648,0.0,0.3071 +2649,0.0,0.3564 +2650,0.0,0.4055 +2651,0.112,0.4006 +2652,0.214,0.4909 +2653,0.0,0.5697 +2654,0.0,0.6132 +2655,0.0,0.6187 +2656,0.0,0.5801 +2657,0.0,0.5636 +2658,0.0,0.4393 +2659,0.083,0.4067 +2660,0.142,0.4686 +2661,0.0,0.4235 +2662,0.0,0.4587 +2663,0.0,0.3625 +2664,0.0,0.2833 +2665,0.0,0.2323 +2666,0.0,0.1965 +2667,0.315,0.1531 +2668,0.316,0.1388 +2669,0.0,0.087 +2670,0.0,0.0799 +2671,0.0,0.0603 +2672,0.0,0.0271 +2673,0.0,0.0393 +2674,0.0,0.0996 +2675,0.207,0.1595 +2676,0.25,0.2128 +2677,0.0,0.2052 +2678,0.0,0.201 +2679,0.0,0.1532 +2680,0.0,0.1101 +2681,0.0,0.1033 +2682,0.0,0.0804 +2683,0.239,0.1921 +2684,0.28,0.3192 +2685,0.0,0.3546 +2686,0.0,0.4091 +2687,0.0,0.3865 +2688,0.0,0.3568 +2689,0.0,0.2433 +2690,0.0,0.1627 +2691,0.243,0.0815 +2692,0.255,0.0636 +2693,0.0,0.0464 +2694,0.0,0.0615 +2695,0.0,0.0908 +2696,0.0,0.0866 +2697,0.0,0.0686 +2698,0.0,0.0775 +2699,0.385,0.1278 +2700,0.401,0.1523 +2701,0.0,0.2432 +2702,0.0,0.3444 +2703,0.0,0.3245 +2704,0.0,0.2962 +2705,0.0,0.3022 +2706,0.0,0.3318 +2707,0.388,0.3367 +2708,0.4,0.2856 +2709,0.0,0.2678 +2710,0.0,0.2939 +2711,0.0,0.3226 +2712,0.0,0.3092 +2713,0.0,0.3342 +2714,0.0,0.4044 +2715,0.329,0.4154 +2716,0.367,0.4151 +2717,0.001,0.4856 +2718,0.0,0.5161 +2719,0.0,0.5154 +2720,0.0,0.5564 +2721,0.0,0.5963 +2722,0.0,0.6763 +2723,0.103,0.6269 +2724,0.091,0.5529 +2725,0.0,0.535 +2726,0.0,0.5086 +2727,0.0,0.4452 +2728,0.0,0.4015 +2729,0.0,0.4161 +2730,0.0,0.5224 +2731,0.132,0.5645 +2732,0.12,0.6441 +2733,0.0,0.6431 +2734,0.0,0.562 +2735,0.0,0.4978 +2736,0.0,0.4938 +2737,0.0,0.4832 +2738,0.0,0.4583 +2739,0.077,0.4306 +2740,0.125,0.407 +2741,0.0,0.3375 +2742,0.0,0.2608 +2743,0.0,0.2351 +2744,0.0,0.4231 +2745,0.0,0.4105 +2746,0.0,0.4289 +2747,0.253,0.3382 +2748,0.337,0.3007 +2749,0.0,0.2018 +2750,0.0,0.1678 +2751,0.0,0.1386 +2752,0.0,0.1736 +2753,0.0,0.202 +2754,0.0,0.2304 +2755,0.382,0.2341 +2756,0.366,0.207 +2757,0.001,0.2735 +2758,0.0,0.2777 +2759,0.0,0.244 +2760,0.0,0.2308 +2761,0.0,0.2014 +2762,0.0,0.1701 +2763,0.154,0.134 +2764,0.146,0.1129 +2765,0.0,0.1122 +2766,0.0,0.1259 +2767,0.0,0.1078 +2768,0.0,0.1522 +2769,0.0,0.2369 +2770,0.0,0.3338 +2771,0.156,0.4572 +2772,0.092,0.4635 +2773,0.0,0.4504 +2774,0.0,0.4806 +2775,0.0,0.4311 +2776,0.0,0.4754 +2777,0.0,0.4891 +2778,0.0,0.4197 +2779,0.147,0.4762 +2780,0.096,0.7978 +2781,0.0,0.8603 +2782,0.0,0.8244 +2783,0.0,0.7544 +2784,0.0,0.5569 +2785,0.0,0.2896 +2786,0.0,0.3624 +2787,0.068,0.6753 +2788,0.103,0.6046 +2789,0.0,0.4778 +2790,0.0,0.4142 +2791,0.0,0.3282 +2792,0.0,0.2109 +2793,0.0,0.1264 +2794,0.0,0.1746 +2795,0.093,0.2475 +2796,0.171,0.2003 +2797,0.0,0.2968 +2798,0.0,0.2454 +2799,0.0,0.2336 +2800,0.0,0.2236 +2801,0.0,0.2272 +2802,0.0,0.2381 +2803,0.072,0.2122 +2804,0.088,0.1679 +2805,0.0,0.2451 +2806,0.0,0.2234 +2807,0.0,0.2712 +2808,0.0,0.3401 +2809,0.0,0.4348 +2810,0.0,0.3367 +2811,0.124,0.1383 +2812,0.176,0.0685 +2813,0.0,0.0714 +2814,0.0,0.0403 +2815,0.0,0.0555 +2816,0.0,0.0817 +2817,0.0,0.1034 +2818,0.0,0.1801 +2819,0.057,0.1907 +2820,0.113,0.2611 +2821,0.0,0.3403 +2822,0.0,0.3893 +2823,0.0,0.3865 +2824,0.0,0.3573 +2825,0.0,0.3634 +2826,0.0,0.4112 +2827,0.131,0.4593 +2828,0.229,0.4558 +2829,0.0,0.5211 +2830,0.0,0.4347 +2831,0.0,0.3644 +2832,0.0,0.3384 +2833,0.0,0.2378 +2834,0.0,0.1696 +2835,0.068,0.0702 +2836,0.039,0.0338 +2837,0.0,0.0858 +2838,0.0,0.1365 +2839,0.0,0.1208 +2840,0.0,0.1283 +2841,0.0,0.1212 +2842,0.0,0.0854 +2843,0.053,0.0762 +2844,0.061,0.0596 +2845,0.0,0.1006 +2846,0.0,0.1396 +2847,0.0,0.2217 +2848,0.0,0.3209 +2849,0.0,0.4251 +2850,0.0,0.433 +2851,0.03,0.3988 +2852,0.041,0.4509 +2853,0.0,0.5241 +2854,0.0,0.5324 +2855,0.0,0.5054 +2856,0.0,0.5338 +2857,0.0,0.4732 +2858,0.0,0.3851 +2859,0.161,0.2861 +2860,0.191,0.1982 +2861,0.0,0.2422 +2862,0.0,0.4488 +2863,0.0,0.4703 +2864,0.0,0.4248 +2865,0.0,0.3962 +2866,0.0,0.3383 +2867,0.048,0.249 +2868,0.095,0.302 +2869,0.0,0.3353 +2870,0.0,0.3292 +2871,0.0,0.2804 +2872,0.0,0.2429 +2873,0.0,0.239 +2874,0.0,0.188 +2875,0.154,0.1504 +2876,0.236,0.1429 +2877,0.0,0.1148 +2878,0.0,0.0902 +2879,0.0,0.1252 +2880,0.0,0.1124 +2881,0.0,0.0782 +2882,0.0,0.0718 +2883,0.199,0.0885 +2884,0.213,0.2041 +2885,0.0,0.247 +2886,0.0,0.2303 +2887,0.0,0.2238 +2888,0.0,0.308 +2889,0.0,0.3034 +2890,0.0,0.288 +2891,0.207,0.3691 +2892,0.209,0.3128 +2893,0.0,0.2538 +2894,0.0,0.2286 +2895,0.0,0.1536 +2896,0.0,0.1325 +2897,0.0,0.1094 +2898,0.0,0.0973 +2899,0.292,0.105 +2900,0.344,0.0617 +2901,0.0,0.0429 +2902,0.0,0.0796 +2903,0.0,0.1274 +2904,0.0,0.1063 +2905,0.0,0.1226 +2906,0.0,0.1642 +2907,0.286,0.1457 +2908,0.322,0.1776 +2909,0.0,0.3718 +2910,0.0,0.3891 +2911,0.0,0.3962 +2912,0.0,0.4466 +2913,0.0,0.4713 +2914,0.0,0.4636 +2915,0.195,0.3049 +2916,0.135,0.3944 +2917,0.0,0.3788 +2918,0.0,0.3157 +2919,0.0,0.2636 diff --git a/PyPSA/source/examples/networks/model-energy/model-energy/generators.csv b/PyPSA/source/examples/networks/model-energy/model-energy/generators.csv new file mode 100644 index 0000000000000000000000000000000000000000..8760716c3259647bc8cc26aa53feca2bc836d08f --- /dev/null +++ b/PyPSA/source/examples/networks/model-energy/model-energy/generators.csv @@ -0,0 +1,4 @@ +name,bus,p_nom,p_nom_extendable,carrier,marginal_cost,capital_cost +load shedding,electricity,10901.16,False,load shedding,2000.0,0.0 +wind,electricity,0.0,True,wind,0.0,101644.12332388276 +solar,electricity,0.0,True,solar,0.0,51346.82981964593 diff --git a/PyPSA/source/examples/networks/model-energy/model-energy/investment_periods.csv b/PyPSA/source/examples/networks/model-energy/model-energy/investment_periods.csv new file mode 100644 index 0000000000000000000000000000000000000000..34dd42197768b92fdb696fcfe70396544f01bb62 --- /dev/null +++ b/PyPSA/source/examples/networks/model-energy/model-energy/investment_periods.csv @@ -0,0 +1 @@ +period,objective,years diff --git a/PyPSA/source/examples/networks/model-energy/model-energy/links.csv b/PyPSA/source/examples/networks/model-energy/model-energy/links.csv new file mode 100644 index 0000000000000000000000000000000000000000..22179023da09c6d7b831af4826c7e874b0719c29 --- /dev/null +++ b/PyPSA/source/examples/networks/model-energy/model-energy/links.csv @@ -0,0 +1,3 @@ +name,bus0,bus1,carrier,efficiency,p_nom_extendable,capital_cost +electrolysis,electricity,hydrogen,electrolysis,0.6217,True,188715.7758309984 +turbine,hydrogen,electricity,turbine,0.41,True,116387.00137488062 diff --git a/PyPSA/source/examples/networks/model-energy/model-energy/loads-p_set.csv b/PyPSA/source/examples/networks/model-energy/model-energy/loads-p_set.csv new file mode 100644 index 0000000000000000000000000000000000000000..66a7e23f154a000c6a63d3b1ed165292e94b7ae4 --- /dev/null +++ b/PyPSA/source/examples/networks/model-energy/model-energy/loads-p_set.csv @@ -0,0 +1,2921 @@ +,demand +0,5719.26 +1,5474.74 +2,5413.39 +3,5891.23 +4,6662.42 +5,6848.63 +6,6936.06 +7,6446.72 +8,5844.14 +9,5844.16 +10,7467.41 +11,8709.61 +12,9146.41 +13,9165.62 +14,9014.41 +15,8189.57 +16,7063.72 +17,6944.61 +18,8546.99 +19,9459.78 +20,9688.05 +21,9618.63 +22,9404.76 +23,8565.91 +24,7404.83 +25,7267.58 +26,8889.04 +27,9729.44 +28,9821.96 +29,9738.89 +30,9358.34 +31,8388.26 +32,6989.99 +33,6937.92 +34,7186.81 +35,8222.91 +36,8654.07 +37,8430.03 +38,8202.31 +39,7435.26 +40,6463.1 +41,6474.78 +42,6671.23 +43,7822.54 +44,8398.32 +45,8373.94 +46,8457.52 +47,7832.01 +48,7215.09 +49,7136.61 +50,9131.06 +51,9951.5 +52,10024.33 +53,10039.87 +54,9719.19 +55,8838.49 +56,7597.88 +57,7480.63 +58,9302.02 +59,10155.77 +60,10177.92 +61,9993.63 +62,9734.28 +63,8700.38 +64,7536.08 +65,7441.63 +66,9265.68 +67,10043.03 +68,10138.72 +69,10093.93 +70,9784.77 +71,8900.42 +72,7607.0 +73,7460.37 +74,9304.14 +75,10107.35 +76,10040.81 +77,10047.72 +78,9769.46 +79,8897.32 +80,7713.69 +81,7581.68 +82,9464.69 +83,10254.34 +84,10115.58 +85,10087.78 +86,9800.91 +87,8752.44 +88,7197.92 +89,7059.86 +90,7472.38 +91,8543.1 +92,8785.8 +93,8654.43 +94,8450.03 +95,7645.42 +96,6682.45 +97,6664.26 +98,6826.97 +99,8015.51 +100,8397.91 +101,8371.5 +102,8497.05 +103,7794.61 +104,7165.43 +105,7034.59 +106,8916.68 +107,9832.83 +108,9952.62 +109,9933.16 +110,9766.31 +111,8855.04 +112,7651.6 +113,7543.05 +114,9377.4 +115,10145.98 +116,10070.25 +117,10090.64 +118,9735.84 +119,8761.52 +120,7547.15 +121,7374.19 +122,9155.25 +123,9879.32 +124,9827.51 +125,9738.66 +126,9625.65 +127,8691.96 +128,7567.07 +129,7438.72 +130,9179.65 +131,9870.34 +132,9782.86 +133,9671.29 +134,9559.59 +135,8628.24 +136,7405.45 +137,7274.03 +138,9146.51 +139,9957.56 +140,9782.02 +141,9612.6 +142,9539.36 +143,8592.87 +144,7276.2 +145,7233.53 +146,7798.07 +147,8815.34 +148,8705.96 +149,8538.53 +150,8656.67 +151,7852.72 +152,6959.38 +153,7031.48 +154,7224.96 +155,8403.64 +156,8678.02 +157,8660.67 +158,8813.5 +159,8244.14 +160,7746.95 +161,7786.51 +162,9772.77 +163,10486.74 +164,10305.38 +165,10190.69 +166,10231.23 +167,9393.51 +168,8233.34 +169,8256.06 +170,10109.1 +171,10897.82 +172,10641.85 +173,10459.75 +174,10409.73 +175,9584.41 +176,8443.82 +177,8430.17 +178,10212.14 +179,10901.16 +180,10713.92 +181,10668.74 +182,10485.13 +183,9546.98 +184,8346.39 +185,8253.51 +186,9993.55 +187,10803.12 +188,10818.99 +189,10679.95 +190,10382.18 +191,9426.51 +192,8253.85 +193,8130.58 +194,9857.08 +195,10690.27 +196,10544.95 +197,10493.0 +198,10162.48 +199,9105.93 +200,7641.14 +201,7623.17 +202,8056.02 +203,9239.5 +204,9355.52 +205,9074.96 +206,8915.62 +207,8019.81 +208,7029.35 +209,7045.77 +210,7202.83 +211,8354.42 +212,8614.75 +213,8503.8 +214,8733.15 +215,8109.04 +216,7484.97 +217,7415.15 +218,9376.01 +219,10256.73 +220,10304.3 +221,10157.11 +222,9930.86 +223,9006.29 +224,7809.22 +225,7696.83 +226,9587.04 +227,10289.31 +228,10131.01 +229,10086.86 +230,10024.32 +231,9145.59 +232,8002.15 +233,7971.9 +234,9812.3 +235,10395.56 +236,10088.99 +237,9957.4 +238,10028.34 +239,9116.35 +240,7887.2 +241,7834.83 +242,9642.74 +243,10280.58 +244,10078.4 +245,10073.61 +246,10003.2 +247,9118.71 +248,7867.15 +249,7706.2 +250,9307.51 +251,10177.7 +252,10128.95 +253,9875.15 +254,9458.46 +255,8380.69 +256,6949.05 +257,6865.79 +258,7279.95 +259,8303.9 +260,8496.02 +261,8301.57 +262,8225.98 +263,7459.24 +264,6522.9 +265,6568.94 +266,6814.09 +267,8089.19 +268,8528.56 +269,8405.26 +270,8523.33 +271,8035.33 +272,7408.95 +273,7387.75 +274,9143.89 +275,9950.46 +276,9911.56 +277,9790.61 +278,9864.23 +279,9037.73 +280,7948.06 +281,7928.89 +282,9748.58 +283,10397.39 +284,10046.4 +285,9815.11 +286,9974.98 +287,9100.47 +288,7870.69 +289,7768.99 +290,9584.94 +291,10336.78 +292,10223.83 +293,9987.23 +294,10014.99 +295,9154.91 +296,7987.72 +297,7956.16 +298,9774.57 +299,10346.71 +300,10211.6 +301,9990.27 +302,9955.66 +303,9087.98 +304,7885.62 +305,7743.95 +306,9471.17 +307,10124.43 +308,9824.57 +309,9487.28 +310,9538.73 +311,8546.96 +312,7184.99 +313,7091.29 +314,7503.93 +315,8594.01 +316,8625.41 +317,8285.93 +318,8410.12 +319,7569.38 +320,6587.37 +321,6608.06 +322,6866.41 +323,7957.3 +324,8152.75 +325,8107.23 +326,8376.68 +327,7770.63 +328,7157.15 +329,7045.77 +330,8922.02 +331,9832.24 +332,9902.56 +333,9724.6 +334,9546.94 +335,8660.46 +336,7623.38 +337,7472.82 +338,9331.28 +339,9997.65 +340,10018.43 +341,9801.22 +342,9694.05 +343,8810.11 +344,7674.88 +345,7529.68 +346,9291.4 +347,9994.88 +348,9950.14 +349,9797.7 +350,9555.77 +351,8692.37 +352,7503.26 +353,7322.4 +354,9114.26 +355,9722.25 +356,9661.61 +357,9338.01 +358,9383.15 +359,8584.84 +360,7437.03 +361,7306.01 +362,9077.49 +363,9592.55 +364,9283.87 +365,8940.21 +366,9082.7 +367,8209.05 +368,6854.17 +369,6858.01 +370,7468.59 +371,8348.55 +372,8041.81 +373,7614.71 +374,8017.15 +375,7350.07 +376,6428.87 +377,6471.13 +378,6795.79 +379,7784.03 +380,7655.33 +381,7439.5 +382,8144.89 +383,7647.65 +384,7141.55 +385,7053.39 +386,9047.72 +387,9583.17 +388,9307.59 +389,8947.79 +390,9270.42 +391,8527.15 +392,7422.68 +393,7276.51 +394,9184.63 +395,9623.37 +396,9315.09 +397,9114.87 +398,9318.55 +399,8481.13 +400,7397.42 +401,7173.29 +402,9025.49 +403,9579.6 +404,9468.46 +405,9295.5 +406,9362.69 +407,8550.53 +408,7403.49 +409,7193.04 +410,9092.14 +411,9566.34 +412,9497.75 +413,9455.3 +414,9347.3 +415,8463.76 +416,7216.64 +417,7062.19 +418,8738.58 +419,9504.77 +420,9606.36 +421,9323.93 +422,9252.67 +423,8299.12 +424,6989.04 +425,7007.65 +426,7555.23 +427,8483.62 +428,8393.63 +429,8000.78 +430,8323.01 +431,7665.03 +432,6733.62 +433,6819.01 +434,7041.54 +435,8149.99 +436,8277.53 +437,7918.38 +438,8303.07 +439,7782.41 +440,7218.49 +441,7190.94 +442,9051.27 +443,9526.2 +444,9309.22 +445,9004.07 +446,9218.97 +447,8424.8 +448,7274.81 +449,7091.24 +450,8869.67 +451,9342.83 +452,9244.64 +453,9057.26 +454,9126.53 +455,8272.52 +456,7170.46 +457,7062.82 +458,8854.62 +459,9217.3 +460,9025.92 +461,8676.84 +462,9039.47 +463,8345.76 +464,7175.64 +465,6991.94 +466,8744.52 +467,9030.84 +468,8822.65 +469,8609.74 +470,8838.02 +471,7988.45 +472,6840.87 +473,6666.64 +474,8389.76 +475,9163.71 +476,9161.74 +477,8920.51 +478,8717.33 +479,7805.33 +480,6441.43 +481,6345.25 +482,6814.31 +483,7987.08 +484,8172.73 +485,7931.36 +486,7885.18 +487,7158.26 +488,6185.32 +489,6145.57 +490,6337.43 +491,7481.22 +492,7626.48 +493,7466.61 +494,7873.1 +495,7347.22 +496,6701.65 +497,6554.0 +498,8340.94 +499,8737.96 +500,8763.02 +501,8688.26 +502,8804.7 +503,8042.7 +504,6965.82 +505,6771.96 +506,8557.69 +507,9162.54 +508,9208.22 +509,9056.3 +510,9044.66 +511,8231.98 +512,7099.89 +513,6883.2 +514,8685.55 +515,9077.98 +516,8922.57 +517,8584.6 +518,8872.03 +519,8098.83 +520,6968.13 +521,6738.66 +522,8486.96 +523,8971.02 +524,8840.46 +525,8786.4 +526,8788.39 +527,8069.53 +528,6871.94 +529,6682.69 +530,8414.4 +531,8996.76 +532,9008.52 +533,8743.04 +534,8585.67 +535,7771.05 +536,6440.29 +537,6370.61 +538,6752.49 +539,7897.61 +540,7968.56 +541,7633.22 +542,7619.69 +543,6840.32 +544,5922.38 +545,5922.73 +546,6091.67 +547,7342.32 +548,7674.76 +549,7545.66 +550,7587.09 +551,7012.77 +552,6305.49 +553,6296.08 +554,8182.14 +555,9114.07 +556,9229.58 +557,9228.08 +558,9241.16 +559,8357.39 +560,7234.34 +561,7026.95 +562,8703.36 +563,9233.49 +564,9301.47 +565,9091.24 +566,9130.0 +567,8442.52 +568,7196.88 +569,7005.72 +570,8770.38 +571,9423.65 +572,9286.38 +573,9123.09 +574,9080.72 +575,8405.8 +576,7195.67 +577,7049.58 +578,8701.98 +579,9340.75 +580,9361.18 +581,9285.25 +582,9163.19 +583,8443.27 +584,7199.02 +585,6977.17 +586,8572.75 +587,9179.26 +588,9211.03 +589,9085.83 +590,8837.04 +591,7834.81 +592,6428.34 +593,6381.94 +594,6821.32 +595,8032.2 +596,8121.94 +597,7953.88 +598,7800.59 +599,7032.51 +600,6135.81 +601,6140.81 +602,6323.34 +603,7361.33 +604,7205.24 +605,6951.21 +606,7508.81 +607,7177.21 +608,6643.08 +609,6601.99 +610,8387.78 +611,9117.62 +612,9239.03 +613,9043.68 +614,8913.28 +615,8274.9 +616,7230.39 +617,7039.68 +618,8769.12 +619,9206.58 +620,9262.75 +621,9099.7 +622,9031.64 +623,8480.81 +624,7284.39 +625,7088.16 +626,8783.13 +627,9166.37 +628,9195.09 +629,8829.25 +630,8869.12 +631,8413.58 +632,7289.74 +633,7111.13 +634,8798.8 +635,9142.0 +636,8986.75 +637,8625.3 +638,8733.82 +639,8226.35 +640,7049.33 +641,6904.27 +642,8518.29 +643,8897.86 +644,8749.39 +645,8377.66 +646,8342.88 +647,7720.02 +648,6292.23 +649,6200.14 +650,6641.84 +651,7463.17 +652,7304.82 +653,6849.25 +654,7031.28 +655,6656.1 +656,5734.38 +657,5683.64 +658,5874.18 +659,7158.03 +660,7343.56 +661,7201.39 +662,7498.27 +663,7104.97 +664,6539.98 +665,6397.16 +666,8139.2 +667,8883.34 +668,9155.15 +669,8880.06 +670,8765.07 +671,8223.32 +672,7023.94 +673,6801.78 +674,8493.95 +675,9137.25 +676,9250.43 +677,8991.9 +678,8940.04 +679,8240.57 +680,7051.73 +681,6839.84 +682,8524.02 +683,9074.06 +684,9140.88 +685,9004.32 +686,8826.24 +687,8270.16 +688,7225.7 +689,6787.0 +690,8469.82 +691,9173.76 +692,9274.65 +693,9054.66 +694,8781.08 +695,8205.0 +696,6929.6 +697,6730.89 +698,8397.01 +699,8857.51 +700,8781.8 +701,8353.04 +702,8155.01 +703,7709.25 +704,6271.71 +705,6245.93 +706,6725.19 +707,7490.55 +708,7318.37 +709,6821.75 +710,6953.84 +711,6774.88 +712,5817.81 +713,5920.2 +714,6167.69 +715,7188.8 +716,6965.47 +717,6627.19 +718,7215.14 +719,6995.6 +720,6447.28 +721,6381.13 +722,8570.54 +723,8610.84 +724,8622.24 +725,8273.59 +726,8460.67 +727,7551.69 +728,6867.6 +729,6769.01 +730,8751.92 +731,8547.76 +732,8463.79 +733,8114.93 +734,8281.83 +735,7315.01 +736,6660.67 +737,6534.82 +738,8428.91 +739,8397.98 +740,8350.78 +741,8057.67 +742,8163.74 +743,7247.59 +744,6684.58 +745,6570.94 +746,8473.78 +747,8445.77 +748,8498.55 +749,8226.39 +750,8305.94 +751,7333.33 +752,6620.19 +753,6486.23 +754,8453.49 +755,8386.57 +756,8401.23 +757,8089.45 +758,8065.59 +759,6959.02 +760,5904.6 +761,5946.33 +762,6667.72 +763,7565.9 +764,7296.42 +765,6923.53 +766,6895.03 +767,6388.0 +768,5499.45 +769,5657.67 +770,6207.61 +771,7130.37 +772,6849.58 +773,6746.14 +774,7078.22 +775,6836.99 +776,6389.75 +777,6289.86 +778,8480.16 +779,8510.55 +780,8575.83 +781,8270.43 +782,8288.98 +783,7326.23 +784,6595.48 +785,6489.58 +786,8488.8 +787,8450.02 +788,8530.9 +789,8225.34 +790,8379.22 +791,7417.44 +792,6754.86 +793,6648.7 +794,8781.78 +795,8755.68 +796,8782.26 +797,8483.58 +798,8561.06 +799,7548.49 +800,6826.85 +801,6764.08 +802,8860.82 +803,8992.19 +804,9013.23 +805,8711.31 +806,8634.3 +807,7704.79 +808,7035.92 +809,6954.25 +810,8975.52 +811,9211.06 +812,9170.29 +813,8889.11 +814,8677.52 +815,7395.82 +816,6366.21 +817,6436.55 +818,7239.37 +819,8180.58 +820,7930.53 +821,7648.05 +822,7479.94 +823,6870.93 +824,6000.31 +825,6084.37 +826,6631.3 +827,7866.54 +828,7613.19 +829,7404.13 +830,7477.4 +831,7189.65 +832,6782.68 +833,6736.66 +834,8892.12 +835,8815.12 +836,8785.35 +837,8361.56 +838,8452.55 +839,7698.89 +840,7072.4 +841,7015.12 +842,9029.15 +843,8784.63 +844,8757.42 +845,8382.34 +846,8506.31 +847,7758.49 +848,6996.47 +849,6947.7 +850,8970.37 +851,8811.03 +852,8750.64 +853,8480.66 +854,8475.02 +855,7730.26 +856,6970.6 +857,6834.69 +858,8707.9 +859,8670.38 +860,8584.72 +861,8141.6 +862,7834.83 +863,7123.65 +864,6039.0 +865,5991.14 +866,6550.96 +867,7145.39 +868,7030.56 +869,6717.16 +870,6572.19 +871,6302.96 +872,5455.92 +873,5548.3 +874,6158.69 +875,6895.06 +876,6714.57 +877,6422.63 +878,6272.32 +879,6077.81 +880,5254.24 +881,5345.45 +882,5754.3 +883,6680.71 +884,6369.86 +885,6150.04 +886,6099.43 +887,5985.78 +888,5141.01 +889,5184.88 +890,5648.87 +891,6136.56 +892,6101.98 +893,6096.26 +894,6319.57 +895,6230.83 +896,5850.72 +897,5793.42 +898,8010.23 +899,8345.77 +900,8526.21 +901,8473.48 +902,8405.39 +903,7286.59 +904,6575.5 +905,6396.79 +906,8408.13 +907,8450.26 +908,8388.46 +909,8068.34 +910,7944.42 +911,7221.14 +912,6506.24 +913,6366.02 +914,8149.17 +915,8280.15 +916,8386.51 +917,8102.42 +918,7886.06 +919,7214.58 +920,6453.96 +921,6230.71 +922,8036.01 +923,8243.1 +924,8319.6 +925,8044.14 +926,7723.51 +927,6783.09 +928,5815.25 +929,5738.04 +930,6379.89 +931,7487.69 +932,7270.0 +933,6854.3 +934,6627.59 +935,6293.98 +936,5450.72 +937,5457.39 +938,5915.97 +939,7123.66 +940,6855.85 +941,6751.55 +942,6947.05 +943,6838.34 +944,6303.51 +945,6215.38 +946,8436.45 +947,8870.14 +948,8964.55 +949,8670.08 +950,8375.66 +951,7340.76 +952,6615.4 +953,6541.29 +954,8455.21 +955,8678.6 +956,8606.33 +957,8100.9 +958,7496.18 +959,6903.6 +960,5995.02 +961,6030.1 +962,6479.11 +963,7417.5 +964,7244.61 +965,6993.85 +966,6934.18 +967,6746.41 +968,6243.97 +969,6098.16 +970,8065.35 +971,8217.28 +972,8279.59 +973,7934.96 +974,7795.68 +975,7093.58 +976,6295.65 +977,6140.83 +978,8051.79 +979,8461.11 +980,8415.65 +981,8078.22 +982,7753.95 +983,6858.81 +984,5806.25 +985,5839.75 +986,6457.57 +987,7346.57 +988,7187.7 +989,7136.62 +990,6913.07 +991,6443.54 +992,5626.02 +993,5719.34 +994,6169.95 +995,7288.81 +996,7008.12 +997,6986.63 +998,7029.62 +999,6906.69 +1000,6504.61 +1001,6466.3 +1002,8455.86 +1003,8622.39 +1004,8732.77 +1005,8430.24 +1006,8220.56 +1007,7468.03 +1008,6822.32 +1009,6764.27 +1010,8528.11 +1011,8549.99 +1012,8630.48 +1013,8386.0 +1014,8085.81 +1015,7262.97 +1016,6295.46 +1017,6417.85 +1018,6855.54 +1019,7456.41 +1020,7205.11 +1021,6936.28 +1022,7085.88 +1023,6830.44 +1024,6337.53 +1025,6192.0 +1026,8415.52 +1027,8686.44 +1028,8619.22 +1029,8194.64 +1030,7930.3 +1031,7159.13 +1032,6401.14 +1033,6259.79 +1034,8005.44 +1035,8267.96 +1036,8257.32 +1037,7869.58 +1038,7435.39 +1039,6788.59 +1040,5812.89 +1041,5813.77 +1042,6413.38 +1043,7221.18 +1044,7020.74 +1045,6846.61 +1046,6672.09 +1047,6277.87 +1048,5461.39 +1049,5439.56 +1050,5906.95 +1051,7254.57 +1052,7022.55 +1053,6947.32 +1054,7014.58 +1055,6809.37 +1056,6359.55 +1057,6299.59 +1058,8262.67 +1059,8449.76 +1060,8566.15 +1061,8348.64 +1062,8129.66 +1063,7395.56 +1064,6697.78 +1065,6656.36 +1066,8403.09 +1067,8631.49 +1068,8763.05 +1069,8588.45 +1070,8394.23 +1071,7495.82 +1072,6785.31 +1073,6668.05 +1074,8638.59 +1075,9034.65 +1076,9136.86 +1077,8917.9 +1078,8603.11 +1079,7596.29 +1080,6806.07 +1081,6674.52 +1082,8671.3 +1083,9020.26 +1084,9020.07 +1085,8684.4 +1086,8380.49 +1087,7581.45 +1088,6770.51 +1089,6610.96 +1090,8521.71 +1091,8567.7 +1092,8501.78 +1093,8079.09 +1094,7642.58 +1095,6997.54 +1096,5913.46 +1097,5887.51 +1098,6502.52 +1099,7266.78 +1100,6967.57 +1101,6588.03 +1102,6347.17 +1103,6296.19 +1104,5432.85 +1105,5434.79 +1106,5772.91 +1107,6818.22 +1108,6622.32 +1109,6556.67 +1110,6638.88 +1111,6624.12 +1112,6147.84 +1113,5999.32 +1114,8099.21 +1115,8490.97 +1116,8622.67 +1117,8181.12 +1118,7896.2 +1119,7145.82 +1120,6401.02 +1121,6217.6 +1122,8079.15 +1123,8245.83 +1124,8397.14 +1125,8089.36 +1126,7808.67 +1127,7211.18 +1128,6460.46 +1129,6252.34 +1130,8249.05 +1131,8691.75 +1132,8760.73 +1133,8508.65 +1134,8111.76 +1135,7167.93 +1136,6423.94 +1137,6305.63 +1138,8342.59 +1139,8557.91 +1140,8589.07 +1141,8167.01 +1142,7847.27 +1143,7228.57 +1144,6492.17 +1145,6268.62 +1146,8119.48 +1147,8248.55 +1148,8158.87 +1149,7789.14 +1150,7299.88 +1151,6703.68 +1152,5632.45 +1153,5559.31 +1154,6134.15 +1155,6931.29 +1156,6765.48 +1157,6468.29 +1158,6252.98 +1159,6052.11 +1160,5235.67 +1161,5116.4 +1162,5536.62 +1163,6682.68 +1164,6545.05 +1165,6508.92 +1166,6469.06 +1167,6564.81 +1168,6003.41 +1169,5830.98 +1170,7854.39 +1171,8229.88 +1172,8353.6 +1173,8066.26 +1174,7850.37 +1175,7025.94 +1176,6240.72 +1177,6029.26 +1178,7845.78 +1179,8298.03 +1180,8455.2 +1181,8258.18 +1182,7845.47 +1183,7077.9 +1184,6261.36 +1185,6068.22 +1186,8064.33 +1187,8482.68 +1188,8496.42 +1189,8063.07 +1190,7744.48 +1191,7139.68 +1192,6299.16 +1193,6143.95 +1194,8037.72 +1195,8261.04 +1196,8288.8 +1197,7968.18 +1198,7607.78 +1199,7092.09 +1200,6219.77 +1201,6013.32 +1202,7956.6 +1203,8136.32 +1204,8107.21 +1205,7764.2 +1206,7258.46 +1207,6647.74 +1208,5572.57 +1209,5479.15 +1210,6041.7 +1211,6989.76 +1212,6818.0 +1213,6483.86 +1214,6204.51 +1215,6123.9 +1216,5204.02 +1217,5097.42 +1218,5498.35 +1219,6720.45 +1220,6624.74 +1221,6501.5 +1222,6440.33 +1223,6597.94 +1224,5940.6 +1225,5727.3 +1226,7769.48 +1227,8252.14 +1228,8449.09 +1229,8151.29 +1230,7766.89 +1231,7123.85 +1232,6271.58 +1233,5955.94 +1234,7869.26 +1235,8403.24 +1236,8532.4 +1237,8213.75 +1238,7775.07 +1239,7211.97 +1240,6270.0 +1241,5949.67 +1242,7927.02 +1243,8472.54 +1244,8601.28 +1245,8220.78 +1246,7770.38 +1247,7219.8 +1248,6258.27 +1249,5997.95 +1250,7939.62 +1251,8373.45 +1252,8474.19 +1253,8202.28 +1254,7734.98 +1255,7017.76 +1256,6179.2 +1257,5937.35 +1258,7854.83 +1259,8272.31 +1260,8208.3 +1261,7911.83 +1262,7294.57 +1263,6624.57 +1264,5498.04 +1265,5344.6 +1266,5982.68 +1267,6961.75 +1268,6789.77 +1269,6477.18 +1270,6055.56 +1271,5954.4 +1272,5105.42 +1273,4890.67 +1274,5353.81 +1275,6616.78 +1276,6550.39 +1277,6461.57 +1278,6465.28 +1279,6571.48 +1280,5969.78 +1281,5676.72 +1282,7725.66 +1283,8232.64 +1284,8499.61 +1285,8235.08 +1286,7832.12 +1287,7130.18 +1288,6234.24 +1289,5999.4 +1290,7949.17 +1291,8441.17 +1292,8685.37 +1293,8440.79 +1294,7938.51 +1295,7367.72 +1296,6427.56 +1297,6128.13 +1298,8045.79 +1299,8568.02 +1300,8729.03 +1301,8469.63 +1302,7947.93 +1303,7303.03 +1304,6362.72 +1305,6093.24 +1306,7987.67 +1307,8497.91 +1308,8662.59 +1309,8306.99 +1310,7803.05 +1311,7206.67 +1312,6306.71 +1313,5959.83 +1314,7890.74 +1315,8448.87 +1316,8513.77 +1317,8190.13 +1318,7525.77 +1319,6804.78 +1320,5716.27 +1321,5478.0 +1322,6142.49 +1323,7167.67 +1324,7131.77 +1325,6893.83 +1326,6399.46 +1327,6077.43 +1328,5235.78 +1329,5012.76 +1330,5435.31 +1331,6602.27 +1332,6539.69 +1333,6461.59 +1334,6451.46 +1335,6511.29 +1336,5931.24 +1337,5667.72 +1338,7633.52 +1339,8143.28 +1340,8370.57 +1341,8069.47 +1342,7623.66 +1343,7075.93 +1344,6189.98 +1345,5873.57 +1346,7742.85 +1347,8278.31 +1348,8431.64 +1349,8113.32 +1350,7691.92 +1351,7131.88 +1352,6244.54 +1353,5977.03 +1354,7886.1 +1355,8417.43 +1356,8619.08 +1357,8207.14 +1358,7736.12 +1359,7122.65 +1360,6272.1 +1361,6008.3 +1362,7852.94 +1363,8289.0 +1364,8494.4 +1365,8245.3 +1366,7688.54 +1367,7065.38 +1368,6236.03 +1369,5957.95 +1370,7824.07 +1371,8352.9 +1372,8386.1 +1373,8069.35 +1374,7391.95 +1375,6680.72 +1376,5624.88 +1377,5434.37 +1378,6026.79 +1379,7034.72 +1380,7024.89 +1381,6665.33 +1382,6231.8 +1383,6025.36 +1384,5211.98 +1385,5064.79 +1386,5428.28 +1387,6701.64 +1388,6695.36 +1389,6567.79 +1390,6503.0 +1391,6594.71 +1392,5949.33 +1393,5694.7 +1394,7638.14 +1395,8187.07 +1396,8500.41 +1397,8239.81 +1398,7748.36 +1399,7227.56 +1400,6295.12 +1401,5994.99 +1402,7911.66 +1403,8481.96 +1404,8770.91 +1405,8462.08 +1406,7931.56 +1407,7407.44 +1408,6417.61 +1409,6147.87 +1410,8050.96 +1411,8677.83 +1412,8839.39 +1413,8550.76 +1414,8094.3 +1415,7554.87 +1416,6590.14 +1417,6300.82 +1418,8228.09 +1419,8660.91 +1420,8707.58 +1421,8362.99 +1422,7872.17 +1423,7273.92 +1424,6329.5 +1425,6004.86 +1426,7748.21 +1427,8156.65 +1428,8144.43 +1429,7836.68 +1430,7197.95 +1431,6543.23 +1432,5547.95 +1433,5310.5 +1434,5878.88 +1435,6898.01 +1436,6786.53 +1437,6568.62 +1438,6138.57 +1439,5991.19 +1440,5150.39 +1441,4952.07 +1442,5377.56 +1443,6638.15 +1444,6719.13 +1445,6659.64 +1446,6575.79 +1447,6715.1 +1448,6024.94 +1449,5783.54 +1450,7655.01 +1451,8563.3 +1452,8748.65 +1453,8225.25 +1454,7708.24 +1455,7087.93 +1456,6257.83 +1457,6023.37 +1458,7760.88 +1459,8237.13 +1460,8373.41 +1461,8038.39 +1462,7576.14 +1463,7054.7 +1464,6150.93 +1465,5912.93 +1466,7550.7 +1467,8032.08 +1468,8130.84 +1469,7874.72 +1470,7447.95 +1471,6965.91 +1472,6112.84 +1473,5852.01 +1474,7442.43 +1475,7949.59 +1476,8051.01 +1477,7653.89 +1478,7105.52 +1479,6421.26 +1480,5522.73 +1481,5139.17 +1482,5654.37 +1483,6622.33 +1484,6642.09 +1485,6478.23 +1486,6097.63 +1487,5862.54 +1488,5035.46 +1489,4871.31 +1490,5367.52 +1491,6452.58 +1492,6505.27 +1493,6384.83 +1494,6087.65 +1495,5848.73 +1496,5004.37 +1497,4874.78 +1498,5284.76 +1499,6414.33 +1500,6332.64 +1501,6253.28 +1502,6228.75 +1503,6306.22 +1504,5682.47 +1505,5531.98 +1506,7265.03 +1507,7830.37 +1508,8043.48 +1509,7744.56 +1510,7292.16 +1511,6777.65 +1512,6064.55 +1513,5805.4 +1514,7390.02 +1515,7881.45 +1516,8070.25 +1517,7763.76 +1518,7324.12 +1519,6723.86 +1520,6010.1 +1521,5819.67 +1522,7378.84 +1523,7883.8 +1524,8017.3 +1525,7673.55 +1526,7323.94 +1527,6822.92 +1528,6024.32 +1529,5779.68 +1530,7397.4 +1531,7911.62 +1532,8007.64 +1533,7731.33 +1534,7312.65 +1535,6793.21 +1536,5986.04 +1537,5702.0 +1538,7384.91 +1539,7961.18 +1540,7960.74 +1541,7580.76 +1542,7026.83 +1543,6270.13 +1544,5343.4 +1545,5223.17 +1546,5679.51 +1547,6841.8 +1548,6706.57 +1549,6424.17 +1550,5991.95 +1551,5752.94 +1552,4972.22 +1553,4853.96 +1554,5188.35 +1555,6483.68 +1556,6379.75 +1557,6269.35 +1558,6174.85 +1559,6231.34 +1560,5679.17 +1561,5504.31 +1562,7280.39 +1563,7731.88 +1564,7930.45 +1565,7620.99 +1566,7269.57 +1567,6800.45 +1568,5950.82 +1569,5716.44 +1570,7377.56 +1571,7901.71 +1572,7990.09 +1573,7617.46 +1574,7287.96 +1575,6837.55 +1576,5981.03 +1577,5753.31 +1578,7402.97 +1579,7855.67 +1580,8008.16 +1581,7724.1 +1582,7357.84 +1583,6861.18 +1584,6040.39 +1585,5801.13 +1586,7445.96 +1587,7952.06 +1588,8078.59 +1589,7763.64 +1590,7328.57 +1591,6865.72 +1592,6036.59 +1593,5770.82 +1594,7403.83 +1595,7961.93 +1596,7971.06 +1597,7585.69 +1598,7004.97 +1599,6283.03 +1600,5313.12 +1601,5241.75 +1602,5779.04 +1603,6794.32 +1604,6717.48 +1605,6555.81 +1606,6201.28 +1607,5969.15 +1608,5105.18 +1609,4979.97 +1610,5265.52 +1611,6521.78 +1612,6453.73 +1613,6363.49 +1614,6267.26 +1615,6201.56 +1616,5577.53 +1617,5376.76 +1618,7104.62 +1619,7789.34 +1620,7968.08 +1621,7630.04 +1622,7208.81 +1623,6702.16 +1624,5858.66 +1625,5625.57 +1626,7304.29 +1627,7989.87 +1628,8089.86 +1629,7816.06 +1630,7301.9 +1631,6770.41 +1632,5918.85 +1633,5642.86 +1634,7241.62 +1635,7924.45 +1636,8087.67 +1637,7819.82 +1638,7328.77 +1639,6859.49 +1640,5960.13 +1641,5700.75 +1642,7254.84 +1643,7968.26 +1644,8157.0 +1645,7817.54 +1646,7358.99 +1647,6867.95 +1648,5965.03 +1649,5668.51 +1650,7136.68 +1651,7882.42 +1652,7959.64 +1653,7588.36 +1654,6974.87 +1655,6375.58 +1656,5307.53 +1657,5156.91 +1658,5635.1 +1659,6635.99 +1660,6551.0 +1661,6384.43 +1662,5991.57 +1663,5737.42 +1664,4905.72 +1665,4767.03 +1666,5115.54 +1667,6392.82 +1668,6365.44 +1669,6263.88 +1670,6149.16 +1671,5998.77 +1672,5387.69 +1673,5143.69 +1674,6803.33 +1675,7578.67 +1676,7826.79 +1677,7521.4 +1678,6949.08 +1679,6345.29 +1680,5564.08 +1681,5329.58 +1682,6986.2 +1683,7673.67 +1684,7849.66 +1685,7520.37 +1686,6982.59 +1687,6481.78 +1688,5619.1 +1689,5390.12 +1690,6895.14 +1691,7654.53 +1692,7773.46 +1693,7371.11 +1694,6914.99 +1695,6248.75 +1696,5521.9 +1697,5304.45 +1698,6800.16 +1699,7458.16 +1700,7604.81 +1701,7245.79 +1702,6724.89 +1703,6283.26 +1704,5492.27 +1705,5237.28 +1706,6682.07 +1707,7388.16 +1708,7491.72 +1709,7155.26 +1710,6570.01 +1711,5930.33 +1712,5023.13 +1713,4920.0 +1714,5414.58 +1715,6440.77 +1716,6329.55 +1717,6086.95 +1718,5717.55 +1719,5448.11 +1720,4699.55 +1721,4612.65 +1722,4964.55 +1723,6218.91 +1724,6173.23 +1725,6065.46 +1726,5930.53 +1727,5914.75 +1728,5344.89 +1729,5160.86 +1730,6863.7 +1731,7553.05 +1732,7827.82 +1733,7468.31 +1734,7070.45 +1735,6457.28 +1736,5680.08 +1737,5509.86 +1738,7172.1 +1739,7864.51 +1740,8072.15 +1741,7720.1 +1742,7257.04 +1743,6614.33 +1744,5837.7 +1745,5628.56 +1746,7251.62 +1747,7992.9 +1748,8112.51 +1749,7770.27 +1750,7348.43 +1751,6538.66 +1752,5796.75 +1753,5607.15 +1754,7187.66 +1755,7827.99 +1756,7938.34 +1757,7616.84 +1758,7149.45 +1759,6632.98 +1760,5834.85 +1761,5588.59 +1762,7131.3 +1763,7844.89 +1764,7883.19 +1765,7563.22 +1766,6986.08 +1767,6352.76 +1768,5332.56 +1769,5250.89 +1770,5723.58 +1771,6888.96 +1772,6831.3 +1773,6554.83 +1774,6130.81 +1775,5729.7 +1776,5003.51 +1777,4910.15 +1778,5311.36 +1779,6601.2 +1780,6503.37 +1781,6412.3 +1782,6321.3 +1783,6382.79 +1784,5746.02 +1785,5575.12 +1786,7392.23 +1787,8099.06 +1788,8225.87 +1789,7890.68 +1790,7480.44 +1791,6674.09 +1792,5967.63 +1793,5764.63 +1794,7439.23 +1795,8096.62 +1796,8174.97 +1797,7812.98 +1798,7400.47 +1799,6756.82 +1800,5945.06 +1801,5787.03 +1802,7456.57 +1803,7956.69 +1804,8047.94 +1805,7731.56 +1806,7314.95 +1807,6758.55 +1808,5990.08 +1809,5780.74 +1810,7411.66 +1811,7946.77 +1812,8048.47 +1813,7733.79 +1814,7331.6 +1815,6673.43 +1816,5971.22 +1817,5736.05 +1818,7422.51 +1819,8027.29 +1820,7974.12 +1821,7573.36 +1822,7024.78 +1823,6291.58 +1824,5327.27 +1825,5232.7 +1826,5776.65 +1827,6729.86 +1828,6626.01 +1829,6412.1 +1830,6128.8 +1831,5781.54 +1832,5049.74 +1833,4962.86 +1834,5338.39 +1835,6581.97 +1836,6592.4 +1837,6561.8 +1838,6475.41 +1839,6438.22 +1840,5897.88 +1841,5671.84 +1842,7554.97 +1843,8195.4 +1844,8359.83 +1845,8015.74 +1846,7613.39 +1847,6911.12 +1848,6179.58 +1849,5987.99 +1850,7763.54 +1851,8300.29 +1852,8446.7 +1853,8131.58 +1854,7898.38 +1855,6953.93 +1856,6096.5 +1857,5904.53 +1858,7657.12 +1859,8206.4 +1860,8232.37 +1861,7846.3 +1862,7417.63 +1863,6800.2 +1864,5991.11 +1865,5829.86 +1866,7547.33 +1867,8009.4 +1868,8137.11 +1869,7800.52 +1870,7416.03 +1871,6804.68 +1872,6002.51 +1873,5761.87 +1874,7464.32 +1875,8024.01 +1876,8064.46 +1877,7756.4 +1878,7125.05 +1879,6363.49 +1880,5425.15 +1881,5356.2 +1882,5876.34 +1883,6913.09 +1884,6810.46 +1885,6639.98 +1886,6232.54 +1887,5919.84 +1888,5140.34 +1889,5022.85 +1890,5373.16 +1891,6656.1 +1892,6702.93 +1893,6593.48 +1894,6561.55 +1895,6468.14 +1896,5920.03 +1897,5747.04 +1898,7640.54 +1899,8271.75 +1900,8608.91 +1901,8258.94 +1902,7890.57 +1903,7018.83 +1904,6219.15 +1905,6045.94 +1906,7835.07 +1907,8496.82 +1908,8633.16 +1909,8306.62 +1910,7872.93 +1911,7176.41 +1912,6337.36 +1913,6101.09 +1914,7807.49 +1915,8499.43 +1916,8699.91 +1917,8356.8 +1918,7965.89 +1919,7133.76 +1920,6308.43 +1921,6090.96 +1922,7837.66 +1923,8540.55 +1924,8747.87 +1925,8329.35 +1926,7495.37 +1927,7047.87 +1928,6260.7 +1929,6060.16 +1930,7716.04 +1931,8409.72 +1932,8479.28 +1933,8107.59 +1934,7458.68 +1935,6504.96 +1936,5598.11 +1937,5372.41 +1938,5947.78 +1939,7154.0 +1940,7094.88 +1941,6840.89 +1942,6398.42 +1943,6036.16 +1944,5246.57 +1945,5094.89 +1946,5530.22 +1947,6898.03 +1948,6888.83 +1949,6704.95 +1950,6745.42 +1951,6437.48 +1952,5960.71 +1953,5808.26 +1954,7868.09 +1955,8283.98 +1956,8415.43 +1957,7989.91 +1958,7704.97 +1959,6716.84 +1960,6034.76 +1961,5895.92 +1962,7808.47 +1963,8093.33 +1964,8114.55 +1965,7902.77 +1966,7677.03 +1967,6839.06 +1968,6091.56 +1969,5939.36 +1970,7836.28 +1971,8074.06 +1972,8271.21 +1973,7964.2 +1974,7685.39 +1975,6883.43 +1976,6066.39 +1977,5916.0 +1978,7784.64 +1979,8157.24 +1980,8250.15 +1981,8003.06 +1982,7736.53 +1983,6757.44 +1984,6056.63 +1985,5879.91 +1986,7851.88 +1987,8092.13 +1988,7991.83 +1989,7695.15 +1990,7457.54 +1991,6386.54 +1992,5484.69 +1993,5396.68 +1994,5929.55 +1995,7085.07 +1996,6897.7 +1997,6661.67 +1998,6472.2 +1999,5848.68 +2000,5088.32 +2001,5046.93 +2002,5520.72 +2003,6776.76 +2004,6618.68 +2005,6526.77 +2006,6811.56 +2007,6306.87 +2008,5879.74 +2009,5747.77 +2010,7920.92 +2011,8322.7 +2012,8383.81 +2013,8117.2 +2014,8062.5 +2015,6875.42 +2016,6147.43 +2017,6049.9 +2018,8032.6 +2019,8241.91 +2020,8174.84 +2021,7919.56 +2022,7951.66 +2023,6931.37 +2024,6201.13 +2025,6039.7 +2026,8056.17 +2027,8263.32 +2028,8268.48 +2029,8028.51 +2030,8013.28 +2031,7019.4 +2032,6292.34 +2033,6071.25 +2034,8035.97 +2035,8240.18 +2036,8293.18 +2037,8041.15 +2038,7902.3 +2039,6939.8 +2040,6256.93 +2041,6028.94 +2042,7932.51 +2043,8174.19 +2044,8138.74 +2045,7866.32 +2046,7636.21 +2047,6435.34 +2048,5510.93 +2049,5465.73 +2050,6080.4 +2051,7059.61 +2052,6778.11 +2053,6520.63 +2054,6516.89 +2055,5904.72 +2056,5156.35 +2057,5207.35 +2058,5618.59 +2059,6749.64 +2060,6593.97 +2061,6509.85 +2062,6845.41 +2063,6435.92 +2064,5995.61 +2065,5870.57 +2066,7875.65 +2067,8195.93 +2068,8302.14 +2069,8138.67 +2070,8260.64 +2071,6980.31 +2072,6278.69 +2073,6149.17 +2074,8192.56 +2075,8343.78 +2076,8293.45 +2077,8059.04 +2078,8236.56 +2079,7036.93 +2080,6323.84 +2081,6138.84 +2082,8096.32 +2083,8230.34 +2084,8249.57 +2085,8038.22 +2086,8259.02 +2087,7144.63 +2088,6417.68 +2089,6276.92 +2090,8322.85 +2091,8345.59 +2092,8397.24 +2093,8152.43 +2094,8407.03 +2095,7185.75 +2096,6481.6 +2097,6363.44 +2098,8357.21 +2099,8427.72 +2100,8206.33 +2101,7987.61 +2102,8071.1 +2103,6844.81 +2104,5861.48 +2105,5886.19 +2106,6552.01 +2107,7243.04 +2108,6892.71 +2109,6658.84 +2110,6911.38 +2111,6244.35 +2112,5374.06 +2113,5395.6 +2114,5876.02 +2115,6874.58 +2116,6647.78 +2117,6570.37 +2118,7230.64 +2119,6637.18 +2120,6219.95 +2121,6126.37 +2122,8244.63 +2123,8372.57 +2124,8405.65 +2125,8227.14 +2126,8514.79 +2127,7199.03 +2128,6463.11 +2129,6258.59 +2130,8320.66 +2131,8471.63 +2132,8426.58 +2133,8148.48 +2134,8413.25 +2135,7042.06 +2136,6392.1 +2137,6214.67 +2138,8286.34 +2139,8536.74 +2140,8576.81 +2141,8265.21 +2142,8436.99 +2143,7145.64 +2144,6430.83 +2145,6233.68 +2146,8221.68 +2147,8345.68 +2148,8378.09 +2149,8118.11 +2150,8385.4 +2151,7138.07 +2152,6380.55 +2153,6172.8 +2154,8187.01 +2155,8374.28 +2156,8313.56 +2157,8081.41 +2158,8082.85 +2159,6630.04 +2160,5638.28 +2161,5556.09 +2162,6116.72 +2163,7065.68 +2164,6861.55 +2165,6567.08 +2166,6851.93 +2167,6007.78 +2168,5284.01 +2169,5300.22 +2170,5750.27 +2171,6882.9 +2172,6695.31 +2173,6530.51 +2174,7246.42 +2175,6546.88 +2176,6071.24 +2177,5946.68 +2178,8093.11 +2179,8294.21 +2180,8214.94 +2181,8132.81 +2182,8334.51 +2183,7006.9 +2184,6344.02 +2185,6193.78 +2186,8289.97 +2187,8239.96 +2188,8215.22 +2189,8140.98 +2190,8407.41 +2191,7098.41 +2192,6391.92 +2193,6229.27 +2194,8358.57 +2195,8587.68 +2196,8684.25 +2197,8632.63 +2198,8681.86 +2199,7337.15 +2200,6553.05 +2201,6463.64 +2202,8665.15 +2203,8744.77 +2204,8767.67 +2205,8566.94 +2206,8740.52 +2207,7342.08 +2208,6625.1 +2209,6470.57 +2210,8620.62 +2211,8739.34 +2212,8555.2 +2213,8352.08 +2214,8412.99 +2215,6875.89 +2216,5880.85 +2217,5882.05 +2218,6677.68 +2219,7791.11 +2220,7616.82 +2221,7444.91 +2222,7360.77 +2223,6446.77 +2224,5561.05 +2225,5627.57 +2226,6178.45 +2227,7373.49 +2228,7102.12 +2229,7065.22 +2230,7718.28 +2231,6996.24 +2232,6526.77 +2233,6481.5 +2234,8792.85 +2235,8812.62 +2236,8819.35 +2237,8498.28 +2238,9021.48 +2239,7524.59 +2240,6895.26 +2241,6807.24 +2242,8851.99 +2243,9186.86 +2244,9166.23 +2245,9045.67 +2246,8831.63 +2247,7414.46 +2248,6792.36 +2249,6689.66 +2250,8847.21 +2251,8881.61 +2252,8882.96 +2253,8733.78 +2254,8746.7 +2255,7346.94 +2256,6731.39 +2257,6635.98 +2258,8642.48 +2259,8878.76 +2260,8934.84 +2261,8717.72 +2262,8919.51 +2263,7491.25 +2264,6765.65 +2265,6620.59 +2266,8798.51 +2267,8626.81 +2268,8401.06 +2269,8213.76 +2270,8324.03 +2271,6833.84 +2272,5791.05 +2273,5860.69 +2274,6615.16 +2275,7397.45 +2276,7052.59 +2277,6693.83 +2278,7007.44 +2279,6132.83 +2280,5342.77 +2281,5380.07 +2282,5863.07 +2283,6941.03 +2284,6691.57 +2285,6543.64 +2286,7314.81 +2287,6561.46 +2288,6180.67 +2289,6081.34 +2290,8387.46 +2291,8370.58 +2292,8402.0 +2293,8152.85 +2294,8527.66 +2295,7016.39 +2296,6311.58 +2297,6203.68 +2298,8477.49 +2299,8482.19 +2300,8393.01 +2301,8128.81 +2302,8478.51 +2303,7055.79 +2304,6367.98 +2305,6300.93 +2306,8553.66 +2307,8761.51 +2308,8646.46 +2309,8337.96 +2310,8665.31 +2311,7276.77 +2312,6507.21 +2313,6428.23 +2314,8626.4 +2315,8477.52 +2316,8381.02 +2317,8136.79 +2318,8605.4 +2319,7165.49 +2320,6437.06 +2321,6332.14 +2322,8480.51 +2323,8575.53 +2324,8386.63 +2325,8162.59 +2326,8292.71 +2327,6828.61 +2328,5800.05 +2329,5863.71 +2330,6607.57 +2331,7552.29 +2332,7205.87 +2333,7008.45 +2334,7243.44 +2335,6305.54 +2336,5415.3 +2337,5462.74 +2338,5988.09 +2339,7176.45 +2340,6935.25 +2341,6728.09 +2342,7381.97 +2343,6595.02 +2344,6202.68 +2345,6158.04 +2346,8515.05 +2347,8525.56 +2348,8432.64 +2349,8209.37 +2350,8590.46 +2351,7173.94 +2352,6552.66 +2353,6424.75 +2354,8735.56 +2355,8670.67 +2356,8582.9 +2357,8366.76 +2358,8697.84 +2359,7275.88 +2360,6541.32 +2361,6390.1 +2362,8685.08 +2363,8655.02 +2364,8604.66 +2365,8414.52 +2366,8697.23 +2367,7217.78 +2368,6510.07 +2369,6335.37 +2370,8648.82 +2371,8563.4 +2372,8465.82 +2373,8245.3 +2374,8581.19 +2375,7215.89 +2376,6508.64 +2377,6389.8 +2378,8634.94 +2379,8550.26 +2380,8356.5 +2381,8130.2 +2382,8279.54 +2383,6832.81 +2384,5842.53 +2385,5871.92 +2386,6693.35 +2387,7389.25 +2388,6988.33 +2389,6737.33 +2390,7154.91 +2391,6228.67 +2392,5454.04 +2393,5455.75 +2394,5864.18 +2395,6744.05 +2396,6590.53 +2397,6605.96 +2398,7136.82 +2399,6352.06 +2400,5552.75 +2401,5531.01 +2402,6039.09 +2403,6970.1 +2404,7102.64 +2405,6948.94 +2406,7507.54 +2407,6860.79 +2408,6330.1 +2409,6247.78 +2410,8198.06 +2411,9049.43 +2412,8881.32 +2413,8774.25 +2414,8973.6 +2415,8106.99 +2416,6804.89 +2417,6667.74 +2418,8506.64 +2419,8945.03 +2420,9041.29 +2421,8842.28 +2422,9236.48 +2423,8303.91 +2424,7010.12 +2425,6875.51 +2426,8865.41 +2427,9433.59 +2428,9264.64 +2429,9042.35 +2430,9250.79 +2431,8453.3 +2432,7172.07 +2433,7010.59 +2434,8856.25 +2435,9427.88 +2436,9134.33 +2437,8978.04 +2438,8885.64 +2439,7818.95 +2440,6316.56 +2441,6260.08 +2442,6712.36 +2443,7848.69 +2444,8020.63 +2445,7722.4 +2446,7675.06 +2447,6819.99 +2448,5806.36 +2449,5765.85 +2450,5997.04 +2451,7210.86 +2452,7506.85 +2453,7344.21 +2454,7570.01 +2455,6929.18 +2456,6347.05 +2457,6226.13 +2458,8221.81 +2459,9015.09 +2460,9017.27 +2461,8781.66 +2462,8875.13 +2463,7896.5 +2464,6620.38 +2465,6447.36 +2466,8443.76 +2467,9126.94 +2468,9172.22 +2469,9058.95 +2470,8978.37 +2471,8053.08 +2472,6820.88 +2473,6622.96 +2474,8477.94 +2475,9015.82 +2476,8934.69 +2477,8877.86 +2478,8954.77 +2479,8003.16 +2480,6826.21 +2481,6654.97 +2482,8533.65 +2483,9055.32 +2484,8931.62 +2485,8825.13 +2486,8979.76 +2487,8046.49 +2488,6779.47 +2489,6570.53 +2490,8453.95 +2491,9125.09 +2492,9080.98 +2493,8883.68 +2494,8701.26 +2495,7604.9 +2496,6236.24 +2497,6149.54 +2498,6601.5 +2499,7796.73 +2500,8099.86 +2501,7841.28 +2502,7642.89 +2503,6928.57 +2504,6050.51 +2505,6080.35 +2506,6361.79 +2507,7573.88 +2508,7771.37 +2509,7796.99 +2510,7973.13 +2511,7398.44 +2512,6808.07 +2513,6720.22 +2514,8813.96 +2515,9574.97 +2516,9639.49 +2517,9632.74 +2518,9342.57 +2519,8363.75 +2520,7119.7 +2521,6911.67 +2522,8849.79 +2523,9616.75 +2524,9668.7 +2525,9721.25 +2526,9327.42 +2527,8447.06 +2528,7117.31 +2529,6976.96 +2530,8836.72 +2531,9692.02 +2532,9666.89 +2533,9697.07 +2534,9361.74 +2535,8430.25 +2536,7139.4 +2537,7065.66 +2538,8954.53 +2539,9549.58 +2540,9311.3 +2541,9381.95 +2542,9399.43 +2543,8434.54 +2544,7129.89 +2545,6961.9 +2546,8770.55 +2547,9318.94 +2548,9200.82 +2549,9137.92 +2550,8787.7 +2551,7704.62 +2552,6344.41 +2553,6271.95 +2554,6639.72 +2555,7595.9 +2556,7749.85 +2557,7531.51 +2558,7658.89 +2559,6817.75 +2560,5926.02 +2561,5847.09 +2562,6004.89 +2563,7084.95 +2564,7352.57 +2565,7178.26 +2566,7600.45 +2567,6939.0 +2568,6336.99 +2569,6186.79 +2570,8179.12 +2571,9096.55 +2572,9285.83 +2573,9081.79 +2574,9081.31 +2575,8136.78 +2576,6996.93 +2577,6849.88 +2578,8863.48 +2579,9342.43 +2580,9258.0 +2581,9169.88 +2582,9112.69 +2583,8207.62 +2584,6976.47 +2585,6780.8 +2586,8703.29 +2587,9355.21 +2588,9293.82 +2589,9340.89 +2590,9194.84 +2591,8172.1 +2592,6970.81 +2593,6748.56 +2594,8556.25 +2595,9274.97 +2596,9374.61 +2597,9322.66 +2598,9026.8 +2599,8103.92 +2600,6870.26 +2601,6674.44 +2602,8507.91 +2603,9235.86 +2604,9210.5 +2605,9135.38 +2606,8819.38 +2607,7701.26 +2608,6325.89 +2609,6260.16 +2610,6721.68 +2611,7685.78333333 +2612,8073.46 +2613,7845.25 +2614,7718.01 +2615,6959.11 +2616,5956.56 +2617,5944.92 +2618,6184.27 +2619,7356.53 +2620,7678.91 +2621,7668.25 +2622,7750.44 +2623,7157.37 +2624,6620.98 +2625,6547.43 +2626,8497.8 +2627,9267.07 +2628,9346.95 +2629,9377.04 +2630,9150.64 +2631,8155.68 +2632,6920.55 +2633,6741.04 +2634,8797.38 +2635,9581.72 +2636,9673.06 +2637,9629.82 +2638,9280.71 +2639,8334.09 +2640,7038.29 +2641,6807.16 +2642,8523.82 +2643,9565.15 +2644,9689.54 +2645,9724.3 +2646,9319.66 +2647,8352.69 +2648,7092.02 +2649,6886.04 +2650,8874.29 +2651,9469.9 +2652,9420.75 +2653,9315.72 +2654,9180.22 +2655,8254.58 +2656,7016.36 +2657,6783.48 +2658,8644.84 +2659,9362.16 +2660,9431.61 +2661,9289.99 +2662,8834.47 +2663,7937.0 +2664,6509.96 +2665,6540.23 +2666,7007.1 +2667,8046.78 +2668,8090.74 +2669,8017.07 +2670,7918.93 +2671,7322.63 +2672,6367.32 +2673,6391.09 +2674,6700.32 +2675,7770.08 +2676,7956.38 +2677,8070.65 +2678,8222.88 +2679,7689.32 +2680,7007.04 +2681,6949.12 +2682,9034.03 +2683,9912.14 +2684,9993.57 +2685,9921.78 +2686,9542.94 +2687,8648.27 +2688,7358.78 +2689,7245.65 +2690,9202.04 +2691,9885.41 +2692,9782.08 +2693,9886.07 +2694,9803.73 +2695,8902.25 +2696,7590.98 +2697,7496.62 +2698,9456.96 +2699,9999.64 +2700,9760.33 +2701,9770.32 +2702,9763.14 +2703,8905.37 +2704,7627.28 +2705,7548.66 +2706,9491.38 +2707,10292.18 +2708,10334.84 +2709,10397.52 +2710,9907.01 +2711,9073.37 +2712,7735.79 +2713,7683.32 +2714,9530.64 +2715,10347.53 +2716,10130.37 +2717,9988.14 +2718,9648.52 +2719,8731.82 +2720,7273.06 +2721,7193.21 +2722,7613.89 +2723,8616.0 +2724,8728.46 +2725,8656.82 +2726,8333.86 +2727,7581.37 +2728,6530.32 +2729,6524.04 +2730,6717.83 +2731,7820.07 +2732,8065.59 +2733,8141.6 +2734,8245.07 +2735,7670.29 +2736,6934.84 +2737,6925.17 +2738,8870.91 +2739,9707.93 +2740,9687.36 +2741,9733.7 +2742,9409.29 +2743,8553.72 +2744,7237.21 +2745,7129.83 +2746,9122.49 +2747,9618.11 +2748,9573.4 +2749,9691.86 +2750,9776.71 +2751,8921.48 +2752,7674.94 +2753,7601.22 +2754,9582.91 +2755,10147.3 +2756,9817.06 +2757,9969.9 +2758,9889.09 +2759,9074.54 +2760,7826.49 +2761,7660.06 +2762,9510.52 +2763,10293.21 +2764,10253.03 +2765,10363.27 +2766,9925.58 +2767,9029.81 +2768,7703.28 +2769,7632.21 +2770,9433.64 +2771,10131.8 +2772,10142.19 +2773,10112.77 +2774,9452.36 +2775,8458.7 +2776,6988.02 +2777,6957.29 +2778,7358.57 +2779,8372.14 +2780,8666.11 +2781,8580.2 +2782,8317.62 +2783,7538.91 +2784,6544.25 +2785,6484.55 +2786,6666.43 +2787,7854.51 +2788,8083.81 +2789,8125.6 +2790,8168.96 +2791,7597.69 +2792,6880.43 +2793,6852.74 +2794,8807.12 +2795,9429.95 +2796,9408.2 +2797,9431.3 +2798,9245.25 +2799,8415.08 +2800,7101.3 +2801,6965.03 +2802,8917.28 +2803,9527.58 +2804,9339.56 +2805,9421.62 +2806,9159.15 +2807,8323.17 +2808,7058.48 +2809,6866.05 +2810,8666.11 +2811,9301.29 +2812,9078.37 +2813,9038.26 +2814,8884.0 +2815,8138.38 +2816,6870.48 +2817,6731.58 +2818,8393.32 +2819,9068.42 +2820,8971.55 +2821,8929.76 +2822,8598.91 +2823,7779.34 +2824,6573.85 +2825,6333.85 +2826,7690.47 +2827,8210.02 +2828,7883.0 +2829,7994.19 +2830,7718.2 +2831,7040.05 +2832,5789.05 +2833,5653.77 +2834,6031.57 +2835,7116.99 +2836,7553.47 +2837,7577.6 +2838,7332.01 +2839,6603.9 +2840,5588.01 +2841,5554.0 +2842,5843.6 +2843,6913.85 +2844,7239.42 +2845,7402.23 +2846,7248.18 +2847,6525.17 +2848,5634.07 +2849,5450.66 +2850,6016.51 +2851,7065.8 +2852,7441.67 +2853,7570.35 +2854,7233.36 +2855,6598.45 +2856,5331.28 +2857,5199.16 +2858,5401.37 +2859,6173.51 +2860,6203.62 +2861,6559.23 +2862,6104.98 +2863,5604.65 +2864,5087.48 +2865,4949.41 +2866,5148.86 +2867,6022.91 +2868,6161.86 +2869,6246.49 +2870,6279.51 +2871,5903.86 +2872,5130.42 +2873,5131.07 +2874,5407.43 +2875,6353.89 +2876,6507.9 +2877,6523.45 +2878,6533.03 +2879,6149.49 +2880,5448.85 +2881,5261.9 +2882,5882.38 +2883,6951.74 +2884,7329.46 +2885,7436.13 +2886,7183.41 +2887,6711.12 +2888,5618.75 +2889,5598.25 +2890,6030.38 +2891,6948.61 +2892,7421.31 +2893,7482.36 +2894,7338.69 +2895,6697.08 +2896,5736.0 +2897,5792.84 +2898,6042.8 +2899,6964.03 +2900,7369.76 +2901,7333.97 +2902,7475.57 +2903,6888.2 +2904,6123.94 +2905,5953.2 +2906,6446.23 +2907,7375.34 +2908,7662.53 +2909,7708.1 +2910,7660.93 +2911,7085.32 +2912,6009.63 +2913,5828.26 +2914,6204.05 +2915,7029.73 +2916,7342.83 +2917,7267.22 +2918,6821.12 +2919,6191.86 diff --git a/PyPSA/source/examples/networks/model-energy/model-energy/loads.csv b/PyPSA/source/examples/networks/model-energy/model-energy/loads.csv new file mode 100644 index 0000000000000000000000000000000000000000..3b31a15920b7365a8acd624bc03d9ceb2c53dc0b --- /dev/null +++ b/PyPSA/source/examples/networks/model-energy/model-energy/loads.csv @@ -0,0 +1,2 @@ +name,bus +demand,electricity diff --git a/PyPSA/source/examples/networks/model-energy/model-energy/meta.json b/PyPSA/source/examples/networks/model-energy/model-energy/meta.json new file mode 100644 index 0000000000000000000000000000000000000000..9e26dfeeb6e641a33dae4961196235bdb965b21b --- /dev/null +++ b/PyPSA/source/examples/networks/model-energy/model-energy/meta.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/PyPSA/source/examples/networks/model-energy/model-energy/network.csv b/PyPSA/source/examples/networks/model-energy/model-energy/network.csv new file mode 100644 index 0000000000000000000000000000000000000000..8b1c5f88b2e796fdc6d0fd945ddf5fdd49a6f25b --- /dev/null +++ b/PyPSA/source/examples/networks/model-energy/model-energy/network.csv @@ -0,0 +1,2 @@ +name,_multi_invest,pypsa_version,srid +Model-Energy,0,1.0.3,4326 diff --git a/PyPSA/source/examples/networks/model-energy/model-energy/snapshots.csv b/PyPSA/source/examples/networks/model-energy/model-energy/snapshots.csv new file mode 100644 index 0000000000000000000000000000000000000000..de45353cd846c83db2930f7bb6d1b12e2213f52e --- /dev/null +++ b/PyPSA/source/examples/networks/model-energy/model-energy/snapshots.csv @@ -0,0 +1,2921 @@ +,snapshot,objective,stores,generators +0,2019-01-01 00:00:00,3.0,3.0,3.0 +1,2019-01-01 03:00:00,3.0,3.0,3.0 +2,2019-01-01 06:00:00,3.0,3.0,3.0 +3,2019-01-01 09:00:00,3.0,3.0,3.0 +4,2019-01-01 12:00:00,3.0,3.0,3.0 +5,2019-01-01 15:00:00,3.0,3.0,3.0 +6,2019-01-01 18:00:00,3.0,3.0,3.0 +7,2019-01-01 21:00:00,3.0,3.0,3.0 +8,2019-01-02 00:00:00,3.0,3.0,3.0 +9,2019-01-02 03:00:00,3.0,3.0,3.0 +10,2019-01-02 06:00:00,3.0,3.0,3.0 +11,2019-01-02 09:00:00,3.0,3.0,3.0 +12,2019-01-02 12:00:00,3.0,3.0,3.0 +13,2019-01-02 15:00:00,3.0,3.0,3.0 +14,2019-01-02 18:00:00,3.0,3.0,3.0 +15,2019-01-02 21:00:00,3.0,3.0,3.0 +16,2019-01-03 00:00:00,3.0,3.0,3.0 +17,2019-01-03 03:00:00,3.0,3.0,3.0 +18,2019-01-03 06:00:00,3.0,3.0,3.0 +19,2019-01-03 09:00:00,3.0,3.0,3.0 +20,2019-01-03 12:00:00,3.0,3.0,3.0 +21,2019-01-03 15:00:00,3.0,3.0,3.0 +22,2019-01-03 18:00:00,3.0,3.0,3.0 +23,2019-01-03 21:00:00,3.0,3.0,3.0 +24,2019-01-04 00:00:00,3.0,3.0,3.0 +25,2019-01-04 03:00:00,3.0,3.0,3.0 +26,2019-01-04 06:00:00,3.0,3.0,3.0 +27,2019-01-04 09:00:00,3.0,3.0,3.0 +28,2019-01-04 12:00:00,3.0,3.0,3.0 +29,2019-01-04 15:00:00,3.0,3.0,3.0 +30,2019-01-04 18:00:00,3.0,3.0,3.0 +31,2019-01-04 21:00:00,3.0,3.0,3.0 +32,2019-01-05 00:00:00,3.0,3.0,3.0 +33,2019-01-05 03:00:00,3.0,3.0,3.0 +34,2019-01-05 06:00:00,3.0,3.0,3.0 +35,2019-01-05 09:00:00,3.0,3.0,3.0 +36,2019-01-05 12:00:00,3.0,3.0,3.0 +37,2019-01-05 15:00:00,3.0,3.0,3.0 +38,2019-01-05 18:00:00,3.0,3.0,3.0 +39,2019-01-05 21:00:00,3.0,3.0,3.0 +40,2019-01-06 00:00:00,3.0,3.0,3.0 +41,2019-01-06 03:00:00,3.0,3.0,3.0 +42,2019-01-06 06:00:00,3.0,3.0,3.0 +43,2019-01-06 09:00:00,3.0,3.0,3.0 +44,2019-01-06 12:00:00,3.0,3.0,3.0 +45,2019-01-06 15:00:00,3.0,3.0,3.0 +46,2019-01-06 18:00:00,3.0,3.0,3.0 +47,2019-01-06 21:00:00,3.0,3.0,3.0 +48,2019-01-07 00:00:00,3.0,3.0,3.0 +49,2019-01-07 03:00:00,3.0,3.0,3.0 +50,2019-01-07 06:00:00,3.0,3.0,3.0 +51,2019-01-07 09:00:00,3.0,3.0,3.0 +52,2019-01-07 12:00:00,3.0,3.0,3.0 +53,2019-01-07 15:00:00,3.0,3.0,3.0 +54,2019-01-07 18:00:00,3.0,3.0,3.0 +55,2019-01-07 21:00:00,3.0,3.0,3.0 +56,2019-01-08 00:00:00,3.0,3.0,3.0 +57,2019-01-08 03:00:00,3.0,3.0,3.0 +58,2019-01-08 06:00:00,3.0,3.0,3.0 +59,2019-01-08 09:00:00,3.0,3.0,3.0 +60,2019-01-08 12:00:00,3.0,3.0,3.0 +61,2019-01-08 15:00:00,3.0,3.0,3.0 +62,2019-01-08 18:00:00,3.0,3.0,3.0 +63,2019-01-08 21:00:00,3.0,3.0,3.0 +64,2019-01-09 00:00:00,3.0,3.0,3.0 +65,2019-01-09 03:00:00,3.0,3.0,3.0 +66,2019-01-09 06:00:00,3.0,3.0,3.0 +67,2019-01-09 09:00:00,3.0,3.0,3.0 +68,2019-01-09 12:00:00,3.0,3.0,3.0 +69,2019-01-09 15:00:00,3.0,3.0,3.0 +70,2019-01-09 18:00:00,3.0,3.0,3.0 +71,2019-01-09 21:00:00,3.0,3.0,3.0 +72,2019-01-10 00:00:00,3.0,3.0,3.0 +73,2019-01-10 03:00:00,3.0,3.0,3.0 +74,2019-01-10 06:00:00,3.0,3.0,3.0 +75,2019-01-10 09:00:00,3.0,3.0,3.0 +76,2019-01-10 12:00:00,3.0,3.0,3.0 +77,2019-01-10 15:00:00,3.0,3.0,3.0 +78,2019-01-10 18:00:00,3.0,3.0,3.0 +79,2019-01-10 21:00:00,3.0,3.0,3.0 +80,2019-01-11 00:00:00,3.0,3.0,3.0 +81,2019-01-11 03:00:00,3.0,3.0,3.0 +82,2019-01-11 06:00:00,3.0,3.0,3.0 +83,2019-01-11 09:00:00,3.0,3.0,3.0 +84,2019-01-11 12:00:00,3.0,3.0,3.0 +85,2019-01-11 15:00:00,3.0,3.0,3.0 +86,2019-01-11 18:00:00,3.0,3.0,3.0 +87,2019-01-11 21:00:00,3.0,3.0,3.0 +88,2019-01-12 00:00:00,3.0,3.0,3.0 +89,2019-01-12 03:00:00,3.0,3.0,3.0 +90,2019-01-12 06:00:00,3.0,3.0,3.0 +91,2019-01-12 09:00:00,3.0,3.0,3.0 +92,2019-01-12 12:00:00,3.0,3.0,3.0 +93,2019-01-12 15:00:00,3.0,3.0,3.0 +94,2019-01-12 18:00:00,3.0,3.0,3.0 +95,2019-01-12 21:00:00,3.0,3.0,3.0 +96,2019-01-13 00:00:00,3.0,3.0,3.0 +97,2019-01-13 03:00:00,3.0,3.0,3.0 +98,2019-01-13 06:00:00,3.0,3.0,3.0 +99,2019-01-13 09:00:00,3.0,3.0,3.0 +100,2019-01-13 12:00:00,3.0,3.0,3.0 +101,2019-01-13 15:00:00,3.0,3.0,3.0 +102,2019-01-13 18:00:00,3.0,3.0,3.0 +103,2019-01-13 21:00:00,3.0,3.0,3.0 +104,2019-01-14 00:00:00,3.0,3.0,3.0 +105,2019-01-14 03:00:00,3.0,3.0,3.0 +106,2019-01-14 06:00:00,3.0,3.0,3.0 +107,2019-01-14 09:00:00,3.0,3.0,3.0 +108,2019-01-14 12:00:00,3.0,3.0,3.0 +109,2019-01-14 15:00:00,3.0,3.0,3.0 +110,2019-01-14 18:00:00,3.0,3.0,3.0 +111,2019-01-14 21:00:00,3.0,3.0,3.0 +112,2019-01-15 00:00:00,3.0,3.0,3.0 +113,2019-01-15 03:00:00,3.0,3.0,3.0 +114,2019-01-15 06:00:00,3.0,3.0,3.0 +115,2019-01-15 09:00:00,3.0,3.0,3.0 +116,2019-01-15 12:00:00,3.0,3.0,3.0 +117,2019-01-15 15:00:00,3.0,3.0,3.0 +118,2019-01-15 18:00:00,3.0,3.0,3.0 +119,2019-01-15 21:00:00,3.0,3.0,3.0 +120,2019-01-16 00:00:00,3.0,3.0,3.0 +121,2019-01-16 03:00:00,3.0,3.0,3.0 +122,2019-01-16 06:00:00,3.0,3.0,3.0 +123,2019-01-16 09:00:00,3.0,3.0,3.0 +124,2019-01-16 12:00:00,3.0,3.0,3.0 +125,2019-01-16 15:00:00,3.0,3.0,3.0 +126,2019-01-16 18:00:00,3.0,3.0,3.0 +127,2019-01-16 21:00:00,3.0,3.0,3.0 +128,2019-01-17 00:00:00,3.0,3.0,3.0 +129,2019-01-17 03:00:00,3.0,3.0,3.0 +130,2019-01-17 06:00:00,3.0,3.0,3.0 +131,2019-01-17 09:00:00,3.0,3.0,3.0 +132,2019-01-17 12:00:00,3.0,3.0,3.0 +133,2019-01-17 15:00:00,3.0,3.0,3.0 +134,2019-01-17 18:00:00,3.0,3.0,3.0 +135,2019-01-17 21:00:00,3.0,3.0,3.0 +136,2019-01-18 00:00:00,3.0,3.0,3.0 +137,2019-01-18 03:00:00,3.0,3.0,3.0 +138,2019-01-18 06:00:00,3.0,3.0,3.0 +139,2019-01-18 09:00:00,3.0,3.0,3.0 +140,2019-01-18 12:00:00,3.0,3.0,3.0 +141,2019-01-18 15:00:00,3.0,3.0,3.0 +142,2019-01-18 18:00:00,3.0,3.0,3.0 +143,2019-01-18 21:00:00,3.0,3.0,3.0 +144,2019-01-19 00:00:00,3.0,3.0,3.0 +145,2019-01-19 03:00:00,3.0,3.0,3.0 +146,2019-01-19 06:00:00,3.0,3.0,3.0 +147,2019-01-19 09:00:00,3.0,3.0,3.0 +148,2019-01-19 12:00:00,3.0,3.0,3.0 +149,2019-01-19 15:00:00,3.0,3.0,3.0 +150,2019-01-19 18:00:00,3.0,3.0,3.0 +151,2019-01-19 21:00:00,3.0,3.0,3.0 +152,2019-01-20 00:00:00,3.0,3.0,3.0 +153,2019-01-20 03:00:00,3.0,3.0,3.0 +154,2019-01-20 06:00:00,3.0,3.0,3.0 +155,2019-01-20 09:00:00,3.0,3.0,3.0 +156,2019-01-20 12:00:00,3.0,3.0,3.0 +157,2019-01-20 15:00:00,3.0,3.0,3.0 +158,2019-01-20 18:00:00,3.0,3.0,3.0 +159,2019-01-20 21:00:00,3.0,3.0,3.0 +160,2019-01-21 00:00:00,3.0,3.0,3.0 +161,2019-01-21 03:00:00,3.0,3.0,3.0 +162,2019-01-21 06:00:00,3.0,3.0,3.0 +163,2019-01-21 09:00:00,3.0,3.0,3.0 +164,2019-01-21 12:00:00,3.0,3.0,3.0 +165,2019-01-21 15:00:00,3.0,3.0,3.0 +166,2019-01-21 18:00:00,3.0,3.0,3.0 +167,2019-01-21 21:00:00,3.0,3.0,3.0 +168,2019-01-22 00:00:00,3.0,3.0,3.0 +169,2019-01-22 03:00:00,3.0,3.0,3.0 +170,2019-01-22 06:00:00,3.0,3.0,3.0 +171,2019-01-22 09:00:00,3.0,3.0,3.0 +172,2019-01-22 12:00:00,3.0,3.0,3.0 +173,2019-01-22 15:00:00,3.0,3.0,3.0 +174,2019-01-22 18:00:00,3.0,3.0,3.0 +175,2019-01-22 21:00:00,3.0,3.0,3.0 +176,2019-01-23 00:00:00,3.0,3.0,3.0 +177,2019-01-23 03:00:00,3.0,3.0,3.0 +178,2019-01-23 06:00:00,3.0,3.0,3.0 +179,2019-01-23 09:00:00,3.0,3.0,3.0 +180,2019-01-23 12:00:00,3.0,3.0,3.0 +181,2019-01-23 15:00:00,3.0,3.0,3.0 +182,2019-01-23 18:00:00,3.0,3.0,3.0 +183,2019-01-23 21:00:00,3.0,3.0,3.0 +184,2019-01-24 00:00:00,3.0,3.0,3.0 +185,2019-01-24 03:00:00,3.0,3.0,3.0 +186,2019-01-24 06:00:00,3.0,3.0,3.0 +187,2019-01-24 09:00:00,3.0,3.0,3.0 +188,2019-01-24 12:00:00,3.0,3.0,3.0 +189,2019-01-24 15:00:00,3.0,3.0,3.0 +190,2019-01-24 18:00:00,3.0,3.0,3.0 +191,2019-01-24 21:00:00,3.0,3.0,3.0 +192,2019-01-25 00:00:00,3.0,3.0,3.0 +193,2019-01-25 03:00:00,3.0,3.0,3.0 +194,2019-01-25 06:00:00,3.0,3.0,3.0 +195,2019-01-25 09:00:00,3.0,3.0,3.0 +196,2019-01-25 12:00:00,3.0,3.0,3.0 +197,2019-01-25 15:00:00,3.0,3.0,3.0 +198,2019-01-25 18:00:00,3.0,3.0,3.0 +199,2019-01-25 21:00:00,3.0,3.0,3.0 +200,2019-01-26 00:00:00,3.0,3.0,3.0 +201,2019-01-26 03:00:00,3.0,3.0,3.0 +202,2019-01-26 06:00:00,3.0,3.0,3.0 +203,2019-01-26 09:00:00,3.0,3.0,3.0 +204,2019-01-26 12:00:00,3.0,3.0,3.0 +205,2019-01-26 15:00:00,3.0,3.0,3.0 +206,2019-01-26 18:00:00,3.0,3.0,3.0 +207,2019-01-26 21:00:00,3.0,3.0,3.0 +208,2019-01-27 00:00:00,3.0,3.0,3.0 +209,2019-01-27 03:00:00,3.0,3.0,3.0 +210,2019-01-27 06:00:00,3.0,3.0,3.0 +211,2019-01-27 09:00:00,3.0,3.0,3.0 +212,2019-01-27 12:00:00,3.0,3.0,3.0 +213,2019-01-27 15:00:00,3.0,3.0,3.0 +214,2019-01-27 18:00:00,3.0,3.0,3.0 +215,2019-01-27 21:00:00,3.0,3.0,3.0 +216,2019-01-28 00:00:00,3.0,3.0,3.0 +217,2019-01-28 03:00:00,3.0,3.0,3.0 +218,2019-01-28 06:00:00,3.0,3.0,3.0 +219,2019-01-28 09:00:00,3.0,3.0,3.0 +220,2019-01-28 12:00:00,3.0,3.0,3.0 +221,2019-01-28 15:00:00,3.0,3.0,3.0 +222,2019-01-28 18:00:00,3.0,3.0,3.0 +223,2019-01-28 21:00:00,3.0,3.0,3.0 +224,2019-01-29 00:00:00,3.0,3.0,3.0 +225,2019-01-29 03:00:00,3.0,3.0,3.0 +226,2019-01-29 06:00:00,3.0,3.0,3.0 +227,2019-01-29 09:00:00,3.0,3.0,3.0 +228,2019-01-29 12:00:00,3.0,3.0,3.0 +229,2019-01-29 15:00:00,3.0,3.0,3.0 +230,2019-01-29 18:00:00,3.0,3.0,3.0 +231,2019-01-29 21:00:00,3.0,3.0,3.0 +232,2019-01-30 00:00:00,3.0,3.0,3.0 +233,2019-01-30 03:00:00,3.0,3.0,3.0 +234,2019-01-30 06:00:00,3.0,3.0,3.0 +235,2019-01-30 09:00:00,3.0,3.0,3.0 +236,2019-01-30 12:00:00,3.0,3.0,3.0 +237,2019-01-30 15:00:00,3.0,3.0,3.0 +238,2019-01-30 18:00:00,3.0,3.0,3.0 +239,2019-01-30 21:00:00,3.0,3.0,3.0 +240,2019-01-31 00:00:00,3.0,3.0,3.0 +241,2019-01-31 03:00:00,3.0,3.0,3.0 +242,2019-01-31 06:00:00,3.0,3.0,3.0 +243,2019-01-31 09:00:00,3.0,3.0,3.0 +244,2019-01-31 12:00:00,3.0,3.0,3.0 +245,2019-01-31 15:00:00,3.0,3.0,3.0 +246,2019-01-31 18:00:00,3.0,3.0,3.0 +247,2019-01-31 21:00:00,3.0,3.0,3.0 +248,2019-02-01 00:00:00,3.0,3.0,3.0 +249,2019-02-01 03:00:00,3.0,3.0,3.0 +250,2019-02-01 06:00:00,3.0,3.0,3.0 +251,2019-02-01 09:00:00,3.0,3.0,3.0 +252,2019-02-01 12:00:00,3.0,3.0,3.0 +253,2019-02-01 15:00:00,3.0,3.0,3.0 +254,2019-02-01 18:00:00,3.0,3.0,3.0 +255,2019-02-01 21:00:00,3.0,3.0,3.0 +256,2019-02-02 00:00:00,3.0,3.0,3.0 +257,2019-02-02 03:00:00,3.0,3.0,3.0 +258,2019-02-02 06:00:00,3.0,3.0,3.0 +259,2019-02-02 09:00:00,3.0,3.0,3.0 +260,2019-02-02 12:00:00,3.0,3.0,3.0 +261,2019-02-02 15:00:00,3.0,3.0,3.0 +262,2019-02-02 18:00:00,3.0,3.0,3.0 +263,2019-02-02 21:00:00,3.0,3.0,3.0 +264,2019-02-03 00:00:00,3.0,3.0,3.0 +265,2019-02-03 03:00:00,3.0,3.0,3.0 +266,2019-02-03 06:00:00,3.0,3.0,3.0 +267,2019-02-03 09:00:00,3.0,3.0,3.0 +268,2019-02-03 12:00:00,3.0,3.0,3.0 +269,2019-02-03 15:00:00,3.0,3.0,3.0 +270,2019-02-03 18:00:00,3.0,3.0,3.0 +271,2019-02-03 21:00:00,3.0,3.0,3.0 +272,2019-02-04 00:00:00,3.0,3.0,3.0 +273,2019-02-04 03:00:00,3.0,3.0,3.0 +274,2019-02-04 06:00:00,3.0,3.0,3.0 +275,2019-02-04 09:00:00,3.0,3.0,3.0 +276,2019-02-04 12:00:00,3.0,3.0,3.0 +277,2019-02-04 15:00:00,3.0,3.0,3.0 +278,2019-02-04 18:00:00,3.0,3.0,3.0 +279,2019-02-04 21:00:00,3.0,3.0,3.0 +280,2019-02-05 00:00:00,3.0,3.0,3.0 +281,2019-02-05 03:00:00,3.0,3.0,3.0 +282,2019-02-05 06:00:00,3.0,3.0,3.0 +283,2019-02-05 09:00:00,3.0,3.0,3.0 +284,2019-02-05 12:00:00,3.0,3.0,3.0 +285,2019-02-05 15:00:00,3.0,3.0,3.0 +286,2019-02-05 18:00:00,3.0,3.0,3.0 +287,2019-02-05 21:00:00,3.0,3.0,3.0 +288,2019-02-06 00:00:00,3.0,3.0,3.0 +289,2019-02-06 03:00:00,3.0,3.0,3.0 +290,2019-02-06 06:00:00,3.0,3.0,3.0 +291,2019-02-06 09:00:00,3.0,3.0,3.0 +292,2019-02-06 12:00:00,3.0,3.0,3.0 +293,2019-02-06 15:00:00,3.0,3.0,3.0 +294,2019-02-06 18:00:00,3.0,3.0,3.0 +295,2019-02-06 21:00:00,3.0,3.0,3.0 +296,2019-02-07 00:00:00,3.0,3.0,3.0 +297,2019-02-07 03:00:00,3.0,3.0,3.0 +298,2019-02-07 06:00:00,3.0,3.0,3.0 +299,2019-02-07 09:00:00,3.0,3.0,3.0 +300,2019-02-07 12:00:00,3.0,3.0,3.0 +301,2019-02-07 15:00:00,3.0,3.0,3.0 +302,2019-02-07 18:00:00,3.0,3.0,3.0 +303,2019-02-07 21:00:00,3.0,3.0,3.0 +304,2019-02-08 00:00:00,3.0,3.0,3.0 +305,2019-02-08 03:00:00,3.0,3.0,3.0 +306,2019-02-08 06:00:00,3.0,3.0,3.0 +307,2019-02-08 09:00:00,3.0,3.0,3.0 +308,2019-02-08 12:00:00,3.0,3.0,3.0 +309,2019-02-08 15:00:00,3.0,3.0,3.0 +310,2019-02-08 18:00:00,3.0,3.0,3.0 +311,2019-02-08 21:00:00,3.0,3.0,3.0 +312,2019-02-09 00:00:00,3.0,3.0,3.0 +313,2019-02-09 03:00:00,3.0,3.0,3.0 +314,2019-02-09 06:00:00,3.0,3.0,3.0 +315,2019-02-09 09:00:00,3.0,3.0,3.0 +316,2019-02-09 12:00:00,3.0,3.0,3.0 +317,2019-02-09 15:00:00,3.0,3.0,3.0 +318,2019-02-09 18:00:00,3.0,3.0,3.0 +319,2019-02-09 21:00:00,3.0,3.0,3.0 +320,2019-02-10 00:00:00,3.0,3.0,3.0 +321,2019-02-10 03:00:00,3.0,3.0,3.0 +322,2019-02-10 06:00:00,3.0,3.0,3.0 +323,2019-02-10 09:00:00,3.0,3.0,3.0 +324,2019-02-10 12:00:00,3.0,3.0,3.0 +325,2019-02-10 15:00:00,3.0,3.0,3.0 +326,2019-02-10 18:00:00,3.0,3.0,3.0 +327,2019-02-10 21:00:00,3.0,3.0,3.0 +328,2019-02-11 00:00:00,3.0,3.0,3.0 +329,2019-02-11 03:00:00,3.0,3.0,3.0 +330,2019-02-11 06:00:00,3.0,3.0,3.0 +331,2019-02-11 09:00:00,3.0,3.0,3.0 +332,2019-02-11 12:00:00,3.0,3.0,3.0 +333,2019-02-11 15:00:00,3.0,3.0,3.0 +334,2019-02-11 18:00:00,3.0,3.0,3.0 +335,2019-02-11 21:00:00,3.0,3.0,3.0 +336,2019-02-12 00:00:00,3.0,3.0,3.0 +337,2019-02-12 03:00:00,3.0,3.0,3.0 +338,2019-02-12 06:00:00,3.0,3.0,3.0 +339,2019-02-12 09:00:00,3.0,3.0,3.0 +340,2019-02-12 12:00:00,3.0,3.0,3.0 +341,2019-02-12 15:00:00,3.0,3.0,3.0 +342,2019-02-12 18:00:00,3.0,3.0,3.0 +343,2019-02-12 21:00:00,3.0,3.0,3.0 +344,2019-02-13 00:00:00,3.0,3.0,3.0 +345,2019-02-13 03:00:00,3.0,3.0,3.0 +346,2019-02-13 06:00:00,3.0,3.0,3.0 +347,2019-02-13 09:00:00,3.0,3.0,3.0 +348,2019-02-13 12:00:00,3.0,3.0,3.0 +349,2019-02-13 15:00:00,3.0,3.0,3.0 +350,2019-02-13 18:00:00,3.0,3.0,3.0 +351,2019-02-13 21:00:00,3.0,3.0,3.0 +352,2019-02-14 00:00:00,3.0,3.0,3.0 +353,2019-02-14 03:00:00,3.0,3.0,3.0 +354,2019-02-14 06:00:00,3.0,3.0,3.0 +355,2019-02-14 09:00:00,3.0,3.0,3.0 +356,2019-02-14 12:00:00,3.0,3.0,3.0 +357,2019-02-14 15:00:00,3.0,3.0,3.0 +358,2019-02-14 18:00:00,3.0,3.0,3.0 +359,2019-02-14 21:00:00,3.0,3.0,3.0 +360,2019-02-15 00:00:00,3.0,3.0,3.0 +361,2019-02-15 03:00:00,3.0,3.0,3.0 +362,2019-02-15 06:00:00,3.0,3.0,3.0 +363,2019-02-15 09:00:00,3.0,3.0,3.0 +364,2019-02-15 12:00:00,3.0,3.0,3.0 +365,2019-02-15 15:00:00,3.0,3.0,3.0 +366,2019-02-15 18:00:00,3.0,3.0,3.0 +367,2019-02-15 21:00:00,3.0,3.0,3.0 +368,2019-02-16 00:00:00,3.0,3.0,3.0 +369,2019-02-16 03:00:00,3.0,3.0,3.0 +370,2019-02-16 06:00:00,3.0,3.0,3.0 +371,2019-02-16 09:00:00,3.0,3.0,3.0 +372,2019-02-16 12:00:00,3.0,3.0,3.0 +373,2019-02-16 15:00:00,3.0,3.0,3.0 +374,2019-02-16 18:00:00,3.0,3.0,3.0 +375,2019-02-16 21:00:00,3.0,3.0,3.0 +376,2019-02-17 00:00:00,3.0,3.0,3.0 +377,2019-02-17 03:00:00,3.0,3.0,3.0 +378,2019-02-17 06:00:00,3.0,3.0,3.0 +379,2019-02-17 09:00:00,3.0,3.0,3.0 +380,2019-02-17 12:00:00,3.0,3.0,3.0 +381,2019-02-17 15:00:00,3.0,3.0,3.0 +382,2019-02-17 18:00:00,3.0,3.0,3.0 +383,2019-02-17 21:00:00,3.0,3.0,3.0 +384,2019-02-18 00:00:00,3.0,3.0,3.0 +385,2019-02-18 03:00:00,3.0,3.0,3.0 +386,2019-02-18 06:00:00,3.0,3.0,3.0 +387,2019-02-18 09:00:00,3.0,3.0,3.0 +388,2019-02-18 12:00:00,3.0,3.0,3.0 +389,2019-02-18 15:00:00,3.0,3.0,3.0 +390,2019-02-18 18:00:00,3.0,3.0,3.0 +391,2019-02-18 21:00:00,3.0,3.0,3.0 +392,2019-02-19 00:00:00,3.0,3.0,3.0 +393,2019-02-19 03:00:00,3.0,3.0,3.0 +394,2019-02-19 06:00:00,3.0,3.0,3.0 +395,2019-02-19 09:00:00,3.0,3.0,3.0 +396,2019-02-19 12:00:00,3.0,3.0,3.0 +397,2019-02-19 15:00:00,3.0,3.0,3.0 +398,2019-02-19 18:00:00,3.0,3.0,3.0 +399,2019-02-19 21:00:00,3.0,3.0,3.0 +400,2019-02-20 00:00:00,3.0,3.0,3.0 +401,2019-02-20 03:00:00,3.0,3.0,3.0 +402,2019-02-20 06:00:00,3.0,3.0,3.0 +403,2019-02-20 09:00:00,3.0,3.0,3.0 +404,2019-02-20 12:00:00,3.0,3.0,3.0 +405,2019-02-20 15:00:00,3.0,3.0,3.0 +406,2019-02-20 18:00:00,3.0,3.0,3.0 +407,2019-02-20 21:00:00,3.0,3.0,3.0 +408,2019-02-21 00:00:00,3.0,3.0,3.0 +409,2019-02-21 03:00:00,3.0,3.0,3.0 +410,2019-02-21 06:00:00,3.0,3.0,3.0 +411,2019-02-21 09:00:00,3.0,3.0,3.0 +412,2019-02-21 12:00:00,3.0,3.0,3.0 +413,2019-02-21 15:00:00,3.0,3.0,3.0 +414,2019-02-21 18:00:00,3.0,3.0,3.0 +415,2019-02-21 21:00:00,3.0,3.0,3.0 +416,2019-02-22 00:00:00,3.0,3.0,3.0 +417,2019-02-22 03:00:00,3.0,3.0,3.0 +418,2019-02-22 06:00:00,3.0,3.0,3.0 +419,2019-02-22 09:00:00,3.0,3.0,3.0 +420,2019-02-22 12:00:00,3.0,3.0,3.0 +421,2019-02-22 15:00:00,3.0,3.0,3.0 +422,2019-02-22 18:00:00,3.0,3.0,3.0 +423,2019-02-22 21:00:00,3.0,3.0,3.0 +424,2019-02-23 00:00:00,3.0,3.0,3.0 +425,2019-02-23 03:00:00,3.0,3.0,3.0 +426,2019-02-23 06:00:00,3.0,3.0,3.0 +427,2019-02-23 09:00:00,3.0,3.0,3.0 +428,2019-02-23 12:00:00,3.0,3.0,3.0 +429,2019-02-23 15:00:00,3.0,3.0,3.0 +430,2019-02-23 18:00:00,3.0,3.0,3.0 +431,2019-02-23 21:00:00,3.0,3.0,3.0 +432,2019-02-24 00:00:00,3.0,3.0,3.0 +433,2019-02-24 03:00:00,3.0,3.0,3.0 +434,2019-02-24 06:00:00,3.0,3.0,3.0 +435,2019-02-24 09:00:00,3.0,3.0,3.0 +436,2019-02-24 12:00:00,3.0,3.0,3.0 +437,2019-02-24 15:00:00,3.0,3.0,3.0 +438,2019-02-24 18:00:00,3.0,3.0,3.0 +439,2019-02-24 21:00:00,3.0,3.0,3.0 +440,2019-02-25 00:00:00,3.0,3.0,3.0 +441,2019-02-25 03:00:00,3.0,3.0,3.0 +442,2019-02-25 06:00:00,3.0,3.0,3.0 +443,2019-02-25 09:00:00,3.0,3.0,3.0 +444,2019-02-25 12:00:00,3.0,3.0,3.0 +445,2019-02-25 15:00:00,3.0,3.0,3.0 +446,2019-02-25 18:00:00,3.0,3.0,3.0 +447,2019-02-25 21:00:00,3.0,3.0,3.0 +448,2019-02-26 00:00:00,3.0,3.0,3.0 +449,2019-02-26 03:00:00,3.0,3.0,3.0 +450,2019-02-26 06:00:00,3.0,3.0,3.0 +451,2019-02-26 09:00:00,3.0,3.0,3.0 +452,2019-02-26 12:00:00,3.0,3.0,3.0 +453,2019-02-26 15:00:00,3.0,3.0,3.0 +454,2019-02-26 18:00:00,3.0,3.0,3.0 +455,2019-02-26 21:00:00,3.0,3.0,3.0 +456,2019-02-27 00:00:00,3.0,3.0,3.0 +457,2019-02-27 03:00:00,3.0,3.0,3.0 +458,2019-02-27 06:00:00,3.0,3.0,3.0 +459,2019-02-27 09:00:00,3.0,3.0,3.0 +460,2019-02-27 12:00:00,3.0,3.0,3.0 +461,2019-02-27 15:00:00,3.0,3.0,3.0 +462,2019-02-27 18:00:00,3.0,3.0,3.0 +463,2019-02-27 21:00:00,3.0,3.0,3.0 +464,2019-02-28 00:00:00,3.0,3.0,3.0 +465,2019-02-28 03:00:00,3.0,3.0,3.0 +466,2019-02-28 06:00:00,3.0,3.0,3.0 +467,2019-02-28 09:00:00,3.0,3.0,3.0 +468,2019-02-28 12:00:00,3.0,3.0,3.0 +469,2019-02-28 15:00:00,3.0,3.0,3.0 +470,2019-02-28 18:00:00,3.0,3.0,3.0 +471,2019-02-28 21:00:00,3.0,3.0,3.0 +472,2019-03-01 00:00:00,3.0,3.0,3.0 +473,2019-03-01 03:00:00,3.0,3.0,3.0 +474,2019-03-01 06:00:00,3.0,3.0,3.0 +475,2019-03-01 09:00:00,3.0,3.0,3.0 +476,2019-03-01 12:00:00,3.0,3.0,3.0 +477,2019-03-01 15:00:00,3.0,3.0,3.0 +478,2019-03-01 18:00:00,3.0,3.0,3.0 +479,2019-03-01 21:00:00,3.0,3.0,3.0 +480,2019-03-02 00:00:00,3.0,3.0,3.0 +481,2019-03-02 03:00:00,3.0,3.0,3.0 +482,2019-03-02 06:00:00,3.0,3.0,3.0 +483,2019-03-02 09:00:00,3.0,3.0,3.0 +484,2019-03-02 12:00:00,3.0,3.0,3.0 +485,2019-03-02 15:00:00,3.0,3.0,3.0 +486,2019-03-02 18:00:00,3.0,3.0,3.0 +487,2019-03-02 21:00:00,3.0,3.0,3.0 +488,2019-03-03 00:00:00,3.0,3.0,3.0 +489,2019-03-03 03:00:00,3.0,3.0,3.0 +490,2019-03-03 06:00:00,3.0,3.0,3.0 +491,2019-03-03 09:00:00,3.0,3.0,3.0 +492,2019-03-03 12:00:00,3.0,3.0,3.0 +493,2019-03-03 15:00:00,3.0,3.0,3.0 +494,2019-03-03 18:00:00,3.0,3.0,3.0 +495,2019-03-03 21:00:00,3.0,3.0,3.0 +496,2019-03-04 00:00:00,3.0,3.0,3.0 +497,2019-03-04 03:00:00,3.0,3.0,3.0 +498,2019-03-04 06:00:00,3.0,3.0,3.0 +499,2019-03-04 09:00:00,3.0,3.0,3.0 +500,2019-03-04 12:00:00,3.0,3.0,3.0 +501,2019-03-04 15:00:00,3.0,3.0,3.0 +502,2019-03-04 18:00:00,3.0,3.0,3.0 +503,2019-03-04 21:00:00,3.0,3.0,3.0 +504,2019-03-05 00:00:00,3.0,3.0,3.0 +505,2019-03-05 03:00:00,3.0,3.0,3.0 +506,2019-03-05 06:00:00,3.0,3.0,3.0 +507,2019-03-05 09:00:00,3.0,3.0,3.0 +508,2019-03-05 12:00:00,3.0,3.0,3.0 +509,2019-03-05 15:00:00,3.0,3.0,3.0 +510,2019-03-05 18:00:00,3.0,3.0,3.0 +511,2019-03-05 21:00:00,3.0,3.0,3.0 +512,2019-03-06 00:00:00,3.0,3.0,3.0 +513,2019-03-06 03:00:00,3.0,3.0,3.0 +514,2019-03-06 06:00:00,3.0,3.0,3.0 +515,2019-03-06 09:00:00,3.0,3.0,3.0 +516,2019-03-06 12:00:00,3.0,3.0,3.0 +517,2019-03-06 15:00:00,3.0,3.0,3.0 +518,2019-03-06 18:00:00,3.0,3.0,3.0 +519,2019-03-06 21:00:00,3.0,3.0,3.0 +520,2019-03-07 00:00:00,3.0,3.0,3.0 +521,2019-03-07 03:00:00,3.0,3.0,3.0 +522,2019-03-07 06:00:00,3.0,3.0,3.0 +523,2019-03-07 09:00:00,3.0,3.0,3.0 +524,2019-03-07 12:00:00,3.0,3.0,3.0 +525,2019-03-07 15:00:00,3.0,3.0,3.0 +526,2019-03-07 18:00:00,3.0,3.0,3.0 +527,2019-03-07 21:00:00,3.0,3.0,3.0 +528,2019-03-08 00:00:00,3.0,3.0,3.0 +529,2019-03-08 03:00:00,3.0,3.0,3.0 +530,2019-03-08 06:00:00,3.0,3.0,3.0 +531,2019-03-08 09:00:00,3.0,3.0,3.0 +532,2019-03-08 12:00:00,3.0,3.0,3.0 +533,2019-03-08 15:00:00,3.0,3.0,3.0 +534,2019-03-08 18:00:00,3.0,3.0,3.0 +535,2019-03-08 21:00:00,3.0,3.0,3.0 +536,2019-03-09 00:00:00,3.0,3.0,3.0 +537,2019-03-09 03:00:00,3.0,3.0,3.0 +538,2019-03-09 06:00:00,3.0,3.0,3.0 +539,2019-03-09 09:00:00,3.0,3.0,3.0 +540,2019-03-09 12:00:00,3.0,3.0,3.0 +541,2019-03-09 15:00:00,3.0,3.0,3.0 +542,2019-03-09 18:00:00,3.0,3.0,3.0 +543,2019-03-09 21:00:00,3.0,3.0,3.0 +544,2019-03-10 00:00:00,3.0,3.0,3.0 +545,2019-03-10 03:00:00,3.0,3.0,3.0 +546,2019-03-10 06:00:00,3.0,3.0,3.0 +547,2019-03-10 09:00:00,3.0,3.0,3.0 +548,2019-03-10 12:00:00,3.0,3.0,3.0 +549,2019-03-10 15:00:00,3.0,3.0,3.0 +550,2019-03-10 18:00:00,3.0,3.0,3.0 +551,2019-03-10 21:00:00,3.0,3.0,3.0 +552,2019-03-11 00:00:00,3.0,3.0,3.0 +553,2019-03-11 03:00:00,3.0,3.0,3.0 +554,2019-03-11 06:00:00,3.0,3.0,3.0 +555,2019-03-11 09:00:00,3.0,3.0,3.0 +556,2019-03-11 12:00:00,3.0,3.0,3.0 +557,2019-03-11 15:00:00,3.0,3.0,3.0 +558,2019-03-11 18:00:00,3.0,3.0,3.0 +559,2019-03-11 21:00:00,3.0,3.0,3.0 +560,2019-03-12 00:00:00,3.0,3.0,3.0 +561,2019-03-12 03:00:00,3.0,3.0,3.0 +562,2019-03-12 06:00:00,3.0,3.0,3.0 +563,2019-03-12 09:00:00,3.0,3.0,3.0 +564,2019-03-12 12:00:00,3.0,3.0,3.0 +565,2019-03-12 15:00:00,3.0,3.0,3.0 +566,2019-03-12 18:00:00,3.0,3.0,3.0 +567,2019-03-12 21:00:00,3.0,3.0,3.0 +568,2019-03-13 00:00:00,3.0,3.0,3.0 +569,2019-03-13 03:00:00,3.0,3.0,3.0 +570,2019-03-13 06:00:00,3.0,3.0,3.0 +571,2019-03-13 09:00:00,3.0,3.0,3.0 +572,2019-03-13 12:00:00,3.0,3.0,3.0 +573,2019-03-13 15:00:00,3.0,3.0,3.0 +574,2019-03-13 18:00:00,3.0,3.0,3.0 +575,2019-03-13 21:00:00,3.0,3.0,3.0 +576,2019-03-14 00:00:00,3.0,3.0,3.0 +577,2019-03-14 03:00:00,3.0,3.0,3.0 +578,2019-03-14 06:00:00,3.0,3.0,3.0 +579,2019-03-14 09:00:00,3.0,3.0,3.0 +580,2019-03-14 12:00:00,3.0,3.0,3.0 +581,2019-03-14 15:00:00,3.0,3.0,3.0 +582,2019-03-14 18:00:00,3.0,3.0,3.0 +583,2019-03-14 21:00:00,3.0,3.0,3.0 +584,2019-03-15 00:00:00,3.0,3.0,3.0 +585,2019-03-15 03:00:00,3.0,3.0,3.0 +586,2019-03-15 06:00:00,3.0,3.0,3.0 +587,2019-03-15 09:00:00,3.0,3.0,3.0 +588,2019-03-15 12:00:00,3.0,3.0,3.0 +589,2019-03-15 15:00:00,3.0,3.0,3.0 +590,2019-03-15 18:00:00,3.0,3.0,3.0 +591,2019-03-15 21:00:00,3.0,3.0,3.0 +592,2019-03-16 00:00:00,3.0,3.0,3.0 +593,2019-03-16 03:00:00,3.0,3.0,3.0 +594,2019-03-16 06:00:00,3.0,3.0,3.0 +595,2019-03-16 09:00:00,3.0,3.0,3.0 +596,2019-03-16 12:00:00,3.0,3.0,3.0 +597,2019-03-16 15:00:00,3.0,3.0,3.0 +598,2019-03-16 18:00:00,3.0,3.0,3.0 +599,2019-03-16 21:00:00,3.0,3.0,3.0 +600,2019-03-17 00:00:00,3.0,3.0,3.0 +601,2019-03-17 03:00:00,3.0,3.0,3.0 +602,2019-03-17 06:00:00,3.0,3.0,3.0 +603,2019-03-17 09:00:00,3.0,3.0,3.0 +604,2019-03-17 12:00:00,3.0,3.0,3.0 +605,2019-03-17 15:00:00,3.0,3.0,3.0 +606,2019-03-17 18:00:00,3.0,3.0,3.0 +607,2019-03-17 21:00:00,3.0,3.0,3.0 +608,2019-03-18 00:00:00,3.0,3.0,3.0 +609,2019-03-18 03:00:00,3.0,3.0,3.0 +610,2019-03-18 06:00:00,3.0,3.0,3.0 +611,2019-03-18 09:00:00,3.0,3.0,3.0 +612,2019-03-18 12:00:00,3.0,3.0,3.0 +613,2019-03-18 15:00:00,3.0,3.0,3.0 +614,2019-03-18 18:00:00,3.0,3.0,3.0 +615,2019-03-18 21:00:00,3.0,3.0,3.0 +616,2019-03-19 00:00:00,3.0,3.0,3.0 +617,2019-03-19 03:00:00,3.0,3.0,3.0 +618,2019-03-19 06:00:00,3.0,3.0,3.0 +619,2019-03-19 09:00:00,3.0,3.0,3.0 +620,2019-03-19 12:00:00,3.0,3.0,3.0 +621,2019-03-19 15:00:00,3.0,3.0,3.0 +622,2019-03-19 18:00:00,3.0,3.0,3.0 +623,2019-03-19 21:00:00,3.0,3.0,3.0 +624,2019-03-20 00:00:00,3.0,3.0,3.0 +625,2019-03-20 03:00:00,3.0,3.0,3.0 +626,2019-03-20 06:00:00,3.0,3.0,3.0 +627,2019-03-20 09:00:00,3.0,3.0,3.0 +628,2019-03-20 12:00:00,3.0,3.0,3.0 +629,2019-03-20 15:00:00,3.0,3.0,3.0 +630,2019-03-20 18:00:00,3.0,3.0,3.0 +631,2019-03-20 21:00:00,3.0,3.0,3.0 +632,2019-03-21 00:00:00,3.0,3.0,3.0 +633,2019-03-21 03:00:00,3.0,3.0,3.0 +634,2019-03-21 06:00:00,3.0,3.0,3.0 +635,2019-03-21 09:00:00,3.0,3.0,3.0 +636,2019-03-21 12:00:00,3.0,3.0,3.0 +637,2019-03-21 15:00:00,3.0,3.0,3.0 +638,2019-03-21 18:00:00,3.0,3.0,3.0 +639,2019-03-21 21:00:00,3.0,3.0,3.0 +640,2019-03-22 00:00:00,3.0,3.0,3.0 +641,2019-03-22 03:00:00,3.0,3.0,3.0 +642,2019-03-22 06:00:00,3.0,3.0,3.0 +643,2019-03-22 09:00:00,3.0,3.0,3.0 +644,2019-03-22 12:00:00,3.0,3.0,3.0 +645,2019-03-22 15:00:00,3.0,3.0,3.0 +646,2019-03-22 18:00:00,3.0,3.0,3.0 +647,2019-03-22 21:00:00,3.0,3.0,3.0 +648,2019-03-23 00:00:00,3.0,3.0,3.0 +649,2019-03-23 03:00:00,3.0,3.0,3.0 +650,2019-03-23 06:00:00,3.0,3.0,3.0 +651,2019-03-23 09:00:00,3.0,3.0,3.0 +652,2019-03-23 12:00:00,3.0,3.0,3.0 +653,2019-03-23 15:00:00,3.0,3.0,3.0 +654,2019-03-23 18:00:00,3.0,3.0,3.0 +655,2019-03-23 21:00:00,3.0,3.0,3.0 +656,2019-03-24 00:00:00,3.0,3.0,3.0 +657,2019-03-24 03:00:00,3.0,3.0,3.0 +658,2019-03-24 06:00:00,3.0,3.0,3.0 +659,2019-03-24 09:00:00,3.0,3.0,3.0 +660,2019-03-24 12:00:00,3.0,3.0,3.0 +661,2019-03-24 15:00:00,3.0,3.0,3.0 +662,2019-03-24 18:00:00,3.0,3.0,3.0 +663,2019-03-24 21:00:00,3.0,3.0,3.0 +664,2019-03-25 00:00:00,3.0,3.0,3.0 +665,2019-03-25 03:00:00,3.0,3.0,3.0 +666,2019-03-25 06:00:00,3.0,3.0,3.0 +667,2019-03-25 09:00:00,3.0,3.0,3.0 +668,2019-03-25 12:00:00,3.0,3.0,3.0 +669,2019-03-25 15:00:00,3.0,3.0,3.0 +670,2019-03-25 18:00:00,3.0,3.0,3.0 +671,2019-03-25 21:00:00,3.0,3.0,3.0 +672,2019-03-26 00:00:00,3.0,3.0,3.0 +673,2019-03-26 03:00:00,3.0,3.0,3.0 +674,2019-03-26 06:00:00,3.0,3.0,3.0 +675,2019-03-26 09:00:00,3.0,3.0,3.0 +676,2019-03-26 12:00:00,3.0,3.0,3.0 +677,2019-03-26 15:00:00,3.0,3.0,3.0 +678,2019-03-26 18:00:00,3.0,3.0,3.0 +679,2019-03-26 21:00:00,3.0,3.0,3.0 +680,2019-03-27 00:00:00,3.0,3.0,3.0 +681,2019-03-27 03:00:00,3.0,3.0,3.0 +682,2019-03-27 06:00:00,3.0,3.0,3.0 +683,2019-03-27 09:00:00,3.0,3.0,3.0 +684,2019-03-27 12:00:00,3.0,3.0,3.0 +685,2019-03-27 15:00:00,3.0,3.0,3.0 +686,2019-03-27 18:00:00,3.0,3.0,3.0 +687,2019-03-27 21:00:00,3.0,3.0,3.0 +688,2019-03-28 00:00:00,3.0,3.0,3.0 +689,2019-03-28 03:00:00,3.0,3.0,3.0 +690,2019-03-28 06:00:00,3.0,3.0,3.0 +691,2019-03-28 09:00:00,3.0,3.0,3.0 +692,2019-03-28 12:00:00,3.0,3.0,3.0 +693,2019-03-28 15:00:00,3.0,3.0,3.0 +694,2019-03-28 18:00:00,3.0,3.0,3.0 +695,2019-03-28 21:00:00,3.0,3.0,3.0 +696,2019-03-29 00:00:00,3.0,3.0,3.0 +697,2019-03-29 03:00:00,3.0,3.0,3.0 +698,2019-03-29 06:00:00,3.0,3.0,3.0 +699,2019-03-29 09:00:00,3.0,3.0,3.0 +700,2019-03-29 12:00:00,3.0,3.0,3.0 +701,2019-03-29 15:00:00,3.0,3.0,3.0 +702,2019-03-29 18:00:00,3.0,3.0,3.0 +703,2019-03-29 21:00:00,3.0,3.0,3.0 +704,2019-03-30 00:00:00,3.0,3.0,3.0 +705,2019-03-30 03:00:00,3.0,3.0,3.0 +706,2019-03-30 06:00:00,3.0,3.0,3.0 +707,2019-03-30 09:00:00,3.0,3.0,3.0 +708,2019-03-30 12:00:00,3.0,3.0,3.0 +709,2019-03-30 15:00:00,3.0,3.0,3.0 +710,2019-03-30 18:00:00,3.0,3.0,3.0 +711,2019-03-30 21:00:00,3.0,3.0,3.0 +712,2019-03-31 00:00:00,3.0,3.0,3.0 +713,2019-03-31 03:00:00,3.0,3.0,3.0 +714,2019-03-31 06:00:00,3.0,3.0,3.0 +715,2019-03-31 09:00:00,3.0,3.0,3.0 +716,2019-03-31 12:00:00,3.0,3.0,3.0 +717,2019-03-31 15:00:00,3.0,3.0,3.0 +718,2019-03-31 18:00:00,3.0,3.0,3.0 +719,2019-03-31 21:00:00,3.0,3.0,3.0 +720,2019-04-01 00:00:00,3.0,3.0,3.0 +721,2019-04-01 03:00:00,3.0,3.0,3.0 +722,2019-04-01 06:00:00,3.0,3.0,3.0 +723,2019-04-01 09:00:00,3.0,3.0,3.0 +724,2019-04-01 12:00:00,3.0,3.0,3.0 +725,2019-04-01 15:00:00,3.0,3.0,3.0 +726,2019-04-01 18:00:00,3.0,3.0,3.0 +727,2019-04-01 21:00:00,3.0,3.0,3.0 +728,2019-04-02 00:00:00,3.0,3.0,3.0 +729,2019-04-02 03:00:00,3.0,3.0,3.0 +730,2019-04-02 06:00:00,3.0,3.0,3.0 +731,2019-04-02 09:00:00,3.0,3.0,3.0 +732,2019-04-02 12:00:00,3.0,3.0,3.0 +733,2019-04-02 15:00:00,3.0,3.0,3.0 +734,2019-04-02 18:00:00,3.0,3.0,3.0 +735,2019-04-02 21:00:00,3.0,3.0,3.0 +736,2019-04-03 00:00:00,3.0,3.0,3.0 +737,2019-04-03 03:00:00,3.0,3.0,3.0 +738,2019-04-03 06:00:00,3.0,3.0,3.0 +739,2019-04-03 09:00:00,3.0,3.0,3.0 +740,2019-04-03 12:00:00,3.0,3.0,3.0 +741,2019-04-03 15:00:00,3.0,3.0,3.0 +742,2019-04-03 18:00:00,3.0,3.0,3.0 +743,2019-04-03 21:00:00,3.0,3.0,3.0 +744,2019-04-04 00:00:00,3.0,3.0,3.0 +745,2019-04-04 03:00:00,3.0,3.0,3.0 +746,2019-04-04 06:00:00,3.0,3.0,3.0 +747,2019-04-04 09:00:00,3.0,3.0,3.0 +748,2019-04-04 12:00:00,3.0,3.0,3.0 +749,2019-04-04 15:00:00,3.0,3.0,3.0 +750,2019-04-04 18:00:00,3.0,3.0,3.0 +751,2019-04-04 21:00:00,3.0,3.0,3.0 +752,2019-04-05 00:00:00,3.0,3.0,3.0 +753,2019-04-05 03:00:00,3.0,3.0,3.0 +754,2019-04-05 06:00:00,3.0,3.0,3.0 +755,2019-04-05 09:00:00,3.0,3.0,3.0 +756,2019-04-05 12:00:00,3.0,3.0,3.0 +757,2019-04-05 15:00:00,3.0,3.0,3.0 +758,2019-04-05 18:00:00,3.0,3.0,3.0 +759,2019-04-05 21:00:00,3.0,3.0,3.0 +760,2019-04-06 00:00:00,3.0,3.0,3.0 +761,2019-04-06 03:00:00,3.0,3.0,3.0 +762,2019-04-06 06:00:00,3.0,3.0,3.0 +763,2019-04-06 09:00:00,3.0,3.0,3.0 +764,2019-04-06 12:00:00,3.0,3.0,3.0 +765,2019-04-06 15:00:00,3.0,3.0,3.0 +766,2019-04-06 18:00:00,3.0,3.0,3.0 +767,2019-04-06 21:00:00,3.0,3.0,3.0 +768,2019-04-07 00:00:00,3.0,3.0,3.0 +769,2019-04-07 03:00:00,3.0,3.0,3.0 +770,2019-04-07 06:00:00,3.0,3.0,3.0 +771,2019-04-07 09:00:00,3.0,3.0,3.0 +772,2019-04-07 12:00:00,3.0,3.0,3.0 +773,2019-04-07 15:00:00,3.0,3.0,3.0 +774,2019-04-07 18:00:00,3.0,3.0,3.0 +775,2019-04-07 21:00:00,3.0,3.0,3.0 +776,2019-04-08 00:00:00,3.0,3.0,3.0 +777,2019-04-08 03:00:00,3.0,3.0,3.0 +778,2019-04-08 06:00:00,3.0,3.0,3.0 +779,2019-04-08 09:00:00,3.0,3.0,3.0 +780,2019-04-08 12:00:00,3.0,3.0,3.0 +781,2019-04-08 15:00:00,3.0,3.0,3.0 +782,2019-04-08 18:00:00,3.0,3.0,3.0 +783,2019-04-08 21:00:00,3.0,3.0,3.0 +784,2019-04-09 00:00:00,3.0,3.0,3.0 +785,2019-04-09 03:00:00,3.0,3.0,3.0 +786,2019-04-09 06:00:00,3.0,3.0,3.0 +787,2019-04-09 09:00:00,3.0,3.0,3.0 +788,2019-04-09 12:00:00,3.0,3.0,3.0 +789,2019-04-09 15:00:00,3.0,3.0,3.0 +790,2019-04-09 18:00:00,3.0,3.0,3.0 +791,2019-04-09 21:00:00,3.0,3.0,3.0 +792,2019-04-10 00:00:00,3.0,3.0,3.0 +793,2019-04-10 03:00:00,3.0,3.0,3.0 +794,2019-04-10 06:00:00,3.0,3.0,3.0 +795,2019-04-10 09:00:00,3.0,3.0,3.0 +796,2019-04-10 12:00:00,3.0,3.0,3.0 +797,2019-04-10 15:00:00,3.0,3.0,3.0 +798,2019-04-10 18:00:00,3.0,3.0,3.0 +799,2019-04-10 21:00:00,3.0,3.0,3.0 +800,2019-04-11 00:00:00,3.0,3.0,3.0 +801,2019-04-11 03:00:00,3.0,3.0,3.0 +802,2019-04-11 06:00:00,3.0,3.0,3.0 +803,2019-04-11 09:00:00,3.0,3.0,3.0 +804,2019-04-11 12:00:00,3.0,3.0,3.0 +805,2019-04-11 15:00:00,3.0,3.0,3.0 +806,2019-04-11 18:00:00,3.0,3.0,3.0 +807,2019-04-11 21:00:00,3.0,3.0,3.0 +808,2019-04-12 00:00:00,3.0,3.0,3.0 +809,2019-04-12 03:00:00,3.0,3.0,3.0 +810,2019-04-12 06:00:00,3.0,3.0,3.0 +811,2019-04-12 09:00:00,3.0,3.0,3.0 +812,2019-04-12 12:00:00,3.0,3.0,3.0 +813,2019-04-12 15:00:00,3.0,3.0,3.0 +814,2019-04-12 18:00:00,3.0,3.0,3.0 +815,2019-04-12 21:00:00,3.0,3.0,3.0 +816,2019-04-13 00:00:00,3.0,3.0,3.0 +817,2019-04-13 03:00:00,3.0,3.0,3.0 +818,2019-04-13 06:00:00,3.0,3.0,3.0 +819,2019-04-13 09:00:00,3.0,3.0,3.0 +820,2019-04-13 12:00:00,3.0,3.0,3.0 +821,2019-04-13 15:00:00,3.0,3.0,3.0 +822,2019-04-13 18:00:00,3.0,3.0,3.0 +823,2019-04-13 21:00:00,3.0,3.0,3.0 +824,2019-04-14 00:00:00,3.0,3.0,3.0 +825,2019-04-14 03:00:00,3.0,3.0,3.0 +826,2019-04-14 06:00:00,3.0,3.0,3.0 +827,2019-04-14 09:00:00,3.0,3.0,3.0 +828,2019-04-14 12:00:00,3.0,3.0,3.0 +829,2019-04-14 15:00:00,3.0,3.0,3.0 +830,2019-04-14 18:00:00,3.0,3.0,3.0 +831,2019-04-14 21:00:00,3.0,3.0,3.0 +832,2019-04-15 00:00:00,3.0,3.0,3.0 +833,2019-04-15 03:00:00,3.0,3.0,3.0 +834,2019-04-15 06:00:00,3.0,3.0,3.0 +835,2019-04-15 09:00:00,3.0,3.0,3.0 +836,2019-04-15 12:00:00,3.0,3.0,3.0 +837,2019-04-15 15:00:00,3.0,3.0,3.0 +838,2019-04-15 18:00:00,3.0,3.0,3.0 +839,2019-04-15 21:00:00,3.0,3.0,3.0 +840,2019-04-16 00:00:00,3.0,3.0,3.0 +841,2019-04-16 03:00:00,3.0,3.0,3.0 +842,2019-04-16 06:00:00,3.0,3.0,3.0 +843,2019-04-16 09:00:00,3.0,3.0,3.0 +844,2019-04-16 12:00:00,3.0,3.0,3.0 +845,2019-04-16 15:00:00,3.0,3.0,3.0 +846,2019-04-16 18:00:00,3.0,3.0,3.0 +847,2019-04-16 21:00:00,3.0,3.0,3.0 +848,2019-04-17 00:00:00,3.0,3.0,3.0 +849,2019-04-17 03:00:00,3.0,3.0,3.0 +850,2019-04-17 06:00:00,3.0,3.0,3.0 +851,2019-04-17 09:00:00,3.0,3.0,3.0 +852,2019-04-17 12:00:00,3.0,3.0,3.0 +853,2019-04-17 15:00:00,3.0,3.0,3.0 +854,2019-04-17 18:00:00,3.0,3.0,3.0 +855,2019-04-17 21:00:00,3.0,3.0,3.0 +856,2019-04-18 00:00:00,3.0,3.0,3.0 +857,2019-04-18 03:00:00,3.0,3.0,3.0 +858,2019-04-18 06:00:00,3.0,3.0,3.0 +859,2019-04-18 09:00:00,3.0,3.0,3.0 +860,2019-04-18 12:00:00,3.0,3.0,3.0 +861,2019-04-18 15:00:00,3.0,3.0,3.0 +862,2019-04-18 18:00:00,3.0,3.0,3.0 +863,2019-04-18 21:00:00,3.0,3.0,3.0 +864,2019-04-19 00:00:00,3.0,3.0,3.0 +865,2019-04-19 03:00:00,3.0,3.0,3.0 +866,2019-04-19 06:00:00,3.0,3.0,3.0 +867,2019-04-19 09:00:00,3.0,3.0,3.0 +868,2019-04-19 12:00:00,3.0,3.0,3.0 +869,2019-04-19 15:00:00,3.0,3.0,3.0 +870,2019-04-19 18:00:00,3.0,3.0,3.0 +871,2019-04-19 21:00:00,3.0,3.0,3.0 +872,2019-04-20 00:00:00,3.0,3.0,3.0 +873,2019-04-20 03:00:00,3.0,3.0,3.0 +874,2019-04-20 06:00:00,3.0,3.0,3.0 +875,2019-04-20 09:00:00,3.0,3.0,3.0 +876,2019-04-20 12:00:00,3.0,3.0,3.0 +877,2019-04-20 15:00:00,3.0,3.0,3.0 +878,2019-04-20 18:00:00,3.0,3.0,3.0 +879,2019-04-20 21:00:00,3.0,3.0,3.0 +880,2019-04-21 00:00:00,3.0,3.0,3.0 +881,2019-04-21 03:00:00,3.0,3.0,3.0 +882,2019-04-21 06:00:00,3.0,3.0,3.0 +883,2019-04-21 09:00:00,3.0,3.0,3.0 +884,2019-04-21 12:00:00,3.0,3.0,3.0 +885,2019-04-21 15:00:00,3.0,3.0,3.0 +886,2019-04-21 18:00:00,3.0,3.0,3.0 +887,2019-04-21 21:00:00,3.0,3.0,3.0 +888,2019-04-22 00:00:00,3.0,3.0,3.0 +889,2019-04-22 03:00:00,3.0,3.0,3.0 +890,2019-04-22 06:00:00,3.0,3.0,3.0 +891,2019-04-22 09:00:00,3.0,3.0,3.0 +892,2019-04-22 12:00:00,3.0,3.0,3.0 +893,2019-04-22 15:00:00,3.0,3.0,3.0 +894,2019-04-22 18:00:00,3.0,3.0,3.0 +895,2019-04-22 21:00:00,3.0,3.0,3.0 +896,2019-04-23 00:00:00,3.0,3.0,3.0 +897,2019-04-23 03:00:00,3.0,3.0,3.0 +898,2019-04-23 06:00:00,3.0,3.0,3.0 +899,2019-04-23 09:00:00,3.0,3.0,3.0 +900,2019-04-23 12:00:00,3.0,3.0,3.0 +901,2019-04-23 15:00:00,3.0,3.0,3.0 +902,2019-04-23 18:00:00,3.0,3.0,3.0 +903,2019-04-23 21:00:00,3.0,3.0,3.0 +904,2019-04-24 00:00:00,3.0,3.0,3.0 +905,2019-04-24 03:00:00,3.0,3.0,3.0 +906,2019-04-24 06:00:00,3.0,3.0,3.0 +907,2019-04-24 09:00:00,3.0,3.0,3.0 +908,2019-04-24 12:00:00,3.0,3.0,3.0 +909,2019-04-24 15:00:00,3.0,3.0,3.0 +910,2019-04-24 18:00:00,3.0,3.0,3.0 +911,2019-04-24 21:00:00,3.0,3.0,3.0 +912,2019-04-25 00:00:00,3.0,3.0,3.0 +913,2019-04-25 03:00:00,3.0,3.0,3.0 +914,2019-04-25 06:00:00,3.0,3.0,3.0 +915,2019-04-25 09:00:00,3.0,3.0,3.0 +916,2019-04-25 12:00:00,3.0,3.0,3.0 +917,2019-04-25 15:00:00,3.0,3.0,3.0 +918,2019-04-25 18:00:00,3.0,3.0,3.0 +919,2019-04-25 21:00:00,3.0,3.0,3.0 +920,2019-04-26 00:00:00,3.0,3.0,3.0 +921,2019-04-26 03:00:00,3.0,3.0,3.0 +922,2019-04-26 06:00:00,3.0,3.0,3.0 +923,2019-04-26 09:00:00,3.0,3.0,3.0 +924,2019-04-26 12:00:00,3.0,3.0,3.0 +925,2019-04-26 15:00:00,3.0,3.0,3.0 +926,2019-04-26 18:00:00,3.0,3.0,3.0 +927,2019-04-26 21:00:00,3.0,3.0,3.0 +928,2019-04-27 00:00:00,3.0,3.0,3.0 +929,2019-04-27 03:00:00,3.0,3.0,3.0 +930,2019-04-27 06:00:00,3.0,3.0,3.0 +931,2019-04-27 09:00:00,3.0,3.0,3.0 +932,2019-04-27 12:00:00,3.0,3.0,3.0 +933,2019-04-27 15:00:00,3.0,3.0,3.0 +934,2019-04-27 18:00:00,3.0,3.0,3.0 +935,2019-04-27 21:00:00,3.0,3.0,3.0 +936,2019-04-28 00:00:00,3.0,3.0,3.0 +937,2019-04-28 03:00:00,3.0,3.0,3.0 +938,2019-04-28 06:00:00,3.0,3.0,3.0 +939,2019-04-28 09:00:00,3.0,3.0,3.0 +940,2019-04-28 12:00:00,3.0,3.0,3.0 +941,2019-04-28 15:00:00,3.0,3.0,3.0 +942,2019-04-28 18:00:00,3.0,3.0,3.0 +943,2019-04-28 21:00:00,3.0,3.0,3.0 +944,2019-04-29 00:00:00,3.0,3.0,3.0 +945,2019-04-29 03:00:00,3.0,3.0,3.0 +946,2019-04-29 06:00:00,3.0,3.0,3.0 +947,2019-04-29 09:00:00,3.0,3.0,3.0 +948,2019-04-29 12:00:00,3.0,3.0,3.0 +949,2019-04-29 15:00:00,3.0,3.0,3.0 +950,2019-04-29 18:00:00,3.0,3.0,3.0 +951,2019-04-29 21:00:00,3.0,3.0,3.0 +952,2019-04-30 00:00:00,3.0,3.0,3.0 +953,2019-04-30 03:00:00,3.0,3.0,3.0 +954,2019-04-30 06:00:00,3.0,3.0,3.0 +955,2019-04-30 09:00:00,3.0,3.0,3.0 +956,2019-04-30 12:00:00,3.0,3.0,3.0 +957,2019-04-30 15:00:00,3.0,3.0,3.0 +958,2019-04-30 18:00:00,3.0,3.0,3.0 +959,2019-04-30 21:00:00,3.0,3.0,3.0 +960,2019-05-01 00:00:00,3.0,3.0,3.0 +961,2019-05-01 03:00:00,3.0,3.0,3.0 +962,2019-05-01 06:00:00,3.0,3.0,3.0 +963,2019-05-01 09:00:00,3.0,3.0,3.0 +964,2019-05-01 12:00:00,3.0,3.0,3.0 +965,2019-05-01 15:00:00,3.0,3.0,3.0 +966,2019-05-01 18:00:00,3.0,3.0,3.0 +967,2019-05-01 21:00:00,3.0,3.0,3.0 +968,2019-05-02 00:00:00,3.0,3.0,3.0 +969,2019-05-02 03:00:00,3.0,3.0,3.0 +970,2019-05-02 06:00:00,3.0,3.0,3.0 +971,2019-05-02 09:00:00,3.0,3.0,3.0 +972,2019-05-02 12:00:00,3.0,3.0,3.0 +973,2019-05-02 15:00:00,3.0,3.0,3.0 +974,2019-05-02 18:00:00,3.0,3.0,3.0 +975,2019-05-02 21:00:00,3.0,3.0,3.0 +976,2019-05-03 00:00:00,3.0,3.0,3.0 +977,2019-05-03 03:00:00,3.0,3.0,3.0 +978,2019-05-03 06:00:00,3.0,3.0,3.0 +979,2019-05-03 09:00:00,3.0,3.0,3.0 +980,2019-05-03 12:00:00,3.0,3.0,3.0 +981,2019-05-03 15:00:00,3.0,3.0,3.0 +982,2019-05-03 18:00:00,3.0,3.0,3.0 +983,2019-05-03 21:00:00,3.0,3.0,3.0 +984,2019-05-04 00:00:00,3.0,3.0,3.0 +985,2019-05-04 03:00:00,3.0,3.0,3.0 +986,2019-05-04 06:00:00,3.0,3.0,3.0 +987,2019-05-04 09:00:00,3.0,3.0,3.0 +988,2019-05-04 12:00:00,3.0,3.0,3.0 +989,2019-05-04 15:00:00,3.0,3.0,3.0 +990,2019-05-04 18:00:00,3.0,3.0,3.0 +991,2019-05-04 21:00:00,3.0,3.0,3.0 +992,2019-05-05 00:00:00,3.0,3.0,3.0 +993,2019-05-05 03:00:00,3.0,3.0,3.0 +994,2019-05-05 06:00:00,3.0,3.0,3.0 +995,2019-05-05 09:00:00,3.0,3.0,3.0 +996,2019-05-05 12:00:00,3.0,3.0,3.0 +997,2019-05-05 15:00:00,3.0,3.0,3.0 +998,2019-05-05 18:00:00,3.0,3.0,3.0 +999,2019-05-05 21:00:00,3.0,3.0,3.0 +1000,2019-05-06 00:00:00,3.0,3.0,3.0 +1001,2019-05-06 03:00:00,3.0,3.0,3.0 +1002,2019-05-06 06:00:00,3.0,3.0,3.0 +1003,2019-05-06 09:00:00,3.0,3.0,3.0 +1004,2019-05-06 12:00:00,3.0,3.0,3.0 +1005,2019-05-06 15:00:00,3.0,3.0,3.0 +1006,2019-05-06 18:00:00,3.0,3.0,3.0 +1007,2019-05-06 21:00:00,3.0,3.0,3.0 +1008,2019-05-07 00:00:00,3.0,3.0,3.0 +1009,2019-05-07 03:00:00,3.0,3.0,3.0 +1010,2019-05-07 06:00:00,3.0,3.0,3.0 +1011,2019-05-07 09:00:00,3.0,3.0,3.0 +1012,2019-05-07 12:00:00,3.0,3.0,3.0 +1013,2019-05-07 15:00:00,3.0,3.0,3.0 +1014,2019-05-07 18:00:00,3.0,3.0,3.0 +1015,2019-05-07 21:00:00,3.0,3.0,3.0 +1016,2019-05-08 00:00:00,3.0,3.0,3.0 +1017,2019-05-08 03:00:00,3.0,3.0,3.0 +1018,2019-05-08 06:00:00,3.0,3.0,3.0 +1019,2019-05-08 09:00:00,3.0,3.0,3.0 +1020,2019-05-08 12:00:00,3.0,3.0,3.0 +1021,2019-05-08 15:00:00,3.0,3.0,3.0 +1022,2019-05-08 18:00:00,3.0,3.0,3.0 +1023,2019-05-08 21:00:00,3.0,3.0,3.0 +1024,2019-05-09 00:00:00,3.0,3.0,3.0 +1025,2019-05-09 03:00:00,3.0,3.0,3.0 +1026,2019-05-09 06:00:00,3.0,3.0,3.0 +1027,2019-05-09 09:00:00,3.0,3.0,3.0 +1028,2019-05-09 12:00:00,3.0,3.0,3.0 +1029,2019-05-09 15:00:00,3.0,3.0,3.0 +1030,2019-05-09 18:00:00,3.0,3.0,3.0 +1031,2019-05-09 21:00:00,3.0,3.0,3.0 +1032,2019-05-10 00:00:00,3.0,3.0,3.0 +1033,2019-05-10 03:00:00,3.0,3.0,3.0 +1034,2019-05-10 06:00:00,3.0,3.0,3.0 +1035,2019-05-10 09:00:00,3.0,3.0,3.0 +1036,2019-05-10 12:00:00,3.0,3.0,3.0 +1037,2019-05-10 15:00:00,3.0,3.0,3.0 +1038,2019-05-10 18:00:00,3.0,3.0,3.0 +1039,2019-05-10 21:00:00,3.0,3.0,3.0 +1040,2019-05-11 00:00:00,3.0,3.0,3.0 +1041,2019-05-11 03:00:00,3.0,3.0,3.0 +1042,2019-05-11 06:00:00,3.0,3.0,3.0 +1043,2019-05-11 09:00:00,3.0,3.0,3.0 +1044,2019-05-11 12:00:00,3.0,3.0,3.0 +1045,2019-05-11 15:00:00,3.0,3.0,3.0 +1046,2019-05-11 18:00:00,3.0,3.0,3.0 +1047,2019-05-11 21:00:00,3.0,3.0,3.0 +1048,2019-05-12 00:00:00,3.0,3.0,3.0 +1049,2019-05-12 03:00:00,3.0,3.0,3.0 +1050,2019-05-12 06:00:00,3.0,3.0,3.0 +1051,2019-05-12 09:00:00,3.0,3.0,3.0 +1052,2019-05-12 12:00:00,3.0,3.0,3.0 +1053,2019-05-12 15:00:00,3.0,3.0,3.0 +1054,2019-05-12 18:00:00,3.0,3.0,3.0 +1055,2019-05-12 21:00:00,3.0,3.0,3.0 +1056,2019-05-13 00:00:00,3.0,3.0,3.0 +1057,2019-05-13 03:00:00,3.0,3.0,3.0 +1058,2019-05-13 06:00:00,3.0,3.0,3.0 +1059,2019-05-13 09:00:00,3.0,3.0,3.0 +1060,2019-05-13 12:00:00,3.0,3.0,3.0 +1061,2019-05-13 15:00:00,3.0,3.0,3.0 +1062,2019-05-13 18:00:00,3.0,3.0,3.0 +1063,2019-05-13 21:00:00,3.0,3.0,3.0 +1064,2019-05-14 00:00:00,3.0,3.0,3.0 +1065,2019-05-14 03:00:00,3.0,3.0,3.0 +1066,2019-05-14 06:00:00,3.0,3.0,3.0 +1067,2019-05-14 09:00:00,3.0,3.0,3.0 +1068,2019-05-14 12:00:00,3.0,3.0,3.0 +1069,2019-05-14 15:00:00,3.0,3.0,3.0 +1070,2019-05-14 18:00:00,3.0,3.0,3.0 +1071,2019-05-14 21:00:00,3.0,3.0,3.0 +1072,2019-05-15 00:00:00,3.0,3.0,3.0 +1073,2019-05-15 03:00:00,3.0,3.0,3.0 +1074,2019-05-15 06:00:00,3.0,3.0,3.0 +1075,2019-05-15 09:00:00,3.0,3.0,3.0 +1076,2019-05-15 12:00:00,3.0,3.0,3.0 +1077,2019-05-15 15:00:00,3.0,3.0,3.0 +1078,2019-05-15 18:00:00,3.0,3.0,3.0 +1079,2019-05-15 21:00:00,3.0,3.0,3.0 +1080,2019-05-16 00:00:00,3.0,3.0,3.0 +1081,2019-05-16 03:00:00,3.0,3.0,3.0 +1082,2019-05-16 06:00:00,3.0,3.0,3.0 +1083,2019-05-16 09:00:00,3.0,3.0,3.0 +1084,2019-05-16 12:00:00,3.0,3.0,3.0 +1085,2019-05-16 15:00:00,3.0,3.0,3.0 +1086,2019-05-16 18:00:00,3.0,3.0,3.0 +1087,2019-05-16 21:00:00,3.0,3.0,3.0 +1088,2019-05-17 00:00:00,3.0,3.0,3.0 +1089,2019-05-17 03:00:00,3.0,3.0,3.0 +1090,2019-05-17 06:00:00,3.0,3.0,3.0 +1091,2019-05-17 09:00:00,3.0,3.0,3.0 +1092,2019-05-17 12:00:00,3.0,3.0,3.0 +1093,2019-05-17 15:00:00,3.0,3.0,3.0 +1094,2019-05-17 18:00:00,3.0,3.0,3.0 +1095,2019-05-17 21:00:00,3.0,3.0,3.0 +1096,2019-05-18 00:00:00,3.0,3.0,3.0 +1097,2019-05-18 03:00:00,3.0,3.0,3.0 +1098,2019-05-18 06:00:00,3.0,3.0,3.0 +1099,2019-05-18 09:00:00,3.0,3.0,3.0 +1100,2019-05-18 12:00:00,3.0,3.0,3.0 +1101,2019-05-18 15:00:00,3.0,3.0,3.0 +1102,2019-05-18 18:00:00,3.0,3.0,3.0 +1103,2019-05-18 21:00:00,3.0,3.0,3.0 +1104,2019-05-19 00:00:00,3.0,3.0,3.0 +1105,2019-05-19 03:00:00,3.0,3.0,3.0 +1106,2019-05-19 06:00:00,3.0,3.0,3.0 +1107,2019-05-19 09:00:00,3.0,3.0,3.0 +1108,2019-05-19 12:00:00,3.0,3.0,3.0 +1109,2019-05-19 15:00:00,3.0,3.0,3.0 +1110,2019-05-19 18:00:00,3.0,3.0,3.0 +1111,2019-05-19 21:00:00,3.0,3.0,3.0 +1112,2019-05-20 00:00:00,3.0,3.0,3.0 +1113,2019-05-20 03:00:00,3.0,3.0,3.0 +1114,2019-05-20 06:00:00,3.0,3.0,3.0 +1115,2019-05-20 09:00:00,3.0,3.0,3.0 +1116,2019-05-20 12:00:00,3.0,3.0,3.0 +1117,2019-05-20 15:00:00,3.0,3.0,3.0 +1118,2019-05-20 18:00:00,3.0,3.0,3.0 +1119,2019-05-20 21:00:00,3.0,3.0,3.0 +1120,2019-05-21 00:00:00,3.0,3.0,3.0 +1121,2019-05-21 03:00:00,3.0,3.0,3.0 +1122,2019-05-21 06:00:00,3.0,3.0,3.0 +1123,2019-05-21 09:00:00,3.0,3.0,3.0 +1124,2019-05-21 12:00:00,3.0,3.0,3.0 +1125,2019-05-21 15:00:00,3.0,3.0,3.0 +1126,2019-05-21 18:00:00,3.0,3.0,3.0 +1127,2019-05-21 21:00:00,3.0,3.0,3.0 +1128,2019-05-22 00:00:00,3.0,3.0,3.0 +1129,2019-05-22 03:00:00,3.0,3.0,3.0 +1130,2019-05-22 06:00:00,3.0,3.0,3.0 +1131,2019-05-22 09:00:00,3.0,3.0,3.0 +1132,2019-05-22 12:00:00,3.0,3.0,3.0 +1133,2019-05-22 15:00:00,3.0,3.0,3.0 +1134,2019-05-22 18:00:00,3.0,3.0,3.0 +1135,2019-05-22 21:00:00,3.0,3.0,3.0 +1136,2019-05-23 00:00:00,3.0,3.0,3.0 +1137,2019-05-23 03:00:00,3.0,3.0,3.0 +1138,2019-05-23 06:00:00,3.0,3.0,3.0 +1139,2019-05-23 09:00:00,3.0,3.0,3.0 +1140,2019-05-23 12:00:00,3.0,3.0,3.0 +1141,2019-05-23 15:00:00,3.0,3.0,3.0 +1142,2019-05-23 18:00:00,3.0,3.0,3.0 +1143,2019-05-23 21:00:00,3.0,3.0,3.0 +1144,2019-05-24 00:00:00,3.0,3.0,3.0 +1145,2019-05-24 03:00:00,3.0,3.0,3.0 +1146,2019-05-24 06:00:00,3.0,3.0,3.0 +1147,2019-05-24 09:00:00,3.0,3.0,3.0 +1148,2019-05-24 12:00:00,3.0,3.0,3.0 +1149,2019-05-24 15:00:00,3.0,3.0,3.0 +1150,2019-05-24 18:00:00,3.0,3.0,3.0 +1151,2019-05-24 21:00:00,3.0,3.0,3.0 +1152,2019-05-25 00:00:00,3.0,3.0,3.0 +1153,2019-05-25 03:00:00,3.0,3.0,3.0 +1154,2019-05-25 06:00:00,3.0,3.0,3.0 +1155,2019-05-25 09:00:00,3.0,3.0,3.0 +1156,2019-05-25 12:00:00,3.0,3.0,3.0 +1157,2019-05-25 15:00:00,3.0,3.0,3.0 +1158,2019-05-25 18:00:00,3.0,3.0,3.0 +1159,2019-05-25 21:00:00,3.0,3.0,3.0 +1160,2019-05-26 00:00:00,3.0,3.0,3.0 +1161,2019-05-26 03:00:00,3.0,3.0,3.0 +1162,2019-05-26 06:00:00,3.0,3.0,3.0 +1163,2019-05-26 09:00:00,3.0,3.0,3.0 +1164,2019-05-26 12:00:00,3.0,3.0,3.0 +1165,2019-05-26 15:00:00,3.0,3.0,3.0 +1166,2019-05-26 18:00:00,3.0,3.0,3.0 +1167,2019-05-26 21:00:00,3.0,3.0,3.0 +1168,2019-05-27 00:00:00,3.0,3.0,3.0 +1169,2019-05-27 03:00:00,3.0,3.0,3.0 +1170,2019-05-27 06:00:00,3.0,3.0,3.0 +1171,2019-05-27 09:00:00,3.0,3.0,3.0 +1172,2019-05-27 12:00:00,3.0,3.0,3.0 +1173,2019-05-27 15:00:00,3.0,3.0,3.0 +1174,2019-05-27 18:00:00,3.0,3.0,3.0 +1175,2019-05-27 21:00:00,3.0,3.0,3.0 +1176,2019-05-28 00:00:00,3.0,3.0,3.0 +1177,2019-05-28 03:00:00,3.0,3.0,3.0 +1178,2019-05-28 06:00:00,3.0,3.0,3.0 +1179,2019-05-28 09:00:00,3.0,3.0,3.0 +1180,2019-05-28 12:00:00,3.0,3.0,3.0 +1181,2019-05-28 15:00:00,3.0,3.0,3.0 +1182,2019-05-28 18:00:00,3.0,3.0,3.0 +1183,2019-05-28 21:00:00,3.0,3.0,3.0 +1184,2019-05-29 00:00:00,3.0,3.0,3.0 +1185,2019-05-29 03:00:00,3.0,3.0,3.0 +1186,2019-05-29 06:00:00,3.0,3.0,3.0 +1187,2019-05-29 09:00:00,3.0,3.0,3.0 +1188,2019-05-29 12:00:00,3.0,3.0,3.0 +1189,2019-05-29 15:00:00,3.0,3.0,3.0 +1190,2019-05-29 18:00:00,3.0,3.0,3.0 +1191,2019-05-29 21:00:00,3.0,3.0,3.0 +1192,2019-05-30 00:00:00,3.0,3.0,3.0 +1193,2019-05-30 03:00:00,3.0,3.0,3.0 +1194,2019-05-30 06:00:00,3.0,3.0,3.0 +1195,2019-05-30 09:00:00,3.0,3.0,3.0 +1196,2019-05-30 12:00:00,3.0,3.0,3.0 +1197,2019-05-30 15:00:00,3.0,3.0,3.0 +1198,2019-05-30 18:00:00,3.0,3.0,3.0 +1199,2019-05-30 21:00:00,3.0,3.0,3.0 +1200,2019-05-31 00:00:00,3.0,3.0,3.0 +1201,2019-05-31 03:00:00,3.0,3.0,3.0 +1202,2019-05-31 06:00:00,3.0,3.0,3.0 +1203,2019-05-31 09:00:00,3.0,3.0,3.0 +1204,2019-05-31 12:00:00,3.0,3.0,3.0 +1205,2019-05-31 15:00:00,3.0,3.0,3.0 +1206,2019-05-31 18:00:00,3.0,3.0,3.0 +1207,2019-05-31 21:00:00,3.0,3.0,3.0 +1208,2019-06-01 00:00:00,3.0,3.0,3.0 +1209,2019-06-01 03:00:00,3.0,3.0,3.0 +1210,2019-06-01 06:00:00,3.0,3.0,3.0 +1211,2019-06-01 09:00:00,3.0,3.0,3.0 +1212,2019-06-01 12:00:00,3.0,3.0,3.0 +1213,2019-06-01 15:00:00,3.0,3.0,3.0 +1214,2019-06-01 18:00:00,3.0,3.0,3.0 +1215,2019-06-01 21:00:00,3.0,3.0,3.0 +1216,2019-06-02 00:00:00,3.0,3.0,3.0 +1217,2019-06-02 03:00:00,3.0,3.0,3.0 +1218,2019-06-02 06:00:00,3.0,3.0,3.0 +1219,2019-06-02 09:00:00,3.0,3.0,3.0 +1220,2019-06-02 12:00:00,3.0,3.0,3.0 +1221,2019-06-02 15:00:00,3.0,3.0,3.0 +1222,2019-06-02 18:00:00,3.0,3.0,3.0 +1223,2019-06-02 21:00:00,3.0,3.0,3.0 +1224,2019-06-03 00:00:00,3.0,3.0,3.0 +1225,2019-06-03 03:00:00,3.0,3.0,3.0 +1226,2019-06-03 06:00:00,3.0,3.0,3.0 +1227,2019-06-03 09:00:00,3.0,3.0,3.0 +1228,2019-06-03 12:00:00,3.0,3.0,3.0 +1229,2019-06-03 15:00:00,3.0,3.0,3.0 +1230,2019-06-03 18:00:00,3.0,3.0,3.0 +1231,2019-06-03 21:00:00,3.0,3.0,3.0 +1232,2019-06-04 00:00:00,3.0,3.0,3.0 +1233,2019-06-04 03:00:00,3.0,3.0,3.0 +1234,2019-06-04 06:00:00,3.0,3.0,3.0 +1235,2019-06-04 09:00:00,3.0,3.0,3.0 +1236,2019-06-04 12:00:00,3.0,3.0,3.0 +1237,2019-06-04 15:00:00,3.0,3.0,3.0 +1238,2019-06-04 18:00:00,3.0,3.0,3.0 +1239,2019-06-04 21:00:00,3.0,3.0,3.0 +1240,2019-06-05 00:00:00,3.0,3.0,3.0 +1241,2019-06-05 03:00:00,3.0,3.0,3.0 +1242,2019-06-05 06:00:00,3.0,3.0,3.0 +1243,2019-06-05 09:00:00,3.0,3.0,3.0 +1244,2019-06-05 12:00:00,3.0,3.0,3.0 +1245,2019-06-05 15:00:00,3.0,3.0,3.0 +1246,2019-06-05 18:00:00,3.0,3.0,3.0 +1247,2019-06-05 21:00:00,3.0,3.0,3.0 +1248,2019-06-06 00:00:00,3.0,3.0,3.0 +1249,2019-06-06 03:00:00,3.0,3.0,3.0 +1250,2019-06-06 06:00:00,3.0,3.0,3.0 +1251,2019-06-06 09:00:00,3.0,3.0,3.0 +1252,2019-06-06 12:00:00,3.0,3.0,3.0 +1253,2019-06-06 15:00:00,3.0,3.0,3.0 +1254,2019-06-06 18:00:00,3.0,3.0,3.0 +1255,2019-06-06 21:00:00,3.0,3.0,3.0 +1256,2019-06-07 00:00:00,3.0,3.0,3.0 +1257,2019-06-07 03:00:00,3.0,3.0,3.0 +1258,2019-06-07 06:00:00,3.0,3.0,3.0 +1259,2019-06-07 09:00:00,3.0,3.0,3.0 +1260,2019-06-07 12:00:00,3.0,3.0,3.0 +1261,2019-06-07 15:00:00,3.0,3.0,3.0 +1262,2019-06-07 18:00:00,3.0,3.0,3.0 +1263,2019-06-07 21:00:00,3.0,3.0,3.0 +1264,2019-06-08 00:00:00,3.0,3.0,3.0 +1265,2019-06-08 03:00:00,3.0,3.0,3.0 +1266,2019-06-08 06:00:00,3.0,3.0,3.0 +1267,2019-06-08 09:00:00,3.0,3.0,3.0 +1268,2019-06-08 12:00:00,3.0,3.0,3.0 +1269,2019-06-08 15:00:00,3.0,3.0,3.0 +1270,2019-06-08 18:00:00,3.0,3.0,3.0 +1271,2019-06-08 21:00:00,3.0,3.0,3.0 +1272,2019-06-09 00:00:00,3.0,3.0,3.0 +1273,2019-06-09 03:00:00,3.0,3.0,3.0 +1274,2019-06-09 06:00:00,3.0,3.0,3.0 +1275,2019-06-09 09:00:00,3.0,3.0,3.0 +1276,2019-06-09 12:00:00,3.0,3.0,3.0 +1277,2019-06-09 15:00:00,3.0,3.0,3.0 +1278,2019-06-09 18:00:00,3.0,3.0,3.0 +1279,2019-06-09 21:00:00,3.0,3.0,3.0 +1280,2019-06-10 00:00:00,3.0,3.0,3.0 +1281,2019-06-10 03:00:00,3.0,3.0,3.0 +1282,2019-06-10 06:00:00,3.0,3.0,3.0 +1283,2019-06-10 09:00:00,3.0,3.0,3.0 +1284,2019-06-10 12:00:00,3.0,3.0,3.0 +1285,2019-06-10 15:00:00,3.0,3.0,3.0 +1286,2019-06-10 18:00:00,3.0,3.0,3.0 +1287,2019-06-10 21:00:00,3.0,3.0,3.0 +1288,2019-06-11 00:00:00,3.0,3.0,3.0 +1289,2019-06-11 03:00:00,3.0,3.0,3.0 +1290,2019-06-11 06:00:00,3.0,3.0,3.0 +1291,2019-06-11 09:00:00,3.0,3.0,3.0 +1292,2019-06-11 12:00:00,3.0,3.0,3.0 +1293,2019-06-11 15:00:00,3.0,3.0,3.0 +1294,2019-06-11 18:00:00,3.0,3.0,3.0 +1295,2019-06-11 21:00:00,3.0,3.0,3.0 +1296,2019-06-12 00:00:00,3.0,3.0,3.0 +1297,2019-06-12 03:00:00,3.0,3.0,3.0 +1298,2019-06-12 06:00:00,3.0,3.0,3.0 +1299,2019-06-12 09:00:00,3.0,3.0,3.0 +1300,2019-06-12 12:00:00,3.0,3.0,3.0 +1301,2019-06-12 15:00:00,3.0,3.0,3.0 +1302,2019-06-12 18:00:00,3.0,3.0,3.0 +1303,2019-06-12 21:00:00,3.0,3.0,3.0 +1304,2019-06-13 00:00:00,3.0,3.0,3.0 +1305,2019-06-13 03:00:00,3.0,3.0,3.0 +1306,2019-06-13 06:00:00,3.0,3.0,3.0 +1307,2019-06-13 09:00:00,3.0,3.0,3.0 +1308,2019-06-13 12:00:00,3.0,3.0,3.0 +1309,2019-06-13 15:00:00,3.0,3.0,3.0 +1310,2019-06-13 18:00:00,3.0,3.0,3.0 +1311,2019-06-13 21:00:00,3.0,3.0,3.0 +1312,2019-06-14 00:00:00,3.0,3.0,3.0 +1313,2019-06-14 03:00:00,3.0,3.0,3.0 +1314,2019-06-14 06:00:00,3.0,3.0,3.0 +1315,2019-06-14 09:00:00,3.0,3.0,3.0 +1316,2019-06-14 12:00:00,3.0,3.0,3.0 +1317,2019-06-14 15:00:00,3.0,3.0,3.0 +1318,2019-06-14 18:00:00,3.0,3.0,3.0 +1319,2019-06-14 21:00:00,3.0,3.0,3.0 +1320,2019-06-15 00:00:00,3.0,3.0,3.0 +1321,2019-06-15 03:00:00,3.0,3.0,3.0 +1322,2019-06-15 06:00:00,3.0,3.0,3.0 +1323,2019-06-15 09:00:00,3.0,3.0,3.0 +1324,2019-06-15 12:00:00,3.0,3.0,3.0 +1325,2019-06-15 15:00:00,3.0,3.0,3.0 +1326,2019-06-15 18:00:00,3.0,3.0,3.0 +1327,2019-06-15 21:00:00,3.0,3.0,3.0 +1328,2019-06-16 00:00:00,3.0,3.0,3.0 +1329,2019-06-16 03:00:00,3.0,3.0,3.0 +1330,2019-06-16 06:00:00,3.0,3.0,3.0 +1331,2019-06-16 09:00:00,3.0,3.0,3.0 +1332,2019-06-16 12:00:00,3.0,3.0,3.0 +1333,2019-06-16 15:00:00,3.0,3.0,3.0 +1334,2019-06-16 18:00:00,3.0,3.0,3.0 +1335,2019-06-16 21:00:00,3.0,3.0,3.0 +1336,2019-06-17 00:00:00,3.0,3.0,3.0 +1337,2019-06-17 03:00:00,3.0,3.0,3.0 +1338,2019-06-17 06:00:00,3.0,3.0,3.0 +1339,2019-06-17 09:00:00,3.0,3.0,3.0 +1340,2019-06-17 12:00:00,3.0,3.0,3.0 +1341,2019-06-17 15:00:00,3.0,3.0,3.0 +1342,2019-06-17 18:00:00,3.0,3.0,3.0 +1343,2019-06-17 21:00:00,3.0,3.0,3.0 +1344,2019-06-18 00:00:00,3.0,3.0,3.0 +1345,2019-06-18 03:00:00,3.0,3.0,3.0 +1346,2019-06-18 06:00:00,3.0,3.0,3.0 +1347,2019-06-18 09:00:00,3.0,3.0,3.0 +1348,2019-06-18 12:00:00,3.0,3.0,3.0 +1349,2019-06-18 15:00:00,3.0,3.0,3.0 +1350,2019-06-18 18:00:00,3.0,3.0,3.0 +1351,2019-06-18 21:00:00,3.0,3.0,3.0 +1352,2019-06-19 00:00:00,3.0,3.0,3.0 +1353,2019-06-19 03:00:00,3.0,3.0,3.0 +1354,2019-06-19 06:00:00,3.0,3.0,3.0 +1355,2019-06-19 09:00:00,3.0,3.0,3.0 +1356,2019-06-19 12:00:00,3.0,3.0,3.0 +1357,2019-06-19 15:00:00,3.0,3.0,3.0 +1358,2019-06-19 18:00:00,3.0,3.0,3.0 +1359,2019-06-19 21:00:00,3.0,3.0,3.0 +1360,2019-06-20 00:00:00,3.0,3.0,3.0 +1361,2019-06-20 03:00:00,3.0,3.0,3.0 +1362,2019-06-20 06:00:00,3.0,3.0,3.0 +1363,2019-06-20 09:00:00,3.0,3.0,3.0 +1364,2019-06-20 12:00:00,3.0,3.0,3.0 +1365,2019-06-20 15:00:00,3.0,3.0,3.0 +1366,2019-06-20 18:00:00,3.0,3.0,3.0 +1367,2019-06-20 21:00:00,3.0,3.0,3.0 +1368,2019-06-21 00:00:00,3.0,3.0,3.0 +1369,2019-06-21 03:00:00,3.0,3.0,3.0 +1370,2019-06-21 06:00:00,3.0,3.0,3.0 +1371,2019-06-21 09:00:00,3.0,3.0,3.0 +1372,2019-06-21 12:00:00,3.0,3.0,3.0 +1373,2019-06-21 15:00:00,3.0,3.0,3.0 +1374,2019-06-21 18:00:00,3.0,3.0,3.0 +1375,2019-06-21 21:00:00,3.0,3.0,3.0 +1376,2019-06-22 00:00:00,3.0,3.0,3.0 +1377,2019-06-22 03:00:00,3.0,3.0,3.0 +1378,2019-06-22 06:00:00,3.0,3.0,3.0 +1379,2019-06-22 09:00:00,3.0,3.0,3.0 +1380,2019-06-22 12:00:00,3.0,3.0,3.0 +1381,2019-06-22 15:00:00,3.0,3.0,3.0 +1382,2019-06-22 18:00:00,3.0,3.0,3.0 +1383,2019-06-22 21:00:00,3.0,3.0,3.0 +1384,2019-06-23 00:00:00,3.0,3.0,3.0 +1385,2019-06-23 03:00:00,3.0,3.0,3.0 +1386,2019-06-23 06:00:00,3.0,3.0,3.0 +1387,2019-06-23 09:00:00,3.0,3.0,3.0 +1388,2019-06-23 12:00:00,3.0,3.0,3.0 +1389,2019-06-23 15:00:00,3.0,3.0,3.0 +1390,2019-06-23 18:00:00,3.0,3.0,3.0 +1391,2019-06-23 21:00:00,3.0,3.0,3.0 +1392,2019-06-24 00:00:00,3.0,3.0,3.0 +1393,2019-06-24 03:00:00,3.0,3.0,3.0 +1394,2019-06-24 06:00:00,3.0,3.0,3.0 +1395,2019-06-24 09:00:00,3.0,3.0,3.0 +1396,2019-06-24 12:00:00,3.0,3.0,3.0 +1397,2019-06-24 15:00:00,3.0,3.0,3.0 +1398,2019-06-24 18:00:00,3.0,3.0,3.0 +1399,2019-06-24 21:00:00,3.0,3.0,3.0 +1400,2019-06-25 00:00:00,3.0,3.0,3.0 +1401,2019-06-25 03:00:00,3.0,3.0,3.0 +1402,2019-06-25 06:00:00,3.0,3.0,3.0 +1403,2019-06-25 09:00:00,3.0,3.0,3.0 +1404,2019-06-25 12:00:00,3.0,3.0,3.0 +1405,2019-06-25 15:00:00,3.0,3.0,3.0 +1406,2019-06-25 18:00:00,3.0,3.0,3.0 +1407,2019-06-25 21:00:00,3.0,3.0,3.0 +1408,2019-06-26 00:00:00,3.0,3.0,3.0 +1409,2019-06-26 03:00:00,3.0,3.0,3.0 +1410,2019-06-26 06:00:00,3.0,3.0,3.0 +1411,2019-06-26 09:00:00,3.0,3.0,3.0 +1412,2019-06-26 12:00:00,3.0,3.0,3.0 +1413,2019-06-26 15:00:00,3.0,3.0,3.0 +1414,2019-06-26 18:00:00,3.0,3.0,3.0 +1415,2019-06-26 21:00:00,3.0,3.0,3.0 +1416,2019-06-27 00:00:00,3.0,3.0,3.0 +1417,2019-06-27 03:00:00,3.0,3.0,3.0 +1418,2019-06-27 06:00:00,3.0,3.0,3.0 +1419,2019-06-27 09:00:00,3.0,3.0,3.0 +1420,2019-06-27 12:00:00,3.0,3.0,3.0 +1421,2019-06-27 15:00:00,3.0,3.0,3.0 +1422,2019-06-27 18:00:00,3.0,3.0,3.0 +1423,2019-06-27 21:00:00,3.0,3.0,3.0 +1424,2019-06-28 00:00:00,3.0,3.0,3.0 +1425,2019-06-28 03:00:00,3.0,3.0,3.0 +1426,2019-06-28 06:00:00,3.0,3.0,3.0 +1427,2019-06-28 09:00:00,3.0,3.0,3.0 +1428,2019-06-28 12:00:00,3.0,3.0,3.0 +1429,2019-06-28 15:00:00,3.0,3.0,3.0 +1430,2019-06-28 18:00:00,3.0,3.0,3.0 +1431,2019-06-28 21:00:00,3.0,3.0,3.0 +1432,2019-06-29 00:00:00,3.0,3.0,3.0 +1433,2019-06-29 03:00:00,3.0,3.0,3.0 +1434,2019-06-29 06:00:00,3.0,3.0,3.0 +1435,2019-06-29 09:00:00,3.0,3.0,3.0 +1436,2019-06-29 12:00:00,3.0,3.0,3.0 +1437,2019-06-29 15:00:00,3.0,3.0,3.0 +1438,2019-06-29 18:00:00,3.0,3.0,3.0 +1439,2019-06-29 21:00:00,3.0,3.0,3.0 +1440,2019-06-30 00:00:00,3.0,3.0,3.0 +1441,2019-06-30 03:00:00,3.0,3.0,3.0 +1442,2019-06-30 06:00:00,3.0,3.0,3.0 +1443,2019-06-30 09:00:00,3.0,3.0,3.0 +1444,2019-06-30 12:00:00,3.0,3.0,3.0 +1445,2019-06-30 15:00:00,3.0,3.0,3.0 +1446,2019-06-30 18:00:00,3.0,3.0,3.0 +1447,2019-06-30 21:00:00,3.0,3.0,3.0 +1448,2019-07-01 00:00:00,3.0,3.0,3.0 +1449,2019-07-01 03:00:00,3.0,3.0,3.0 +1450,2019-07-01 06:00:00,3.0,3.0,3.0 +1451,2019-07-01 09:00:00,3.0,3.0,3.0 +1452,2019-07-01 12:00:00,3.0,3.0,3.0 +1453,2019-07-01 15:00:00,3.0,3.0,3.0 +1454,2019-07-01 18:00:00,3.0,3.0,3.0 +1455,2019-07-01 21:00:00,3.0,3.0,3.0 +1456,2019-07-02 00:00:00,3.0,3.0,3.0 +1457,2019-07-02 03:00:00,3.0,3.0,3.0 +1458,2019-07-02 06:00:00,3.0,3.0,3.0 +1459,2019-07-02 09:00:00,3.0,3.0,3.0 +1460,2019-07-02 12:00:00,3.0,3.0,3.0 +1461,2019-07-02 15:00:00,3.0,3.0,3.0 +1462,2019-07-02 18:00:00,3.0,3.0,3.0 +1463,2019-07-02 21:00:00,3.0,3.0,3.0 +1464,2019-07-03 00:00:00,3.0,3.0,3.0 +1465,2019-07-03 03:00:00,3.0,3.0,3.0 +1466,2019-07-03 06:00:00,3.0,3.0,3.0 +1467,2019-07-03 09:00:00,3.0,3.0,3.0 +1468,2019-07-03 12:00:00,3.0,3.0,3.0 +1469,2019-07-03 15:00:00,3.0,3.0,3.0 +1470,2019-07-03 18:00:00,3.0,3.0,3.0 +1471,2019-07-03 21:00:00,3.0,3.0,3.0 +1472,2019-07-04 00:00:00,3.0,3.0,3.0 +1473,2019-07-04 03:00:00,3.0,3.0,3.0 +1474,2019-07-04 06:00:00,3.0,3.0,3.0 +1475,2019-07-04 09:00:00,3.0,3.0,3.0 +1476,2019-07-04 12:00:00,3.0,3.0,3.0 +1477,2019-07-04 15:00:00,3.0,3.0,3.0 +1478,2019-07-04 18:00:00,3.0,3.0,3.0 +1479,2019-07-04 21:00:00,3.0,3.0,3.0 +1480,2019-07-05 00:00:00,3.0,3.0,3.0 +1481,2019-07-05 03:00:00,3.0,3.0,3.0 +1482,2019-07-05 06:00:00,3.0,3.0,3.0 +1483,2019-07-05 09:00:00,3.0,3.0,3.0 +1484,2019-07-05 12:00:00,3.0,3.0,3.0 +1485,2019-07-05 15:00:00,3.0,3.0,3.0 +1486,2019-07-05 18:00:00,3.0,3.0,3.0 +1487,2019-07-05 21:00:00,3.0,3.0,3.0 +1488,2019-07-06 00:00:00,3.0,3.0,3.0 +1489,2019-07-06 03:00:00,3.0,3.0,3.0 +1490,2019-07-06 06:00:00,3.0,3.0,3.0 +1491,2019-07-06 09:00:00,3.0,3.0,3.0 +1492,2019-07-06 12:00:00,3.0,3.0,3.0 +1493,2019-07-06 15:00:00,3.0,3.0,3.0 +1494,2019-07-06 18:00:00,3.0,3.0,3.0 +1495,2019-07-06 21:00:00,3.0,3.0,3.0 +1496,2019-07-07 00:00:00,3.0,3.0,3.0 +1497,2019-07-07 03:00:00,3.0,3.0,3.0 +1498,2019-07-07 06:00:00,3.0,3.0,3.0 +1499,2019-07-07 09:00:00,3.0,3.0,3.0 +1500,2019-07-07 12:00:00,3.0,3.0,3.0 +1501,2019-07-07 15:00:00,3.0,3.0,3.0 +1502,2019-07-07 18:00:00,3.0,3.0,3.0 +1503,2019-07-07 21:00:00,3.0,3.0,3.0 +1504,2019-07-08 00:00:00,3.0,3.0,3.0 +1505,2019-07-08 03:00:00,3.0,3.0,3.0 +1506,2019-07-08 06:00:00,3.0,3.0,3.0 +1507,2019-07-08 09:00:00,3.0,3.0,3.0 +1508,2019-07-08 12:00:00,3.0,3.0,3.0 +1509,2019-07-08 15:00:00,3.0,3.0,3.0 +1510,2019-07-08 18:00:00,3.0,3.0,3.0 +1511,2019-07-08 21:00:00,3.0,3.0,3.0 +1512,2019-07-09 00:00:00,3.0,3.0,3.0 +1513,2019-07-09 03:00:00,3.0,3.0,3.0 +1514,2019-07-09 06:00:00,3.0,3.0,3.0 +1515,2019-07-09 09:00:00,3.0,3.0,3.0 +1516,2019-07-09 12:00:00,3.0,3.0,3.0 +1517,2019-07-09 15:00:00,3.0,3.0,3.0 +1518,2019-07-09 18:00:00,3.0,3.0,3.0 +1519,2019-07-09 21:00:00,3.0,3.0,3.0 +1520,2019-07-10 00:00:00,3.0,3.0,3.0 +1521,2019-07-10 03:00:00,3.0,3.0,3.0 +1522,2019-07-10 06:00:00,3.0,3.0,3.0 +1523,2019-07-10 09:00:00,3.0,3.0,3.0 +1524,2019-07-10 12:00:00,3.0,3.0,3.0 +1525,2019-07-10 15:00:00,3.0,3.0,3.0 +1526,2019-07-10 18:00:00,3.0,3.0,3.0 +1527,2019-07-10 21:00:00,3.0,3.0,3.0 +1528,2019-07-11 00:00:00,3.0,3.0,3.0 +1529,2019-07-11 03:00:00,3.0,3.0,3.0 +1530,2019-07-11 06:00:00,3.0,3.0,3.0 +1531,2019-07-11 09:00:00,3.0,3.0,3.0 +1532,2019-07-11 12:00:00,3.0,3.0,3.0 +1533,2019-07-11 15:00:00,3.0,3.0,3.0 +1534,2019-07-11 18:00:00,3.0,3.0,3.0 +1535,2019-07-11 21:00:00,3.0,3.0,3.0 +1536,2019-07-12 00:00:00,3.0,3.0,3.0 +1537,2019-07-12 03:00:00,3.0,3.0,3.0 +1538,2019-07-12 06:00:00,3.0,3.0,3.0 +1539,2019-07-12 09:00:00,3.0,3.0,3.0 +1540,2019-07-12 12:00:00,3.0,3.0,3.0 +1541,2019-07-12 15:00:00,3.0,3.0,3.0 +1542,2019-07-12 18:00:00,3.0,3.0,3.0 +1543,2019-07-12 21:00:00,3.0,3.0,3.0 +1544,2019-07-13 00:00:00,3.0,3.0,3.0 +1545,2019-07-13 03:00:00,3.0,3.0,3.0 +1546,2019-07-13 06:00:00,3.0,3.0,3.0 +1547,2019-07-13 09:00:00,3.0,3.0,3.0 +1548,2019-07-13 12:00:00,3.0,3.0,3.0 +1549,2019-07-13 15:00:00,3.0,3.0,3.0 +1550,2019-07-13 18:00:00,3.0,3.0,3.0 +1551,2019-07-13 21:00:00,3.0,3.0,3.0 +1552,2019-07-14 00:00:00,3.0,3.0,3.0 +1553,2019-07-14 03:00:00,3.0,3.0,3.0 +1554,2019-07-14 06:00:00,3.0,3.0,3.0 +1555,2019-07-14 09:00:00,3.0,3.0,3.0 +1556,2019-07-14 12:00:00,3.0,3.0,3.0 +1557,2019-07-14 15:00:00,3.0,3.0,3.0 +1558,2019-07-14 18:00:00,3.0,3.0,3.0 +1559,2019-07-14 21:00:00,3.0,3.0,3.0 +1560,2019-07-15 00:00:00,3.0,3.0,3.0 +1561,2019-07-15 03:00:00,3.0,3.0,3.0 +1562,2019-07-15 06:00:00,3.0,3.0,3.0 +1563,2019-07-15 09:00:00,3.0,3.0,3.0 +1564,2019-07-15 12:00:00,3.0,3.0,3.0 +1565,2019-07-15 15:00:00,3.0,3.0,3.0 +1566,2019-07-15 18:00:00,3.0,3.0,3.0 +1567,2019-07-15 21:00:00,3.0,3.0,3.0 +1568,2019-07-16 00:00:00,3.0,3.0,3.0 +1569,2019-07-16 03:00:00,3.0,3.0,3.0 +1570,2019-07-16 06:00:00,3.0,3.0,3.0 +1571,2019-07-16 09:00:00,3.0,3.0,3.0 +1572,2019-07-16 12:00:00,3.0,3.0,3.0 +1573,2019-07-16 15:00:00,3.0,3.0,3.0 +1574,2019-07-16 18:00:00,3.0,3.0,3.0 +1575,2019-07-16 21:00:00,3.0,3.0,3.0 +1576,2019-07-17 00:00:00,3.0,3.0,3.0 +1577,2019-07-17 03:00:00,3.0,3.0,3.0 +1578,2019-07-17 06:00:00,3.0,3.0,3.0 +1579,2019-07-17 09:00:00,3.0,3.0,3.0 +1580,2019-07-17 12:00:00,3.0,3.0,3.0 +1581,2019-07-17 15:00:00,3.0,3.0,3.0 +1582,2019-07-17 18:00:00,3.0,3.0,3.0 +1583,2019-07-17 21:00:00,3.0,3.0,3.0 +1584,2019-07-18 00:00:00,3.0,3.0,3.0 +1585,2019-07-18 03:00:00,3.0,3.0,3.0 +1586,2019-07-18 06:00:00,3.0,3.0,3.0 +1587,2019-07-18 09:00:00,3.0,3.0,3.0 +1588,2019-07-18 12:00:00,3.0,3.0,3.0 +1589,2019-07-18 15:00:00,3.0,3.0,3.0 +1590,2019-07-18 18:00:00,3.0,3.0,3.0 +1591,2019-07-18 21:00:00,3.0,3.0,3.0 +1592,2019-07-19 00:00:00,3.0,3.0,3.0 +1593,2019-07-19 03:00:00,3.0,3.0,3.0 +1594,2019-07-19 06:00:00,3.0,3.0,3.0 +1595,2019-07-19 09:00:00,3.0,3.0,3.0 +1596,2019-07-19 12:00:00,3.0,3.0,3.0 +1597,2019-07-19 15:00:00,3.0,3.0,3.0 +1598,2019-07-19 18:00:00,3.0,3.0,3.0 +1599,2019-07-19 21:00:00,3.0,3.0,3.0 +1600,2019-07-20 00:00:00,3.0,3.0,3.0 +1601,2019-07-20 03:00:00,3.0,3.0,3.0 +1602,2019-07-20 06:00:00,3.0,3.0,3.0 +1603,2019-07-20 09:00:00,3.0,3.0,3.0 +1604,2019-07-20 12:00:00,3.0,3.0,3.0 +1605,2019-07-20 15:00:00,3.0,3.0,3.0 +1606,2019-07-20 18:00:00,3.0,3.0,3.0 +1607,2019-07-20 21:00:00,3.0,3.0,3.0 +1608,2019-07-21 00:00:00,3.0,3.0,3.0 +1609,2019-07-21 03:00:00,3.0,3.0,3.0 +1610,2019-07-21 06:00:00,3.0,3.0,3.0 +1611,2019-07-21 09:00:00,3.0,3.0,3.0 +1612,2019-07-21 12:00:00,3.0,3.0,3.0 +1613,2019-07-21 15:00:00,3.0,3.0,3.0 +1614,2019-07-21 18:00:00,3.0,3.0,3.0 +1615,2019-07-21 21:00:00,3.0,3.0,3.0 +1616,2019-07-22 00:00:00,3.0,3.0,3.0 +1617,2019-07-22 03:00:00,3.0,3.0,3.0 +1618,2019-07-22 06:00:00,3.0,3.0,3.0 +1619,2019-07-22 09:00:00,3.0,3.0,3.0 +1620,2019-07-22 12:00:00,3.0,3.0,3.0 +1621,2019-07-22 15:00:00,3.0,3.0,3.0 +1622,2019-07-22 18:00:00,3.0,3.0,3.0 +1623,2019-07-22 21:00:00,3.0,3.0,3.0 +1624,2019-07-23 00:00:00,3.0,3.0,3.0 +1625,2019-07-23 03:00:00,3.0,3.0,3.0 +1626,2019-07-23 06:00:00,3.0,3.0,3.0 +1627,2019-07-23 09:00:00,3.0,3.0,3.0 +1628,2019-07-23 12:00:00,3.0,3.0,3.0 +1629,2019-07-23 15:00:00,3.0,3.0,3.0 +1630,2019-07-23 18:00:00,3.0,3.0,3.0 +1631,2019-07-23 21:00:00,3.0,3.0,3.0 +1632,2019-07-24 00:00:00,3.0,3.0,3.0 +1633,2019-07-24 03:00:00,3.0,3.0,3.0 +1634,2019-07-24 06:00:00,3.0,3.0,3.0 +1635,2019-07-24 09:00:00,3.0,3.0,3.0 +1636,2019-07-24 12:00:00,3.0,3.0,3.0 +1637,2019-07-24 15:00:00,3.0,3.0,3.0 +1638,2019-07-24 18:00:00,3.0,3.0,3.0 +1639,2019-07-24 21:00:00,3.0,3.0,3.0 +1640,2019-07-25 00:00:00,3.0,3.0,3.0 +1641,2019-07-25 03:00:00,3.0,3.0,3.0 +1642,2019-07-25 06:00:00,3.0,3.0,3.0 +1643,2019-07-25 09:00:00,3.0,3.0,3.0 +1644,2019-07-25 12:00:00,3.0,3.0,3.0 +1645,2019-07-25 15:00:00,3.0,3.0,3.0 +1646,2019-07-25 18:00:00,3.0,3.0,3.0 +1647,2019-07-25 21:00:00,3.0,3.0,3.0 +1648,2019-07-26 00:00:00,3.0,3.0,3.0 +1649,2019-07-26 03:00:00,3.0,3.0,3.0 +1650,2019-07-26 06:00:00,3.0,3.0,3.0 +1651,2019-07-26 09:00:00,3.0,3.0,3.0 +1652,2019-07-26 12:00:00,3.0,3.0,3.0 +1653,2019-07-26 15:00:00,3.0,3.0,3.0 +1654,2019-07-26 18:00:00,3.0,3.0,3.0 +1655,2019-07-26 21:00:00,3.0,3.0,3.0 +1656,2019-07-27 00:00:00,3.0,3.0,3.0 +1657,2019-07-27 03:00:00,3.0,3.0,3.0 +1658,2019-07-27 06:00:00,3.0,3.0,3.0 +1659,2019-07-27 09:00:00,3.0,3.0,3.0 +1660,2019-07-27 12:00:00,3.0,3.0,3.0 +1661,2019-07-27 15:00:00,3.0,3.0,3.0 +1662,2019-07-27 18:00:00,3.0,3.0,3.0 +1663,2019-07-27 21:00:00,3.0,3.0,3.0 +1664,2019-07-28 00:00:00,3.0,3.0,3.0 +1665,2019-07-28 03:00:00,3.0,3.0,3.0 +1666,2019-07-28 06:00:00,3.0,3.0,3.0 +1667,2019-07-28 09:00:00,3.0,3.0,3.0 +1668,2019-07-28 12:00:00,3.0,3.0,3.0 +1669,2019-07-28 15:00:00,3.0,3.0,3.0 +1670,2019-07-28 18:00:00,3.0,3.0,3.0 +1671,2019-07-28 21:00:00,3.0,3.0,3.0 +1672,2019-07-29 00:00:00,3.0,3.0,3.0 +1673,2019-07-29 03:00:00,3.0,3.0,3.0 +1674,2019-07-29 06:00:00,3.0,3.0,3.0 +1675,2019-07-29 09:00:00,3.0,3.0,3.0 +1676,2019-07-29 12:00:00,3.0,3.0,3.0 +1677,2019-07-29 15:00:00,3.0,3.0,3.0 +1678,2019-07-29 18:00:00,3.0,3.0,3.0 +1679,2019-07-29 21:00:00,3.0,3.0,3.0 +1680,2019-07-30 00:00:00,3.0,3.0,3.0 +1681,2019-07-30 03:00:00,3.0,3.0,3.0 +1682,2019-07-30 06:00:00,3.0,3.0,3.0 +1683,2019-07-30 09:00:00,3.0,3.0,3.0 +1684,2019-07-30 12:00:00,3.0,3.0,3.0 +1685,2019-07-30 15:00:00,3.0,3.0,3.0 +1686,2019-07-30 18:00:00,3.0,3.0,3.0 +1687,2019-07-30 21:00:00,3.0,3.0,3.0 +1688,2019-07-31 00:00:00,3.0,3.0,3.0 +1689,2019-07-31 03:00:00,3.0,3.0,3.0 +1690,2019-07-31 06:00:00,3.0,3.0,3.0 +1691,2019-07-31 09:00:00,3.0,3.0,3.0 +1692,2019-07-31 12:00:00,3.0,3.0,3.0 +1693,2019-07-31 15:00:00,3.0,3.0,3.0 +1694,2019-07-31 18:00:00,3.0,3.0,3.0 +1695,2019-07-31 21:00:00,3.0,3.0,3.0 +1696,2019-08-01 00:00:00,3.0,3.0,3.0 +1697,2019-08-01 03:00:00,3.0,3.0,3.0 +1698,2019-08-01 06:00:00,3.0,3.0,3.0 +1699,2019-08-01 09:00:00,3.0,3.0,3.0 +1700,2019-08-01 12:00:00,3.0,3.0,3.0 +1701,2019-08-01 15:00:00,3.0,3.0,3.0 +1702,2019-08-01 18:00:00,3.0,3.0,3.0 +1703,2019-08-01 21:00:00,3.0,3.0,3.0 +1704,2019-08-02 00:00:00,3.0,3.0,3.0 +1705,2019-08-02 03:00:00,3.0,3.0,3.0 +1706,2019-08-02 06:00:00,3.0,3.0,3.0 +1707,2019-08-02 09:00:00,3.0,3.0,3.0 +1708,2019-08-02 12:00:00,3.0,3.0,3.0 +1709,2019-08-02 15:00:00,3.0,3.0,3.0 +1710,2019-08-02 18:00:00,3.0,3.0,3.0 +1711,2019-08-02 21:00:00,3.0,3.0,3.0 +1712,2019-08-03 00:00:00,3.0,3.0,3.0 +1713,2019-08-03 03:00:00,3.0,3.0,3.0 +1714,2019-08-03 06:00:00,3.0,3.0,3.0 +1715,2019-08-03 09:00:00,3.0,3.0,3.0 +1716,2019-08-03 12:00:00,3.0,3.0,3.0 +1717,2019-08-03 15:00:00,3.0,3.0,3.0 +1718,2019-08-03 18:00:00,3.0,3.0,3.0 +1719,2019-08-03 21:00:00,3.0,3.0,3.0 +1720,2019-08-04 00:00:00,3.0,3.0,3.0 +1721,2019-08-04 03:00:00,3.0,3.0,3.0 +1722,2019-08-04 06:00:00,3.0,3.0,3.0 +1723,2019-08-04 09:00:00,3.0,3.0,3.0 +1724,2019-08-04 12:00:00,3.0,3.0,3.0 +1725,2019-08-04 15:00:00,3.0,3.0,3.0 +1726,2019-08-04 18:00:00,3.0,3.0,3.0 +1727,2019-08-04 21:00:00,3.0,3.0,3.0 +1728,2019-08-05 00:00:00,3.0,3.0,3.0 +1729,2019-08-05 03:00:00,3.0,3.0,3.0 +1730,2019-08-05 06:00:00,3.0,3.0,3.0 +1731,2019-08-05 09:00:00,3.0,3.0,3.0 +1732,2019-08-05 12:00:00,3.0,3.0,3.0 +1733,2019-08-05 15:00:00,3.0,3.0,3.0 +1734,2019-08-05 18:00:00,3.0,3.0,3.0 +1735,2019-08-05 21:00:00,3.0,3.0,3.0 +1736,2019-08-06 00:00:00,3.0,3.0,3.0 +1737,2019-08-06 03:00:00,3.0,3.0,3.0 +1738,2019-08-06 06:00:00,3.0,3.0,3.0 +1739,2019-08-06 09:00:00,3.0,3.0,3.0 +1740,2019-08-06 12:00:00,3.0,3.0,3.0 +1741,2019-08-06 15:00:00,3.0,3.0,3.0 +1742,2019-08-06 18:00:00,3.0,3.0,3.0 +1743,2019-08-06 21:00:00,3.0,3.0,3.0 +1744,2019-08-07 00:00:00,3.0,3.0,3.0 +1745,2019-08-07 03:00:00,3.0,3.0,3.0 +1746,2019-08-07 06:00:00,3.0,3.0,3.0 +1747,2019-08-07 09:00:00,3.0,3.0,3.0 +1748,2019-08-07 12:00:00,3.0,3.0,3.0 +1749,2019-08-07 15:00:00,3.0,3.0,3.0 +1750,2019-08-07 18:00:00,3.0,3.0,3.0 +1751,2019-08-07 21:00:00,3.0,3.0,3.0 +1752,2019-08-08 00:00:00,3.0,3.0,3.0 +1753,2019-08-08 03:00:00,3.0,3.0,3.0 +1754,2019-08-08 06:00:00,3.0,3.0,3.0 +1755,2019-08-08 09:00:00,3.0,3.0,3.0 +1756,2019-08-08 12:00:00,3.0,3.0,3.0 +1757,2019-08-08 15:00:00,3.0,3.0,3.0 +1758,2019-08-08 18:00:00,3.0,3.0,3.0 +1759,2019-08-08 21:00:00,3.0,3.0,3.0 +1760,2019-08-09 00:00:00,3.0,3.0,3.0 +1761,2019-08-09 03:00:00,3.0,3.0,3.0 +1762,2019-08-09 06:00:00,3.0,3.0,3.0 +1763,2019-08-09 09:00:00,3.0,3.0,3.0 +1764,2019-08-09 12:00:00,3.0,3.0,3.0 +1765,2019-08-09 15:00:00,3.0,3.0,3.0 +1766,2019-08-09 18:00:00,3.0,3.0,3.0 +1767,2019-08-09 21:00:00,3.0,3.0,3.0 +1768,2019-08-10 00:00:00,3.0,3.0,3.0 +1769,2019-08-10 03:00:00,3.0,3.0,3.0 +1770,2019-08-10 06:00:00,3.0,3.0,3.0 +1771,2019-08-10 09:00:00,3.0,3.0,3.0 +1772,2019-08-10 12:00:00,3.0,3.0,3.0 +1773,2019-08-10 15:00:00,3.0,3.0,3.0 +1774,2019-08-10 18:00:00,3.0,3.0,3.0 +1775,2019-08-10 21:00:00,3.0,3.0,3.0 +1776,2019-08-11 00:00:00,3.0,3.0,3.0 +1777,2019-08-11 03:00:00,3.0,3.0,3.0 +1778,2019-08-11 06:00:00,3.0,3.0,3.0 +1779,2019-08-11 09:00:00,3.0,3.0,3.0 +1780,2019-08-11 12:00:00,3.0,3.0,3.0 +1781,2019-08-11 15:00:00,3.0,3.0,3.0 +1782,2019-08-11 18:00:00,3.0,3.0,3.0 +1783,2019-08-11 21:00:00,3.0,3.0,3.0 +1784,2019-08-12 00:00:00,3.0,3.0,3.0 +1785,2019-08-12 03:00:00,3.0,3.0,3.0 +1786,2019-08-12 06:00:00,3.0,3.0,3.0 +1787,2019-08-12 09:00:00,3.0,3.0,3.0 +1788,2019-08-12 12:00:00,3.0,3.0,3.0 +1789,2019-08-12 15:00:00,3.0,3.0,3.0 +1790,2019-08-12 18:00:00,3.0,3.0,3.0 +1791,2019-08-12 21:00:00,3.0,3.0,3.0 +1792,2019-08-13 00:00:00,3.0,3.0,3.0 +1793,2019-08-13 03:00:00,3.0,3.0,3.0 +1794,2019-08-13 06:00:00,3.0,3.0,3.0 +1795,2019-08-13 09:00:00,3.0,3.0,3.0 +1796,2019-08-13 12:00:00,3.0,3.0,3.0 +1797,2019-08-13 15:00:00,3.0,3.0,3.0 +1798,2019-08-13 18:00:00,3.0,3.0,3.0 +1799,2019-08-13 21:00:00,3.0,3.0,3.0 +1800,2019-08-14 00:00:00,3.0,3.0,3.0 +1801,2019-08-14 03:00:00,3.0,3.0,3.0 +1802,2019-08-14 06:00:00,3.0,3.0,3.0 +1803,2019-08-14 09:00:00,3.0,3.0,3.0 +1804,2019-08-14 12:00:00,3.0,3.0,3.0 +1805,2019-08-14 15:00:00,3.0,3.0,3.0 +1806,2019-08-14 18:00:00,3.0,3.0,3.0 +1807,2019-08-14 21:00:00,3.0,3.0,3.0 +1808,2019-08-15 00:00:00,3.0,3.0,3.0 +1809,2019-08-15 03:00:00,3.0,3.0,3.0 +1810,2019-08-15 06:00:00,3.0,3.0,3.0 +1811,2019-08-15 09:00:00,3.0,3.0,3.0 +1812,2019-08-15 12:00:00,3.0,3.0,3.0 +1813,2019-08-15 15:00:00,3.0,3.0,3.0 +1814,2019-08-15 18:00:00,3.0,3.0,3.0 +1815,2019-08-15 21:00:00,3.0,3.0,3.0 +1816,2019-08-16 00:00:00,3.0,3.0,3.0 +1817,2019-08-16 03:00:00,3.0,3.0,3.0 +1818,2019-08-16 06:00:00,3.0,3.0,3.0 +1819,2019-08-16 09:00:00,3.0,3.0,3.0 +1820,2019-08-16 12:00:00,3.0,3.0,3.0 +1821,2019-08-16 15:00:00,3.0,3.0,3.0 +1822,2019-08-16 18:00:00,3.0,3.0,3.0 +1823,2019-08-16 21:00:00,3.0,3.0,3.0 +1824,2019-08-17 00:00:00,3.0,3.0,3.0 +1825,2019-08-17 03:00:00,3.0,3.0,3.0 +1826,2019-08-17 06:00:00,3.0,3.0,3.0 +1827,2019-08-17 09:00:00,3.0,3.0,3.0 +1828,2019-08-17 12:00:00,3.0,3.0,3.0 +1829,2019-08-17 15:00:00,3.0,3.0,3.0 +1830,2019-08-17 18:00:00,3.0,3.0,3.0 +1831,2019-08-17 21:00:00,3.0,3.0,3.0 +1832,2019-08-18 00:00:00,3.0,3.0,3.0 +1833,2019-08-18 03:00:00,3.0,3.0,3.0 +1834,2019-08-18 06:00:00,3.0,3.0,3.0 +1835,2019-08-18 09:00:00,3.0,3.0,3.0 +1836,2019-08-18 12:00:00,3.0,3.0,3.0 +1837,2019-08-18 15:00:00,3.0,3.0,3.0 +1838,2019-08-18 18:00:00,3.0,3.0,3.0 +1839,2019-08-18 21:00:00,3.0,3.0,3.0 +1840,2019-08-19 00:00:00,3.0,3.0,3.0 +1841,2019-08-19 03:00:00,3.0,3.0,3.0 +1842,2019-08-19 06:00:00,3.0,3.0,3.0 +1843,2019-08-19 09:00:00,3.0,3.0,3.0 +1844,2019-08-19 12:00:00,3.0,3.0,3.0 +1845,2019-08-19 15:00:00,3.0,3.0,3.0 +1846,2019-08-19 18:00:00,3.0,3.0,3.0 +1847,2019-08-19 21:00:00,3.0,3.0,3.0 +1848,2019-08-20 00:00:00,3.0,3.0,3.0 +1849,2019-08-20 03:00:00,3.0,3.0,3.0 +1850,2019-08-20 06:00:00,3.0,3.0,3.0 +1851,2019-08-20 09:00:00,3.0,3.0,3.0 +1852,2019-08-20 12:00:00,3.0,3.0,3.0 +1853,2019-08-20 15:00:00,3.0,3.0,3.0 +1854,2019-08-20 18:00:00,3.0,3.0,3.0 +1855,2019-08-20 21:00:00,3.0,3.0,3.0 +1856,2019-08-21 00:00:00,3.0,3.0,3.0 +1857,2019-08-21 03:00:00,3.0,3.0,3.0 +1858,2019-08-21 06:00:00,3.0,3.0,3.0 +1859,2019-08-21 09:00:00,3.0,3.0,3.0 +1860,2019-08-21 12:00:00,3.0,3.0,3.0 +1861,2019-08-21 15:00:00,3.0,3.0,3.0 +1862,2019-08-21 18:00:00,3.0,3.0,3.0 +1863,2019-08-21 21:00:00,3.0,3.0,3.0 +1864,2019-08-22 00:00:00,3.0,3.0,3.0 +1865,2019-08-22 03:00:00,3.0,3.0,3.0 +1866,2019-08-22 06:00:00,3.0,3.0,3.0 +1867,2019-08-22 09:00:00,3.0,3.0,3.0 +1868,2019-08-22 12:00:00,3.0,3.0,3.0 +1869,2019-08-22 15:00:00,3.0,3.0,3.0 +1870,2019-08-22 18:00:00,3.0,3.0,3.0 +1871,2019-08-22 21:00:00,3.0,3.0,3.0 +1872,2019-08-23 00:00:00,3.0,3.0,3.0 +1873,2019-08-23 03:00:00,3.0,3.0,3.0 +1874,2019-08-23 06:00:00,3.0,3.0,3.0 +1875,2019-08-23 09:00:00,3.0,3.0,3.0 +1876,2019-08-23 12:00:00,3.0,3.0,3.0 +1877,2019-08-23 15:00:00,3.0,3.0,3.0 +1878,2019-08-23 18:00:00,3.0,3.0,3.0 +1879,2019-08-23 21:00:00,3.0,3.0,3.0 +1880,2019-08-24 00:00:00,3.0,3.0,3.0 +1881,2019-08-24 03:00:00,3.0,3.0,3.0 +1882,2019-08-24 06:00:00,3.0,3.0,3.0 +1883,2019-08-24 09:00:00,3.0,3.0,3.0 +1884,2019-08-24 12:00:00,3.0,3.0,3.0 +1885,2019-08-24 15:00:00,3.0,3.0,3.0 +1886,2019-08-24 18:00:00,3.0,3.0,3.0 +1887,2019-08-24 21:00:00,3.0,3.0,3.0 +1888,2019-08-25 00:00:00,3.0,3.0,3.0 +1889,2019-08-25 03:00:00,3.0,3.0,3.0 +1890,2019-08-25 06:00:00,3.0,3.0,3.0 +1891,2019-08-25 09:00:00,3.0,3.0,3.0 +1892,2019-08-25 12:00:00,3.0,3.0,3.0 +1893,2019-08-25 15:00:00,3.0,3.0,3.0 +1894,2019-08-25 18:00:00,3.0,3.0,3.0 +1895,2019-08-25 21:00:00,3.0,3.0,3.0 +1896,2019-08-26 00:00:00,3.0,3.0,3.0 +1897,2019-08-26 03:00:00,3.0,3.0,3.0 +1898,2019-08-26 06:00:00,3.0,3.0,3.0 +1899,2019-08-26 09:00:00,3.0,3.0,3.0 +1900,2019-08-26 12:00:00,3.0,3.0,3.0 +1901,2019-08-26 15:00:00,3.0,3.0,3.0 +1902,2019-08-26 18:00:00,3.0,3.0,3.0 +1903,2019-08-26 21:00:00,3.0,3.0,3.0 +1904,2019-08-27 00:00:00,3.0,3.0,3.0 +1905,2019-08-27 03:00:00,3.0,3.0,3.0 +1906,2019-08-27 06:00:00,3.0,3.0,3.0 +1907,2019-08-27 09:00:00,3.0,3.0,3.0 +1908,2019-08-27 12:00:00,3.0,3.0,3.0 +1909,2019-08-27 15:00:00,3.0,3.0,3.0 +1910,2019-08-27 18:00:00,3.0,3.0,3.0 +1911,2019-08-27 21:00:00,3.0,3.0,3.0 +1912,2019-08-28 00:00:00,3.0,3.0,3.0 +1913,2019-08-28 03:00:00,3.0,3.0,3.0 +1914,2019-08-28 06:00:00,3.0,3.0,3.0 +1915,2019-08-28 09:00:00,3.0,3.0,3.0 +1916,2019-08-28 12:00:00,3.0,3.0,3.0 +1917,2019-08-28 15:00:00,3.0,3.0,3.0 +1918,2019-08-28 18:00:00,3.0,3.0,3.0 +1919,2019-08-28 21:00:00,3.0,3.0,3.0 +1920,2019-08-29 00:00:00,3.0,3.0,3.0 +1921,2019-08-29 03:00:00,3.0,3.0,3.0 +1922,2019-08-29 06:00:00,3.0,3.0,3.0 +1923,2019-08-29 09:00:00,3.0,3.0,3.0 +1924,2019-08-29 12:00:00,3.0,3.0,3.0 +1925,2019-08-29 15:00:00,3.0,3.0,3.0 +1926,2019-08-29 18:00:00,3.0,3.0,3.0 +1927,2019-08-29 21:00:00,3.0,3.0,3.0 +1928,2019-08-30 00:00:00,3.0,3.0,3.0 +1929,2019-08-30 03:00:00,3.0,3.0,3.0 +1930,2019-08-30 06:00:00,3.0,3.0,3.0 +1931,2019-08-30 09:00:00,3.0,3.0,3.0 +1932,2019-08-30 12:00:00,3.0,3.0,3.0 +1933,2019-08-30 15:00:00,3.0,3.0,3.0 +1934,2019-08-30 18:00:00,3.0,3.0,3.0 +1935,2019-08-30 21:00:00,3.0,3.0,3.0 +1936,2019-08-31 00:00:00,3.0,3.0,3.0 +1937,2019-08-31 03:00:00,3.0,3.0,3.0 +1938,2019-08-31 06:00:00,3.0,3.0,3.0 +1939,2019-08-31 09:00:00,3.0,3.0,3.0 +1940,2019-08-31 12:00:00,3.0,3.0,3.0 +1941,2019-08-31 15:00:00,3.0,3.0,3.0 +1942,2019-08-31 18:00:00,3.0,3.0,3.0 +1943,2019-08-31 21:00:00,3.0,3.0,3.0 +1944,2019-09-01 00:00:00,3.0,3.0,3.0 +1945,2019-09-01 03:00:00,3.0,3.0,3.0 +1946,2019-09-01 06:00:00,3.0,3.0,3.0 +1947,2019-09-01 09:00:00,3.0,3.0,3.0 +1948,2019-09-01 12:00:00,3.0,3.0,3.0 +1949,2019-09-01 15:00:00,3.0,3.0,3.0 +1950,2019-09-01 18:00:00,3.0,3.0,3.0 +1951,2019-09-01 21:00:00,3.0,3.0,3.0 +1952,2019-09-02 00:00:00,3.0,3.0,3.0 +1953,2019-09-02 03:00:00,3.0,3.0,3.0 +1954,2019-09-02 06:00:00,3.0,3.0,3.0 +1955,2019-09-02 09:00:00,3.0,3.0,3.0 +1956,2019-09-02 12:00:00,3.0,3.0,3.0 +1957,2019-09-02 15:00:00,3.0,3.0,3.0 +1958,2019-09-02 18:00:00,3.0,3.0,3.0 +1959,2019-09-02 21:00:00,3.0,3.0,3.0 +1960,2019-09-03 00:00:00,3.0,3.0,3.0 +1961,2019-09-03 03:00:00,3.0,3.0,3.0 +1962,2019-09-03 06:00:00,3.0,3.0,3.0 +1963,2019-09-03 09:00:00,3.0,3.0,3.0 +1964,2019-09-03 12:00:00,3.0,3.0,3.0 +1965,2019-09-03 15:00:00,3.0,3.0,3.0 +1966,2019-09-03 18:00:00,3.0,3.0,3.0 +1967,2019-09-03 21:00:00,3.0,3.0,3.0 +1968,2019-09-04 00:00:00,3.0,3.0,3.0 +1969,2019-09-04 03:00:00,3.0,3.0,3.0 +1970,2019-09-04 06:00:00,3.0,3.0,3.0 +1971,2019-09-04 09:00:00,3.0,3.0,3.0 +1972,2019-09-04 12:00:00,3.0,3.0,3.0 +1973,2019-09-04 15:00:00,3.0,3.0,3.0 +1974,2019-09-04 18:00:00,3.0,3.0,3.0 +1975,2019-09-04 21:00:00,3.0,3.0,3.0 +1976,2019-09-05 00:00:00,3.0,3.0,3.0 +1977,2019-09-05 03:00:00,3.0,3.0,3.0 +1978,2019-09-05 06:00:00,3.0,3.0,3.0 +1979,2019-09-05 09:00:00,3.0,3.0,3.0 +1980,2019-09-05 12:00:00,3.0,3.0,3.0 +1981,2019-09-05 15:00:00,3.0,3.0,3.0 +1982,2019-09-05 18:00:00,3.0,3.0,3.0 +1983,2019-09-05 21:00:00,3.0,3.0,3.0 +1984,2019-09-06 00:00:00,3.0,3.0,3.0 +1985,2019-09-06 03:00:00,3.0,3.0,3.0 +1986,2019-09-06 06:00:00,3.0,3.0,3.0 +1987,2019-09-06 09:00:00,3.0,3.0,3.0 +1988,2019-09-06 12:00:00,3.0,3.0,3.0 +1989,2019-09-06 15:00:00,3.0,3.0,3.0 +1990,2019-09-06 18:00:00,3.0,3.0,3.0 +1991,2019-09-06 21:00:00,3.0,3.0,3.0 +1992,2019-09-07 00:00:00,3.0,3.0,3.0 +1993,2019-09-07 03:00:00,3.0,3.0,3.0 +1994,2019-09-07 06:00:00,3.0,3.0,3.0 +1995,2019-09-07 09:00:00,3.0,3.0,3.0 +1996,2019-09-07 12:00:00,3.0,3.0,3.0 +1997,2019-09-07 15:00:00,3.0,3.0,3.0 +1998,2019-09-07 18:00:00,3.0,3.0,3.0 +1999,2019-09-07 21:00:00,3.0,3.0,3.0 +2000,2019-09-08 00:00:00,3.0,3.0,3.0 +2001,2019-09-08 03:00:00,3.0,3.0,3.0 +2002,2019-09-08 06:00:00,3.0,3.0,3.0 +2003,2019-09-08 09:00:00,3.0,3.0,3.0 +2004,2019-09-08 12:00:00,3.0,3.0,3.0 +2005,2019-09-08 15:00:00,3.0,3.0,3.0 +2006,2019-09-08 18:00:00,3.0,3.0,3.0 +2007,2019-09-08 21:00:00,3.0,3.0,3.0 +2008,2019-09-09 00:00:00,3.0,3.0,3.0 +2009,2019-09-09 03:00:00,3.0,3.0,3.0 +2010,2019-09-09 06:00:00,3.0,3.0,3.0 +2011,2019-09-09 09:00:00,3.0,3.0,3.0 +2012,2019-09-09 12:00:00,3.0,3.0,3.0 +2013,2019-09-09 15:00:00,3.0,3.0,3.0 +2014,2019-09-09 18:00:00,3.0,3.0,3.0 +2015,2019-09-09 21:00:00,3.0,3.0,3.0 +2016,2019-09-10 00:00:00,3.0,3.0,3.0 +2017,2019-09-10 03:00:00,3.0,3.0,3.0 +2018,2019-09-10 06:00:00,3.0,3.0,3.0 +2019,2019-09-10 09:00:00,3.0,3.0,3.0 +2020,2019-09-10 12:00:00,3.0,3.0,3.0 +2021,2019-09-10 15:00:00,3.0,3.0,3.0 +2022,2019-09-10 18:00:00,3.0,3.0,3.0 +2023,2019-09-10 21:00:00,3.0,3.0,3.0 +2024,2019-09-11 00:00:00,3.0,3.0,3.0 +2025,2019-09-11 03:00:00,3.0,3.0,3.0 +2026,2019-09-11 06:00:00,3.0,3.0,3.0 +2027,2019-09-11 09:00:00,3.0,3.0,3.0 +2028,2019-09-11 12:00:00,3.0,3.0,3.0 +2029,2019-09-11 15:00:00,3.0,3.0,3.0 +2030,2019-09-11 18:00:00,3.0,3.0,3.0 +2031,2019-09-11 21:00:00,3.0,3.0,3.0 +2032,2019-09-12 00:00:00,3.0,3.0,3.0 +2033,2019-09-12 03:00:00,3.0,3.0,3.0 +2034,2019-09-12 06:00:00,3.0,3.0,3.0 +2035,2019-09-12 09:00:00,3.0,3.0,3.0 +2036,2019-09-12 12:00:00,3.0,3.0,3.0 +2037,2019-09-12 15:00:00,3.0,3.0,3.0 +2038,2019-09-12 18:00:00,3.0,3.0,3.0 +2039,2019-09-12 21:00:00,3.0,3.0,3.0 +2040,2019-09-13 00:00:00,3.0,3.0,3.0 +2041,2019-09-13 03:00:00,3.0,3.0,3.0 +2042,2019-09-13 06:00:00,3.0,3.0,3.0 +2043,2019-09-13 09:00:00,3.0,3.0,3.0 +2044,2019-09-13 12:00:00,3.0,3.0,3.0 +2045,2019-09-13 15:00:00,3.0,3.0,3.0 +2046,2019-09-13 18:00:00,3.0,3.0,3.0 +2047,2019-09-13 21:00:00,3.0,3.0,3.0 +2048,2019-09-14 00:00:00,3.0,3.0,3.0 +2049,2019-09-14 03:00:00,3.0,3.0,3.0 +2050,2019-09-14 06:00:00,3.0,3.0,3.0 +2051,2019-09-14 09:00:00,3.0,3.0,3.0 +2052,2019-09-14 12:00:00,3.0,3.0,3.0 +2053,2019-09-14 15:00:00,3.0,3.0,3.0 +2054,2019-09-14 18:00:00,3.0,3.0,3.0 +2055,2019-09-14 21:00:00,3.0,3.0,3.0 +2056,2019-09-15 00:00:00,3.0,3.0,3.0 +2057,2019-09-15 03:00:00,3.0,3.0,3.0 +2058,2019-09-15 06:00:00,3.0,3.0,3.0 +2059,2019-09-15 09:00:00,3.0,3.0,3.0 +2060,2019-09-15 12:00:00,3.0,3.0,3.0 +2061,2019-09-15 15:00:00,3.0,3.0,3.0 +2062,2019-09-15 18:00:00,3.0,3.0,3.0 +2063,2019-09-15 21:00:00,3.0,3.0,3.0 +2064,2019-09-16 00:00:00,3.0,3.0,3.0 +2065,2019-09-16 03:00:00,3.0,3.0,3.0 +2066,2019-09-16 06:00:00,3.0,3.0,3.0 +2067,2019-09-16 09:00:00,3.0,3.0,3.0 +2068,2019-09-16 12:00:00,3.0,3.0,3.0 +2069,2019-09-16 15:00:00,3.0,3.0,3.0 +2070,2019-09-16 18:00:00,3.0,3.0,3.0 +2071,2019-09-16 21:00:00,3.0,3.0,3.0 +2072,2019-09-17 00:00:00,3.0,3.0,3.0 +2073,2019-09-17 03:00:00,3.0,3.0,3.0 +2074,2019-09-17 06:00:00,3.0,3.0,3.0 +2075,2019-09-17 09:00:00,3.0,3.0,3.0 +2076,2019-09-17 12:00:00,3.0,3.0,3.0 +2077,2019-09-17 15:00:00,3.0,3.0,3.0 +2078,2019-09-17 18:00:00,3.0,3.0,3.0 +2079,2019-09-17 21:00:00,3.0,3.0,3.0 +2080,2019-09-18 00:00:00,3.0,3.0,3.0 +2081,2019-09-18 03:00:00,3.0,3.0,3.0 +2082,2019-09-18 06:00:00,3.0,3.0,3.0 +2083,2019-09-18 09:00:00,3.0,3.0,3.0 +2084,2019-09-18 12:00:00,3.0,3.0,3.0 +2085,2019-09-18 15:00:00,3.0,3.0,3.0 +2086,2019-09-18 18:00:00,3.0,3.0,3.0 +2087,2019-09-18 21:00:00,3.0,3.0,3.0 +2088,2019-09-19 00:00:00,3.0,3.0,3.0 +2089,2019-09-19 03:00:00,3.0,3.0,3.0 +2090,2019-09-19 06:00:00,3.0,3.0,3.0 +2091,2019-09-19 09:00:00,3.0,3.0,3.0 +2092,2019-09-19 12:00:00,3.0,3.0,3.0 +2093,2019-09-19 15:00:00,3.0,3.0,3.0 +2094,2019-09-19 18:00:00,3.0,3.0,3.0 +2095,2019-09-19 21:00:00,3.0,3.0,3.0 +2096,2019-09-20 00:00:00,3.0,3.0,3.0 +2097,2019-09-20 03:00:00,3.0,3.0,3.0 +2098,2019-09-20 06:00:00,3.0,3.0,3.0 +2099,2019-09-20 09:00:00,3.0,3.0,3.0 +2100,2019-09-20 12:00:00,3.0,3.0,3.0 +2101,2019-09-20 15:00:00,3.0,3.0,3.0 +2102,2019-09-20 18:00:00,3.0,3.0,3.0 +2103,2019-09-20 21:00:00,3.0,3.0,3.0 +2104,2019-09-21 00:00:00,3.0,3.0,3.0 +2105,2019-09-21 03:00:00,3.0,3.0,3.0 +2106,2019-09-21 06:00:00,3.0,3.0,3.0 +2107,2019-09-21 09:00:00,3.0,3.0,3.0 +2108,2019-09-21 12:00:00,3.0,3.0,3.0 +2109,2019-09-21 15:00:00,3.0,3.0,3.0 +2110,2019-09-21 18:00:00,3.0,3.0,3.0 +2111,2019-09-21 21:00:00,3.0,3.0,3.0 +2112,2019-09-22 00:00:00,3.0,3.0,3.0 +2113,2019-09-22 03:00:00,3.0,3.0,3.0 +2114,2019-09-22 06:00:00,3.0,3.0,3.0 +2115,2019-09-22 09:00:00,3.0,3.0,3.0 +2116,2019-09-22 12:00:00,3.0,3.0,3.0 +2117,2019-09-22 15:00:00,3.0,3.0,3.0 +2118,2019-09-22 18:00:00,3.0,3.0,3.0 +2119,2019-09-22 21:00:00,3.0,3.0,3.0 +2120,2019-09-23 00:00:00,3.0,3.0,3.0 +2121,2019-09-23 03:00:00,3.0,3.0,3.0 +2122,2019-09-23 06:00:00,3.0,3.0,3.0 +2123,2019-09-23 09:00:00,3.0,3.0,3.0 +2124,2019-09-23 12:00:00,3.0,3.0,3.0 +2125,2019-09-23 15:00:00,3.0,3.0,3.0 +2126,2019-09-23 18:00:00,3.0,3.0,3.0 +2127,2019-09-23 21:00:00,3.0,3.0,3.0 +2128,2019-09-24 00:00:00,3.0,3.0,3.0 +2129,2019-09-24 03:00:00,3.0,3.0,3.0 +2130,2019-09-24 06:00:00,3.0,3.0,3.0 +2131,2019-09-24 09:00:00,3.0,3.0,3.0 +2132,2019-09-24 12:00:00,3.0,3.0,3.0 +2133,2019-09-24 15:00:00,3.0,3.0,3.0 +2134,2019-09-24 18:00:00,3.0,3.0,3.0 +2135,2019-09-24 21:00:00,3.0,3.0,3.0 +2136,2019-09-25 00:00:00,3.0,3.0,3.0 +2137,2019-09-25 03:00:00,3.0,3.0,3.0 +2138,2019-09-25 06:00:00,3.0,3.0,3.0 +2139,2019-09-25 09:00:00,3.0,3.0,3.0 +2140,2019-09-25 12:00:00,3.0,3.0,3.0 +2141,2019-09-25 15:00:00,3.0,3.0,3.0 +2142,2019-09-25 18:00:00,3.0,3.0,3.0 +2143,2019-09-25 21:00:00,3.0,3.0,3.0 +2144,2019-09-26 00:00:00,3.0,3.0,3.0 +2145,2019-09-26 03:00:00,3.0,3.0,3.0 +2146,2019-09-26 06:00:00,3.0,3.0,3.0 +2147,2019-09-26 09:00:00,3.0,3.0,3.0 +2148,2019-09-26 12:00:00,3.0,3.0,3.0 +2149,2019-09-26 15:00:00,3.0,3.0,3.0 +2150,2019-09-26 18:00:00,3.0,3.0,3.0 +2151,2019-09-26 21:00:00,3.0,3.0,3.0 +2152,2019-09-27 00:00:00,3.0,3.0,3.0 +2153,2019-09-27 03:00:00,3.0,3.0,3.0 +2154,2019-09-27 06:00:00,3.0,3.0,3.0 +2155,2019-09-27 09:00:00,3.0,3.0,3.0 +2156,2019-09-27 12:00:00,3.0,3.0,3.0 +2157,2019-09-27 15:00:00,3.0,3.0,3.0 +2158,2019-09-27 18:00:00,3.0,3.0,3.0 +2159,2019-09-27 21:00:00,3.0,3.0,3.0 +2160,2019-09-28 00:00:00,3.0,3.0,3.0 +2161,2019-09-28 03:00:00,3.0,3.0,3.0 +2162,2019-09-28 06:00:00,3.0,3.0,3.0 +2163,2019-09-28 09:00:00,3.0,3.0,3.0 +2164,2019-09-28 12:00:00,3.0,3.0,3.0 +2165,2019-09-28 15:00:00,3.0,3.0,3.0 +2166,2019-09-28 18:00:00,3.0,3.0,3.0 +2167,2019-09-28 21:00:00,3.0,3.0,3.0 +2168,2019-09-29 00:00:00,3.0,3.0,3.0 +2169,2019-09-29 03:00:00,3.0,3.0,3.0 +2170,2019-09-29 06:00:00,3.0,3.0,3.0 +2171,2019-09-29 09:00:00,3.0,3.0,3.0 +2172,2019-09-29 12:00:00,3.0,3.0,3.0 +2173,2019-09-29 15:00:00,3.0,3.0,3.0 +2174,2019-09-29 18:00:00,3.0,3.0,3.0 +2175,2019-09-29 21:00:00,3.0,3.0,3.0 +2176,2019-09-30 00:00:00,3.0,3.0,3.0 +2177,2019-09-30 03:00:00,3.0,3.0,3.0 +2178,2019-09-30 06:00:00,3.0,3.0,3.0 +2179,2019-09-30 09:00:00,3.0,3.0,3.0 +2180,2019-09-30 12:00:00,3.0,3.0,3.0 +2181,2019-09-30 15:00:00,3.0,3.0,3.0 +2182,2019-09-30 18:00:00,3.0,3.0,3.0 +2183,2019-09-30 21:00:00,3.0,3.0,3.0 +2184,2019-10-01 00:00:00,3.0,3.0,3.0 +2185,2019-10-01 03:00:00,3.0,3.0,3.0 +2186,2019-10-01 06:00:00,3.0,3.0,3.0 +2187,2019-10-01 09:00:00,3.0,3.0,3.0 +2188,2019-10-01 12:00:00,3.0,3.0,3.0 +2189,2019-10-01 15:00:00,3.0,3.0,3.0 +2190,2019-10-01 18:00:00,3.0,3.0,3.0 +2191,2019-10-01 21:00:00,3.0,3.0,3.0 +2192,2019-10-02 00:00:00,3.0,3.0,3.0 +2193,2019-10-02 03:00:00,3.0,3.0,3.0 +2194,2019-10-02 06:00:00,3.0,3.0,3.0 +2195,2019-10-02 09:00:00,3.0,3.0,3.0 +2196,2019-10-02 12:00:00,3.0,3.0,3.0 +2197,2019-10-02 15:00:00,3.0,3.0,3.0 +2198,2019-10-02 18:00:00,3.0,3.0,3.0 +2199,2019-10-02 21:00:00,3.0,3.0,3.0 +2200,2019-10-03 00:00:00,3.0,3.0,3.0 +2201,2019-10-03 03:00:00,3.0,3.0,3.0 +2202,2019-10-03 06:00:00,3.0,3.0,3.0 +2203,2019-10-03 09:00:00,3.0,3.0,3.0 +2204,2019-10-03 12:00:00,3.0,3.0,3.0 +2205,2019-10-03 15:00:00,3.0,3.0,3.0 +2206,2019-10-03 18:00:00,3.0,3.0,3.0 +2207,2019-10-03 21:00:00,3.0,3.0,3.0 +2208,2019-10-04 00:00:00,3.0,3.0,3.0 +2209,2019-10-04 03:00:00,3.0,3.0,3.0 +2210,2019-10-04 06:00:00,3.0,3.0,3.0 +2211,2019-10-04 09:00:00,3.0,3.0,3.0 +2212,2019-10-04 12:00:00,3.0,3.0,3.0 +2213,2019-10-04 15:00:00,3.0,3.0,3.0 +2214,2019-10-04 18:00:00,3.0,3.0,3.0 +2215,2019-10-04 21:00:00,3.0,3.0,3.0 +2216,2019-10-05 00:00:00,3.0,3.0,3.0 +2217,2019-10-05 03:00:00,3.0,3.0,3.0 +2218,2019-10-05 06:00:00,3.0,3.0,3.0 +2219,2019-10-05 09:00:00,3.0,3.0,3.0 +2220,2019-10-05 12:00:00,3.0,3.0,3.0 +2221,2019-10-05 15:00:00,3.0,3.0,3.0 +2222,2019-10-05 18:00:00,3.0,3.0,3.0 +2223,2019-10-05 21:00:00,3.0,3.0,3.0 +2224,2019-10-06 00:00:00,3.0,3.0,3.0 +2225,2019-10-06 03:00:00,3.0,3.0,3.0 +2226,2019-10-06 06:00:00,3.0,3.0,3.0 +2227,2019-10-06 09:00:00,3.0,3.0,3.0 +2228,2019-10-06 12:00:00,3.0,3.0,3.0 +2229,2019-10-06 15:00:00,3.0,3.0,3.0 +2230,2019-10-06 18:00:00,3.0,3.0,3.0 +2231,2019-10-06 21:00:00,3.0,3.0,3.0 +2232,2019-10-07 00:00:00,3.0,3.0,3.0 +2233,2019-10-07 03:00:00,3.0,3.0,3.0 +2234,2019-10-07 06:00:00,3.0,3.0,3.0 +2235,2019-10-07 09:00:00,3.0,3.0,3.0 +2236,2019-10-07 12:00:00,3.0,3.0,3.0 +2237,2019-10-07 15:00:00,3.0,3.0,3.0 +2238,2019-10-07 18:00:00,3.0,3.0,3.0 +2239,2019-10-07 21:00:00,3.0,3.0,3.0 +2240,2019-10-08 00:00:00,3.0,3.0,3.0 +2241,2019-10-08 03:00:00,3.0,3.0,3.0 +2242,2019-10-08 06:00:00,3.0,3.0,3.0 +2243,2019-10-08 09:00:00,3.0,3.0,3.0 +2244,2019-10-08 12:00:00,3.0,3.0,3.0 +2245,2019-10-08 15:00:00,3.0,3.0,3.0 +2246,2019-10-08 18:00:00,3.0,3.0,3.0 +2247,2019-10-08 21:00:00,3.0,3.0,3.0 +2248,2019-10-09 00:00:00,3.0,3.0,3.0 +2249,2019-10-09 03:00:00,3.0,3.0,3.0 +2250,2019-10-09 06:00:00,3.0,3.0,3.0 +2251,2019-10-09 09:00:00,3.0,3.0,3.0 +2252,2019-10-09 12:00:00,3.0,3.0,3.0 +2253,2019-10-09 15:00:00,3.0,3.0,3.0 +2254,2019-10-09 18:00:00,3.0,3.0,3.0 +2255,2019-10-09 21:00:00,3.0,3.0,3.0 +2256,2019-10-10 00:00:00,3.0,3.0,3.0 +2257,2019-10-10 03:00:00,3.0,3.0,3.0 +2258,2019-10-10 06:00:00,3.0,3.0,3.0 +2259,2019-10-10 09:00:00,3.0,3.0,3.0 +2260,2019-10-10 12:00:00,3.0,3.0,3.0 +2261,2019-10-10 15:00:00,3.0,3.0,3.0 +2262,2019-10-10 18:00:00,3.0,3.0,3.0 +2263,2019-10-10 21:00:00,3.0,3.0,3.0 +2264,2019-10-11 00:00:00,3.0,3.0,3.0 +2265,2019-10-11 03:00:00,3.0,3.0,3.0 +2266,2019-10-11 06:00:00,3.0,3.0,3.0 +2267,2019-10-11 09:00:00,3.0,3.0,3.0 +2268,2019-10-11 12:00:00,3.0,3.0,3.0 +2269,2019-10-11 15:00:00,3.0,3.0,3.0 +2270,2019-10-11 18:00:00,3.0,3.0,3.0 +2271,2019-10-11 21:00:00,3.0,3.0,3.0 +2272,2019-10-12 00:00:00,3.0,3.0,3.0 +2273,2019-10-12 03:00:00,3.0,3.0,3.0 +2274,2019-10-12 06:00:00,3.0,3.0,3.0 +2275,2019-10-12 09:00:00,3.0,3.0,3.0 +2276,2019-10-12 12:00:00,3.0,3.0,3.0 +2277,2019-10-12 15:00:00,3.0,3.0,3.0 +2278,2019-10-12 18:00:00,3.0,3.0,3.0 +2279,2019-10-12 21:00:00,3.0,3.0,3.0 +2280,2019-10-13 00:00:00,3.0,3.0,3.0 +2281,2019-10-13 03:00:00,3.0,3.0,3.0 +2282,2019-10-13 06:00:00,3.0,3.0,3.0 +2283,2019-10-13 09:00:00,3.0,3.0,3.0 +2284,2019-10-13 12:00:00,3.0,3.0,3.0 +2285,2019-10-13 15:00:00,3.0,3.0,3.0 +2286,2019-10-13 18:00:00,3.0,3.0,3.0 +2287,2019-10-13 21:00:00,3.0,3.0,3.0 +2288,2019-10-14 00:00:00,3.0,3.0,3.0 +2289,2019-10-14 03:00:00,3.0,3.0,3.0 +2290,2019-10-14 06:00:00,3.0,3.0,3.0 +2291,2019-10-14 09:00:00,3.0,3.0,3.0 +2292,2019-10-14 12:00:00,3.0,3.0,3.0 +2293,2019-10-14 15:00:00,3.0,3.0,3.0 +2294,2019-10-14 18:00:00,3.0,3.0,3.0 +2295,2019-10-14 21:00:00,3.0,3.0,3.0 +2296,2019-10-15 00:00:00,3.0,3.0,3.0 +2297,2019-10-15 03:00:00,3.0,3.0,3.0 +2298,2019-10-15 06:00:00,3.0,3.0,3.0 +2299,2019-10-15 09:00:00,3.0,3.0,3.0 +2300,2019-10-15 12:00:00,3.0,3.0,3.0 +2301,2019-10-15 15:00:00,3.0,3.0,3.0 +2302,2019-10-15 18:00:00,3.0,3.0,3.0 +2303,2019-10-15 21:00:00,3.0,3.0,3.0 +2304,2019-10-16 00:00:00,3.0,3.0,3.0 +2305,2019-10-16 03:00:00,3.0,3.0,3.0 +2306,2019-10-16 06:00:00,3.0,3.0,3.0 +2307,2019-10-16 09:00:00,3.0,3.0,3.0 +2308,2019-10-16 12:00:00,3.0,3.0,3.0 +2309,2019-10-16 15:00:00,3.0,3.0,3.0 +2310,2019-10-16 18:00:00,3.0,3.0,3.0 +2311,2019-10-16 21:00:00,3.0,3.0,3.0 +2312,2019-10-17 00:00:00,3.0,3.0,3.0 +2313,2019-10-17 03:00:00,3.0,3.0,3.0 +2314,2019-10-17 06:00:00,3.0,3.0,3.0 +2315,2019-10-17 09:00:00,3.0,3.0,3.0 +2316,2019-10-17 12:00:00,3.0,3.0,3.0 +2317,2019-10-17 15:00:00,3.0,3.0,3.0 +2318,2019-10-17 18:00:00,3.0,3.0,3.0 +2319,2019-10-17 21:00:00,3.0,3.0,3.0 +2320,2019-10-18 00:00:00,3.0,3.0,3.0 +2321,2019-10-18 03:00:00,3.0,3.0,3.0 +2322,2019-10-18 06:00:00,3.0,3.0,3.0 +2323,2019-10-18 09:00:00,3.0,3.0,3.0 +2324,2019-10-18 12:00:00,3.0,3.0,3.0 +2325,2019-10-18 15:00:00,3.0,3.0,3.0 +2326,2019-10-18 18:00:00,3.0,3.0,3.0 +2327,2019-10-18 21:00:00,3.0,3.0,3.0 +2328,2019-10-19 00:00:00,3.0,3.0,3.0 +2329,2019-10-19 03:00:00,3.0,3.0,3.0 +2330,2019-10-19 06:00:00,3.0,3.0,3.0 +2331,2019-10-19 09:00:00,3.0,3.0,3.0 +2332,2019-10-19 12:00:00,3.0,3.0,3.0 +2333,2019-10-19 15:00:00,3.0,3.0,3.0 +2334,2019-10-19 18:00:00,3.0,3.0,3.0 +2335,2019-10-19 21:00:00,3.0,3.0,3.0 +2336,2019-10-20 00:00:00,3.0,3.0,3.0 +2337,2019-10-20 03:00:00,3.0,3.0,3.0 +2338,2019-10-20 06:00:00,3.0,3.0,3.0 +2339,2019-10-20 09:00:00,3.0,3.0,3.0 +2340,2019-10-20 12:00:00,3.0,3.0,3.0 +2341,2019-10-20 15:00:00,3.0,3.0,3.0 +2342,2019-10-20 18:00:00,3.0,3.0,3.0 +2343,2019-10-20 21:00:00,3.0,3.0,3.0 +2344,2019-10-21 00:00:00,3.0,3.0,3.0 +2345,2019-10-21 03:00:00,3.0,3.0,3.0 +2346,2019-10-21 06:00:00,3.0,3.0,3.0 +2347,2019-10-21 09:00:00,3.0,3.0,3.0 +2348,2019-10-21 12:00:00,3.0,3.0,3.0 +2349,2019-10-21 15:00:00,3.0,3.0,3.0 +2350,2019-10-21 18:00:00,3.0,3.0,3.0 +2351,2019-10-21 21:00:00,3.0,3.0,3.0 +2352,2019-10-22 00:00:00,3.0,3.0,3.0 +2353,2019-10-22 03:00:00,3.0,3.0,3.0 +2354,2019-10-22 06:00:00,3.0,3.0,3.0 +2355,2019-10-22 09:00:00,3.0,3.0,3.0 +2356,2019-10-22 12:00:00,3.0,3.0,3.0 +2357,2019-10-22 15:00:00,3.0,3.0,3.0 +2358,2019-10-22 18:00:00,3.0,3.0,3.0 +2359,2019-10-22 21:00:00,3.0,3.0,3.0 +2360,2019-10-23 00:00:00,3.0,3.0,3.0 +2361,2019-10-23 03:00:00,3.0,3.0,3.0 +2362,2019-10-23 06:00:00,3.0,3.0,3.0 +2363,2019-10-23 09:00:00,3.0,3.0,3.0 +2364,2019-10-23 12:00:00,3.0,3.0,3.0 +2365,2019-10-23 15:00:00,3.0,3.0,3.0 +2366,2019-10-23 18:00:00,3.0,3.0,3.0 +2367,2019-10-23 21:00:00,3.0,3.0,3.0 +2368,2019-10-24 00:00:00,3.0,3.0,3.0 +2369,2019-10-24 03:00:00,3.0,3.0,3.0 +2370,2019-10-24 06:00:00,3.0,3.0,3.0 +2371,2019-10-24 09:00:00,3.0,3.0,3.0 +2372,2019-10-24 12:00:00,3.0,3.0,3.0 +2373,2019-10-24 15:00:00,3.0,3.0,3.0 +2374,2019-10-24 18:00:00,3.0,3.0,3.0 +2375,2019-10-24 21:00:00,3.0,3.0,3.0 +2376,2019-10-25 00:00:00,3.0,3.0,3.0 +2377,2019-10-25 03:00:00,3.0,3.0,3.0 +2378,2019-10-25 06:00:00,3.0,3.0,3.0 +2379,2019-10-25 09:00:00,3.0,3.0,3.0 +2380,2019-10-25 12:00:00,3.0,3.0,3.0 +2381,2019-10-25 15:00:00,3.0,3.0,3.0 +2382,2019-10-25 18:00:00,3.0,3.0,3.0 +2383,2019-10-25 21:00:00,3.0,3.0,3.0 +2384,2019-10-26 00:00:00,3.0,3.0,3.0 +2385,2019-10-26 03:00:00,3.0,3.0,3.0 +2386,2019-10-26 06:00:00,3.0,3.0,3.0 +2387,2019-10-26 09:00:00,3.0,3.0,3.0 +2388,2019-10-26 12:00:00,3.0,3.0,3.0 +2389,2019-10-26 15:00:00,3.0,3.0,3.0 +2390,2019-10-26 18:00:00,3.0,3.0,3.0 +2391,2019-10-26 21:00:00,3.0,3.0,3.0 +2392,2019-10-27 00:00:00,3.0,3.0,3.0 +2393,2019-10-27 03:00:00,3.0,3.0,3.0 +2394,2019-10-27 06:00:00,3.0,3.0,3.0 +2395,2019-10-27 09:00:00,3.0,3.0,3.0 +2396,2019-10-27 12:00:00,3.0,3.0,3.0 +2397,2019-10-27 15:00:00,3.0,3.0,3.0 +2398,2019-10-27 18:00:00,3.0,3.0,3.0 +2399,2019-10-27 21:00:00,3.0,3.0,3.0 +2400,2019-10-28 00:00:00,3.0,3.0,3.0 +2401,2019-10-28 03:00:00,3.0,3.0,3.0 +2402,2019-10-28 06:00:00,3.0,3.0,3.0 +2403,2019-10-28 09:00:00,3.0,3.0,3.0 +2404,2019-10-28 12:00:00,3.0,3.0,3.0 +2405,2019-10-28 15:00:00,3.0,3.0,3.0 +2406,2019-10-28 18:00:00,3.0,3.0,3.0 +2407,2019-10-28 21:00:00,3.0,3.0,3.0 +2408,2019-10-29 00:00:00,3.0,3.0,3.0 +2409,2019-10-29 03:00:00,3.0,3.0,3.0 +2410,2019-10-29 06:00:00,3.0,3.0,3.0 +2411,2019-10-29 09:00:00,3.0,3.0,3.0 +2412,2019-10-29 12:00:00,3.0,3.0,3.0 +2413,2019-10-29 15:00:00,3.0,3.0,3.0 +2414,2019-10-29 18:00:00,3.0,3.0,3.0 +2415,2019-10-29 21:00:00,3.0,3.0,3.0 +2416,2019-10-30 00:00:00,3.0,3.0,3.0 +2417,2019-10-30 03:00:00,3.0,3.0,3.0 +2418,2019-10-30 06:00:00,3.0,3.0,3.0 +2419,2019-10-30 09:00:00,3.0,3.0,3.0 +2420,2019-10-30 12:00:00,3.0,3.0,3.0 +2421,2019-10-30 15:00:00,3.0,3.0,3.0 +2422,2019-10-30 18:00:00,3.0,3.0,3.0 +2423,2019-10-30 21:00:00,3.0,3.0,3.0 +2424,2019-10-31 00:00:00,3.0,3.0,3.0 +2425,2019-10-31 03:00:00,3.0,3.0,3.0 +2426,2019-10-31 06:00:00,3.0,3.0,3.0 +2427,2019-10-31 09:00:00,3.0,3.0,3.0 +2428,2019-10-31 12:00:00,3.0,3.0,3.0 +2429,2019-10-31 15:00:00,3.0,3.0,3.0 +2430,2019-10-31 18:00:00,3.0,3.0,3.0 +2431,2019-10-31 21:00:00,3.0,3.0,3.0 +2432,2019-11-01 00:00:00,3.0,3.0,3.0 +2433,2019-11-01 03:00:00,3.0,3.0,3.0 +2434,2019-11-01 06:00:00,3.0,3.0,3.0 +2435,2019-11-01 09:00:00,3.0,3.0,3.0 +2436,2019-11-01 12:00:00,3.0,3.0,3.0 +2437,2019-11-01 15:00:00,3.0,3.0,3.0 +2438,2019-11-01 18:00:00,3.0,3.0,3.0 +2439,2019-11-01 21:00:00,3.0,3.0,3.0 +2440,2019-11-02 00:00:00,3.0,3.0,3.0 +2441,2019-11-02 03:00:00,3.0,3.0,3.0 +2442,2019-11-02 06:00:00,3.0,3.0,3.0 +2443,2019-11-02 09:00:00,3.0,3.0,3.0 +2444,2019-11-02 12:00:00,3.0,3.0,3.0 +2445,2019-11-02 15:00:00,3.0,3.0,3.0 +2446,2019-11-02 18:00:00,3.0,3.0,3.0 +2447,2019-11-02 21:00:00,3.0,3.0,3.0 +2448,2019-11-03 00:00:00,3.0,3.0,3.0 +2449,2019-11-03 03:00:00,3.0,3.0,3.0 +2450,2019-11-03 06:00:00,3.0,3.0,3.0 +2451,2019-11-03 09:00:00,3.0,3.0,3.0 +2452,2019-11-03 12:00:00,3.0,3.0,3.0 +2453,2019-11-03 15:00:00,3.0,3.0,3.0 +2454,2019-11-03 18:00:00,3.0,3.0,3.0 +2455,2019-11-03 21:00:00,3.0,3.0,3.0 +2456,2019-11-04 00:00:00,3.0,3.0,3.0 +2457,2019-11-04 03:00:00,3.0,3.0,3.0 +2458,2019-11-04 06:00:00,3.0,3.0,3.0 +2459,2019-11-04 09:00:00,3.0,3.0,3.0 +2460,2019-11-04 12:00:00,3.0,3.0,3.0 +2461,2019-11-04 15:00:00,3.0,3.0,3.0 +2462,2019-11-04 18:00:00,3.0,3.0,3.0 +2463,2019-11-04 21:00:00,3.0,3.0,3.0 +2464,2019-11-05 00:00:00,3.0,3.0,3.0 +2465,2019-11-05 03:00:00,3.0,3.0,3.0 +2466,2019-11-05 06:00:00,3.0,3.0,3.0 +2467,2019-11-05 09:00:00,3.0,3.0,3.0 +2468,2019-11-05 12:00:00,3.0,3.0,3.0 +2469,2019-11-05 15:00:00,3.0,3.0,3.0 +2470,2019-11-05 18:00:00,3.0,3.0,3.0 +2471,2019-11-05 21:00:00,3.0,3.0,3.0 +2472,2019-11-06 00:00:00,3.0,3.0,3.0 +2473,2019-11-06 03:00:00,3.0,3.0,3.0 +2474,2019-11-06 06:00:00,3.0,3.0,3.0 +2475,2019-11-06 09:00:00,3.0,3.0,3.0 +2476,2019-11-06 12:00:00,3.0,3.0,3.0 +2477,2019-11-06 15:00:00,3.0,3.0,3.0 +2478,2019-11-06 18:00:00,3.0,3.0,3.0 +2479,2019-11-06 21:00:00,3.0,3.0,3.0 +2480,2019-11-07 00:00:00,3.0,3.0,3.0 +2481,2019-11-07 03:00:00,3.0,3.0,3.0 +2482,2019-11-07 06:00:00,3.0,3.0,3.0 +2483,2019-11-07 09:00:00,3.0,3.0,3.0 +2484,2019-11-07 12:00:00,3.0,3.0,3.0 +2485,2019-11-07 15:00:00,3.0,3.0,3.0 +2486,2019-11-07 18:00:00,3.0,3.0,3.0 +2487,2019-11-07 21:00:00,3.0,3.0,3.0 +2488,2019-11-08 00:00:00,3.0,3.0,3.0 +2489,2019-11-08 03:00:00,3.0,3.0,3.0 +2490,2019-11-08 06:00:00,3.0,3.0,3.0 +2491,2019-11-08 09:00:00,3.0,3.0,3.0 +2492,2019-11-08 12:00:00,3.0,3.0,3.0 +2493,2019-11-08 15:00:00,3.0,3.0,3.0 +2494,2019-11-08 18:00:00,3.0,3.0,3.0 +2495,2019-11-08 21:00:00,3.0,3.0,3.0 +2496,2019-11-09 00:00:00,3.0,3.0,3.0 +2497,2019-11-09 03:00:00,3.0,3.0,3.0 +2498,2019-11-09 06:00:00,3.0,3.0,3.0 +2499,2019-11-09 09:00:00,3.0,3.0,3.0 +2500,2019-11-09 12:00:00,3.0,3.0,3.0 +2501,2019-11-09 15:00:00,3.0,3.0,3.0 +2502,2019-11-09 18:00:00,3.0,3.0,3.0 +2503,2019-11-09 21:00:00,3.0,3.0,3.0 +2504,2019-11-10 00:00:00,3.0,3.0,3.0 +2505,2019-11-10 03:00:00,3.0,3.0,3.0 +2506,2019-11-10 06:00:00,3.0,3.0,3.0 +2507,2019-11-10 09:00:00,3.0,3.0,3.0 +2508,2019-11-10 12:00:00,3.0,3.0,3.0 +2509,2019-11-10 15:00:00,3.0,3.0,3.0 +2510,2019-11-10 18:00:00,3.0,3.0,3.0 +2511,2019-11-10 21:00:00,3.0,3.0,3.0 +2512,2019-11-11 00:00:00,3.0,3.0,3.0 +2513,2019-11-11 03:00:00,3.0,3.0,3.0 +2514,2019-11-11 06:00:00,3.0,3.0,3.0 +2515,2019-11-11 09:00:00,3.0,3.0,3.0 +2516,2019-11-11 12:00:00,3.0,3.0,3.0 +2517,2019-11-11 15:00:00,3.0,3.0,3.0 +2518,2019-11-11 18:00:00,3.0,3.0,3.0 +2519,2019-11-11 21:00:00,3.0,3.0,3.0 +2520,2019-11-12 00:00:00,3.0,3.0,3.0 +2521,2019-11-12 03:00:00,3.0,3.0,3.0 +2522,2019-11-12 06:00:00,3.0,3.0,3.0 +2523,2019-11-12 09:00:00,3.0,3.0,3.0 +2524,2019-11-12 12:00:00,3.0,3.0,3.0 +2525,2019-11-12 15:00:00,3.0,3.0,3.0 +2526,2019-11-12 18:00:00,3.0,3.0,3.0 +2527,2019-11-12 21:00:00,3.0,3.0,3.0 +2528,2019-11-13 00:00:00,3.0,3.0,3.0 +2529,2019-11-13 03:00:00,3.0,3.0,3.0 +2530,2019-11-13 06:00:00,3.0,3.0,3.0 +2531,2019-11-13 09:00:00,3.0,3.0,3.0 +2532,2019-11-13 12:00:00,3.0,3.0,3.0 +2533,2019-11-13 15:00:00,3.0,3.0,3.0 +2534,2019-11-13 18:00:00,3.0,3.0,3.0 +2535,2019-11-13 21:00:00,3.0,3.0,3.0 +2536,2019-11-14 00:00:00,3.0,3.0,3.0 +2537,2019-11-14 03:00:00,3.0,3.0,3.0 +2538,2019-11-14 06:00:00,3.0,3.0,3.0 +2539,2019-11-14 09:00:00,3.0,3.0,3.0 +2540,2019-11-14 12:00:00,3.0,3.0,3.0 +2541,2019-11-14 15:00:00,3.0,3.0,3.0 +2542,2019-11-14 18:00:00,3.0,3.0,3.0 +2543,2019-11-14 21:00:00,3.0,3.0,3.0 +2544,2019-11-15 00:00:00,3.0,3.0,3.0 +2545,2019-11-15 03:00:00,3.0,3.0,3.0 +2546,2019-11-15 06:00:00,3.0,3.0,3.0 +2547,2019-11-15 09:00:00,3.0,3.0,3.0 +2548,2019-11-15 12:00:00,3.0,3.0,3.0 +2549,2019-11-15 15:00:00,3.0,3.0,3.0 +2550,2019-11-15 18:00:00,3.0,3.0,3.0 +2551,2019-11-15 21:00:00,3.0,3.0,3.0 +2552,2019-11-16 00:00:00,3.0,3.0,3.0 +2553,2019-11-16 03:00:00,3.0,3.0,3.0 +2554,2019-11-16 06:00:00,3.0,3.0,3.0 +2555,2019-11-16 09:00:00,3.0,3.0,3.0 +2556,2019-11-16 12:00:00,3.0,3.0,3.0 +2557,2019-11-16 15:00:00,3.0,3.0,3.0 +2558,2019-11-16 18:00:00,3.0,3.0,3.0 +2559,2019-11-16 21:00:00,3.0,3.0,3.0 +2560,2019-11-17 00:00:00,3.0,3.0,3.0 +2561,2019-11-17 03:00:00,3.0,3.0,3.0 +2562,2019-11-17 06:00:00,3.0,3.0,3.0 +2563,2019-11-17 09:00:00,3.0,3.0,3.0 +2564,2019-11-17 12:00:00,3.0,3.0,3.0 +2565,2019-11-17 15:00:00,3.0,3.0,3.0 +2566,2019-11-17 18:00:00,3.0,3.0,3.0 +2567,2019-11-17 21:00:00,3.0,3.0,3.0 +2568,2019-11-18 00:00:00,3.0,3.0,3.0 +2569,2019-11-18 03:00:00,3.0,3.0,3.0 +2570,2019-11-18 06:00:00,3.0,3.0,3.0 +2571,2019-11-18 09:00:00,3.0,3.0,3.0 +2572,2019-11-18 12:00:00,3.0,3.0,3.0 +2573,2019-11-18 15:00:00,3.0,3.0,3.0 +2574,2019-11-18 18:00:00,3.0,3.0,3.0 +2575,2019-11-18 21:00:00,3.0,3.0,3.0 +2576,2019-11-19 00:00:00,3.0,3.0,3.0 +2577,2019-11-19 03:00:00,3.0,3.0,3.0 +2578,2019-11-19 06:00:00,3.0,3.0,3.0 +2579,2019-11-19 09:00:00,3.0,3.0,3.0 +2580,2019-11-19 12:00:00,3.0,3.0,3.0 +2581,2019-11-19 15:00:00,3.0,3.0,3.0 +2582,2019-11-19 18:00:00,3.0,3.0,3.0 +2583,2019-11-19 21:00:00,3.0,3.0,3.0 +2584,2019-11-20 00:00:00,3.0,3.0,3.0 +2585,2019-11-20 03:00:00,3.0,3.0,3.0 +2586,2019-11-20 06:00:00,3.0,3.0,3.0 +2587,2019-11-20 09:00:00,3.0,3.0,3.0 +2588,2019-11-20 12:00:00,3.0,3.0,3.0 +2589,2019-11-20 15:00:00,3.0,3.0,3.0 +2590,2019-11-20 18:00:00,3.0,3.0,3.0 +2591,2019-11-20 21:00:00,3.0,3.0,3.0 +2592,2019-11-21 00:00:00,3.0,3.0,3.0 +2593,2019-11-21 03:00:00,3.0,3.0,3.0 +2594,2019-11-21 06:00:00,3.0,3.0,3.0 +2595,2019-11-21 09:00:00,3.0,3.0,3.0 +2596,2019-11-21 12:00:00,3.0,3.0,3.0 +2597,2019-11-21 15:00:00,3.0,3.0,3.0 +2598,2019-11-21 18:00:00,3.0,3.0,3.0 +2599,2019-11-21 21:00:00,3.0,3.0,3.0 +2600,2019-11-22 00:00:00,3.0,3.0,3.0 +2601,2019-11-22 03:00:00,3.0,3.0,3.0 +2602,2019-11-22 06:00:00,3.0,3.0,3.0 +2603,2019-11-22 09:00:00,3.0,3.0,3.0 +2604,2019-11-22 12:00:00,3.0,3.0,3.0 +2605,2019-11-22 15:00:00,3.0,3.0,3.0 +2606,2019-11-22 18:00:00,3.0,3.0,3.0 +2607,2019-11-22 21:00:00,3.0,3.0,3.0 +2608,2019-11-23 00:00:00,3.0,3.0,3.0 +2609,2019-11-23 03:00:00,3.0,3.0,3.0 +2610,2019-11-23 06:00:00,3.0,3.0,3.0 +2611,2019-11-23 09:00:00,3.0,3.0,3.0 +2612,2019-11-23 12:00:00,3.0,3.0,3.0 +2613,2019-11-23 15:00:00,3.0,3.0,3.0 +2614,2019-11-23 18:00:00,3.0,3.0,3.0 +2615,2019-11-23 21:00:00,3.0,3.0,3.0 +2616,2019-11-24 00:00:00,3.0,3.0,3.0 +2617,2019-11-24 03:00:00,3.0,3.0,3.0 +2618,2019-11-24 06:00:00,3.0,3.0,3.0 +2619,2019-11-24 09:00:00,3.0,3.0,3.0 +2620,2019-11-24 12:00:00,3.0,3.0,3.0 +2621,2019-11-24 15:00:00,3.0,3.0,3.0 +2622,2019-11-24 18:00:00,3.0,3.0,3.0 +2623,2019-11-24 21:00:00,3.0,3.0,3.0 +2624,2019-11-25 00:00:00,3.0,3.0,3.0 +2625,2019-11-25 03:00:00,3.0,3.0,3.0 +2626,2019-11-25 06:00:00,3.0,3.0,3.0 +2627,2019-11-25 09:00:00,3.0,3.0,3.0 +2628,2019-11-25 12:00:00,3.0,3.0,3.0 +2629,2019-11-25 15:00:00,3.0,3.0,3.0 +2630,2019-11-25 18:00:00,3.0,3.0,3.0 +2631,2019-11-25 21:00:00,3.0,3.0,3.0 +2632,2019-11-26 00:00:00,3.0,3.0,3.0 +2633,2019-11-26 03:00:00,3.0,3.0,3.0 +2634,2019-11-26 06:00:00,3.0,3.0,3.0 +2635,2019-11-26 09:00:00,3.0,3.0,3.0 +2636,2019-11-26 12:00:00,3.0,3.0,3.0 +2637,2019-11-26 15:00:00,3.0,3.0,3.0 +2638,2019-11-26 18:00:00,3.0,3.0,3.0 +2639,2019-11-26 21:00:00,3.0,3.0,3.0 +2640,2019-11-27 00:00:00,3.0,3.0,3.0 +2641,2019-11-27 03:00:00,3.0,3.0,3.0 +2642,2019-11-27 06:00:00,3.0,3.0,3.0 +2643,2019-11-27 09:00:00,3.0,3.0,3.0 +2644,2019-11-27 12:00:00,3.0,3.0,3.0 +2645,2019-11-27 15:00:00,3.0,3.0,3.0 +2646,2019-11-27 18:00:00,3.0,3.0,3.0 +2647,2019-11-27 21:00:00,3.0,3.0,3.0 +2648,2019-11-28 00:00:00,3.0,3.0,3.0 +2649,2019-11-28 03:00:00,3.0,3.0,3.0 +2650,2019-11-28 06:00:00,3.0,3.0,3.0 +2651,2019-11-28 09:00:00,3.0,3.0,3.0 +2652,2019-11-28 12:00:00,3.0,3.0,3.0 +2653,2019-11-28 15:00:00,3.0,3.0,3.0 +2654,2019-11-28 18:00:00,3.0,3.0,3.0 +2655,2019-11-28 21:00:00,3.0,3.0,3.0 +2656,2019-11-29 00:00:00,3.0,3.0,3.0 +2657,2019-11-29 03:00:00,3.0,3.0,3.0 +2658,2019-11-29 06:00:00,3.0,3.0,3.0 +2659,2019-11-29 09:00:00,3.0,3.0,3.0 +2660,2019-11-29 12:00:00,3.0,3.0,3.0 +2661,2019-11-29 15:00:00,3.0,3.0,3.0 +2662,2019-11-29 18:00:00,3.0,3.0,3.0 +2663,2019-11-29 21:00:00,3.0,3.0,3.0 +2664,2019-11-30 00:00:00,3.0,3.0,3.0 +2665,2019-11-30 03:00:00,3.0,3.0,3.0 +2666,2019-11-30 06:00:00,3.0,3.0,3.0 +2667,2019-11-30 09:00:00,3.0,3.0,3.0 +2668,2019-11-30 12:00:00,3.0,3.0,3.0 +2669,2019-11-30 15:00:00,3.0,3.0,3.0 +2670,2019-11-30 18:00:00,3.0,3.0,3.0 +2671,2019-11-30 21:00:00,3.0,3.0,3.0 +2672,2019-12-01 00:00:00,3.0,3.0,3.0 +2673,2019-12-01 03:00:00,3.0,3.0,3.0 +2674,2019-12-01 06:00:00,3.0,3.0,3.0 +2675,2019-12-01 09:00:00,3.0,3.0,3.0 +2676,2019-12-01 12:00:00,3.0,3.0,3.0 +2677,2019-12-01 15:00:00,3.0,3.0,3.0 +2678,2019-12-01 18:00:00,3.0,3.0,3.0 +2679,2019-12-01 21:00:00,3.0,3.0,3.0 +2680,2019-12-02 00:00:00,3.0,3.0,3.0 +2681,2019-12-02 03:00:00,3.0,3.0,3.0 +2682,2019-12-02 06:00:00,3.0,3.0,3.0 +2683,2019-12-02 09:00:00,3.0,3.0,3.0 +2684,2019-12-02 12:00:00,3.0,3.0,3.0 +2685,2019-12-02 15:00:00,3.0,3.0,3.0 +2686,2019-12-02 18:00:00,3.0,3.0,3.0 +2687,2019-12-02 21:00:00,3.0,3.0,3.0 +2688,2019-12-03 00:00:00,3.0,3.0,3.0 +2689,2019-12-03 03:00:00,3.0,3.0,3.0 +2690,2019-12-03 06:00:00,3.0,3.0,3.0 +2691,2019-12-03 09:00:00,3.0,3.0,3.0 +2692,2019-12-03 12:00:00,3.0,3.0,3.0 +2693,2019-12-03 15:00:00,3.0,3.0,3.0 +2694,2019-12-03 18:00:00,3.0,3.0,3.0 +2695,2019-12-03 21:00:00,3.0,3.0,3.0 +2696,2019-12-04 00:00:00,3.0,3.0,3.0 +2697,2019-12-04 03:00:00,3.0,3.0,3.0 +2698,2019-12-04 06:00:00,3.0,3.0,3.0 +2699,2019-12-04 09:00:00,3.0,3.0,3.0 +2700,2019-12-04 12:00:00,3.0,3.0,3.0 +2701,2019-12-04 15:00:00,3.0,3.0,3.0 +2702,2019-12-04 18:00:00,3.0,3.0,3.0 +2703,2019-12-04 21:00:00,3.0,3.0,3.0 +2704,2019-12-05 00:00:00,3.0,3.0,3.0 +2705,2019-12-05 03:00:00,3.0,3.0,3.0 +2706,2019-12-05 06:00:00,3.0,3.0,3.0 +2707,2019-12-05 09:00:00,3.0,3.0,3.0 +2708,2019-12-05 12:00:00,3.0,3.0,3.0 +2709,2019-12-05 15:00:00,3.0,3.0,3.0 +2710,2019-12-05 18:00:00,3.0,3.0,3.0 +2711,2019-12-05 21:00:00,3.0,3.0,3.0 +2712,2019-12-06 00:00:00,3.0,3.0,3.0 +2713,2019-12-06 03:00:00,3.0,3.0,3.0 +2714,2019-12-06 06:00:00,3.0,3.0,3.0 +2715,2019-12-06 09:00:00,3.0,3.0,3.0 +2716,2019-12-06 12:00:00,3.0,3.0,3.0 +2717,2019-12-06 15:00:00,3.0,3.0,3.0 +2718,2019-12-06 18:00:00,3.0,3.0,3.0 +2719,2019-12-06 21:00:00,3.0,3.0,3.0 +2720,2019-12-07 00:00:00,3.0,3.0,3.0 +2721,2019-12-07 03:00:00,3.0,3.0,3.0 +2722,2019-12-07 06:00:00,3.0,3.0,3.0 +2723,2019-12-07 09:00:00,3.0,3.0,3.0 +2724,2019-12-07 12:00:00,3.0,3.0,3.0 +2725,2019-12-07 15:00:00,3.0,3.0,3.0 +2726,2019-12-07 18:00:00,3.0,3.0,3.0 +2727,2019-12-07 21:00:00,3.0,3.0,3.0 +2728,2019-12-08 00:00:00,3.0,3.0,3.0 +2729,2019-12-08 03:00:00,3.0,3.0,3.0 +2730,2019-12-08 06:00:00,3.0,3.0,3.0 +2731,2019-12-08 09:00:00,3.0,3.0,3.0 +2732,2019-12-08 12:00:00,3.0,3.0,3.0 +2733,2019-12-08 15:00:00,3.0,3.0,3.0 +2734,2019-12-08 18:00:00,3.0,3.0,3.0 +2735,2019-12-08 21:00:00,3.0,3.0,3.0 +2736,2019-12-09 00:00:00,3.0,3.0,3.0 +2737,2019-12-09 03:00:00,3.0,3.0,3.0 +2738,2019-12-09 06:00:00,3.0,3.0,3.0 +2739,2019-12-09 09:00:00,3.0,3.0,3.0 +2740,2019-12-09 12:00:00,3.0,3.0,3.0 +2741,2019-12-09 15:00:00,3.0,3.0,3.0 +2742,2019-12-09 18:00:00,3.0,3.0,3.0 +2743,2019-12-09 21:00:00,3.0,3.0,3.0 +2744,2019-12-10 00:00:00,3.0,3.0,3.0 +2745,2019-12-10 03:00:00,3.0,3.0,3.0 +2746,2019-12-10 06:00:00,3.0,3.0,3.0 +2747,2019-12-10 09:00:00,3.0,3.0,3.0 +2748,2019-12-10 12:00:00,3.0,3.0,3.0 +2749,2019-12-10 15:00:00,3.0,3.0,3.0 +2750,2019-12-10 18:00:00,3.0,3.0,3.0 +2751,2019-12-10 21:00:00,3.0,3.0,3.0 +2752,2019-12-11 00:00:00,3.0,3.0,3.0 +2753,2019-12-11 03:00:00,3.0,3.0,3.0 +2754,2019-12-11 06:00:00,3.0,3.0,3.0 +2755,2019-12-11 09:00:00,3.0,3.0,3.0 +2756,2019-12-11 12:00:00,3.0,3.0,3.0 +2757,2019-12-11 15:00:00,3.0,3.0,3.0 +2758,2019-12-11 18:00:00,3.0,3.0,3.0 +2759,2019-12-11 21:00:00,3.0,3.0,3.0 +2760,2019-12-12 00:00:00,3.0,3.0,3.0 +2761,2019-12-12 03:00:00,3.0,3.0,3.0 +2762,2019-12-12 06:00:00,3.0,3.0,3.0 +2763,2019-12-12 09:00:00,3.0,3.0,3.0 +2764,2019-12-12 12:00:00,3.0,3.0,3.0 +2765,2019-12-12 15:00:00,3.0,3.0,3.0 +2766,2019-12-12 18:00:00,3.0,3.0,3.0 +2767,2019-12-12 21:00:00,3.0,3.0,3.0 +2768,2019-12-13 00:00:00,3.0,3.0,3.0 +2769,2019-12-13 03:00:00,3.0,3.0,3.0 +2770,2019-12-13 06:00:00,3.0,3.0,3.0 +2771,2019-12-13 09:00:00,3.0,3.0,3.0 +2772,2019-12-13 12:00:00,3.0,3.0,3.0 +2773,2019-12-13 15:00:00,3.0,3.0,3.0 +2774,2019-12-13 18:00:00,3.0,3.0,3.0 +2775,2019-12-13 21:00:00,3.0,3.0,3.0 +2776,2019-12-14 00:00:00,3.0,3.0,3.0 +2777,2019-12-14 03:00:00,3.0,3.0,3.0 +2778,2019-12-14 06:00:00,3.0,3.0,3.0 +2779,2019-12-14 09:00:00,3.0,3.0,3.0 +2780,2019-12-14 12:00:00,3.0,3.0,3.0 +2781,2019-12-14 15:00:00,3.0,3.0,3.0 +2782,2019-12-14 18:00:00,3.0,3.0,3.0 +2783,2019-12-14 21:00:00,3.0,3.0,3.0 +2784,2019-12-15 00:00:00,3.0,3.0,3.0 +2785,2019-12-15 03:00:00,3.0,3.0,3.0 +2786,2019-12-15 06:00:00,3.0,3.0,3.0 +2787,2019-12-15 09:00:00,3.0,3.0,3.0 +2788,2019-12-15 12:00:00,3.0,3.0,3.0 +2789,2019-12-15 15:00:00,3.0,3.0,3.0 +2790,2019-12-15 18:00:00,3.0,3.0,3.0 +2791,2019-12-15 21:00:00,3.0,3.0,3.0 +2792,2019-12-16 00:00:00,3.0,3.0,3.0 +2793,2019-12-16 03:00:00,3.0,3.0,3.0 +2794,2019-12-16 06:00:00,3.0,3.0,3.0 +2795,2019-12-16 09:00:00,3.0,3.0,3.0 +2796,2019-12-16 12:00:00,3.0,3.0,3.0 +2797,2019-12-16 15:00:00,3.0,3.0,3.0 +2798,2019-12-16 18:00:00,3.0,3.0,3.0 +2799,2019-12-16 21:00:00,3.0,3.0,3.0 +2800,2019-12-17 00:00:00,3.0,3.0,3.0 +2801,2019-12-17 03:00:00,3.0,3.0,3.0 +2802,2019-12-17 06:00:00,3.0,3.0,3.0 +2803,2019-12-17 09:00:00,3.0,3.0,3.0 +2804,2019-12-17 12:00:00,3.0,3.0,3.0 +2805,2019-12-17 15:00:00,3.0,3.0,3.0 +2806,2019-12-17 18:00:00,3.0,3.0,3.0 +2807,2019-12-17 21:00:00,3.0,3.0,3.0 +2808,2019-12-18 00:00:00,3.0,3.0,3.0 +2809,2019-12-18 03:00:00,3.0,3.0,3.0 +2810,2019-12-18 06:00:00,3.0,3.0,3.0 +2811,2019-12-18 09:00:00,3.0,3.0,3.0 +2812,2019-12-18 12:00:00,3.0,3.0,3.0 +2813,2019-12-18 15:00:00,3.0,3.0,3.0 +2814,2019-12-18 18:00:00,3.0,3.0,3.0 +2815,2019-12-18 21:00:00,3.0,3.0,3.0 +2816,2019-12-19 00:00:00,3.0,3.0,3.0 +2817,2019-12-19 03:00:00,3.0,3.0,3.0 +2818,2019-12-19 06:00:00,3.0,3.0,3.0 +2819,2019-12-19 09:00:00,3.0,3.0,3.0 +2820,2019-12-19 12:00:00,3.0,3.0,3.0 +2821,2019-12-19 15:00:00,3.0,3.0,3.0 +2822,2019-12-19 18:00:00,3.0,3.0,3.0 +2823,2019-12-19 21:00:00,3.0,3.0,3.0 +2824,2019-12-20 00:00:00,3.0,3.0,3.0 +2825,2019-12-20 03:00:00,3.0,3.0,3.0 +2826,2019-12-20 06:00:00,3.0,3.0,3.0 +2827,2019-12-20 09:00:00,3.0,3.0,3.0 +2828,2019-12-20 12:00:00,3.0,3.0,3.0 +2829,2019-12-20 15:00:00,3.0,3.0,3.0 +2830,2019-12-20 18:00:00,3.0,3.0,3.0 +2831,2019-12-20 21:00:00,3.0,3.0,3.0 +2832,2019-12-21 00:00:00,3.0,3.0,3.0 +2833,2019-12-21 03:00:00,3.0,3.0,3.0 +2834,2019-12-21 06:00:00,3.0,3.0,3.0 +2835,2019-12-21 09:00:00,3.0,3.0,3.0 +2836,2019-12-21 12:00:00,3.0,3.0,3.0 +2837,2019-12-21 15:00:00,3.0,3.0,3.0 +2838,2019-12-21 18:00:00,3.0,3.0,3.0 +2839,2019-12-21 21:00:00,3.0,3.0,3.0 +2840,2019-12-22 00:00:00,3.0,3.0,3.0 +2841,2019-12-22 03:00:00,3.0,3.0,3.0 +2842,2019-12-22 06:00:00,3.0,3.0,3.0 +2843,2019-12-22 09:00:00,3.0,3.0,3.0 +2844,2019-12-22 12:00:00,3.0,3.0,3.0 +2845,2019-12-22 15:00:00,3.0,3.0,3.0 +2846,2019-12-22 18:00:00,3.0,3.0,3.0 +2847,2019-12-22 21:00:00,3.0,3.0,3.0 +2848,2019-12-23 00:00:00,3.0,3.0,3.0 +2849,2019-12-23 03:00:00,3.0,3.0,3.0 +2850,2019-12-23 06:00:00,3.0,3.0,3.0 +2851,2019-12-23 09:00:00,3.0,3.0,3.0 +2852,2019-12-23 12:00:00,3.0,3.0,3.0 +2853,2019-12-23 15:00:00,3.0,3.0,3.0 +2854,2019-12-23 18:00:00,3.0,3.0,3.0 +2855,2019-12-23 21:00:00,3.0,3.0,3.0 +2856,2019-12-24 00:00:00,3.0,3.0,3.0 +2857,2019-12-24 03:00:00,3.0,3.0,3.0 +2858,2019-12-24 06:00:00,3.0,3.0,3.0 +2859,2019-12-24 09:00:00,3.0,3.0,3.0 +2860,2019-12-24 12:00:00,3.0,3.0,3.0 +2861,2019-12-24 15:00:00,3.0,3.0,3.0 +2862,2019-12-24 18:00:00,3.0,3.0,3.0 +2863,2019-12-24 21:00:00,3.0,3.0,3.0 +2864,2019-12-25 00:00:00,3.0,3.0,3.0 +2865,2019-12-25 03:00:00,3.0,3.0,3.0 +2866,2019-12-25 06:00:00,3.0,3.0,3.0 +2867,2019-12-25 09:00:00,3.0,3.0,3.0 +2868,2019-12-25 12:00:00,3.0,3.0,3.0 +2869,2019-12-25 15:00:00,3.0,3.0,3.0 +2870,2019-12-25 18:00:00,3.0,3.0,3.0 +2871,2019-12-25 21:00:00,3.0,3.0,3.0 +2872,2019-12-26 00:00:00,3.0,3.0,3.0 +2873,2019-12-26 03:00:00,3.0,3.0,3.0 +2874,2019-12-26 06:00:00,3.0,3.0,3.0 +2875,2019-12-26 09:00:00,3.0,3.0,3.0 +2876,2019-12-26 12:00:00,3.0,3.0,3.0 +2877,2019-12-26 15:00:00,3.0,3.0,3.0 +2878,2019-12-26 18:00:00,3.0,3.0,3.0 +2879,2019-12-26 21:00:00,3.0,3.0,3.0 +2880,2019-12-27 00:00:00,3.0,3.0,3.0 +2881,2019-12-27 03:00:00,3.0,3.0,3.0 +2882,2019-12-27 06:00:00,3.0,3.0,3.0 +2883,2019-12-27 09:00:00,3.0,3.0,3.0 +2884,2019-12-27 12:00:00,3.0,3.0,3.0 +2885,2019-12-27 15:00:00,3.0,3.0,3.0 +2886,2019-12-27 18:00:00,3.0,3.0,3.0 +2887,2019-12-27 21:00:00,3.0,3.0,3.0 +2888,2019-12-28 00:00:00,3.0,3.0,3.0 +2889,2019-12-28 03:00:00,3.0,3.0,3.0 +2890,2019-12-28 06:00:00,3.0,3.0,3.0 +2891,2019-12-28 09:00:00,3.0,3.0,3.0 +2892,2019-12-28 12:00:00,3.0,3.0,3.0 +2893,2019-12-28 15:00:00,3.0,3.0,3.0 +2894,2019-12-28 18:00:00,3.0,3.0,3.0 +2895,2019-12-28 21:00:00,3.0,3.0,3.0 +2896,2019-12-29 00:00:00,3.0,3.0,3.0 +2897,2019-12-29 03:00:00,3.0,3.0,3.0 +2898,2019-12-29 06:00:00,3.0,3.0,3.0 +2899,2019-12-29 09:00:00,3.0,3.0,3.0 +2900,2019-12-29 12:00:00,3.0,3.0,3.0 +2901,2019-12-29 15:00:00,3.0,3.0,3.0 +2902,2019-12-29 18:00:00,3.0,3.0,3.0 +2903,2019-12-29 21:00:00,3.0,3.0,3.0 +2904,2019-12-30 00:00:00,3.0,3.0,3.0 +2905,2019-12-30 03:00:00,3.0,3.0,3.0 +2906,2019-12-30 06:00:00,3.0,3.0,3.0 +2907,2019-12-30 09:00:00,3.0,3.0,3.0 +2908,2019-12-30 12:00:00,3.0,3.0,3.0 +2909,2019-12-30 15:00:00,3.0,3.0,3.0 +2910,2019-12-30 18:00:00,3.0,3.0,3.0 +2911,2019-12-30 21:00:00,3.0,3.0,3.0 +2912,2019-12-31 00:00:00,3.0,3.0,3.0 +2913,2019-12-31 03:00:00,3.0,3.0,3.0 +2914,2019-12-31 06:00:00,3.0,3.0,3.0 +2915,2019-12-31 09:00:00,3.0,3.0,3.0 +2916,2019-12-31 12:00:00,3.0,3.0,3.0 +2917,2019-12-31 15:00:00,3.0,3.0,3.0 +2918,2019-12-31 18:00:00,3.0,3.0,3.0 +2919,2019-12-31 21:00:00,3.0,3.0,3.0 diff --git a/PyPSA/source/examples/networks/model-energy/model-energy/storage_units.csv b/PyPSA/source/examples/networks/model-energy/model-energy/storage_units.csv new file mode 100644 index 0000000000000000000000000000000000000000..df2075592d1d269615b5c6e43f26e7243bec470a --- /dev/null +++ b/PyPSA/source/examples/networks/model-energy/model-energy/storage_units.csv @@ -0,0 +1,2 @@ +name,bus,p_nom_extendable,carrier,capital_cost,cyclic_state_of_charge,max_hours,efficiency_store,efficiency_dispatch +battery storage,electricity,True,battery storage,63361.75356490421,True,3.0,0.96,0.96 diff --git a/PyPSA/source/examples/networks/model-energy/model-energy/stores.csv b/PyPSA/source/examples/networks/model-energy/model-energy/stores.csv new file mode 100644 index 0000000000000000000000000000000000000000..4afeed1cf229c6ff6053d82e9db705ca4d78f8e7 --- /dev/null +++ b/PyPSA/source/examples/networks/model-energy/model-energy/stores.csv @@ -0,0 +1,2 @@ +name,bus,carrier,e_nom_extendable,e_cyclic,capital_cost +hydrogen storage,hydrogen,hydrogen storage,True,True,148.31893020591625 diff --git a/PyPSA/source/examples/networks/scigrid-de/scigrid-de.nc b/PyPSA/source/examples/networks/scigrid-de/scigrid-de.nc new file mode 100644 index 0000000000000000000000000000000000000000..c4eb40805dad6cb907f9e75a1889c767e89626db --- /dev/null +++ b/PyPSA/source/examples/networks/scigrid-de/scigrid-de.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48b5f0fe728b2fc116c92f7995b8ad634a9bb7cb42d3bae8302bac415dc2a7b9 +size 1529767 diff --git a/PyPSA/source/examples/networks/scigrid-de/scigrid-de/buses.csv b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/buses.csv new file mode 100644 index 0000000000000000000000000000000000000000..3de3b8fed146523ee06bd56c913ce742209b0f35 --- /dev/null +++ b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/buses.csv @@ -0,0 +1,586 @@ +name,v_nom,x,y,wkt_srid_4326,voltage,ref,typ,osm_name,operator,frequency +1,220.0,9.52257596986,52.3604090558,SRID=4326;POINT(9.52257596986262 52.3604090557601),220000;110000,,substation,Hannover/West,TenneT;EON_Netz,50 +2,380.0,9.11321007473,52.5438533224,SRID=4326;POINT(9.11321007472722 52.5438533223737),220000,,substation,Umspannwerk Kraftwerk Robert Frank,TenneT,50 +3,380.0,9.38974509625,52.026313066,SRID=4326;POINT(9.38974509624863 52.0263130660355),380000;110000,,substation,UW Grohnde 380 kV / 110 kV,TenneT,50 +4,380.0,9.12526570295,52.5382640871,SRID=4326;POINT(9.12526570294975 52.5382640870749),380000;220000,,substation,Landesbergen,TenneT TSO GmbH,50 +5,380.0,10.3662749375,52.2846467462,SRID=4326;POINT(10.3662749375017 52.2846467462009),380000;220000,,substation,Wahle,TenneT,50 +6,220.0,9.91814864614,52.3799963126,SRID=4326;POINT(9.91814864613865 52.3799963125719),220000;110000,,substation,TenneT TSO GmbH - Betriebszentrum Lehrte,TenneT_TSO,50 +7,380.0,9.91717971972,52.2781686139,SRID=4326;POINT(9.91717971972185 52.2781686139394),380000;110000,,substation,Algermissen,TenneT,50 +8,380.0,10.4149923382,53.412606883,SRID=4326;POINT(10.4149923381504 53.4126068830249),380000;110000,,substation,Krümmel,50Hertz, +9,380.0,10.3787705904,53.2197927686,SRID=4326;POINT(10.3787705903765 53.2197927685849),380000;110000,LUEN,substation,Lüneburg,EON_Netz,50 +10,380.0,12.9416466019,52.5621144046,SRID=4326;POINT(12.9416466019288 52.5621144045848),380000;220000;110000,Wu,substation,Wustermark,50Hertz Transmission GmbH;E.ON edis AG, +11,220.0,13.2061029868,52.6571362773,SRID=4326;POINT(13.2061029867985 52.6571362773373),220000,,substation,Hennigsdorf,50Hertz Transmission,50 +12,380.0,13.7096697058,52.5397952581,SRID=4326;POINT(13.7096697058228 52.5397952580643),380000;220000;110000,NHG TCC,substation,Neuenhagen,50Hertz Transmission GmbH,50 +13,380.0,11.3704262487,48.291492233,SRID=4326;POINT(11.3704262486892 48.2914922329548),380000;220000;110000,OBA,substation,Umspannwerk Oberbachern,TenneT TSO GmbH;E.ON Netz GmbH, +14,220.0,11.8132328973,48.2212664655,SRID=4326;POINT(11.8132328973194 48.2212664654998),220000;110000,NFI,substation,Umspannwerk Neufinsing,"TenneT TSO GmbH, E.ON Netz GmbH", +15,380.0,11.8681562939,48.2068029185,SRID=4326;POINT(11.8681562938884 48.2068029185353),380000;220000,OH,substation,Umspannwerk Ottenhofen,TenneT TSO GmbH,50 +16,380.0,10.7610403786,52.3677293804,SRID=4326;POINT(10.7610403785613 52.3677293804096),380000,,substation,Umspannwerk Hattorf,EON_Netz,50 +17,380.0,13.4947633747,52.5897644436,SRID=4326;POINT(13.4947633747219 52.5897644435618),380000;110000,MOW,substation,Umspannwerk Malchow,50Hertz;Stromnetz Berlin,50 +18,380.0,13.6834769667,54.1390906407,SRID=4326;POINT(13.6834769666546 54.1390906406635),380000;220000,,substation,Umspannwerk Lubmin,50Hertz, +19,220.0,11.2939727001,48.052042772,SRID=4326;POINT(11.2939727001158 48.0520427719624),220000;110000,OBR,substation,Umspannwerk Oberbrunn,"TenneT TSO GmbH, E.ON Netz GmbH", +20,380.0,9.98460982001,53.741128776,SRID=4326;POINT(9.98460982000897 53.7411287759659),380000;220000;110000,,substation,Umspannwerk Hamburg-Nord,50Hertz Transmission,50 +21,380.0,10.1575015165,53.5551316686,SRID=4326;POINT(10.1575015165126 53.5551316685834),380000;110000,,substation,Umspannwerk Hamburg/Ost,50Hertz Transmission;Stromnetz Hamburg GmbH,50 +22,380.0,9.20210420095,53.89515741,SRID=4326;POINT(9.20210420095253 53.8951574100045),380000;220000;110000,,substation,Umspannwerk Kernkraftwerk Brunsbüttel,50Hertz Transmission,50 +23,220.0,9.98853660866,53.7662776686,SRID=4326;POINT(9.98853660865951 53.766277668594),220000;110000,,substation,Hamburg Nord,TenneT;Schleswig-Holstein Netz AG,50 +24,380.0,9.72699196993,54.2914209868,SRID=4326;POINT(9.72699196993402 54.2914209867842),220000;110000,,substation,Audorf,TenneT;Schleswig-Holstein Netz AG,50 +25,380.0,9.34476098807,53.9218370583,SRID=4326;POINT(9.34476098807139 53.9218370583068),380000,,substation,Umspannwerk Wilster,TenneT,50 +26,380.0,9.34400177216,53.8517774635,SRID=4326;POINT(9.34400177215961 53.8517774635399),380000;220000,,substation,Brokdorf,, +27,220.0,9.52960512907,53.6189906942,SRID=4326;POINT(9.52960512906833 53.6189906941937),220000;110000,,substation,Stade,,50 +28,220.0,9.77654483875,53.6906352606,SRID=4326;POINT(9.77654483874895 53.6906352606291),220000;110000,,substation,Kummerfeld,TenneT,50 +29,380.0,10.3821666628,52.9932540373,SRID=4326;POINT(10.3821666628198 52.9932540372914),380000,,substation,Stadorf,TenneT,50 +30,380.0,9.50178966782,53.5428143723,SRID=4326;POINT(9.5017896678173 53.5428143723363),380000;220000;110000,,substation,Dollern,,50 +31,380.0,9.7461508164,50.9168399491,SRID=4326;POINT(9.74615081640288 50.9168399490798),380000;110000,,substation,Mecklar,EON_Netz,50 +32,380.0,11.1228545605,50.998909067,SRID=4326;POINT(11.1228545605428 50.9989090670144),380000;220000;110000,,substation,Umspannwerk Vieselbach,50Hertz Transmission GmbH,50 +33,380.0,10.3859411644,51.0018133015,SRID=4326;POINT(10.3859411643633 51.0018133014781),380000;110000,,substation,Eisenach,50Hertz,50 +34,220.0,8.12264230305,53.560516049,SRID=4326;POINT(8.12264230304551 53.5605160490331),380000;220000,MAAD,substation,Umspannwerk Maade,TenneT,50 +35,220.0,8.08565059716,53.6058341926,SRID=4326;POINT(8.08565059715704 53.605834192587),220000;110000,VOSL,substation,Voslapp,TenneT;EON_Netz,50 +36,220.0,8.06005695814,53.6202025751,SRID=4326;POINT(8.06005695814032 53.6202025750861),220000;155000,INHA,substation,Inhausen,TenneT,50 +37,380.0,11.6389127631,52.2687346818,SRID=4326;POINT(11.6389127631156 52.2687346818467),380000;220000,,substation,Umspannwerk Wolmirstedt,50Hertz Transmission GmbH, +38,380.0,13.2115276454,52.5806278102,SRID=4326;POINT(13.2115276454389 52.5806278102291),380000;110000,,substation,Umspannwerk Teufelsbruch,Vattenfall_Europe_Transmission,50 +39,380.0,11.7273534151,52.5689469939,SRID=4326;POINT(11.7273534150745 52.5689469938591),380000;110000,StW,substation,Stendal/West,50Hertz Transmission GmbH,50 +40,380.0,13.1433929103,53.9081469774,SRID=4326;POINT(13.1433929102518 53.9081469774219),380000;110000,,substation,Siedenbrünzow,50Hertz Transmission GmbH,50 +41,380.0,11.853105708,53.086544475,SRID=4326;POINT(11.8531057080136 53.0865444750041),380000;110000,,substation,Umspannwerk Perleberg,50Hertz;EON_Avacon, +42,380.0,12.1869848227,53.8157751724,SRID=4326;POINT(12.1869848227216 53.8157751724384),380000;220000,,substation,Güstrow,50Hertz,50 +43,220.0,12.499365967,52.4052284252,SRID=4326;POINT(12.4993659669539 52.4052284251973),220000;110000,,substation,Brandenburg-West,50Hertz Transmission GmbH, +44,220.0,13.5061534469,52.4738223092,SRID=4326;POINT(13.5061534469385 52.473822309174),220000;110000,,substation,Umspannwerk Wuhlheide,50Hertz;Vattenfall Europe AG,50 +45,220.0,13.305753295,52.2307818452,SRID=4326;POINT(13.3057532949594 52.2307818451873),380000;220000;110000,,substation,Thyrow,50Hertz Transmission GmbH,50 +46,380.0,13.524198731,52.5356342713,SRID=4326;POINT(13.5241987309997 52.5356342713481),380000;220000;110000,MZA9,substation,Umspannwerk Marzahn,50Hertz,50 +47,380.0,13.4564306928,52.5214517452,SRID=4326;POINT(13.4564306927748 52.5214517451981),380000;110000,,substation,Umspannwerk Friedrichshain,Vattenfall Europe AG;50Hertz,50 +48,380.0,13.5370442127,52.5374565238,SRID=4326;POINT(13.5370442127043 52.5374565238016),110000,BIN,substation,Umspannwerk Biesdorf Nord,Stromnetz Berlin,50 +49,380.0,10.9993330124,52.2001419746,SRID=4326;POINT(10.99933301239 52.2001419745622),380000;110000,,substation,Umspannwerk Helmstedt,TenneT TSO GmbH, +50,380.0,10.9772332275,52.1694333177,SRID=4326;POINT(10.9772332275028 52.1694333176707),,,plant,Kraftwerk Buschhaus,E.ON Kraftwerke GmbH, +51,380.0,9.25938382025,51.0603668474,SRID=4326;POINT(9.25938382024592 51.0603668474477),380000;110000,,substation,Umspannwerk Borken,TenneT,50 +52,380.0,8.31334016593,49.0090434644,SRID=4326;POINT(8.3133401659254 49.0090434643525),380000;220000;110000,,substation,Karlsruhe-Daxlanden,EnBW,50 +53,380.0,7.7534954654,48.0993557006,SRID=4326;POINT(7.75349546540486 48.0993557006259),380000;220000;110000,,substation,380-kV-Umspannwerk Eichstetten,EnBW,50 +54,380.0,8.43846751212,49.2524597289,SRID=4326;POINT(8.43846751211921 49.2524597288652),,,plant,Kernkraftwerk Philippsburg,EnBW Kernkraft GmbH, +55,220.0,8.51211132936,49.2939656496,SRID=4326;POINT(8.51211132935593 49.293965649573),220000;110000,,substation,Umspannwerk Altlußheim,EnBW,50 +56,380.0,8.62320967452,49.3565822493,SRID=4326;POINT(8.62320967451897 49.3565822492832),380000;220000,,substation,Umspannwerk Neurott,EnBW,50 +57,220.0,8.44554248929,48.9860881754,SRID=4326;POINT(8.44554248929306 48.9860881753906),220000;110000,,substation,Umspannwerk Oberwald,,50 +58,220.0,8.64158406338,48.8900524832,SRID=4326;POINT(8.64158406338239 48.8900524832355),220000;110000,,substation,220 kV-Umspannwerk Birkenfeld,EnBW,50 +59,380.0,8.56374399744,53.0317215836,SRID=4326;POINT(8.56374399744213 53.0317215835514),380000;110000,GANK,substation,Ganderkesee,TenneT;EON_Netz;EWE,50 +60,380.0,8.47470704899,53.4286511943,SRID=4326;POINT(8.47470704898602 53.4286511943138),380000;220000;110000,UWES,substation,Unterweser,EON_Netz,50 +61,380.0,8.51888180952,53.2026352837,SRID=4326;POINT(8.51888180951659 53.2026352836834),110000,FARG,substation,Umspannwerk Farge,Avacon AG,50 +62,220.0,8.82002487242,53.1073882919,SRID=4326;POINT(8.82002487241632 53.1073882918815),220000;110000,,substation,Umspannwerk Blockland,swb Netze GmbH & Co. KG,50 +63,220.0,8.12076909802,48.704835426,SRID=4326;POINT(8.12076909802284 48.7048354259965),220000;110000,,substation,Bühl,EnBW,50 +64,380.0,11.4407746483,48.1718035473,SRID=4326;POINT(11.4407746483177 48.1718035473026),380000;110000,MZ,substation,Hauptumspannwerk Menzing,Stadtwerke München, +65,380.0,13.3264049985,50.3463721851,SRID=4326;POINT(13.3264049984725 50.3463721850552),400000;220000,,substation,Hradec,ČEPS, +66,380.0,12.1147644054,49.6311550522,SRID=4326;POINT(12.1147644054101 49.6311550521523),380000;220000;110000,ETZ,substation,Umspannwerk Etzenricht,Tennet;EON_Netz,50 +67,380.0,8.69516075602,53.0925360848,SRID=4326;POINT(8.69516075602312 53.0925360847799),380000;110000,NVLD,substation,Niedervieland,Wesernetz,50 +68,380.0,8.77244296019,49.9746686651,SRID=4326;POINT(8.77244296019056 49.974668665137),380000;220000;110000,,substation,Umspannwerk Urberach,RWE, +69,380.0,8.41937105938,49.6337356824,SRID=4326;POINT(8.41937105938048 49.6337356823792),380000;220000,,substation,Station Bürstadt,RWE,50 +70,380.0,8.38855636788,49.9823050841,SRID=4326;POINT(8.38855636787547 49.9823050841271),380000;110000,,substation,Umspannstation Bischofsheim,RWE; Kraftwerke Mainz-Wiesbaden, +71,380.0,12.2621734868,51.7410202756,SRID=4326;POINT(12.2621734868101 51.7410202755519),380000;110000,,substation,Umspannwerk Marke,50Hertz,50 +72,380.0,11.8942661378,51.4057031412,SRID=4326;POINT(11.8942661378135 51.4057031412478),380000;110000,,substation,Umspannwerk Lauchstädt,50Hertz;enviaM,50 +73,380.0,13.1570904872,51.8284516615,SRID=4326;POINT(13.1570904871893 51.8284516615019),380000,,substation,Umspannwerk Schönewalde,50Hertz,50 +74,380.0,13.9024346887,51.874723519,SRID=4326;POINT(13.9024346886892 51.8747235190459),380000;110000,RAG,substation,Umspannwerk Ragow,50Hertz,50 +75,380.0,11.5165549672,51.5843592099,SRID=4326;POINT(11.5165549672107 51.5843592098734),380000;110000,,substation,Umspannwerk Klostermansfeld,50Hertz,50 +76,380.0,8.70221844883,51.7363525828,SRID=4326;POINT(8.70221844882815 51.7363525828065),380000;110000,,substation,Umspannwerk Elsen,TenneT, +77,380.0,8.99279240395,51.3430093979,SRID=4326;POINT(8.99279240395088 51.343009397907),380000;110000,,substation,Umspannwerk Twistetal,Eon_Netz,50 +78,380.0,11.6463786274,50.534876971,SRID=4326;POINT(11.6463786274424 50.534876971042),380000;220000;110000,RDF;RE,substation,Umspannwerk Remptendorf,50Hertz Transmission GmbH,50 +79,380.0,14.4719654743,51.5538127186,SRID=4326;POINT(14.4719654742998 51.5538127185923),380000;110000,GRS,substation,Graustein,50Hertz_Transmission;enviaM,50 +80,380.0,14.5081091811,51.4137103812,SRID=4326;POINT(14.5081091811329 51.4137103812427),380000,,substation,Umspannwerk Bärwalde,50Hertz,50 +81,380.0,14.4281804852,51.8888641394,SRID=4326;POINT(14.4281804852225 51.8888641394403),380000;110000,PRL,substation,Umspannwerk Preilack,50Hertz,50 +82,220.0,8.14191382633,53.564001168,SRID=4326;POINT(8.14191382633093 53.5640011680095),,,plant,EON Kraftwerk Wilhelmshaven,EON_Netz, +83,220.0,10.1562824576,54.275548255,SRID=4326;POINT(10.1562824575947 54.2755482549913),220000;110000,,substation,Kiel-Süd,EON_Netz;TenneT,50 +84,220.0,10.1773541035,54.3395212639,SRID=4326;POINT(10.1773541034729 54.3395212638642),,,generator,Gemeinschaftskraftwerk Kiel,E.ON Kraftwerke GmbH;Stadtwerke Kiel AG, +85,380.0,7.03496796515,52.2028607381,SRID=4326;POINT(7.03496796515083 52.2028607381056),380000;220000;110000,,substation,Station Gronau,RWE, +86,380.0,7.30820430095,52.4768353863,SRID=4326;POINT(7.30820430094578 52.4768353862806),380000;220000;110000,,substation,Hanekenfähr,Amprion,50 +87,380.0,7.06030699176,51.6964760131,SRID=4326;POINT(7.06030699176008 51.6964760130648),380000;220000;110000,,substation,Station Kusenhorst,RWE,50 +88,220.0,6.67467532062,51.3249574728,SRID=4326;POINT(6.6746753206169 51.3249574728255),220000;110000,,substation,Station Gellep,RWE, +89,220.0,6.72376714521,51.3556543809,SRID=4326;POINT(6.72376714521247 51.3556543809247),220000;110000,,substation,Station Mündelheim,RWE,50 +90,380.0,7.54571772262,51.9703244224,SRID=4326;POINT(7.54571772261949 51.9703244223875),380000;110000,,substation,Station Roxel,RWE,50 +91,380.0,7.30561448538,52.4801491896,SRID=4326;POINT(7.30561448538174 52.4801491895706),,,plant,Erdgaskraftwerk Emsland,RWE AG, +92,380.0,7.30433875539,52.4824259958,SRID=4326;POINT(7.30433875539085 52.4824259958004),380000,,substation,,, +93,380.0,7.27715391323,52.7532425947,SRID=4326;POINT(7.27715391322869 52.7532425946802),380000;110000,MEPP,substation,Meppen,TenneT,50 +94,380.0,8.3077568968,52.3456356448,SRID=4326;POINT(8.30775689679575 52.3456356447783),380000;220000;110000,,substation,Wehrendorf,Amprion,50 +95,380.0,7.88957671235,52.2773967624,SRID=4326;POINT(7.88957671235007 52.2773967624078),380000;220000;110000,,substation,Station Westerkappeln,Amprion,50 +96,380.0,7.31784994378,52.4691114903,SRID=4326;POINT(7.31784994377907 52.4691114902799),220000,,substation,Benteler Stahlwerk,,50 +97,380.0,13.3352803847,49.5896898613,SRID=4326;POINT(13.3352803846608 49.5896898612644),400000;220000;110000,,substation,Rozvodna Přeštice,ČEPS,50 +98,380.0,12.2147735097,54.1003234404,SRID=4326;POINT(12.2147735096776 54.1003234403618),380000,,substation,Bentwisch HGÜ,50Hertz Transmission,50 +99,380.0,6.99647659179,49.3754093063,SRID=4326;POINT(6.99647659179489 49.3754093063084),380000;220000;110000,,substation,Umspannwerk Uchtelfangen,Amprion,50 +100,380.0,6.47473977007,49.9224692994,SRID=4326;POINT(6.47473977006816 49.9224692993999),380000;220000;110000,,substation,Station Niederstedem,Amprion; Westnetz,50 +101,380.0,11.6993926965,53.5722744893,SRID=4326;POINT(11.6993926965362 53.5722744892702),380000,,substation,Wessin,50Hertz, +102,380.0,11.3706373689,53.6009910849,SRID=4326;POINT(11.3706373688866 53.6009910848999),380000;110000,,substation,Görries,50Hertz;EON_edis,50 +103,380.0,6.94821343461,51.444578943,SRID=4326;POINT(6.94821343461115 51.4445789430072),380000;110000,,substation,Station Rosenblumendelle,RWE, +104,380.0,6.9267589636,51.4992056838,SRID=4326;POINT(6.92675896360373 51.4992056838496),380000;220000,,substation,Station Büscherhof,RWE,50 +105,380.0,6.68078798894,51.6462959357,SRID=4326;POINT(6.68078798893986 51.6462959356851),380000;220000;110000,,substation,Station Niederrhein,RWE, +106,380.0,7.16721460966,51.3899067582,SRID=4326;POINT(7.16721460965932 51.3899067582106),380000;220000;110000,,substation,Station Hattingen,Amprion,50 +107,380.0,7.12557291067,51.4489772588,SRID=4326;POINT(7.1255729106725 51.4489772588145),380000;220000,,substation,Station Eiberg,RWE,50 +108,380.0,6.99606888011,51.5141695891,SRID=4326;POINT(6.99606888010598 51.5141695891404),380000;110000,,substation,Station Karnap,,50 +109,380.0,7.3131828429,51.4466113896,SRID=4326;POINT(7.31318284290137 51.4466113896128),380000,,substation,Witten,RWE,50 +110,380.0,7.99288723333,51.6837077156,SRID=4326;POINT(7.99288723332679 51.6837077155955),380000,,substation,Station Uentrop,RWE,50 +111,380.0,8.62480052815,51.4278883849,SRID=4326;POINT(8.62480052815036 51.4278883849265),380000;110000,,substation,Station Nehden,Amprion,50 +112,380.0,7.3531118694,51.5758105021,SRID=4326;POINT(7.35311186939787 51.5758105020818),380000,,substation,Station Mengede,RWE,50 +113,380.0,7.1232388295,51.5256121803,SRID=4326;POINT(7.12323882950453 51.5256121803004),380000;110000,,substation,Station Hüllen,RWE,50 +114,380.0,8.21170597595,51.1779854801,SRID=4326;POINT(8.21170597594605 51.1779854801194),380000;110000,,substation,Station Arpe,Amprion,50 +115,220.0,6.57648208812,51.0555954583,SRID=4326;POINT(6.57648208812114 51.0555954583116),,,generator,RWE Kraftwerk Frimmersdorf,, +116,220.0,6.77252309438,51.1471054906,SRID=4326;POINT(6.77252309438106 51.1471054906194),220000;110000,,substation,Station Norf,,50 +117,380.0,6.97257742701,50.7956930175,SRID=4326;POINT(6.97257742700703 50.795693017514),380000;220000;110000,,substation,Station Sechtem,RWE, +118,380.0,7.13689469739,50.6428608767,SRID=4326;POINT(7.13689469738717 50.6428608767133),380000;110000,,substation,Station Ließem,Amprion,50 +119,380.0,6.45655246642,50.8713380369,SRID=4326;POINT(6.45655246642153 50.8713380368705),380000;110000,,substation,Station Oberzier,Net,50 +120,380.0,7.86394571365,50.7682419129,SRID=4326;POINT(7.86394571365263 50.7682419129404),380000;220000;110000,,substation,Station Dauersberg,RWE,50 +121,380.0,6.32166491494,50.8383473628,SRID=4326;POINT(6.32166491493505 50.8383473627586),,,plant,RWE Kraftwerk Weisweiler,RWE, +122,380.0,6.16738169006,50.8020442927,SRID=4326;POINT(6.16738169006425 50.8020442927138),380000;110000,,substation,Station Verlautenheide,Amprion,50 +123,380.0,6.7022033778,51.0088609097,SRID=4326;POINT(6.70220337779745 51.0088609096753),380000;220000,,substation,Station Rommerskirchen,RWE,50 +124,380.0,6.61240231531,51.0359242595,SRID=4326;POINT(6.61240231530956 51.0359242594819),380000;220000;110000,,substation,KW Neurath,,50 +125,380.0,6.96568442352,51.0693295322,SRID=4326;POINT(6.96568442352005 51.0693295322353),380000;220000;110000,,substation,Station Opladen,RWE, +126,380.0,7.23319684332,51.2325069075,SRID=4326;POINT(7.23319684331545 51.2325069075148),380000;110000,,substation,Station Linde,RWE,50 +127,380.0,6.30090123787,50.8289532123,SRID=4326;POINT(6.30090123786765 50.8289532122665),380000;110000,,substation,Umspannwerk Weisweiler,,50 +128,380.0,7.44948529011,50.4025276621,SRID=4326;POINT(7.44948529010545 50.4025276621126),380000;220000;110000,,substation,Station Weißenthurm,Amprion,50 +129,380.0,6.62582790177,51.2606098249,SRID=4326;POINT(6.62582790177014 51.2606098249047),220000;110000,,substation,Station Osterath,RWE,50 +130,380.0,7.12308195236,51.1715887782,SRID=4326;POINT(7.12308195236379 51.1715887782054),380000;110000,,substation,Station Halfeshof,,50 +131,380.0,6.80109333957,51.1225390519,SRID=4326;POINT(6.80109333956604 51.1225390518584),380000;220000,,substation,Station Sankt Peter 380 kV,RWE,50 +132,380.0,7.00877914033,51.174254918,SRID=4326;POINT(7.00877914033175 51.1742549180397),380000;110000,,substation,Station Ohligs,Amprion,50 +133,380.0,6.84265066151,50.8635385009,SRID=4326;POINT(6.8426506615144 50.8635385008881),380000;110000,,substation,Station Knapsack,Amprion, +134,220.0,6.80461158013,50.9662452961,SRID=4326;POINT(6.80461158012622 50.9662452961379),220000;110000,,substation,Station Brauweiler,RWE, +135,380.0,7.71862721454,51.5342075115,SRID=4326;POINT(7.7186272145384 51.5342075115271),380000;110000,,substation,Station Unna,Amprion, +136,380.0,7.93077008561,51.6729710197,SRID=4326;POINT(7.93077008561427 51.6729710197069),380000,,substation,Geithe,Trianel, +137,220.0,6.22183144596,51.6946456422,SRID=4326;POINT(6.22183144595687 51.6946456422206),220000;110000,,substation,Station Pfalzdorf,RWE, +138,380.0,7.0067498786,51.599911422,SRID=4326;POINT(7.00674987859998 51.5999114220203),,,generator,Kraftwerk Scholven,, +139,220.0,7.03761988504,51.5985496312,SRID=4326;POINT(7.03761988504233 51.5985496311832),220000;110000,,substation,Station Bergmannsglück,RWE, +140,220.0,7.26408346259,51.5735734104,SRID=4326;POINT(7.2640834625924 51.5735734103802),220000;110000,,substation,Station Pöppinghausen,Amprion,50 +141,220.0,7.27894711303,51.4722816773,SRID=4326;POINT(7.2789471130269 51.4722816773138),220000;110000,,substation,Station Laer,Amprion,50 +142,220.0,7.18667755312,51.5505890706,SRID=4326;POINT(7.18667755311932 51.550589070577),,,generator,Kraftwerk Herne Baukau,Evonik Industries AG, +143,380.0,7.35135784055,51.5683686081,SRID=4326;POINT(7.35135784054859 51.568368608134),,,generator,Kraftwerk Knepper,E.On Kraftwerke GmbH, +144,220.0,7.17937912539,51.4722019484,SRID=4326;POINT(7.17937912538938 51.4722019484347),220000;110000,,substation,Station Bochum,Amprion, +145,220.0,6.93488869659,51.0335223351,SRID=4326;POINT(6.93488869659333 51.0335223350589),220000;110000,,substation,UW Fühlingen,RheinEnergie,50 +146,220.0,6.82813596908,51.1626762414,SRID=4326;POINT(6.82813596908193 51.1626762413706),220000;110000,,substation,Station Reisholz,,50 +147,220.0,7.50597329672,51.4015925504,SRID=4326;POINT(7.50597329671546 51.401592550355),220000;110000,,substation,Umspannwerk Garenfeld,markE, +148,220.0,6.84699931065,51.2055917698,SRID=4326;POINT(6.84699931064759 51.2055917698058),220000;110000,,substation,Station Eller,RWE;Stadtwerke Düsseldorf,50 +149,220.0,7.19316578267,50.7983350647,SRID=4326;POINT(7.19316578267403 50.798335064694),220000;110000,,substation,Station Siegburg,RWE, +150,220.0,7.04738452411,50.9055286095,SRID=4326;POINT(7.0473845241055 50.9055286094924),220000;110000,,substation,Station Gremberghoven,RWE,50 +151,220.0,6.70967369517,51.0909407428,SRID=4326;POINT(6.70967369516713 51.0909407427733),220000;110000,,substation,Station Gohrpunkt,Amprion, +152,220.0,7.0129374264,50.6176584938,SRID=4326;POINT(7.01293742640329 50.6176584938251),220000;110000,,substation,Station Meckenheim,RWE,50 +153,220.0,6.33006858949,51.2429161504,SRID=4326;POINT(6.33006858948983 51.2429161503794),380000;220000;110000,,substation,Station Dülken,RWE,50 +154,220.0,7.02354449916,50.9990684096,SRID=4326;POINT(7.0235444991641 50.999068409611),220000,,substation,Station Dünnwald,,50 +155,220.0,7.72438401522,51.4093341306,SRID=4326;POINT(7.72438401522458 51.4093341305984),220000;110000,,substation,Umspannwerk Bixterheide,Amprion,50 +156,380.0,6.84223115148,50.8598647244,SRID=4326;POINT(6.84223115147656 50.8598647243573),380000;220000,,substation,Kraftwerkschaltanlage Knapsack 1,, +157,380.0,8.3642295307,51.9271204905,SRID=4326;POINT(8.364229530696 51.9271204904611),380000;220000;110000,,substation,Station Gütersloh,Amprion,50 +158,380.0,6.48812326382,50.8810703126,SRID=4326;POINT(6.48812326381716 50.8810703125704),380000,,substation,Station Hambach,RWE,50 +159,380.0,8.05408735442,53.3323026934,SRID=4326;POINT(8.05408735442182 53.3323026933898),380000;220000;110000,CONN,substation,Conneforde,TenneT,50 +160,380.0,8.47791063609,53.428485469,SRID=4326;POINT(8.47791063608626 53.428485468955),380000;220000,,substation,Kernkraftwerk Unterweser,EON_Netz,50 +161,220.0,8.05283751634,53.3338809599,SRID=4326;POINT(8.05283751634017 53.3338809598955),220000;110000,CONN,substation,Conneforde 110/220 kV,TenneT;EON_Netz,50 +162,220.0,6.85153113795,50.984170468,SRID=4326;POINT(6.85153113794826 50.9841704680083),220000;110000,,substation,Bocklemünd,,50 +163,380.0,6.59601979462,50.9525274624,SRID=4326;POINT(6.59601979462215 50.9525274624278),380000;110000,,substation,Station Paffendorf,Amprion,50 +164,380.0,7.33861258614,51.631978125,SRID=4326;POINT(7.33861258614081 51.6319781250427),380000;110000,,substation,Schaltanlage Datteln 4,E.ON,50;16.7 +165,380.0,7.70831848873,51.6762033088,SRID=4326;POINT(7.70831848872651 51.6762033088437),380000;220000;110000,,substation,Station Gersteinwerk,Amprion,50 +166,380.0,7.6170209951,51.6371337641,SRID=4326;POINT(7.61702099509812 51.6371337640588),,,generator,Kraftwerk Bergkamen,RWE; evonik, +167,220.0,7.52228010104,51.5321980552,SRID=4326;POINT(7.52228010104186 51.5321980552231),220000;110000,,substation,Station Wambel,Ampirion,50 +168,380.0,6.90660779029,51.5652424465,SRID=4326;POINT(6.90660779028721 51.5652424465435),380000;110000,,substation,Station Nordlicht,RWE; ELE,50 +169,220.0,6.96734393054,51.4908618864,SRID=4326;POINT(6.96734393053748 51.4908618864265),220000,,substation,LMG,,50 +170,220.0,6.87287359352,51.4457833372,SRID=4326;POINT(6.87287359352087 51.4457833372124),220000;110000,,substation,Styrum,,50 +171,220.0,6.86804496308,51.3744747806,SRID=4326;POINT(6.86804496307922 51.3744747805902),220000;110000,,substation,Station Selbeck,RWE, +172,220.0,6.78752251671,51.5055501,SRID=4326;POINT(6.78752251671348 51.505550100013),220000;110000,,substation,Station Hamborn,Amprion,50 +173,380.0,6.62284323547,51.2587133681,SRID=4326;POINT(6.62284323546559 51.258713368098),380000,,substation,Station Osterath,RWE,50 +174,220.0,6.81216588075,51.5196200118,SRID=4326;POINT(6.81216588075166 51.5196200118095),220000;110000,,substation,Station Handbach,Amprion,50 +175,220.0,6.72031000056,51.391532538,SRID=4326;POINT(6.72031000055597 51.3915325380171),220000,,substation,Station Rheinhausen,Amprion, +176,220.0,6.54240844257,51.299445815,SRID=4326;POINT(6.54240844257145 51.2994458149911),220000;110000,,substation,Station Edelstahl Krefeld,,50 +177,220.0,13.0786421048,54.2848461697,SRID=4326;POINT(13.0786421047586 54.2848461697305),220000;110000,Lhg,substation,Umspannwerk Lüdershagen,50Hertz Transmission GmbH;E.ON edis AG, +178,220.0,8.02029033798,50.9236168949,SRID=4326;POINT(8.02029033797883 50.9236168949394),220000,,substation,Setzer Wiese,RWE,50 +179,220.0,13.9570055476,53.2521716258,SRID=4326;POINT(13.9570055475871 53.252171625753),380000;220000,,substation,Bertikow,50Hertz Transmission GmbH, +180,220.0,14.2708346707,53.0962865536,SRID=4326;POINT(14.270834670665 53.0962865535819),220000;110000,,substation,Vierraden,50Hertz Transmission GmbH,50 +181,220.0,14.0336963685,53.5078685981,SRID=4326;POINT(14.0336963684666 53.5078685980791),220000;110000,,substation,Pasewalk,50Hertz Transmission GmbH;E.ON edis AG, +182,220.0,14.4762521007,53.1967495481,SRID=4326;POINT(14.4762521006827 53.1967495480613),400000;220000,KRA,substation,"Stacja elektroenergetyczna ""Krajnik"" 400/220 kV",PSE Operator, +183,380.0,9.90884752815,53.4880466404,SRID=4326;POINT(9.90884752815356 53.4880466403676),380000;110000,,substation,Umspannwerk Hamburg/Süd,50Hertz Transmission;Stromnetz Hamburg GmbH,50 +184,380.0,6.61738109143,51.4757909636,SRID=4326;POINT(6.61738109142906 51.4757909635544),380000;220000;110000,,substation,Station Utfort,Amprion,50 +185,220.0,6.65673519501,51.3795236344,SRID=4326;POINT(6.65673519501182 51.379523634379),220000;110000,,substation,Uerdingen,,50 +186,380.0,6.71114527446,51.5261875491,SRID=4326;POINT(6.7111452744645 51.5261875490663),380000,,substation,Walsum Block 10,Amprion,50 +187,380.0,6.68934277039,51.5764228882,SRID=4326;POINT(6.68934277038931 51.5764228881728),380000,,substation,Station Zensenbusch,Amprion, +188,220.0,6.72365237186,51.3758458109,SRID=4326;POINT(6.72365237186396 51.375845810902),,,generator,Kraftwerk Huckingen,, +189,380.0,8.61664767275,48.0659537907,SRID=4326;POINT(8.61664767275337 48.0659537906797),380000;110000,TRSNG,substation,Umspannwerk Trossingen,,50 +190,380.0,8.87294280488,48.3100912582,SRID=4326;POINT(8.87294280487974 48.3100912582216),380000,ENGST,substation,EnBW Umspannwerk Engstlatt,EnBW,50 +191,380.0,6.71525259692,51.5267908666,SRID=4326;POINT(6.71525259691926 51.5267908666491),220000;110000,,substation,Station Walsum,Amprion,50 +192,220.0,12.1857651812,53.810859799,SRID=4326;POINT(12.1857651811846 53.8108597989599),220000;110000,,substation,Güstrow,EON_edis, +193,220.0,6.7225698095,51.4797876727,SRID=4326;POINT(6.72256980950278 51.4797876727124),380000;220000;110000,,substation,Station Beeck,,50 +194,380.0,6.6830656826,51.5771047777,SRID=4326;POINT(6.68306568260248 51.577104777735),,,generator,Kraftwerk Voerde,evonik, +195,380.0,6.68310697664,51.5756455059,SRID=4326;POINT(6.68310697663871 51.5756455059265),220000,,substation,Station Möllen,, +196,220.0,6.74541676675,51.498641948,SRID=4326;POINT(6.74541676675317 51.4986419480434),220000,,substation,,, +197,220.0,6.61427855504,51.6186788033,SRID=4326;POINT(6.61427855504049 51.6186788033284),220000;110000,,substation,Spellen,,50 +198,380.0,7.97065689371,51.6768446668,SRID=4326;POINT(7.9706568937083 51.6768446667846),380000,,substation,,,50 +199,220.0,7.44506930371,51.6185000869,SRID=4326;POINT(7.44506930371379 51.6185000869374),220000;110000,,substation,Station Elmenhorst,Amprion, +200,220.0,6.73212495036,51.4594437366,SRID=4326;POINT(6.73212495035583 51.4594437365975),220000;110000,,substation,Station Ruhrort,Amprion,50 +201,380.0,9.25103425144,53.1144323051,SRID=4326;POINT(9.25103425143703 53.1144323051271),380000;220000;110000,,substation,Sottrum,EON_Netz,50 +202,380.0,7.32089257542,52.4714572265,SRID=4326;POINT(7.32089257541703 52.4714572265383),,KKE,plant,Kernkraftwerk Emsland,KKW Lippe-Ems, +203,380.0,6.75917485282,52.2488578799,SRID=4326;POINT(6.75917485282445 52.2488578798871),380000,,substation,380kV Hengelo,TenneT, +204,220.0,7.70632726934,51.2785831552,SRID=4326;POINT(7.70632726933662 51.278583155205),220000;110000,,substation,Elverlingsen,markE, +205,220.0,7.45034204531,51.4136082128,SRID=4326;POINT(7.45034204531003 51.413608212821),,,plant,Koepchenwerk,RWE Power AG, +206,380.0,6.63448162267,51.0359799381,SRID=4326;POINT(6.63448162267058 51.0359799381303),,,plant,RWE Kraftwerk Neurath BoA Bl. F/G,, +207,220.0,7.60711612154,51.8986046029,SRID=4326;POINT(7.60711612154456 51.8986046029193),220000,,substation,Amelsbüren,Amprion,50 +208,380.0,7.40354443406,51.644449488,SRID=4326;POINT(7.4035444340631 51.644449487967),380000,,substation,Station Lippe,RWE,50 +209,380.0,8.93969252741,52.4030332405,SRID=4326;POINT(8.93969252741257 52.4030332405173),380000,3018,substation,Schaltanlage Ovenstädt,TenneT TSO GmbH,50 +210,380.0,9.40952032742,52.0343166659,SRID=4326;POINT(9.40952032742215 52.034316665873),,KWG,plant,Kernkraftwerk Grohnde,Gemeinschaftskernkraftwerk Grohnde GmbH & Co. oHG, +211,380.0,7.23579344537,49.3693923629,SRID=4326;POINT(7.23579344536789 49.3693923629482),380000,,substation,Mittelbexbach,Amprion,50 +212,380.0,6.81368431456,49.7248340737,SRID=4326;POINT(6.81368431456473 49.7248340737293),380000;110000,,substation,Station Osburg,Amprion; Westnetz,50 +213,220.0,8.58869049603,49.815514058,SRID=4326;POINT(8.58869049603326 49.8155140579794),220000;110000,,substation,Umspannanlage Pfungstadt,RWE,50 +214,220.0,8.54716092521,49.4405396661,SRID=4326;POINT(8.5471609252134 49.4405396661002),220000;110000,,substation,Station Rheinau,Net,50 +215,220.0,8.40826973551,49.5285689246,SRID=4326;POINT(8.40826973550855 49.5285689245626),220000,,substation,BASF,,50 +216,380.0,9.19078743692,48.9144168738,SRID=4326;POINT(9.1907874369222 48.914416873799),380000;220000;110000,,substation,Umspannstation Hoheneck Amprion,Amprion,50 +217,380.0,12.1320699705,54.1424598756,SRID=4326;POINT(12.1320699705054 54.14245987564),,,generator,Kraftwerk Rostock,,50 +218,220.0,12.2135605441,54.1119998545,SRID=4326;POINT(12.2135605440795 54.1119998545154),220000;110000,,substation,Bentwisch,E.ON edis AG, +219,380.0,8.57125882787,52.1053649602,SRID=4326;POINT(8.5712588278729 52.1053649602273),380000;110000,,substation,Eickum,TenneT,50 +220,380.0,8.6593729029,51.9996101319,SRID=4326;POINT(8.65937290290079 51.9996101318907),380000;220000;110000,,substation,Umspannwerk Bechterdissen,TenneT,50 +221,380.0,7.31136229106,53.1257665563,SRID=4326;POINT(7.31136229105814 53.1257665563186),380000;150000;110000,DIEL,substation,Diele,TenneT;EON_Netz,50;0 +222,380.0,6.94948397154,53.1235378223,SRID=4326;POINT(6.94948397153705 53.1235378223082),380000;220000,,substation,380kV Meeden,TenneT, +223,220.0,8.27327814618,49.0293980916,SRID=4326;POINT(8.27327814618118 49.0293980916396),220000;110000,,substation,Umspannwerk Maximiliansau,Pfalzwerke AG,50 +224,220.0,8.35233388,49.4212743387,SRID=4326;POINT(8.3523338799966 49.4212743386606),220000;110000,,substation,Netzleitstelle Mutterstadt,Pfalzwerke,50 +225,220.0,7.72920197914,49.4826474298,SRID=4326;POINT(7.72920197913641 49.4826474297682),220000;110000,,substation,Otterbach,Amprion,50 +226,220.0,9.79733602695,52.0008392513,SRID=4326;POINT(9.79733602695253 52.0008392513306),220000;110000,,substation,Godenau,TenneT;EON_Netz,50 +227,380.0,10.0821573265,48.2660901579,SRID=4326;POINT(10.0821573264928 48.2660901578788),380000,,substation,Vöhringen Amprion,Ampirion,50 +228,220.0,8.62716125806,49.543595267,SRID=4326;POINT(8.62716125806072 49.5435952670263),220000;110000,,substation,Umspannwerk Weinheim,,50 +229,220.0,8.50180851653,49.4449115114,SRID=4326;POINT(8.50180851652818 49.4449115114399),220000;110000,,substation,Mannheim,, +230,380.0,10.3917760569,48.5184609118,SRID=4326;POINT(10.391776056934 48.5184609117668),380000,,substation,Umspannwerk Gundelfingen,Amprion, +231,380.0,7.95805402329,47.5954722641,SRID=4326;POINT(7.95805402328778 47.5954722640613),380000;220000,,substation,Lastverteilung/Schaltanlage Kühmoos,Schluchseewerk AG,50 +232,380.0,10.0837512707,48.2688675153,SRID=4326;POINT(10.0837512706734 48.268867515302),380000;220000;110000,Station Nr. 625,substation,Vöhringen LEW,LEW,50 +233,380.0,9.88551509757,48.3040743585,SRID=4326;POINT(9.88551509756969 48.3040743584705),380000;220000;110000,,substation,Umspannwerk Dellmensingen,EnBW,50 +234,220.0,9.01340238046,47.8453486311,SRID=4326;POINT(9.01340238045655 47.8453486310857),220000;110000,,substation,Umspannwerk Stockach,,50 +235,220.0,8.25192548128,47.6397698265,SRID=4326;POINT(8.25192548128222 47.6397698264894),220000;110000,,substation,Umspannwerk Gurtweil,EnBW,50 +236,380.0,9.19675652645,48.912963438,SRID=4326;POINT(9.19675652645089 48.9129634380259),220000;110000,,substation,Umspannwerk Hoheneck EnBW,EnBW,50 +237,220.0,6.13981113771,49.6686086456,SRID=4326;POINT(6.13981113770544 49.6686086456422),220000;65000,,substation,Heisdorf,, +238,220.0,6.62053556843,49.7489386711,SRID=4326;POINT(6.62053556843084 49.7489386710632),220000,,substation,Station Trier,Amprion,50 +239,220.0,6.70228540768,49.8289944201,SRID=4326;POINT(6.70228540768092 49.8289944201161),220000;110000,,substation,Station Quint,Amprion; Westnetz,50 +240,220.0,8.5315149394,50.0478743321,SRID=4326;POINT(8.53151493939883 50.047874332134),220000,,substation,Station Kelsterbach,Süwag,50 +241,380.0,8.47137163304,50.0972077428,SRID=4326;POINT(8.4713716330449 50.0972077427754),380000;110000,,substation,Umspannwerk Kriftel,Amprion,50 +242,380.0,8.0657379664,50.4060681119,SRID=4326;POINT(8.06573796639762 50.4060681118621),380000;110000,,substation,Station Limburg,"Amprion, Süwag", +245,380.0,8.98953614978,50.0602550608,SRID=4326;POINT(8.98953614977701 50.0602550608281),380000;110000,,substation,Karlstein,Amprion,50 +246,380.0,8.95870856177,50.0826078105,SRID=4326;POINT(8.95870856177306 50.0826078105489),380000;220000,,substation,Umspannwerk Großkrotzenburg,TenneT,50 +247,380.0,10.3633842323,47.7383368093,SRID=4326;POINT(10.363384232266 47.7383368092793),380000;220000,,substation,Schaltanlage Leupolz,Amprion,50 +248,380.0,7.46150025902,51.6146143079,SRID=4326;POINT(7.46150025901999 51.6146143079042),,,generator,Trianel Kraftwerk Lünen,Trianel, +249,380.0,7.71823231963,51.6743422007,SRID=4326;POINT(7.71823231962748 51.6743422007117),,,generator,Gersteinwerk Block K,, +250,380.0,7.7155434195,51.6736430745,SRID=4326;POINT(7.71554341949648 51.6736430744508),,,generator,Gersteinwerk Block F,, +251,380.0,7.7145945335,51.6730996381,SRID=4326;POINT(7.71459453349968 51.6730996381138),,,generator,Gersteinwerk Block G,, +252,380.0,7.71270093431,51.6726636622,SRID=4326;POINT(7.71270093431027 51.672663662233),,,generator,Gersteinwerk Block I,, +253,380.0,7.98122159643,51.6735262619,SRID=4326;POINT(7.98122159643085 51.6735262619046),380000;110000,,substation,Station Lippborg,Amprion,50 +254,220.0,7.79256714933,49.8105487811,SRID=4326;POINT(7.79256714933307 49.8105487811028),220000;110000,,substation,Niederhausen,,50 +255,220.0,6.92013045453,49.9705818905,SRID=4326;POINT(6.92013045452502 49.9705818904706),220000;110000,,substation,Station Wengerohr,Amprion; Westnetz, +256,220.0,6.75552340557,49.3648695915,SRID=4326;POINT(6.75552340556687 49.3648695914696),220000,,substation,Dieffeln,VSE,50 +257,380.0,8.41467459584,49.7091468139,SRID=4326;POINT(8.41467459584413 49.7091468139245),,,plant,Kernkraftwerk Biblis,RWE, +258,220.0,9.88280429847,51.5155074977,SRID=4326;POINT(9.88280429847019 51.5155074976724),220000;110000,,substation,Umspannwerk Rosdorf,TenneT;AVACON,50 +259,220.0,9.85139357253,51.64913524,SRID=4326;POINT(9.85139357253472 51.6491352399809),220000;110000,,substation,Hardegsen,TenneT;EON_Netz,50 +260,380.0,8.78579302529,48.5837674015,SRID=4326;POINT(8.78579302528601 48.5837674015328),380000;110000,OBJTT,substation,EnBW Umspannwerk Oberjettingen,EnBW,50 +261,380.0,9.04543496799,48.9029620138,SRID=4326;POINT(9.0454349679866 48.9029620137561),380000;220000;110000,,substation,380kV-Umspannwerk Pulverdingen,EnBW Transportnetze AG,50 +262,380.0,9.8013171669,47.6525043241,SRID=4326;POINT(9.8013171668981 47.6525043240978),380000,,substation,Umspannwerk Obermooweiler,,50 +263,380.0,9.81091689215,47.1426452967,SRID=4326;POINT(9.81091689214732 47.142645296653),380000;220000,,substation,Umspannwerk Bürs,Vorarlberger Illwerke AG, +264,380.0,9.42551238607,48.0643507981,SRID=4326;POINT(9.42551238607291 48.0643507981263),380000;220000;110000,,substation,Umspannwerk Herbertingen,EnBW, +265,220.0,8.1049595741,52.264261374,SRID=4326;POINT(8.10495957410199 52.2642613740358),220000;110000,,substation,Station Lüstringen,Amprion,50 +266,220.0,12.1301906295,47.913165482,SRID=4326;POINT(12.1301906295374 47.9131654820025),220000,,substation,Umspannwerk Marienberg,"Tennet TSO GmbH, E.ON Netz GmbH", +267,380.0,6.8018236571,51.1247880606,SRID=4326;POINT(6.8018236570969 51.1247880606241),220000;110000,,substation,Station Sankt Peter 220 kV,RWE,50 +268,220.0,7.98506745762,50.8422254447,SRID=4326;POINT(7.98506745761923 50.8422254447013),220000;110000,,substation,Station Eiserfeld,Amprion,50 +269,380.0,8.28486177867,50.7590735195,SRID=4326;POINT(8.284861778672 50.7590735194915),380000;110000,,substation,Dillenburg,e.on;Mitte AG,50 +270,220.0,7.22423842685,53.3499271469,SRID=4326;POINT(7.22423842685352 53.3499271468647),220000;110000,EMDB,substation,Emden-Borßum,TenneT;EON_Netz,50 +271,220.0,7.20605284001,53.3413192542,SRID=4326;POINT(7.20605284001418 53.3413192541639),220000;110000,EMDE,substation,Umspannwerk Kraftwerk Emden,, +272,380.0,9.31819523639,54.716049409,SRID=4326;POINT(9.31819523638713 54.7160494089748),380000;220000;110000;20000,FLEN,substation,Flensburg,TenneT,50 +273,220.0,9.27063390877,55.0370264816,SRID=4326;POINT(9.27063390877085 55.0370264816237),400000;220000;150000,KAS,substation,400 kV Kassø,Energinet.dk, +274,220.0,9.43342930362,55.018722918,SRID=4326;POINT(9.43342930362242 55.0187229179606),220000;150000;60000,,substation,150 kV Enstedværket,, +275,220.0,7.43348911422,50.4365195361,SRID=4326;POINT(7.43348911422107 50.4365195361077),220000,,substation,,, +276,380.0,7.59330184857,50.3859437917,SRID=4326;POINT(7.59330184856855 50.3859437917469),380000;110000,,substation,Station Koblenz,RWE, +277,220.0,8.12215702006,53.5658323292,SRID=4326;POINT(8.12215702005755 53.5658323292485),220000,,substation,Atlantic,,50 +278,220.0,11.5615074728,52.1238924527,SRID=4326;POINT(11.5615074728142 52.1238924527264),220000;110000,,substation,Magdeburg (50Hertz),50Hertz Transmission,50 +279,380.0,12.8139099117,50.8579185114,SRID=4326;POINT(12.8139099116529 50.8579185114397),380000;220000;110000,ROE,substation,Umspannwerk Röhrsdorf,50Hertz,50 +280,380.0,13.3823233829,51.355996158,SRID=4326;POINT(13.3823233828996 51.3559961579888),380000;110000,STR,substation,Umspannwerk Streumen,50Hertz,50 +281,380.0,12.0449825162,50.7812567341,SRID=4326;POINT(12.0449825161601 50.7812567340735),380000;220000;110000,,substation,Umspannwerk Weida,50Hertz, +282,380.0,12.5182220968,51.1570725851,SRID=4326;POINT(12.5182220968156 51.1570725850832),380000;220000;110000,,substation,Eula,50Hertz Transmission,50 +283,380.0,8.48009523461,50.5909204314,SRID=4326;POINT(8.48009523461158 50.5909204313802),380000;110000,,substation,Umspannwerk Aßlar,E.ON Mitte AG, +284,380.0,8.65659923594,50.6055491256,SRID=4326;POINT(8.65659923594081 50.6055491256016),380000;110000,,substation,Umspannwerk Gießen Nord,,50 +285,220.0,8.08162670615,52.8586374845,SRID=4326;POINT(8.08162670615254 52.858637484526),220000,,substation,Cloppenburg 220 kV,TenneT,50 +286,220.0,9.56710201162,51.3252573277,SRID=4326;POINT(9.56710201161841 51.3252573276817),220000;110000,,substation,Sandershausen,TenneT,50 +287,220.0,7.74679248426,52.2885115081,SRID=4326;POINT(7.74679248425685 52.2885115081219),220000,,substation,Kraftwerkschaltanlage Ibbenbüren,, +288,220.0,7.91072928613,48.5012882982,SRID=4326;POINT(7.91072928612534 48.5012882982432),220000;110000,,substation,Umspannwerk Weier,EnBW,50 +289,220.0,7.72570634781,52.2771949017,SRID=4326;POINT(7.7257063478071 52.2771949017405),220000;110000,,substation,Station Ibbenbüren,Amprion,50 +290,380.0,14.2192282929,51.1207079922,SRID=4326;POINT(14.2192282929357 51.120707992198),380000;110000,,substation,Umspannwerk Schmölln,50Hertz;ENSO, +291,380.0,14.5700865499,51.4190136844,SRID=4326;POINT(14.5700865498873 51.4190136844256),,,generator,Kraftwerk Boxberg,Vattenfall Europe, +292,380.0,11.6441811001,51.9085128734,SRID=4326;POINT(11.6441811001256 51.908512873438),220000;110000,MD 38,substation,Umspannwerk Förderstedt,50Hertz,50 +293,220.0,11.4746908223,50.6040154814,SRID=4326;POINT(11.4746908222742 50.6040154814218),220000,,plant,Pumpspeicherkraftwerk Hohenwarte II,,50 +294,380.0,10.9910146333,50.5735269902,SRID=4326;POINT(10.9910146333086 50.573526990181),380000;110000,,substation,Umspannwerk Altenfeld,50Hertz,50 +295,220.0,10.5054991208,52.3018379056,SRID=4326;POINT(10.5054991208047 52.3018379056382),220000,,substation,Umspannwerk Braunschweig Nord,EON_Netz, +296,380.0,9.11362795987,52.5457924749,SRID=4326;POINT(9.11362795987411 52.5457924749284),,,plant,Kraftwerk Robert Frank,Statkraft Germany, +297,220.0,10.3692300486,52.1670924339,SRID=4326;POINT(10.3692300485918 52.1670924338554),220000;110000,,substation,Salzgitter-Hallendorf,TenneT;EON_Netz,50 +298,220.0,10.4195406346,52.236960662,SRID=4326;POINT(10.4195406346214 52.2369606619834),220000,,substation,Gleidingen 220 kV,TenneT, +299,220.0,10.0876468,52.3151305548,SRID=4326;POINT(10.0876467999739 52.3151305547947),220000;110000,,substation,Mehrum (220 kV),, +300,220.0,8.40314143641,52.6096440114,SRID=4326;POINT(8.40314143640892 52.609644011383),220000;110000,,substation,Station Sankt Hülfe,Amprion,50 +301,380.0,10.2358631058,52.29345954,SRID=4326;POINT(10.2358631058452 52.2934595399896),380000;110000,U4,substation,Klein Ilsede,,50 +302,380.0,9.51424009148,51.2590355806,SRID=4326;POINT(9.5142400914841 51.2590355805955),380000;110000,,substation,Umspannwerk Bergshausen,TenneT, +303,380.0,9.38765125749,51.6407335256,SRID=4326;POINT(9.38765125748855 51.640733525594),380000;220000;110000,,substation,Umspannanlage Würgassen,, +304,380.0,9.77415043834,50.5287496465,SRID=4326;POINT(9.77415043834156 50.5287496465011),380000;110000,,substation,UW Dipperz,Tennet,50 +305,380.0,9.17745967557,49.0378098505,SRID=4326;POINT(9.17745967557079 49.037809850476),380000;110000,,substation,Umrichterwerk Neckarwestheim,DB_Energie,50;16.7 +306,380.0,9.17731110344,49.0393782004,SRID=4326;POINT(9.17731110343941 49.039378200434),380000,,substation,Gemeinschaftskraftwerk Neckarwestheim 2,, +307,380.0,9.15135394991,49.1433834119,SRID=4326;POINT(9.15135394990856 49.1433834119236),380000;220000;110000,,substation,380-kV-Umspannwerk Großgartach,, +308,380.0,10.1878339996,49.9812190408,SRID=4326;POINT(10.1878339995516 49.981219040845),380000,GR,substation,Grafenrheinfeld,, +309,380.0,9.44691867105,49.5902629324,SRID=4326;POINT(9.44691867105201 49.5902629324336),380000;110000,,substation,Umspannwerk Höpfingen,EnBW,50 +310,380.0,9.20736443094,49.176637954,SRID=4326;POINT(9.20736443093694 49.1766379540065),,,generator,Heizkraftwerk Heilbronn,, +311,380.0,9.69465635518,49.2325925412,SRID=4326;POINT(9.69465635518013 49.2325925411591),380000;110000,,substation,Umspannwerk Kupferzell,EnBW,50 +312,380.0,10.1174866947,48.8971053361,SRID=4326;POINT(10.1174866946797 48.8971053361382),380000;220000;110000,,substation,Umspannwerk Goldshöfe,EnBW,50 +313,380.0,12.3374229103,51.1969683571,SRID=4326;POINT(12.3374229103144 51.1969683571228),380000;110000,,substation,Pulgar,50 Hertz Transmission;Stadtwerke Leipzig, +314,380.0,10.8675658805,48.5394690937,SRID=4326;POINT(10.8675658805484 48.5394690937344),380000,,substation,Station Meitingen,Net, +315,380.0,10.8408695619,48.2380310092,SRID=4326;POINT(10.8408695619217 48.2380310092335),380000;220000;110000,173A,substation,Umspannwerk Oberottmarshausen,Lech-Elektrizitätswerke,50 +316,380.0,10.9367289314,48.4013064415,SRID=4326;POINT(10.9367289314287 48.4013064414835),380000;110000,613 U;1000,substation,Umspannwerk Lechhausen; Übernahmestation Lechhausen,Lechwerke;Stadtwerke Augsburg,50 +317,380.0,10.873935757,47.2444239286,SRID=4326;POINT(10.8739357569814 47.2444239286028),380000;220000;110000,,substation,APG Umspannwerk Westtirol,Austrian Power Grid AG,50 +318,380.0,10.8637616199,48.5468231051,SRID=4326;POINT(10.8637616199498 48.5468231051405),380000;110000,122 U,substation,Umspannwerk Meitingen,Elektrizitätswerke Augsburg, +319,380.0,9.70423149899,48.4650637321,SRID=4326;POINT(9.70423149899321 48.465063732143),380000;110000,,substation,EnBW Umspannwerk Laichingen,EnBW,50 +320,380.0,9.40063139462,48.6669424397,SRID=4326;POINT(9.40063139462492 48.6669424396984),380000;220000;110000,,substation,EnBW Umspannwerk Wendlingen,, +321,220.0,7.57243731328,48.0188749889,SRID=4326;POINT(7.5724373132812 48.0188749889275),400000,,substation,Poste électrique de Vogelgrun,, +322,380.0,7.75186249821,48.0964057189,SRID=4326;POINT(7.75186249820802 48.0964057188536),220000;110000,,substation,220-kV-Umspannwerk Eichstetten,EnBW,50 +323,220.0,12.4772465233,50.7531200813,SRID=4326;POINT(12.4772465232803 50.7531200813148),220000;110000,,substation,Crossen,,50 +324,380.0,14.9464972856,51.062586216,SRID=4326;POINT(14.9464972856333 51.0625862159619),380000;110000,HGW,substation,Umspannwerk Hagenwerder,50Hertz,50 +325,380.0,15.1149907449,51.0905795559,SRID=4326;POINT(15.1149907448572 51.0905795559394),400000;220000;110000,MIK,substation,"Stacja elektroenergetyczna ""Mikułowa"" 400/220/110 kV",PSE Operator, +326,380.0,13.8412680841,50.9901759359,SRID=4326;POINT(13.8412680841242 50.9901759359427),380000;110000,,substation,Umspannwerk DD-Süd,Vattenfall;ENSO Strom AG;DREWAG,50 +327,380.0,13.2555550237,50.9301782416,SRID=4326;POINT(13.255555023675 50.9301782416399),380000,,substation,Freiberg/Nord (380 kV),50Hertz Transmission,50 +328,220.0,13.0818771202,48.2565212405,SRID=4326;POINT(13.0818771201792 48.2565212405017),,,substation,Umspannwerk St. Peter,Austrian Power Grid AG, +329,220.0,13.0027821071,48.2619682658,SRID=4326;POINT(13.0027821071279 48.261968265807),220000,,substation,Umspannwerk Simbach,"Tennet, E.on Netz", +330,220.0,12.2208353621,48.5857329877,SRID=4326;POINT(12.2208353620671 48.5857329876532),220000;110000,AHM,substation,Umspannwerk Altheim,"TenneT TSO GmbH, E.ON Netz GmbH",50 +331,220.0,12.7474442277,48.1487558435,SRID=4326;POINT(12.7474442277007 48.1487558434787),220000;110000,PI,substation,Umspannwerk Pirach,"TenneT TSO GmbH, E.ON Netz GmbH",50 +332,380.0,13.1034346393,48.6667929606,SRID=4326;POINT(13.1034346393158 48.6667929606034),380000;220000;110000,PT,substation,Umspannwerk Pleinting,"TenneT TSO GmbH, E.ON Netz GmbH",50 +333,380.0,9.19698235872,54.8510986052,SRID=4326;POINT(9.19698235871826 54.8510986052119),380000;110000;20000,,substation,Umspannwerk Jardelund,TenneT,50 +334,380.0,11.1937277954,50.1622144406,SRID=4326;POINT(11.193727795361 50.1622144406172),380000;110000,RED,substation,Umspannwerk Redwitz,, +335,380.0,10.8360157548,49.9280673897,SRID=4326;POINT(10.8360157547717 49.9280673897058),380000,OD,substation,Umspannwerk Oberhaid,TenneT TSO GmbH, +336,380.0,11.8107025454,50.1837655261,SRID=4326;POINT(11.8107025453557 50.1837655260691),380000;220000;110000,MH,substation,Umspannwerk Mechlenreuth,TenneT TSO GmbH;E.ON Netz GmbH,50 +337,380.0,12.2968837896,48.6069099226,SRID=4326;POINT(12.2968837896062 48.6069099225677),380000,IS,substation,Isar,, +338,380.0,12.0822544457,49.2974477669,SRID=4326;POINT(12.0822544456506 49.2974477669261),380000;220000;110000,SD,substation,Schwandorf,"TenneT TSO GmbH, E.ON Netz GmbH",50 +339,220.0,8.95368121281,49.5674431691,SRID=4326;POINT(8.95368121281126 49.5674431691125),220000;110000,,substation,Beerfelden,,50 +340,220.0,12.4696440086,51.3716629493,SRID=4326;POINT(12.4696440085589 51.3716629493371),220000;110000,,substation,Umspannwerk Taucha,50Hertz;enviaM,50 +341,220.0,12.2488239058,51.1978824278,SRID=4326;POINT(12.2488239057592 51.1978824277899),220000;110000,,substation,Großdalzig,50Hertz Transmission;enviaM,50 +342,220.0,10.7346694298,51.440186758,SRID=4326;POINT(10.7346694298176 51.4401867580454),220000,WK,substation,Wolkramshausen (220 kV),50Hertz,50 +343,380.0,11.0049169149,50.5074149691,SRID=4326;POINT(11.0049169149012 50.5074149690608),380000,,substation,Pumpspeicherwerk Goldisthal,,50 +344,220.0,13.0025898988,50.8659127197,SRID=4326;POINT(13.0025898987886 50.8659127196838),220000;110000,Nws,substation,Niederwiesa,eins-Energie,50 +345,220.0,12.2428369226,50.5674531671,SRID=4326;POINT(12.2428369226029 50.5674531670625),220000;110000,Her,substation,Umspannwerk Herlasgrün,50Hertz;enviaM,50 +346,380.0,13.2490283672,52.5316238928,SRID=4326;POINT(13.249028367237 52.5316238928337),380000;110000,,substation,Umspannwerk Reuter,50Hertz,50 +347,380.0,13.3155158514,52.5219993683,SRID=4326;POINT(13.3155158513838 52.5219993683183),380000;110000,,substation,Umspannwerk Charlottenburg,50Hertz,50 +348,380.0,13.3686584616,52.5031422116,SRID=4326;POINT(13.3686584616208 52.5031422115792),380000;110000,,substation,Berlin/Mitte,50Hertz;Stromnetz Berlin, +349,220.0,11.7924138446,48.8382240227,SRID=4326;POINT(11.7924138446413 48.8382240226632),220000;110000,SIT,substation,Umspannwerk Sittling,TenneT TSO GmbH;E.ON Netz GmbH, +350,220.0,11.3269526752,49.3934731176,SRID=4326;POINT(11.3269526751765 49.393473117636),220000;110000,LU,substation,Umspannwerk Ludersheim,TenneT TSO GmbH;E.ON Netz GmbH,50 +351,380.0,11.5077305511,48.7611239877,SRID=4326;POINT(11.5077305510922 48.7611239877457),380000;220000;110000,IN,substation,Ingolstadt,,50 +352,220.0,11.8076056361,48.4554267991,SRID=4326;POINT(11.8076056361242 48.4554267991033),220000;110000,ZOL,substation,Zolling - Irsching,, +353,380.0,11.5813705216,48.7648940509,SRID=4326;POINT(11.5813705216127 48.7648940508515),380000;220000;110000,IRS,substation,Irsching,,50 +354,380.0,11.6393121108,48.1787834095,SRID=4326;POINT(11.6393121107521 48.1787834095237),380000;110000,,substation,Föhring,,50 +355,220.0,6.17791451185,49.9530650802,SRID=4326;POINT(6.17791451185192 49.9530650802033),220000,,substation,Vianden,SEO, +356,220.0,6.21102031527,49.9586257532,SRID=4326;POINT(6.21102031526762 49.9586257531557),220000,,substation,SA Bauler,Amprion,50 +357,220.0,6.11451700703,49.9053646588,SRID=4326;POINT(6.11451700702834 49.9053646588261),220000;65000,,substation,Flebour,, +358,220.0,6.08346044961,49.7764406189,SRID=4326;POINT(6.08346044961418 49.7764406189112),220000;65000,,substation,Roost,, +359,380.0,6.2769417852,49.1977400674,SRID=4326;POINT(6.27694178519793 49.1977400673896),400000;225000,,substation,Poste électrique de Vigy,RTE, +360,380.0,6.77320783778,49.2910742104,SRID=4326;POINT(6.77320783777607 49.2910742103854),380000;220000;110000,,substation,Umspannanlage Ensdorf,,50 +361,220.0,6.70365235664,49.1385111923,SRID=4326;POINT(6.70365235663769 49.1385111923078),400000;225000;90000,,substation,Poste électrique de Saint-Avold,RTE, +362,380.0,8.04936352422,47.5508818535,SRID=4326;POINT(8.04936352422006 47.550881853478),380000;220000,,substation,Laufenburg,,50 +363,220.0,7.50006173414,51.5351740925,SRID=4326;POINT(7.50006173413631 51.535174092458),220000,,substation,Ratsbusch,,50 +364,220.0,6.97593254033,51.0376007285,SRID=4326;POINT(6.97593254033334 51.0376007284972),220000;110000,,substation,Station Dhünnaue,, +365,380.0,13.2428489964,52.5349539947,SRID=4326;POINT(13.2428489964032 52.5349539946502),,,generator,Heizkraftwerk Reuter West,, +366,380.0,8.56218193445,49.132949936,SRID=4326;POINT(8.56218193445384 49.132949935954),380000;110000,,substation,Umspannwerk Bruchsal-Kändelweg,EnBW;TransnetBW;NetzeBW,50 +367,380.0,8.4413154852,49.251402647,SRID=4326;POINT(8.4413154852023 49.2514026469585),380000,,substation,Philippsburg 380 kV,EnBW,50 +368,380.0,7.94208128553,47.6533415488,SRID=4326;POINT(7.94208128553147 47.6533415488354),,,generator,Kavernenkraftwerk Wehr,, +369,380.0,9.06879105809,53.5666414121,SRID=4326;POINT(9.06879105808819 53.5666414121342),380000;110000,,substation,Alfstedt,EON_Netz,50 +370,220.0,6.7413761725,49.3574919374,SRID=4326;POINT(6.7413761724965 49.3574919373628),220000,,substation,UA Dillinger Hütte Mitte,,50 +371,220.0,6.74970009407,49.3651763748,SRID=4326;POINT(6.74970009407096 49.3651763747607),220000,,substation,UA Obergraben,Amprion,50 +372,220.0,6.76601564521,49.3600771524,SRID=4326;POINT(6.76601564520935 49.3600771523749),220000;110000,,substation,Station Saarwellingen,Amprion,50 +373,220.0,12.1504016214,49.0127518128,SRID=4326;POINT(12.1504016214021 49.0127518128214),220000,RE,substation,Umspannwerk Regensburg,"TenneT TSO GmbH, E.ON Netz GmbH", +374,220.0,7.59135984066,51.928441667,SRID=4326;POINT(7.5913598406626 51.928441667004),220000;110000,,substation,Station Münster,RWE,50 +375,220.0,8.45154437252,49.007402913,SRID=4326;POINT(8.45154437251894 49.0074029130057),220000;110000,,substation,Karlsruhe-Rintheim,,50 +376,380.0,8.99620817966,52.3795879858,SRID=4326;POINT(8.99620817966153 52.379587985752),380000,,substation,KW Heyden,,50 +377,380.0,9.56282337411,48.70148982,SRID=4326;POINT(9.56282337410874 48.7014898200493),380000;110000,BZW,substation,EnBW Umspannwerk Bünzwangen,EnBW,50 +378,380.0,9.3708454159,48.71912333,SRID=4326;POINT(9.37084541589578 48.7191233300187),380000;110000,,substation,EnBW Umspannwerk Altbach 1,,50 +379,380.0,9.37760577644,48.8126068571,SRID=4326;POINT(9.37760577643558 48.812606857093),380000;110000,,substation,EnBW Umspannwerk Endersbach,Netze BW, +380,220.0,9.82158380229,52.4248552546,SRID=4326;POINT(9.82158380229154 52.4248552546066),220000;110000,LAH,substation,Lahe,enercity Netzgesellschaft mbH,50 +381,220.0,9.0762254363,49.3633869626,SRID=4326;POINT(9.0762254363019 49.3633869626072),220000;110000,,substation,Obrigheim,,50 +382,380.0,10.160291269,49.9891947838,SRID=4326;POINT(10.1602912689993 49.9891947837947),380000;220000;110000,BRF,substation,Umspannwerk Bergrheinfeld,TenneT TSO GmbH,50 +383,220.0,10.196491572,50.0279724033,SRID=4326;POINT(10.1964915719944 50.0279724033143),220000;110000,,substation,Schweinfurt,,50 +384,380.0,10.843117799,49.3786926858,SRID=4326;POINT(10.8431177989998 49.3786926858391),380000;220000;110000,RAI,substation,Umspannwerk Raitersaich,TenneT TSO GmbH;E.ON Netz GmbH,50 +385,380.0,9.09051754869,49.9700413736,SRID=4326;POINT(9.09051754868887 49.9700413735997),380000;220000;110000,AS,substation,Umspannwerk Aschaffenburg,TenneT TSO GmbH;E.ON Netz GmbH,50 +386,220.0,8.81399419356,51.9834366294,SRID=4326;POINT(8.8139941935628 51.9834366294197),220000;110000,,substation,Umspannwerk Lage,, +387,220.0,8.93221843866,52.1933124298,SRID=4326;POINT(8.93221843866486 52.1933124298166),220000;110000,,substation,Veltheim,,50 +388,220.0,8.95904413219,52.2784949765,SRID=4326;POINT(8.95904413219088 52.2784949764899),220000;110000,,substation,Umspannwerk Meißen,EON_WestfalenWeser,50 +389,220.0,9.59430158866,49.7984427303,SRID=4326;POINT(9.59430158866246 49.7984427303145),220000;110000,TRE,substation,Umspannwerk Trennfeld,TenneT TSO GmbH;E.ON Netz GmbH,50 +390,380.0,10.9729038959,49.5585073757,SRID=4326;POINT(10.9729038959004 49.5585073756737),380000;110000,KRI,substation,Kriegenbrunn,EON_Netz,50 +391,380.0,8.95464412134,50.0860157333,SRID=4326;POINT(8.95464412133747 50.0860157333265),220000;110000,,substation,Kraftwerkseinspeisung Staudinger,,50 +392,220.0,7.17590438915,51.4755782506,SRID=4326;POINT(7.17590438914786 51.4755782506206),220000,,substation,UW Stahlwerk ThyssenKrupp Nirosta,, +393,380.0,10.2316415496,48.5297622793,SRID=4326;POINT(10.231641549556 48.5297622792522),380000;110000,,substation,Umspannwerk Niederstotzingen,EnBW,50 +394,380.0,10.2224539936,48.7367522348,SRID=4326;POINT(10.2224539935941 48.7367522347984),380000;110000,,substation,Umspannwerk Rotensohl,EnBW,50 +395,380.0,9.64533503255,47.744596109,SRID=4326;POINT(9.64533503255392 47.7445961090448),380000;110000,,substation,380kV-Umspannwerk Grünkraut,,50 +396,380.0,10.4015868558,48.5099297353,SRID=4326;POINT(10.4015868558303 48.5099297352537),380000;110000,Station 179 U,substation,Gundremmingen,LEW,50 +397,380.0,8.80962330906,50.2034748086,SRID=4326;POINT(8.80962330905886 50.2034748086043),380000;110000,,substation,Umspannwerk Karben,TenneT, +398,380.0,9.72460611042,54.2882702503,SRID=4326;POINT(9.72460611042234 54.2882702503445),380000;110000,,substation,Audorf,TenneT,50 +399,220.0,9.0971422094,48.8277618381,SRID=4326;POINT(9.09714220939812 48.8277618380686),220000;110000,,substation,Umspannwerk Weilimdorf,, +400,220.0,8.62886493304,51.9936340707,SRID=4326;POINT(8.62886493303814 51.9936340707213),220000;110000,,substation,Umspannwerk Bielefeld Ost,Stadtwerke Bielefeld GmbH,50 +401,220.0,10.0604081018,54.3487708202,SRID=4326;POINT(10.0604081018258 54.3487708201584),220000;110000,,substation,Umspannwerk Kiel West,TenneT,50 +402,220.0,9.44963652666,53.9672864974,SRID=4326;POINT(9.44963652666114 53.967286497351),220000;110000,,substation,Itzehoe/West,TenneT TSO GmbH,50 +403,380.0,8.25885994833,47.6342116214,SRID=4326;POINT(8.25885994832776 47.6342116213731),380000;220000;110000,,substation,Tiengen,Amprion,50 +404,380.0,8.23209442034,47.5561434831,SRID=4326;POINT(8.23209442033504 47.5561434831307),380000;220000,,substation,Betznau,, +405,380.0,7.75259055668,47.5391330215,SRID=4326;POINT(7.75259055668336 47.5391330214816),380000,,substation,Asphard,ALPIQ AXPO, +406,220.0,8.86152575255,47.8064353221,SRID=4326;POINT(8.86152575255362 47.8064353220893),220000;110000,,substation,Beuren,,50 +409,380.0,9.21565014324,48.8523053232,SRID=4326;POINT(9.21565014323537 48.8523053231829),380000;110000,,substation,Umspannwerk Mühlhausen,EnBW AG,50 +410,380.0,10.4012813869,48.5146084081,SRID=4326;POINT(10.4012813869114 48.5146084080517),,KRB,plant,Kernkraftwerk Gundremmingen,Kernkraftwerk Gundremmingen GmbH, +411,220.0,8.73757268936,51.7001692737,SRID=4326;POINT(8.73757268935919 51.7001692737445),220000;110000,,substation,Umspannwerk Paderborn Süd,TenneT,50 +412,220.0,10.6414677086,53.9191626885,SRID=4326;POINT(10.6414677086397 53.9191626884641),220000;110000,,substation,Lübeck,TenneT;EON_Netz,50 +413,380.0,8.67371728898,49.2904393851,SRID=4326;POINT(8.67371728898445 49.2904393851271),380000;220000;110000,,substation,Umspannwerk Wiesloch,EnBW,50 +414,220.0,9.48789502699,53.6365577623,SRID=4326;POINT(9.48789502699389 53.6365577623199),220000,,substation,Götzdorf,,50 +415,220.0,9.49151079251,53.6580982807,SRID=4326;POINT(9.49151079250535 53.6580982806881),220000,,substation,Abbenfleth,TenneT,50 +416,380.0,7.47226685749,47.6466103448,SRID=4326;POINT(7.47226685748746 47.6466103447741),400000,,substation,Poste électrique de Sierentz,, +417,220.0,13.4428541936,48.4345463644,SRID=4326;POINT(13.4428541936116 48.4345463643775),220000,,substation,Freiluftschaltanlage KW Schärding-Neuhaus,, +418,220.0,13.710690104,48.5193051607,SRID=4326;POINT(13.7106901040113 48.5193051606747),220000,,substation,Jochenstein,, +421,380.0,9.09183529743,49.2946188662,SRID=4326;POINT(9.09183529743294 49.2946188662059),380000;220000;110000,,substation,Umspannwerk Hüffenhardt,,50 +422,220.0,8.66378315807,49.3600873719,SRID=4326;POINT(8.66378315807233 49.3600873718872),220000;110000,,substation,Umspannanlage Heidelberg-Süd,Stadtwerke Heidelberg Netze GmbH,50 +423,220.0,7.95849127307,47.5791483196,SRID=4326;POINT(7.95849127307054 47.5791483196323),,,generator,Kavernenkraftwerk Säckingen,Schluchseewerk AG, +424,220.0,8.34912612289,49.0130521178,SRID=4326;POINT(8.34912612288763 49.0130521177978),220000;110000,,substation,Karlsruhe-West,,50 +425,220.0,10.4203895578,52.1652004277,SRID=4326;POINT(10.4203895577612 52.1652004276784),220000,,substation,,Salzgitter AG,50 +426,220.0,10.3884721772,52.1513649329,SRID=4326;POINT(10.3884721771883 52.1513649329361),220000,,substation,,, +427,380.0,8.30626265141,49.0128258363,SRID=4326;POINT(8.30626265141083 49.0128258362562),380000,,substation,,EnBW AG,50 +428,380.0,14.4598868224,51.8332759246,SRID=4326;POINT(14.4598868223649 51.8332759246019),,,plant,Kraftwerk Jänschwalde,Vattenfall Europe, +429,380.0,6.21619297137,50.9123617957,SRID=4326;POINT(6.21619297137216 50.9123617956925),380000;110000,,substation,Siersdorf,,50 +430,380.0,5.91852734478,51.1484263858,SRID=4326;POINT(5.91852734477995 51.1484263858426),380000,,substation,380kV Maasbracht,TenneT, +431,380.0,6.21401539902,50.9438998574,SRID=4326;POINT(6.2140153990158 50.9438998573728),380000,,auxillary_T_node,Kirchberg Süd,Amprion,50 +432,220.0,10.9662583424,47.2695304689,SRID=4326;POINT(10.9662583424068 47.2695304688802),220000;110000,,substation,Silz,TIWAG-Netz AG, +433,220.0,11.2777210418,47.495872667,SRID=4326;POINT(11.2777210418419 47.4958726669837),220000;110000,,substation,Umspannwerk Krün,E.ON Netz GmbH,50 +434,220.0,11.2869438321,47.4928624569,SRID=4326;POINT(11.2869438320852 47.4928624569052),220000,250;251,auxillary_T_node,Silz (AT) - Krün (DE) - Oberbrunn,,50 +435,220.0,13.5053368398,52.474778852,SRID=4326;POINT(13.5053368398164 52.4747788520482),220000,302,auxillary_T_node,302 Marzahn - Thyrow,50Hertz,50 +436,380.0,8.46542980021,48.0435236211,SRID=4326;POINT(8.46542980021065 48.0435236210637),380000;110000,,substation,Umspannwerk Villingen,EnBW; Energiedienst,50 +437,380.0,8.53055554991,47.9148481846,SRID=4326;POINT(8.53055554991256 47.9148481845625),380000,7630,auxillary_T_node,ENGST-KUHMO-VILLI,EnBW,50 +438,220.0,8.24872901623,48.8463165189,SRID=4326;POINT(8.24872901623214 48.8463165189067),220000;110000,,substation,220-kV-Umspannwerk Kuppenheim,,50 +439,220.0,8.2497504566,48.8491773731,SRID=4326;POINT(8.24975045659741 48.8491773731136),220000,DAXLA-KUMRA-WEIER,auxillary_T_node,Daxlanden - Weier,EnBW,50 +440,220.0,9.45967747872,53.8646574513,SRID=4326;POINT(9.45967747872262 53.8646574512707),220000,202,auxillary_T_node,202,TenneT,50 +441,380.0,11.4703941249,50.9303902295,SRID=4326;POINT(11.4703941249156 50.9303902294723),380000;110000,,substation,Großschwabhausen,50Hertz;EON_TEAG,50 +442,380.0,11.4299065773,50.9114464611,SRID=4326;POINT(11.4299065773025 50.9114464610955),380000,415,auxillary_T_node,415,50Hertz,50 +443,380.0,7.25465688214,53.0266716316,SRID=4326;POINT(7.25465688214231 53.0266716315927),380000,RHED,substation,Umspannwerk Rhede,TenneT,50 +444,380.0,7.25501773539,53.0267136928,SRID=4326;POINT(7.25501773539194 53.0267136928067),380000,305,auxillary_T_node,Emsland West,Amprion;TenneT,50 +445,220.0,7.37185639417,52.1977066548,SRID=4326;POINT(7.37185639416872 52.1977066547834),220000,,auxillary_T_node,Amelsbüren,Amprion,50 +446,220.0,13.3998288404,53.7944477778,SRID=4326;POINT(13.399828840425 53.7944477778114),220000,,substation,Umspannwerk Windpark Iven,50Hertz Transmission,50 +447,220.0,13.3984708282,53.7933318755,SRID=4326;POINT(13.3984708281663 53.7933318754816),220000,316,auxillary_T_node,316,50Hertz Transmission,50 +448,380.0,7.42474524551,51.4592583943,SRID=4326;POINT(7.42474524551043 51.4592583943398),380000,,auxillary_T_node,Schwerterheide Nord,Amprion,50 +449,380.0,7.12336046769,51.4473443869,SRID=4326;POINT(7.12336046768833 51.4473443869048),380000,,auxillary_T_node,Oerkhaus West,Amprion,50 +450,380.0,7.41670791196,51.4570521583,SRID=4326;POINT(7.41670791195847 51.4570521583476),380000;220000;110000,,substation,Station Kruckel,RWE;DSW 21; AVU,50 +451,380.0,7.41658261385,51.4607823587,SRID=4326;POINT(7.41658261384975 51.4607823586596),380000,,auxillary_T_node,Witten West,Amprion,50 +452,380.0,6.53175001009,50.3900512646,SRID=4326;POINT(6.53175001008574 50.39005126464),380000;110000,,substation,Station Dahlem,Amprion; Westnetz, +453,380.0,6.53303653132,50.3922427777,SRID=4326;POINT(6.53303653131644 50.3922427776709),380000,,auxillary_T_node,Selhausen West,Amprion,50 +454,380.0,6.60879226771,51.0346787721,SRID=4326;POINT(6.60879226770999 51.0346787720857),380000,,auxillary_T_node,Neurath 2b,Amprion,50 +455,380.0,6.80890624664,51.2686075941,SRID=4326;POINT(6.80890624663756 51.2686075940546),380000,,substation,Station Rath,,50 +456,380.0,6.72033167479,51.3441235774,SRID=4326;POINT(6.72033167479422 51.3441235773954),380000,,auxillary_T_node,Meerbusch West,Amprion,50 +457,220.0,7.13937473426,51.5608313917,SRID=4326;POINT(7.13937473425833 51.5608313916517),220000,,auxillary_T_node,Hochlarmark West,Amprion,50 +458,380.0,7.41946378046,51.4575100936,SRID=4326;POINT(7.41946378046051 51.4575100936229),220000,,auxillary_T_node,Haarstrang Ost,Amprion,50 +459,220.0,7.19597933005,51.4115753149,SRID=4326;POINT(7.19597933005019 51.4115753148661),220000,,substation,Station Welper,RWE, +460,220.0,7.17920262036,51.4218244824,SRID=4326;POINT(7.17920262035811 51.4218244823956),220000,,auxillary_T_node,Welper Ost,Amprion,50 +461,220.0,7.32427958321,51.6213874676,SRID=4326;POINT(7.32427958320741 51.6213874676194),220000,,substation,Rheinzink,,50 +462,220.0,7.34371334413,51.6131049439,SRID=4326;POINT(7.34371334412713 51.6131049439167),220000,,auxillary_T_node,Elmenhorst Süd,Amprion,50 +463,220.0,6.52272548882,51.3224954693,SRID=4326;POINT(6.52272548882226 51.3224954692993),220000,,auxillary_T_node,Utfort West,Amprion,50 +464,220.0,6.96330545404,50.8337629178,SRID=4326;POINT(6.96330545403678 50.8337629177582),220000;110000,,substation,Station Bollenacker,RWE,50 +465,220.0,6.92909269081,50.8441529722,SRID=4326;POINT(6.92909269080674 50.8441529722133),220000,,auxillary_T_node,Godorf Ost,Amprion,50 +466,220.0,7.09582671881,50.8268716757,SRID=4326;POINT(7.09582671880563 50.8268716757217),220000;110000,,substation,Station Stockem,Westnetz, +467,220.0,7.09435790958,50.8269670893,SRID=4326;POINT(7.09435790958289 50.8269670893312),220000,,auxillary_T_node,Gremberghoven Ost,Amprion,50 +468,220.0,6.57729057688,51.5563911802,SRID=4326;POINT(6.57729057687864 51.5563911802085),220000;110000,,substation,Station Ossenberg,Amprion,50 +469,220.0,6.65066741434,51.5537022721,SRID=4326;POINT(6.65066741434228 51.5537022721424),220000,,auxillary_T_node,Wesel Ost,Amprion,50 +470,380.0,7.82599043341,51.7500675543,SRID=4326;POINT(7.82599043340792 51.7500675542728),380000,,auxillary_T_node,Uentrop Nord,Amprion,50 +471,220.0,6.92897905392,50.8438169469,SRID=4326;POINT(6.9289790539233 50.84381694694),220000,,auxillary_T_node,Olefin West,Amprion,50 +472,380.0,7.99395911966,51.8230954342,SRID=4326;POINT(7.9939591196577 51.823095434189),380000;110000,,substation,Station Enniger,Amprion,50 +473,380.0,7.83402074323,51.7550403465,SRID=4326;POINT(7.83402074322725 51.7550403464858),380000,,auxillary_T_node,Gersteinwerk Ost,Amprion,50 +474,220.0,9.14517687189,52.853246436,SRID=4326;POINT(9.14517687189382 52.8532464360106),220000;110000,,substation,Wechold,TenneT TSO GmbH;Avacon AG,50 +475,220.0,9.13794792584,52.8470269944,SRID=4326;POINT(9.13794792584036 52.8470269944345),220000,1,auxillary_T_node,Leitung 2010/2023,TenneT,50 +476,220.0,8.31888925383,52.0660374597,SRID=4326;POINT(8.31888925383353 52.0660374597241),220000;110000,,substation,Station Hesseln,RWE,50 +477,220.0,8.33466775125,52.0705200427,SRID=4326;POINT(8.33466775124759 52.07052004268),220000,,auxillary_T_node,Halle West,Amprion,50 +478,380.0,7.9818783186,51.6766515665,SRID=4326;POINT(7.9818783185962 51.6766515664854),380000,,auxillary_T_node,Geithe West,Amprion,50 +479,220.0,9.92382983733,51.8975617616,SRID=4326;POINT(9.92382983732643 51.8975617615529),220000,,substation,Erzhausen,,50 +480,220.0,9.8524162497,51.8987652474,SRID=4326;POINT(9.8524162497002 51.8987652474175),220000,2030/2001,auxillary_T_node,Leitung 2030/2001,TenneT,50 +481,380.0,9.41368122089,52.0274828921,SRID=4326;POINT(9.4136812208929 52.0274828920508),380000,3019;3026;3027,auxillary_T_node,Leitung 3019/3026/3027,,50 +482,220.0,10.3552149923,52.1758109607,SRID=4326;POINT(10.3552149923206 52.1758109606876),220000,2,auxillary_T_node,Leitung 2019/2027/2029,TenneT,50 +483,380.0,10.70273189,47.8264597977,SRID=4326;POINT(10.7027318899547 47.8264597977002),380000;110000,,substation,Umspannwerk Bidingen Nr. 697 S,LEW,50 +484,380.0,10.7037422783,47.8264159805,SRID=4326;POINT(10.7037422782749 47.826415980499),380000,,auxillary_T_node,K12,, +485,220.0,10.194933444,47.9709368783,SRID=4326;POINT(10.1949334440241 47.9709368782958),220000;110000,,substation,Umspannwerk Memmingen,LEW,50 +486,220.0,10.6412867739,48.027451241,SRID=4326;POINT(10.6412867739286 48.0274512409907),220000;110000,,substation,Umspannstation Irsingen,,50 +487,220.0,10.6418025411,48.0258492685,SRID=4326;POINT(10.6418025411196 48.0258492685192),220000,O11,auxillary_T_node,,, +488,380.0,8.67009921733,49.3092022843,SRID=4326;POINT(8.67009921732699 49.3092022842852),380000,,auxillary_T_node,HD-Neurott - Hüffenhardt,, +489,380.0,9.22808874458,51.8124533664,SRID=4326;POINT(9.22808874457675 51.8124533663924),380000;110000,,substation,Vörden,transpower, +490,380.0,9.23187002273,51.8101190942,SRID=4326;POINT(9.23187002273009 51.810119094224),380000,3012,auxillary_T_node,,TenneT, +491,380.0,10.2341629654,48.5289320394,SRID=4326;POINT(10.2341629653667 48.5289320393823),380000,,auxillary_T_node,Delmensingen - Niederstotzingen - Rotensohl,,50 +492,220.0,8.73513573367,50.1591915404,SRID=4326;POINT(8.73513573367143 50.1591915403542),220000;110000,,substation,Umspannwerk Frankfurt Nord,TenneT;Mainova, +493,220.0,8.87225963915,50.1297253197,SRID=4326;POINT(8.87225963914833 50.1297253197387),110000,DOEH,substation,Dörnigheim 110 kV,EON_Netz,50 +494,220.0,8.74326052935,50.1623379811,SRID=4326;POINT(8.74326052934848 50.1623379810729),220000,,auxillary_T_node,Staudinger - Dörnigheim - Frankfurt Nord,, +495,380.0,9.3322301656,54.7210227584,SRID=4326;POINT(9.33223016559803 54.7210227584184),380000,,auxillary_T_node,Audorf - Jardelund,TenneT TSO GmbH,50 +2_220kV,220.0,9.11321007473,52.5438533224,SRID=4326;POINT(9.11321007472722 52.5438533223737),220000,,substation,Umspannwerk Kraftwerk Robert Frank,TenneT,50 +5_220kV,220.0,10.3662749375,52.2846467462,SRID=4326;POINT(10.3662749375017 52.2846467462009),380000;220000,,substation,Wahle,TenneT,50 +10_220kV,220.0,12.9416466019,52.5621144046,SRID=4326;POINT(12.9416466019288 52.5621144045848),380000;220000;110000,Wu,substation,Wustermark,50Hertz Transmission GmbH;E.ON edis AG, +12_220kV,220.0,13.7096697058,52.5397952581,SRID=4326;POINT(13.7096697058228 52.5397952580643),380000;220000;110000,NHG TCC,substation,Neuenhagen,50Hertz Transmission GmbH,50 +13_220kV,220.0,11.3704262487,48.291492233,SRID=4326;POINT(11.3704262486892 48.2914922329548),380000;220000;110000,OBA,substation,Umspannwerk Oberbachern,TenneT TSO GmbH;E.ON Netz GmbH, +15_220kV,220.0,11.8681562939,48.2068029185,SRID=4326;POINT(11.8681562938884 48.2068029185353),380000;220000,OH,substation,Umspannwerk Ottenhofen,TenneT TSO GmbH,50 +18_220kV,220.0,13.6834769667,54.1390906407,SRID=4326;POINT(13.6834769666546 54.1390906406635),380000;220000,,substation,Umspannwerk Lubmin,50Hertz, +20_220kV,220.0,9.98460982001,53.741128776,SRID=4326;POINT(9.98460982000897 53.7411287759659),380000;220000;110000,,substation,Umspannwerk Hamburg-Nord,50Hertz Transmission,50 +22_220kV,220.0,9.20210420095,53.89515741,SRID=4326;POINT(9.20210420095253 53.8951574100045),380000;220000;110000,,substation,Umspannwerk Kernkraftwerk Brunsbüttel,50Hertz Transmission,50 +24_220kV,220.0,9.72699196993,54.2914209868,SRID=4326;POINT(9.72699196993402 54.2914209867842),220000;110000,,substation,Audorf,TenneT;Schleswig-Holstein Netz AG,50 +26_220kV,220.0,9.34400177216,53.8517774635,SRID=4326;POINT(9.34400177215961 53.8517774635399),380000;220000,,substation,Brokdorf,, +30_220kV,220.0,9.50178966782,53.5428143723,SRID=4326;POINT(9.5017896678173 53.5428143723363),380000;220000;110000,,substation,Dollern,,50 +32_220kV,220.0,11.1228545605,50.998909067,SRID=4326;POINT(11.1228545605428 50.9989090670144),380000;220000;110000,,substation,Umspannwerk Vieselbach,50Hertz Transmission GmbH,50 +37_220kV,220.0,11.6389127631,52.2687346818,SRID=4326;POINT(11.6389127631156 52.2687346818467),380000;220000,,substation,Umspannwerk Wolmirstedt,50Hertz Transmission GmbH, +42_220kV,220.0,12.1869848227,53.8157751724,SRID=4326;POINT(12.1869848227216 53.8157751724384),380000;220000,,substation,Güstrow,50Hertz,50 +46_220kV,220.0,13.524198731,52.5356342713,SRID=4326;POINT(13.5241987309997 52.5356342713481),380000;220000;110000,MZA9,substation,Umspannwerk Marzahn,50Hertz,50 +52_220kV,220.0,8.31334016593,49.0090434644,SRID=4326;POINT(8.3133401659254 49.0090434643525),380000;220000;110000,,substation,Karlsruhe-Daxlanden,EnBW,50 +56_220kV,220.0,8.62320967452,49.3565822493,SRID=4326;POINT(8.62320967451897 49.3565822492832),380000;220000,,substation,Umspannwerk Neurott,EnBW,50 +61_220kV,220.0,8.51888180952,53.2026352837,SRID=4326;POINT(8.51888180951659 53.2026352836834),110000,FARG,substation,Umspannwerk Farge,Avacon AG,50 +68_220kV,220.0,8.77244296019,49.9746686651,SRID=4326;POINT(8.77244296019056 49.974668665137),380000;220000;110000,,substation,Umspannwerk Urberach,RWE, +69_220kV,220.0,8.41937105938,49.6337356824,SRID=4326;POINT(8.41937105938048 49.6337356823792),380000;220000,,substation,Station Bürstadt,RWE,50 +74_220kV,220.0,13.9024346887,51.874723519,SRID=4326;POINT(13.9024346886892 51.8747235190459),380000;110000,RAG,substation,Umspannwerk Ragow,50Hertz,50 +78_220kV,220.0,11.6463786274,50.534876971,SRID=4326;POINT(11.6463786274424 50.534876971042),380000;220000;110000,RDF;RE,substation,Umspannwerk Remptendorf,50Hertz Transmission GmbH,50 +86_220kV,220.0,7.30820430095,52.4768353863,SRID=4326;POINT(7.30820430094578 52.4768353862806),380000;220000;110000,,substation,Hanekenfähr,Amprion,50 +87_220kV,220.0,7.06030699176,51.6964760131,SRID=4326;POINT(7.06030699176008 51.6964760130648),380000;220000;110000,,substation,Station Kusenhorst,RWE,50 +94_220kV,220.0,8.3077568968,52.3456356448,SRID=4326;POINT(8.30775689679575 52.3456356447783),380000;220000;110000,,substation,Wehrendorf,Amprion,50 +95_220kV,220.0,7.88957671235,52.2773967624,SRID=4326;POINT(7.88957671235007 52.2773967624078),380000;220000;110000,,substation,Station Westerkappeln,Amprion,50 +96_220kV,220.0,7.31784994378,52.4691114903,SRID=4326;POINT(7.31784994377907 52.4691114902799),220000,,substation,Benteler Stahlwerk,,50 +99_220kV,220.0,6.99647659179,49.3754093063,SRID=4326;POINT(6.99647659179489 49.3754093063084),380000;220000;110000,,substation,Umspannwerk Uchtelfangen,Amprion,50 +100_220kV,220.0,6.47473977007,49.9224692994,SRID=4326;POINT(6.47473977006816 49.9224692993999),380000;220000;110000,,substation,Station Niederstedem,Amprion; Westnetz,50 +104_220kV,220.0,6.9267589636,51.4992056838,SRID=4326;POINT(6.92675896360373 51.4992056838496),380000;220000,,substation,Station Büscherhof,RWE,50 +105_220kV,220.0,6.68078798894,51.6462959357,SRID=4326;POINT(6.68078798893986 51.6462959356851),380000;220000;110000,,substation,Station Niederrhein,RWE, +106_220kV,220.0,7.16721460966,51.3899067582,SRID=4326;POINT(7.16721460965932 51.3899067582106),380000;220000;110000,,substation,Station Hattingen,Amprion,50 +107_220kV,220.0,7.12557291067,51.4489772588,SRID=4326;POINT(7.1255729106725 51.4489772588145),380000;220000,,substation,Station Eiberg,RWE,50 +117_220kV,220.0,6.97257742701,50.7956930175,SRID=4326;POINT(6.97257742700703 50.795693017514),380000;220000;110000,,substation,Station Sechtem,RWE, +120_220kV,220.0,7.86394571365,50.7682419129,SRID=4326;POINT(7.86394571365263 50.7682419129404),380000;220000;110000,,substation,Station Dauersberg,RWE,50 +123_220kV,220.0,6.7022033778,51.0088609097,SRID=4326;POINT(6.70220337779745 51.0088609096753),380000;220000,,substation,Station Rommerskirchen,RWE,50 +124_220kV,220.0,6.61240231531,51.0359242595,SRID=4326;POINT(6.61240231530956 51.0359242594819),380000;220000;110000,,substation,KW Neurath,,50 +125_220kV,220.0,6.96568442352,51.0693295322,SRID=4326;POINT(6.96568442352005 51.0693295322353),380000;220000;110000,,substation,Station Opladen,RWE, +128_220kV,220.0,7.44948529011,50.4025276621,SRID=4326;POINT(7.44948529010545 50.4025276621126),380000;220000;110000,,substation,Station Weißenthurm,Amprion,50 +129_220kV,220.0,6.62582790177,51.2606098249,SRID=4326;POINT(6.62582790177014 51.2606098249047),220000;110000,,substation,Station Osterath,RWE,50 +138_220kV,220.0,7.0067498786,51.599911422,SRID=4326;POINT(7.00674987859998 51.5999114220203),,,generator,Kraftwerk Scholven,, +143_220kV,220.0,7.35135784055,51.5683686081,SRID=4326;POINT(7.35135784054859 51.568368608134),,,generator,Kraftwerk Knepper,E.On Kraftwerke GmbH, +156_220kV,220.0,6.84223115148,50.8598647244,SRID=4326;POINT(6.84223115147656 50.8598647243573),380000;220000,,substation,Kraftwerkschaltanlage Knapsack 1,, +157_220kV,220.0,8.3642295307,51.9271204905,SRID=4326;POINT(8.364229530696 51.9271204904611),380000;220000;110000,,substation,Station Gütersloh,Amprion,50 +159_220kV,220.0,8.05408735442,53.3323026934,SRID=4326;POINT(8.05408735442182 53.3323026933898),380000;220000;110000,CONN,substation,Conneforde,TenneT,50 +160_220kV,220.0,8.47791063609,53.428485469,SRID=4326;POINT(8.47791063608626 53.428485468955),380000;220000,,substation,Kernkraftwerk Unterweser,EON_Netz,50 +165_220kV,220.0,7.70831848873,51.6762033088,SRID=4326;POINT(7.70831848872651 51.6762033088437),380000;220000;110000,,substation,Station Gersteinwerk,Amprion,50 +184_220kV,220.0,6.61738109143,51.4757909636,SRID=4326;POINT(6.61738109142906 51.4757909635544),380000;220000;110000,,substation,Station Utfort,Amprion,50 +191_220kV,220.0,6.71525259692,51.5267908666,SRID=4326;POINT(6.71525259691926 51.5267908666491),220000;110000,,substation,Station Walsum,Amprion,50 +195_220kV,220.0,6.68310697664,51.5756455059,SRID=4326;POINT(6.68310697663871 51.5756455059265),220000,,substation,Station Möllen,, +201_220kV,220.0,9.25103425144,53.1144323051,SRID=4326;POINT(9.25103425143703 53.1144323051271),380000;220000;110000,,substation,Sottrum,EON_Netz,50 +220_220kV,220.0,8.6593729029,51.9996101319,SRID=4326;POINT(8.65937290290079 51.9996101318907),380000;220000;110000,,substation,Umspannwerk Bechterdissen,TenneT,50 +231_220kV,220.0,7.95805402329,47.5954722641,SRID=4326;POINT(7.95805402328778 47.5954722640613),380000;220000,,substation,Lastverteilung/Schaltanlage Kühmoos,Schluchseewerk AG,50 +232_220kV,220.0,10.0837512707,48.2688675153,SRID=4326;POINT(10.0837512706734 48.268867515302),380000;220000;110000,Station Nr. 625,substation,Vöhringen LEW,LEW,50 +233_220kV,220.0,9.88551509757,48.3040743585,SRID=4326;POINT(9.88551509756969 48.3040743584705),380000;220000;110000,,substation,Umspannwerk Dellmensingen,EnBW,50 +236_220kV,220.0,9.19675652645,48.912963438,SRID=4326;POINT(9.19675652645089 48.9129634380259),220000;110000,,substation,Umspannwerk Hoheneck EnBW,EnBW,50 +247_220kV,220.0,10.3633842323,47.7383368093,SRID=4326;POINT(10.363384232266 47.7383368092793),380000;220000,,substation,Schaltanlage Leupolz,Amprion,50 +248_220kV,220.0,7.46150025902,51.6146143079,SRID=4326;POINT(7.46150025901999 51.6146143079042),,,generator,Trianel Kraftwerk Lünen,Trianel, +250_220kV,220.0,7.7155434195,51.6736430745,SRID=4326;POINT(7.71554341949648 51.6736430744508),,,generator,Gersteinwerk Block F,, +251_220kV,220.0,7.7145945335,51.6730996381,SRID=4326;POINT(7.71459453349968 51.6730996381138),,,generator,Gersteinwerk Block G,, +252_220kV,220.0,7.71270093431,51.6726636622,SRID=4326;POINT(7.71270093431027 51.672663662233),,,generator,Gersteinwerk Block I,, +261_220kV,220.0,9.04543496799,48.9029620138,SRID=4326;POINT(9.0454349679866 48.9029620137561),380000;220000;110000,,substation,380kV-Umspannwerk Pulverdingen,EnBW Transportnetze AG,50 +263_220kV,220.0,9.81091689215,47.1426452967,SRID=4326;POINT(9.81091689214732 47.142645296653),380000;220000,,substation,Umspannwerk Bürs,Vorarlberger Illwerke AG, +264_220kV,220.0,9.42551238607,48.0643507981,SRID=4326;POINT(9.42551238607291 48.0643507981263),380000;220000;110000,,substation,Umspannwerk Herbertingen,EnBW, +267_220kV,220.0,6.8018236571,51.1247880606,SRID=4326;POINT(6.8018236570969 51.1247880606241),220000;110000,,substation,Station Sankt Peter 220 kV,RWE,50 +272_220kV,220.0,9.31819523639,54.716049409,SRID=4326;POINT(9.31819523638713 54.7160494089748),380000;220000;110000;20000,FLEN,substation,Flensburg,TenneT,50 +279_220kV,220.0,12.8139099117,50.8579185114,SRID=4326;POINT(12.8139099116529 50.8579185114397),380000;220000;110000,ROE,substation,Umspannwerk Röhrsdorf,50Hertz,50 +281_220kV,220.0,12.0449825162,50.7812567341,SRID=4326;POINT(12.0449825161601 50.7812567340735),380000;220000;110000,,substation,Umspannwerk Weida,50Hertz, +282_220kV,220.0,12.5182220968,51.1570725851,SRID=4326;POINT(12.5182220968156 51.1570725850832),380000;220000;110000,,substation,Eula,50Hertz Transmission,50 +292_220kV,220.0,11.6441811001,51.9085128734,SRID=4326;POINT(11.6441811001256 51.908512873438),220000;110000,MD 38,substation,Umspannwerk Förderstedt,50Hertz,50 +303_220kV,220.0,9.38765125749,51.6407335256,SRID=4326;POINT(9.38765125748855 51.640733525594),380000;220000;110000,,substation,Umspannanlage Würgassen,, +307_220kV,220.0,9.15135394991,49.1433834119,SRID=4326;POINT(9.15135394990856 49.1433834119236),380000;220000;110000,,substation,380-kV-Umspannwerk Großgartach,, +308_220kV,220.0,10.1878339996,49.9812190408,SRID=4326;POINT(10.1878339995516 49.981219040845),380000,GR,substation,Grafenrheinfeld,, +312_220kV,220.0,10.1174866947,48.8971053361,SRID=4326;POINT(10.1174866946797 48.8971053361382),380000;220000;110000,,substation,Umspannwerk Goldshöfe,EnBW,50 +315_220kV,220.0,10.8408695619,48.2380310092,SRID=4326;POINT(10.8408695619217 48.2380310092335),380000;220000;110000,173A,substation,Umspannwerk Oberottmarshausen,Lech-Elektrizitätswerke,50 +317_220kV,220.0,10.873935757,47.2444239286,SRID=4326;POINT(10.8739357569814 47.2444239286028),380000;220000;110000,,substation,APG Umspannwerk Westtirol,Austrian Power Grid AG,50 +322_220kV,220.0,7.75186249821,48.0964057189,SRID=4326;POINT(7.75186249820802 48.0964057188536),220000;110000,,substation,220-kV-Umspannwerk Eichstetten,EnBW,50 +332_220kV,220.0,13.1034346393,48.6667929606,SRID=4326;POINT(13.1034346393158 48.6667929606034),380000;220000;110000,PT,substation,Umspannwerk Pleinting,"TenneT TSO GmbH, E.ON Netz GmbH",50 +334_220kV,220.0,11.1937277954,50.1622144406,SRID=4326;POINT(11.193727795361 50.1622144406172),380000;110000,RED,substation,Umspannwerk Redwitz,, +336_220kV,220.0,11.8107025454,50.1837655261,SRID=4326;POINT(11.8107025453557 50.1837655260691),380000;220000;110000,MH,substation,Umspannwerk Mechlenreuth,TenneT TSO GmbH;E.ON Netz GmbH,50 +338_220kV,220.0,12.0822544457,49.2974477669,SRID=4326;POINT(12.0822544456506 49.2974477669261),380000;220000;110000,SD,substation,Schwandorf,"TenneT TSO GmbH, E.ON Netz GmbH",50 +351_220kV,220.0,11.5077305511,48.7611239877,SRID=4326;POINT(11.5077305510922 48.7611239877457),380000;220000;110000,IN,substation,Ingolstadt,,50 +353_220kV,220.0,11.5813705216,48.7648940509,SRID=4326;POINT(11.5813705216127 48.7648940508515),380000;220000;110000,IRS,substation,Irsching,,50 +360_220kV,220.0,6.77320783778,49.2910742104,SRID=4326;POINT(6.77320783777607 49.2910742103854),380000;220000;110000,,substation,Umspannanlage Ensdorf,,50 +362_220kV,220.0,8.04936352422,47.5508818535,SRID=4326;POINT(8.04936352422006 47.550881853478),380000;220000,,substation,Laufenburg,,50 +382_220kV,220.0,10.160291269,49.9891947838,SRID=4326;POINT(10.1602912689993 49.9891947837947),380000;220000;110000,BRF,substation,Umspannwerk Bergrheinfeld,TenneT TSO GmbH,50 +384_220kV,220.0,10.843117799,49.3786926858,SRID=4326;POINT(10.8431177989998 49.3786926858391),380000;220000;110000,RAI,substation,Umspannwerk Raitersaich,TenneT TSO GmbH;E.ON Netz GmbH,50 +385_220kV,220.0,9.09051754869,49.9700413736,SRID=4326;POINT(9.09051754868887 49.9700413735997),380000;220000;110000,AS,substation,Umspannwerk Aschaffenburg,TenneT TSO GmbH;E.ON Netz GmbH,50 +391_220kV,220.0,8.95464412134,50.0860157333,SRID=4326;POINT(8.95464412133747 50.0860157333265),220000;110000,,substation,Kraftwerkseinspeisung Staudinger,,50 +403_220kV,220.0,8.25885994833,47.6342116214,SRID=4326;POINT(8.25885994832776 47.6342116213731),380000;220000;110000,,substation,Tiengen,Amprion,50 +404_220kV,220.0,8.23209442034,47.5561434831,SRID=4326;POINT(8.23209442033504 47.5561434831307),380000;220000,,substation,Betznau,, +413_220kV,220.0,8.67371728898,49.2904393851,SRID=4326;POINT(8.67371728898445 49.2904393851271),380000;220000;110000,,substation,Umspannwerk Wiesloch,EnBW,50 +421_220kV,220.0,9.09183529743,49.2946188662,SRID=4326;POINT(9.09183529743294 49.2946188662059),380000;220000;110000,,substation,Umspannwerk Hüffenhardt,,50 +450_220kV,220.0,7.41670791196,51.4570521583,SRID=4326;POINT(7.41670791195847 51.4570521583476),380000;220000;110000,,substation,Station Kruckel,RWE;DSW 21; AVU,50 +458_220kV,220.0,7.41946378046,51.4575100936,SRID=4326;POINT(7.41946378046051 51.4575100936229),220000,,auxillary_T_node,Haarstrang Ost,Amprion,50 diff --git a/PyPSA/source/examples/networks/scigrid-de/scigrid-de/carriers.csv b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/carriers.csv new file mode 100644 index 0000000000000000000000000000000000000000..d0e5967c3acd410caa2d4ae873eca4b8af868633 --- /dev/null +++ b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/carriers.csv @@ -0,0 +1,17 @@ +name +Pumped Hydro +Gas +Brown Coal +Nuclear +Wind Onshore +Run of River +Oil +Storage Hydro +Multiple +Other +Geothermal +Solar +AC +Wind Offshore +Hard Coal +Waste diff --git a/PyPSA/source/examples/networks/scigrid-de/scigrid-de/crs.json b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/crs.json new file mode 100644 index 0000000000000000000000000000000000000000..49edf3439c1f2d009aeeae1606c27c86fcd50f3f --- /dev/null +++ b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/crs.json @@ -0,0 +1 @@ +{"_crs": "GEOGCRS[\"WGS 84\",ENSEMBLE[\"World Geodetic System 1984 ensemble\",MEMBER[\"World Geodetic System 1984 (Transit)\"],MEMBER[\"World Geodetic System 1984 (G730)\"],MEMBER[\"World Geodetic System 1984 (G873)\"],MEMBER[\"World Geodetic System 1984 (G1150)\"],MEMBER[\"World Geodetic System 1984 (G1674)\"],MEMBER[\"World Geodetic System 1984 (G1762)\"],MEMBER[\"World Geodetic System 1984 (G2139)\"],MEMBER[\"World Geodetic System 1984 (G2296)\"],ELLIPSOID[\"WGS 84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]],ENSEMBLEACCURACY[2.0]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],CS[ellipsoidal,2],AXIS[\"geodetic latitude (Lat)\",north,ORDER[1],ANGLEUNIT[\"degree\",0.0174532925199433]],AXIS[\"geodetic longitude (Lon)\",east,ORDER[2],ANGLEUNIT[\"degree\",0.0174532925199433]],USAGE[SCOPE[\"Horizontal component of 3D system.\"],AREA[\"World.\"],BBOX[-90,-180,90,180]],ID[\"EPSG\",4326]]"} \ No newline at end of file diff --git a/PyPSA/source/examples/networks/scigrid-de/scigrid-de/generators-p_max_pu.csv b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/generators-p_max_pu.csv new file mode 100644 index 0000000000000000000000000000000000000000..dd8dc3d2551909cd24af4fac11bdab1586937de5 --- /dev/null +++ b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/generators-p_max_pu.csv @@ -0,0 +1,25 @@ +,218 Wind Offshore,221 Wind Offshore,270 Wind Offshore,271 Wind Offshore,22_220kV Wind Offshore,1 Wind Onshore,3 Wind Onshore,4 Wind Onshore,6 Wind Onshore,7 Wind Onshore,8 Wind Onshore,9 Wind Onshore,11 Wind Onshore,14 Wind Onshore,16 Wind Onshore,17 Wind Onshore,19 Wind Onshore,21 Wind Onshore,23 Wind Onshore,25 Wind Onshore,27 Wind Onshore,28 Wind Onshore,29 Wind Onshore,31 Wind Onshore,33 Wind Onshore,34 Wind Onshore,35 Wind Onshore,36 Wind Onshore,38 Wind Onshore,39 Wind Onshore,40 Wind Onshore,41 Wind Onshore,43 Wind Onshore,44 Wind Onshore,45 Wind Onshore,47 Wind Onshore,48 Wind Onshore,49 Wind Onshore,50 Wind Onshore,51 Wind Onshore,53 Wind Onshore,54 Wind Onshore,55 Wind Onshore,57 Wind Onshore,58 Wind Onshore,59 Wind Onshore,60 Wind Onshore,62 Wind Onshore,63 Wind Onshore,64 Wind Onshore,65 Wind Onshore,66 Wind Onshore,67 Wind Onshore,70 Wind Onshore,71 Wind Onshore,72 Wind Onshore,73 Wind Onshore,75 Wind Onshore,76 Wind Onshore,77 Wind Onshore,79 Wind Onshore,80 Wind Onshore,81 Wind Onshore,82 Wind Onshore,83 Wind Onshore,84 Wind Onshore,85 Wind Onshore,88 Wind Onshore,89 Wind Onshore,90 Wind Onshore,91 Wind Onshore,92 Wind Onshore,93 Wind Onshore,97 Wind Onshore,98 Wind Onshore,101 Wind Onshore,102 Wind Onshore,103 Wind Onshore,108 Wind Onshore,109 Wind Onshore,110 Wind Onshore,111 Wind Onshore,112 Wind Onshore,113 Wind Onshore,114 Wind Onshore,115 Wind Onshore,116 Wind Onshore,118 Wind Onshore,119 Wind Onshore,121 Wind Onshore,122 Wind Onshore,126 Wind Onshore,127 Wind Onshore,130 Wind Onshore,131 Wind Onshore,132 Wind Onshore,133 Wind Onshore,134 Wind Onshore,135 Wind Onshore,136 Wind Onshore,137 Wind Onshore,139 Wind Onshore,140 Wind Onshore,141 Wind Onshore,142 Wind Onshore,144 Wind Onshore,145 Wind Onshore,146 Wind Onshore,147 Wind Onshore,148 Wind Onshore,149 Wind Onshore,150 Wind Onshore,151 Wind Onshore,152 Wind Onshore,153 Wind Onshore,154 Wind Onshore,155 Wind Onshore,158 Wind Onshore,161 Wind Onshore,162 Wind Onshore,163 Wind Onshore,164 Wind Onshore,166 Wind Onshore,167 Wind Onshore,168 Wind Onshore,169 Wind Onshore,170 Wind Onshore,171 Wind Onshore,172 Wind Onshore,173 Wind Onshore,174 Wind Onshore,175 Wind Onshore,176 Wind Onshore,177 Wind Onshore,178 Wind Onshore,179 Wind Onshore,180 Wind Onshore,181 Wind Onshore,182 Wind Onshore,183 Wind Onshore,185 Wind Onshore,186 Wind Onshore,187 Wind Onshore,188 Wind Onshore,189 Wind Onshore,190 Wind Onshore,192 Wind Onshore,193 Wind Onshore,194 Wind Onshore,196 Wind Onshore,197 Wind Onshore,198 Wind Onshore,199 Wind Onshore,200 Wind Onshore,202 Wind Onshore,203 Wind Onshore,204 Wind Onshore,205 Wind Onshore,206 Wind Onshore,207 Wind Onshore,208 Wind Onshore,209 Wind Onshore,210 Wind Onshore,211 Wind Onshore,212 Wind Onshore,213 Wind Onshore,214 Wind Onshore,215 Wind Onshore,216 Wind Onshore,217 Wind Onshore,218 Wind Onshore,219 Wind Onshore,221 Wind Onshore,222 Wind Onshore,223 Wind Onshore,224 Wind Onshore,225 Wind Onshore,226 Wind Onshore,227 Wind Onshore,228 Wind Onshore,229 Wind Onshore,230 Wind Onshore,234 Wind Onshore,235 Wind Onshore,237 Wind Onshore,238 Wind Onshore,239 Wind Onshore,240 Wind Onshore,241 Wind Onshore,242 Wind Onshore,245 Wind Onshore,246 Wind Onshore,249 Wind Onshore,253 Wind Onshore,254 Wind Onshore,255 Wind Onshore,256 Wind Onshore,257 Wind Onshore,258 Wind Onshore,259 Wind Onshore,260 Wind Onshore,262 Wind Onshore,265 Wind Onshore,266 Wind Onshore,268 Wind Onshore,269 Wind Onshore,270 Wind Onshore,271 Wind Onshore,273 Wind Onshore,274 Wind Onshore,275 Wind Onshore,276 Wind Onshore,277 Wind Onshore,278 Wind Onshore,280 Wind Onshore,283 Wind Onshore,284 Wind Onshore,285 Wind Onshore,286 Wind Onshore,287 Wind Onshore,288 Wind Onshore,289 Wind Onshore,290 Wind Onshore,291 Wind Onshore,293 Wind Onshore,294 Wind Onshore,295 Wind Onshore,296 Wind Onshore,297 Wind Onshore,298 Wind Onshore,299 Wind Onshore,300 Wind Onshore,301 Wind Onshore,302 Wind Onshore,304 Wind Onshore,305 Wind Onshore,306 Wind Onshore,309 Wind Onshore,310 Wind Onshore,311 Wind Onshore,313 Wind Onshore,314 Wind Onshore,316 Wind Onshore,318 Wind Onshore,319 Wind Onshore,320 Wind Onshore,321 Wind Onshore,323 Wind Onshore,324 Wind Onshore,325 Wind Onshore,326 Wind Onshore,327 Wind Onshore,328 Wind Onshore,329 Wind Onshore,330 Wind Onshore,331 Wind Onshore,333 Wind Onshore,335 Wind Onshore,337 Wind Onshore,339 Wind Onshore,340 Wind Onshore,341 Wind Onshore,342 Wind Onshore,343 Wind Onshore,344 Wind Onshore,345 Wind Onshore,346 Wind Onshore,347 Wind Onshore,348 Wind Onshore,349 Wind Onshore,350 Wind Onshore,352 Wind Onshore,354 Wind Onshore,355 Wind Onshore,356 Wind Onshore,357 Wind Onshore,358 Wind Onshore,359 Wind Onshore,361 Wind Onshore,363 Wind Onshore,364 Wind Onshore,365 Wind Onshore,366 Wind Onshore,367 Wind Onshore,368 Wind Onshore,369 Wind Onshore,370 Wind Onshore,371 Wind Onshore,372 Wind Onshore,373 Wind Onshore,374 Wind Onshore,375 Wind Onshore,376 Wind Onshore,377 Wind Onshore,378 Wind Onshore,379 Wind Onshore,380 Wind Onshore,381 Wind Onshore,383 Wind Onshore,386 Wind Onshore,387 Wind Onshore,388 Wind Onshore,389 Wind Onshore,390 Wind Onshore,392 Wind Onshore,393 Wind Onshore,394 Wind Onshore,395 Wind Onshore,396 Wind Onshore,397 Wind Onshore,398 Wind Onshore,399 Wind Onshore,400 Wind Onshore,401 Wind Onshore,402 Wind Onshore,405 Wind Onshore,406 Wind Onshore,409 Wind Onshore,410 Wind Onshore,411 Wind Onshore,412 Wind Onshore,414 Wind Onshore,415 Wind Onshore,416 Wind Onshore,417 Wind Onshore,418 Wind Onshore,422 Wind Onshore,423 Wind Onshore,424 Wind Onshore,425 Wind Onshore,426 Wind Onshore,427 Wind Onshore,428 Wind Onshore,429 Wind Onshore,430 Wind Onshore,431 Wind Onshore,432 Wind Onshore,433 Wind Onshore,434 Wind Onshore,435 Wind Onshore,436 Wind Onshore,437 Wind Onshore,438 Wind Onshore,439 Wind Onshore,440 Wind Onshore,441 Wind Onshore,442 Wind Onshore,443 Wind Onshore,444 Wind Onshore,445 Wind Onshore,446 Wind Onshore,447 Wind Onshore,448 Wind Onshore,449 Wind Onshore,451 Wind Onshore,452 Wind Onshore,453 Wind Onshore,454 Wind Onshore,455 Wind Onshore,456 Wind Onshore,457 Wind Onshore,459 Wind Onshore,460 Wind Onshore,461 Wind Onshore,462 Wind Onshore,463 Wind Onshore,464 Wind Onshore,465 Wind Onshore,466 Wind Onshore,467 Wind Onshore,468 Wind Onshore,469 Wind Onshore,470 Wind Onshore,471 Wind Onshore,472 Wind Onshore,473 Wind Onshore,474 Wind Onshore,475 Wind Onshore,476 Wind Onshore,477 Wind Onshore,478 Wind Onshore,479 Wind Onshore,480 Wind Onshore,481 Wind Onshore,482 Wind Onshore,483 Wind Onshore,484 Wind Onshore,485 Wind Onshore,486 Wind Onshore,487 Wind Onshore,488 Wind Onshore,489 Wind Onshore,490 Wind Onshore,491 Wind Onshore,492 Wind Onshore,493 Wind Onshore,494 Wind Onshore,495 Wind Onshore,2_220kV Wind Onshore,5_220kV Wind Onshore,10_220kV Wind Onshore,12_220kV Wind Onshore,13_220kV Wind Onshore,15_220kV Wind Onshore,18_220kV Wind Onshore,20_220kV Wind Onshore,22_220kV Wind Onshore,24_220kV Wind Onshore,26_220kV Wind Onshore,30_220kV Wind Onshore,32_220kV Wind Onshore,37_220kV Wind Onshore,42_220kV Wind Onshore,46_220kV Wind Onshore,52_220kV Wind Onshore,56_220kV Wind Onshore,61_220kV Wind Onshore,68_220kV Wind Onshore,69_220kV Wind Onshore,74_220kV Wind Onshore,78_220kV Wind Onshore,86_220kV Wind Onshore,87_220kV Wind Onshore,94_220kV Wind Onshore,95_220kV Wind Onshore,96_220kV Wind Onshore,99_220kV Wind Onshore,100_220kV Wind Onshore,104_220kV Wind Onshore,105_220kV Wind Onshore,106_220kV Wind Onshore,107_220kV Wind Onshore,117_220kV Wind Onshore,120_220kV Wind Onshore,123_220kV Wind Onshore,124_220kV Wind Onshore,125_220kV Wind Onshore,128_220kV Wind Onshore,129_220kV Wind Onshore,138_220kV Wind Onshore,143_220kV Wind Onshore,156_220kV Wind Onshore,157_220kV Wind Onshore,159_220kV Wind Onshore,160_220kV Wind Onshore,165_220kV Wind Onshore,184_220kV Wind Onshore,191_220kV Wind Onshore,195_220kV Wind Onshore,201_220kV Wind Onshore,220_220kV Wind Onshore,231_220kV Wind Onshore,232_220kV Wind Onshore,233_220kV Wind Onshore,236_220kV Wind Onshore,247_220kV Wind Onshore,248_220kV Wind Onshore,250_220kV Wind Onshore,251_220kV Wind Onshore,252_220kV Wind Onshore,261_220kV Wind Onshore,264_220kV Wind Onshore,267_220kV Wind Onshore,272_220kV Wind Onshore,279_220kV Wind Onshore,281_220kV Wind Onshore,282_220kV Wind Onshore,292_220kV Wind Onshore,303_220kV Wind Onshore,307_220kV Wind Onshore,308_220kV Wind Onshore,312_220kV Wind Onshore,315_220kV Wind Onshore,317_220kV Wind Onshore,322_220kV Wind Onshore,332_220kV Wind Onshore,334_220kV Wind Onshore,336_220kV Wind Onshore,338_220kV Wind Onshore,351_220kV Wind Onshore,353_220kV Wind Onshore,360_220kV Wind Onshore,362_220kV Wind Onshore,382_220kV Wind Onshore,384_220kV Wind Onshore,385_220kV Wind Onshore,391_220kV Wind Onshore,403_220kV Wind Onshore,404_220kV Wind Onshore,413_220kV Wind Onshore,421_220kV Wind Onshore,450_220kV Wind Onshore,458_220kV Wind Onshore,1 Solar,3 Solar,4 Solar,6 Solar,7 Solar,8 Solar,9 Solar,11 Solar,14 Solar,16 Solar,17 Solar,19 Solar,21 Solar,23 Solar,25 Solar,27 Solar,28 Solar,29 Solar,31 Solar,33 Solar,34 Solar,35 Solar,36 Solar,38 Solar,39 Solar,40 Solar,41 Solar,43 Solar,44 Solar,45 Solar,47 Solar,48 Solar,49 Solar,50 Solar,51 Solar,53 Solar,54 Solar,55 Solar,57 Solar,58 Solar,59 Solar,60 Solar,62 Solar,63 Solar,64 Solar,65 Solar,66 Solar,67 Solar,70 Solar,71 Solar,72 Solar,73 Solar,75 Solar,76 Solar,77 Solar,79 Solar,80 Solar,81 Solar,82 Solar,83 Solar,84 Solar,85 Solar,88 Solar,89 Solar,90 Solar,91 Solar,92 Solar,93 Solar,97 Solar,98 Solar,101 Solar,102 Solar,103 Solar,108 Solar,109 Solar,110 Solar,111 Solar,112 Solar,113 Solar,114 Solar,115 Solar,116 Solar,118 Solar,119 Solar,121 Solar,122 Solar,126 Solar,127 Solar,130 Solar,131 Solar,132 Solar,133 Solar,134 Solar,135 Solar,136 Solar,137 Solar,139 Solar,140 Solar,141 Solar,142 Solar,144 Solar,145 Solar,146 Solar,147 Solar,148 Solar,149 Solar,150 Solar,151 Solar,152 Solar,153 Solar,154 Solar,155 Solar,158 Solar,161 Solar,162 Solar,163 Solar,164 Solar,166 Solar,167 Solar,168 Solar,169 Solar,170 Solar,171 Solar,172 Solar,173 Solar,174 Solar,175 Solar,176 Solar,177 Solar,178 Solar,179 Solar,180 Solar,181 Solar,182 Solar,183 Solar,185 Solar,186 Solar,187 Solar,188 Solar,189 Solar,190 Solar,192 Solar,193 Solar,194 Solar,196 Solar,197 Solar,198 Solar,199 Solar,200 Solar,202 Solar,203 Solar,204 Solar,205 Solar,206 Solar,207 Solar,208 Solar,209 Solar,210 Solar,211 Solar,212 Solar,213 Solar,214 Solar,215 Solar,216 Solar,217 Solar,218 Solar,219 Solar,221 Solar,222 Solar,223 Solar,224 Solar,225 Solar,226 Solar,227 Solar,228 Solar,229 Solar,230 Solar,234 Solar,235 Solar,237 Solar,238 Solar,239 Solar,240 Solar,241 Solar,242 Solar,245 Solar,246 Solar,249 Solar,253 Solar,254 Solar,255 Solar,256 Solar,257 Solar,258 Solar,259 Solar,260 Solar,262 Solar,265 Solar,266 Solar,268 Solar,269 Solar,270 Solar,271 Solar,273 Solar,274 Solar,275 Solar,276 Solar,277 Solar,278 Solar,280 Solar,283 Solar,284 Solar,285 Solar,286 Solar,287 Solar,288 Solar,289 Solar,290 Solar,291 Solar,293 Solar,294 Solar,295 Solar,296 Solar,297 Solar,298 Solar,299 Solar,300 Solar,301 Solar,302 Solar,304 Solar,305 Solar,306 Solar,309 Solar,310 Solar,311 Solar,313 Solar,314 Solar,316 Solar,318 Solar,319 Solar,320 Solar,321 Solar,323 Solar,324 Solar,325 Solar,326 Solar,327 Solar,328 Solar,329 Solar,330 Solar,331 Solar,333 Solar,335 Solar,337 Solar,339 Solar,340 Solar,341 Solar,342 Solar,343 Solar,344 Solar,345 Solar,346 Solar,347 Solar,348 Solar,349 Solar,350 Solar,352 Solar,354 Solar,355 Solar,356 Solar,357 Solar,358 Solar,359 Solar,361 Solar,363 Solar,364 Solar,365 Solar,366 Solar,367 Solar,368 Solar,369 Solar,370 Solar,371 Solar,372 Solar,373 Solar,374 Solar,375 Solar,376 Solar,377 Solar,378 Solar,379 Solar,380 Solar,381 Solar,383 Solar,386 Solar,387 Solar,388 Solar,389 Solar,390 Solar,392 Solar,393 Solar,394 Solar,395 Solar,396 Solar,397 Solar,398 Solar,399 Solar,400 Solar,401 Solar,402 Solar,405 Solar,406 Solar,409 Solar,410 Solar,411 Solar,412 Solar,414 Solar,415 Solar,416 Solar,417 Solar,418 Solar,422 Solar,423 Solar,424 Solar,425 Solar,426 Solar,427 Solar,428 Solar,429 Solar,430 Solar,431 Solar,432 Solar,433 Solar,434 Solar,435 Solar,436 Solar,437 Solar,438 Solar,439 Solar,440 Solar,441 Solar,442 Solar,443 Solar,444 Solar,445 Solar,446 Solar,447 Solar,448 Solar,449 Solar,451 Solar,452 Solar,453 Solar,454 Solar,455 Solar,456 Solar,457 Solar,459 Solar,460 Solar,461 Solar,462 Solar,463 Solar,464 Solar,465 Solar,466 Solar,467 Solar,468 Solar,469 Solar,470 Solar,471 Solar,472 Solar,473 Solar,474 Solar,475 Solar,476 Solar,477 Solar,478 Solar,479 Solar,480 Solar,481 Solar,482 Solar,483 Solar,484 Solar,485 Solar,486 Solar,487 Solar,488 Solar,489 Solar,490 Solar,491 Solar,492 Solar,493 Solar,494 Solar,495 Solar,2_220kV Solar,5_220kV Solar,10_220kV Solar,12_220kV Solar,13_220kV Solar,15_220kV Solar,18_220kV Solar,20_220kV Solar,22_220kV Solar,24_220kV Solar,26_220kV Solar,30_220kV Solar,32_220kV Solar,37_220kV Solar,42_220kV Solar,46_220kV Solar,52_220kV Solar,56_220kV Solar,61_220kV Solar,68_220kV Solar,69_220kV Solar,74_220kV Solar,78_220kV Solar,86_220kV Solar,87_220kV Solar,94_220kV Solar,95_220kV Solar,96_220kV Solar,99_220kV Solar,100_220kV Solar,104_220kV Solar,105_220kV Solar,106_220kV Solar,107_220kV Solar,117_220kV Solar,120_220kV Solar,123_220kV Solar,124_220kV Solar,125_220kV Solar,128_220kV Solar,129_220kV Solar,138_220kV Solar,143_220kV Solar,156_220kV Solar,157_220kV Solar,159_220kV Solar,160_220kV Solar,165_220kV Solar,184_220kV Solar,191_220kV Solar,195_220kV Solar,201_220kV Solar,220_220kV Solar,231_220kV Solar,232_220kV Solar,233_220kV Solar,236_220kV Solar,247_220kV Solar,248_220kV Solar,250_220kV Solar,251_220kV Solar,252_220kV Solar,261_220kV Solar,263_220kV Solar,264_220kV Solar,267_220kV Solar,272_220kV Solar,279_220kV Solar,281_220kV Solar,282_220kV Solar,292_220kV Solar,303_220kV Solar,307_220kV Solar,308_220kV Solar,312_220kV Solar,315_220kV Solar,317_220kV Solar,322_220kV Solar,332_220kV Solar,334_220kV Solar,336_220kV Solar,338_220kV Solar,351_220kV Solar,353_220kV Solar,360_220kV Solar,362_220kV Solar,382_220kV Solar,384_220kV Solar,385_220kV Solar,391_220kV Solar,403_220kV Solar,404_220kV Solar,413_220kV Solar,421_220kV Solar,450_220kV Solar,458_220kV Solar +0,0.999999979268,1.0,1.0,0.999567639851,0.999999973509,0.613006954762,0.386656699576,0.658175883592,0.680148033933,0.592462641769,0.949208613129,0.949190623975,0.956172131725,0.0,0.862403792227,0.901007982344,0.0,0.932214697439,0.961866730966,0.984106498103,0.862469198308,0.939937725416,0.920089866789,0.0464771295955,0.201827291063,0.835864730648,0.851186612984,0.850228190268,0.880093649472,0.871902511454,0.998722446477,0.952068662686,0.814494271575,0.685017616611,0.755299304614,0.845443982964,0.832066712428,0.762162185375,0.646542374485,0.0103129443125,0.000137425540384,0.0,0.0,0.0,0.0,0.768487547732,0.834495339622,0.803542428036,2.77987485218e-05,0.0,0.0679600248649,0.0304614899058,0.799201748589,0.0,0.577019857331,0.404632070495,0.773462080589,0.420544175995,0.160769300652,0.0725768528392,0.727206847434,0.624114384277,0.702730231455,0.865280169162,0.986319143398,0.995787434478,0.391383286389,0.162044626347,0.15558376452,0.399988637859,0.426945262029,0.45617671258,0.560791984113,0.013066331753,0.9989393251,0.977677968828,0.974770470164,0.102362378063,0.234252808561,0.104315297389,0.135522971632,0.0895156700369,0.201951100905,0.196010456169,0.0595707346755,0.0514883221445,0.0481808850595,0.0,0.0396230452655,0.0442563057372,0.0361997466255,0.0502150201238,0.036299550665,0.0456125671077,0.00199646171947,0.0459321869108,0.000363328367241,0.00694993960141,0.170796968072,0.18852524467,0.253867167994,0.223391575601,0.216884241705,0.135501731831,0.214791415378,0.118871316216,0.00199646171947,0.0019996570466,0.0794758173528,0.0180814237565,0.000506394385538,0.00110834594841,0.0503628321016,2.17343078135e-05,0.150160995508,0.00157731379702,0.0833985265065,0.0317130851545,0.761725469916,0.00199646171947,0.0424504322911,0.306418040428,0.197925220155,0.148740565802,0.255740281161,0.215146321046,0.092411617229,0.092411617229,0.247967216223,0.0744390913081,0.254999852495,0.160658818315,0.109304975977,0.990932462494,0.00645825597414,0.969599827558,0.891072872708,0.984718196412,0.932171945589,0.886897456164,0.162077271991,0.212236780356,0.255058636813,0.157232403319,0.000223506220481,5.73440134857e-06,0.981077852425,0.161804466719,0.254264265092,0.245225484655,0.266383154666,0.187245577616,0.198528638779,0.158570981071,0.559674592488,0.415910034251,0.0511005961185,0.083079829239,0.0510590229801,0.358859109944,0.248664450293,0.643377572024,0.421119159206,0.0,0.0,8.81120243559e-05,0.0,0.0,0.0,0.998010250759,0.99946314054,0.425859685466,0.643180119318,0.626162287405,0.0,0.0,0.0,0.390454312219,0.0,0.000118173021941,0.0,0.000663487504321,0.0,0.0,0.0,0.0,0.0,5.40227769704e-06,0.0,8.51231196913e-05,2.87876146618e-09,0.0,0.195453676882,0.117224521436,0.0,0.0,0.0,0.0,0.143785708016,0.112177680171,9.13146043737e-06,0.0,0.513452700792,0.0,0.000819600805946,0.00311176088392,0.896576752429,0.819883726642,0.999995686168,0.999900776294,0.0,0.0,0.835864730648,0.579474147577,0.775649327587,7.22626987432e-06,0.000383484298776,0.719792270262,0.0459335068333,0.681895493887,0.000447693594473,0.467999559329,0.466420722723,0.442804634127,0.180900589046,0.122259014528,0.779258682492,0.647626322601,0.612085756879,0.657929478598,0.652318056361,0.698266033852,0.688562453577,0.0458910194211,0.00560258429599,0.0,0.0,0.000898206593227,0.000304354139458,0.0230767196448,0.41522491976,0.0,0.0,0.0193834066405,1.09103023891e-08,0.0,0.0,0.25930509952,0.222231381856,0.492527666808,0.326882508126,0.272760493433,0.0,0.0,0.0,0.0,0.999649008458,0.000415229124854,0.0,4.13111367032e-05,0.700497856651,0.397969299405,0.339805981676,0.00632154852764,0.368864317772,0.119475372246,0.781192256585,0.824787598175,0.767542479112,2.59175025201e-05,0.000142966524638,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.169200906117,0.00198029766284,0.783337630056,0.0,0.0,0.0,0.908854632069,0.0,0.0,0.0,0.0,0.386126909024,0.0,0.504069855398,7.38405319985e-08,0.0,0.0,0.723104019329,0.00028477235846,5.33159613498e-08,0.316968739226,0.38803997825,0.471531849515,0.00227935209301,0.0233600838826,0.118871316216,0.0,0.00126340264994,0.0,0.0,0.0,0.991825311577,0.0,0.327396474623,0.995267368038,0.986630076339,0.0,0.0,0.0,0.00533031131124,0.155071031695,0.987236775694,0.817002367779,0.929648567603,0.0,0.000343408552231,0.00338396807294,0.0,0.0,0.0,0.602691908606,0.473307341005,0.0,0.729139985628,0.0633794958856,0.105278115355,0.080601306294,0.00409374126431,0.00302359879191,0.0,0.796551672354,0.00031698035223,0.0,0.0,0.0,0.96779157062,0.309232364872,0.258934557585,0.579045991525,0.591422054821,0.426778307078,0.998294271854,0.997301703079,0.0850103905545,0.118871316216,0.0998886222556,0.0,0.00399538395614,0.0510590229801,0.0929153165225,0.158992211912,0.216884241705,0.118871316216,0.118871316216,0.216884241705,0.205229086892,0.173783027696,0.00010110214231,0.000450276681295,0.00105022950711,3.25297735349e-05,0.234739238026,0.247434153262,0.210725825864,7.64602393933e-05,0.278256434906,0.286655654117,0.772164053879,0.737659630756,0.332322636626,0.407199937431,0.137676961185,0.433393800079,0.355871772765,0.362347371167,0.5701245597,0.00361936932372,0.00247581358179,7.44774665136e-08,0.0,0.0,0.0,0.238614856331,0.173935193704,0.0,0.0,0.0,0.0,0.999480922195,0.644253184033,0.694155042466,0.895133901041,0.817714496399,0.0,0.0,0.998081867334,0.934948768344,0.994614164382,0.996033196806,0.962161459783,0.857046207331,0.251193062034,0.653698542082,0.997674236019,0.846870362449,0.0,0.0,0.750789264841,0.000213906862339,0.0,0.738200080736,0.134570487347,0.426505568254,0.331515711961,0.526560670964,0.547051578033,0.43799122508,0.0,0.0,0.248028573007,0.294876343804,0.118871316216,0.118871316216,7.64210987659e-05,0.000115465393624,0.0497830237498,0.0510590229801,0.00196871606129,0.0,0.133889076143,0.255740281161,0.19861132669,0.0129903854977,0.199014355096,0.733655799763,0.872660433228,0.19563974279,0.162668460792,0.253512507243,0.254264265092,0.834206334824,0.327680328142,0.0,0.0,0.0,0.0,0.00174964034047,0.198528638779,0.195453676882,0.195453676882,0.195453676882,0.0,0.0,0.00199646171947,0.997174448617,0.319889741246,0.326439195489,0.459439406276,0.471894041001,0.0842254482663,0.0,0.0228541439124,0.0141219218281,0.0,0.0361907007481,0.0,4.36702745822e-05,0.0171536869557,0.0838584293636,0.000616183159879,0.000495821455724,8.39159437879e-05,0.0,0.0,0.00321369887218,0.0347595804052,4.36025683013e-05,0.0,0.0,0.0,0.0,0.000522334670597,0.083079829239,0.083079829239,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +1,0.999999979268,1.0,1.0,0.999907316065,0.999999973509,0.747117536409,0.534680482367,0.796096511982,0.785664580129,0.708137111164,0.977309442218,0.979628813697,0.985603061691,0.0,0.911629740886,0.970337637461,0.0,0.972409385567,0.987094975374,0.996323444029,0.937597766126,0.977502218587,0.96389048136,0.0682039919173,0.303737691461,0.916106052973,0.924154529484,0.918016667594,0.956840641729,0.920475943313,0.999530908618,0.979378842643,0.88825818538,0.885769056572,0.893379514515,0.951994739088,0.951845527892,0.845643219344,0.778919574449,0.0240643298371,0.0,0.0,0.0,0.0,0.0,0.865990291137,0.903575420795,0.89975240517,0.0,0.0,0.0861653559441,0.0666762369083,0.895542838868,2.20670789272e-05,0.663600268912,0.522542616221,0.872789472038,0.563599549451,0.274740324819,0.180307534071,0.896385578195,0.762880304145,0.907612418125,0.931802087952,0.996307342658,0.998823786382,0.528320019374,0.220803445675,0.212861217623,0.551239356899,0.555209473862,0.519167013944,0.608924652411,0.0227418079612,0.999803248369,0.993627762042,0.990680013514,0.152599813735,0.336333186158,0.15996678139,0.196622674519,0.194756575057,0.288181233352,0.285658478032,0.13513939709,0.0802323601164,0.0764772285078,0.0,0.0603286697607,0.0584109124872,0.0448711930036,0.0859305736191,0.0459909336972,0.0712774590441,0.0194506033323,0.0778465077497,0.00310575038041,0.023149717639,0.239046248465,0.260196823682,0.328734226469,0.322449572456,0.313854689193,0.203923578266,0.311027709251,0.181459291203,0.0194506033323,0.0194546941555,0.123988991819,0.040043404512,0.0149381820084,0.0063462519407,0.0789943737521,6.90197189755e-05,0.198048309152,0.00987659658857,0.13547900159,0.0522175067676,0.843201123596,0.0194506033323,0.0675548494185,0.43190085139,0.279588513418,0.216019372219,0.365175726119,0.308119052497,0.135204766553,0.135204766553,0.354231050767,0.109543918339,0.364133183515,0.219099895325,0.153823261108,0.991289309159,0.0527221079006,0.991577697969,0.974482508518,0.995765375659,0.983204876263,0.952305080163,0.220843576413,0.28868591765,0.356102801684,0.214887861385,0.0,0.0,0.994120949432,0.220508221512,0.345529436532,0.350370622203,0.360683541569,0.258485823599,0.28229724475,0.216533352352,0.660479960958,0.507299676508,0.0911982901099,0.128611813179,0.0798514533617,0.497339366227,0.349709948648,0.803398814878,0.564131233687,0.0,0.0,0.000426213594997,0.0,0.0,0.0,0.999720526296,0.999872869014,0.601060839634,0.722716182229,0.692190848719,0.0,0.0,0.0,0.522042053711,0.0,0.000479147259626,0.0,7.62269279439e-05,0.0,1.26278496887e-08,0.0,0.0,0.0,6.69226655596e-05,0.0,0.00361757479028,2.72653791705e-05,9.99545930118e-06,0.268493816576,0.181662759501,3.33010731181e-06,0.0,0.0,2.70820788734e-05,0.274850997129,0.239184576316,0.0,0.0,0.67240473354,0.0,0.0337222476124,0.0509373550157,0.94067838868,0.883685010704,0.999995107209,0.999913712433,0.0,5.62686963676e-06,0.916106052973,0.699999934331,0.843398195236,0.043460073098,0.00522764285071,0.782057342842,0.112520100982,0.792612986712,0.0,0.618379184583,0.536955573581,0.561848282123,0.25672142448,0.165868532193,0.856068386042,0.797100839968,0.715814399139,0.753038295142,0.757494529325,0.834645151277,0.785475429498,0.110722379011,0.00842779036836,0.0,0.0,0.00399085290293,0.000499157680109,0.0359744947994,0.510737418852,0.0,0.0,0.0142073018438,0.0,0.0,0.0,0.33654673348,0.272174895846,0.617787922113,0.360398266017,0.312821220682,0.0,0.0,4.47836621567e-07,0.0,0.999934805847,0.0141491049563,0.0,0.000159198239076,0.767147331045,0.504051819251,0.506686378834,0.0114987671622,0.427357512458,0.178562895347,0.919585403346,0.943226764154,0.919967884014,0.000155805198299,0.00730493039377,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.243256173048,0.0190813905106,0.919786425926,0.0,0.0,0.0,0.957335291546,0.0,0.0,0.0,3.90499287104e-05,0.534422361559,0.0,0.671497859009,0.0,0.0,0.0,0.843814980669,0.0004670424725,1.54183075662e-05,0.467232597397,0.550465818161,0.646846106324,0.0246618676759,0.0475302460634,0.181459291203,0.0,0.000402513060895,0.0,0.0,9.81273379446e-05,0.998513372828,0.0,0.475735536928,0.998206712921,0.996800704495,0.0,0.0,0.0,0.00057338467373,0.277468696529,0.995867200747,0.907604916424,0.97038712569,0.0,0.00122971120404,0.0070694276055,0.0,0.0,0.0,0.730336950615,0.606138255644,0.0,0.913951057413,0.0830470025825,0.12465750685,0.105644591847,0.0146751390827,0.0108389196914,0.0,0.931894139373,0.0,1.53857707948e-07,0.0,0.0,0.991546945204,0.433752142618,0.371122992115,0.623675373309,0.631276977619,0.57463659387,0.999419678321,0.999205883959,0.131181776006,0.181459291203,0.150987674994,0.0,0.00685389852953,0.0798514533617,0.135823955567,0.217051164292,0.313854689193,0.181459291203,0.181459291203,0.313854689193,0.293816834704,0.234355216112,0.000489326906222,0.00398248727243,0.00365956833514,0.000103301924634,0.316706051838,0.336291491572,0.290014671372,0.000242808019515,0.392640702335,0.39701171616,0.890229655195,0.862352041062,0.470420710734,0.575279381811,0.198047669894,0.567055920132,0.485741797958,0.504520245011,0.681754499733,0.00505448695041,0.00357735671438,8.85049497387e-08,0.0,0.0,0.0,0.379959960143,0.306681901811,0.0,1.92683887691e-07,1.4882993101e-05,1.17220187697e-07,0.999857822785,0.792344371049,0.790424023932,0.963670721208,0.955239246479,0.0,0.0,0.998659448131,0.976571191233,0.99882017303,0.999082003521,0.988402755965,0.932431558109,0.358205680994,0.752110918465,0.999597613965,0.952581152017,0.0,0.0,0.837735394429,0.000824319990405,2.67329981631e-05,0.884114462777,0.191288903621,0.556650930791,0.464677364822,0.695601324054,0.688751571605,0.577194680321,0.0,0.0,0.354317442697,0.406247833314,0.181459291203,0.181459291203,0.000242683724087,0.00372255484344,0.0782117817258,0.0798514533617,0.0188168482682,0.0,0.185044836794,0.365175726119,0.285583891113,0.0227219550753,0.289246462601,0.813019781888,0.938656552097,0.269329061848,0.221643178197,0.359366126151,0.345529436532,0.926831779238,0.480555704319,0.0,0.0,0.0,0.0,0.00233158438956,0.28229724475,0.268493816576,0.268493816576,0.268493816576,0.0,0.0,0.0194506033323,0.999581266706,0.383587881927,0.446429857905,0.539027830836,0.601156110134,0.200696851035,0.0,0.0537098018802,0.0176398287584,0.0,0.051141892132,0.0,0.000157488680077,0.0493243376974,0.129084756587,0.00580421260663,0.00160858422517,0.00030056466686,0.0,0.0,0.0251296695308,0.0523488003038,0.00010428541794,7.99801682552e-05,5.24044801417e-05,9.56640861136e-05,0.0,0.000856657848912,0.128611813179,0.128611813179,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +2,0.999999979268,1.0,1.0,0.999940907791,0.999999973509,0.834304517863,0.629953741051,0.870899784881,0.85790133248,0.795227945769,0.986729908801,0.99022009615,0.992450729708,0.0,0.946991019244,0.982893687215,0.0,0.984823230111,0.995082164166,0.999135898586,0.961804771444,0.989903675607,0.980998291216,0.0900918823916,0.370492131208,0.942976220913,0.948603145636,0.941943806663,0.970090428336,0.942576122003,0.999779764225,0.988609578015,0.911207924385,0.933303343992,0.931056846492,0.971976758369,0.970460014272,0.892589819009,0.844852708157,0.0594909731681,0.0,0.0,0.0,0.0,0.0,0.898855867118,0.926033858977,0.922200257626,0.0,0.0,0.110346376375,0.13235032997,0.91955724848,2.62662448042e-05,0.659704389847,0.553566949551,0.903030631158,0.602489085152,0.393732610583,0.278086928627,0.925565437179,0.792088907002,0.958652747502,0.954530129917,0.998591412895,0.997521174086,0.567503555355,0.236428206344,0.229057643996,0.638252414004,0.583731155759,0.527274697165,0.625012490284,0.0280128323966,0.999918067313,0.997741386106,0.994579050393,0.181111646677,0.379323618751,0.208205667248,0.320154302931,0.322532639215,0.36507079976,0.335252038469,0.272640972568,0.099403741914,0.0954095290756,2.58763078198e-06,0.0766680791502,0.0699597612648,0.053371515536,0.126725092475,0.0562668522208,0.108476397208,0.0371745128641,0.105595645692,0.0156409330677,0.0402634838152,0.341356582378,0.366684046122,0.341479028265,0.373027809355,0.366133437068,0.245733343754,0.363037237585,0.22112973304,0.0371745128641,0.0371787472435,0.183645514454,0.0584899615884,0.0462834142247,0.0314166143488,0.0982563552529,0.00337410744411,0.209459089767,0.0392778285061,0.223810118193,0.0699215904278,0.870744677128,0.0371745128641,0.0861358823436,0.486222335629,0.367074977031,0.289151954796,0.40730052729,0.345352023455,0.156990749093,0.156990749093,0.395387895951,0.128056781843,0.40616578137,0.234847274144,0.171185975548,0.988775804998,0.15428940829,0.996035056758,0.986692277195,0.997955653343,0.99257939801,0.972915679877,0.236465448552,0.305450020049,0.386957664221,0.23093841399,0.000147452350124,1.35890173769e-05,0.997335739849,0.236154231826,0.363250587084,0.391186047946,0.377833229828,0.368461235321,0.364827258499,0.232465465805,0.691746697582,0.523671585014,0.163866267241,0.189351020883,0.0991355740403,0.603342368412,0.430727286868,0.882067005499,0.657268913189,0.0,0.0,0.000384694104033,0.0,0.0,0.0,0.99986602845,0.99993314615,0.727596696832,0.752402268629,0.722208533671,0.0,0.0,0.0,0.59701731778,0.0,0.000430759639443,0.0,6.85460510897e-05,0.000196217808999,1.13631251279e-08,0.0,0.0,0.0,6.1158390457e-05,2.72546372107e-05,0.00938404835262,0.00029529211495,0.000118579056829,0.376281410836,0.304913239997,3.6226907042e-06,0.0,0.0,2.56900869103e-05,0.363348502829,0.298059137457,0.0,0.0,0.782701600833,1.58618191019e-05,0.0978874642553,0.121089345452,0.956828184499,0.909969096074,0.999579565931,0.998848592302,7.48413178103e-05,0.000397262681937,0.942976220913,0.734807049194,0.865397439633,0.0920441038187,0.0467415626864,0.797761109204,0.156835872305,0.829410097521,0.0,0.693801609943,0.538463657546,0.586527077956,0.293349438426,0.192545833415,0.911950987154,0.87191612131,0.791414634666,0.831001498025,0.833122746779,0.893704146809,0.855762758739,0.154426690134,0.0116525656466,0.0,0.0,0.0105092582635,0.000508433996769,0.0418780167753,0.557233197552,0.0,0.0,0.00149468459721,0.0,0.0,0.0,0.383903510534,0.268769587683,0.657363449992,0.367404245971,0.34960285584,0.0,0.0,5.26565663272e-07,0.0,0.999233073973,0.0319448248543,0.0,0.000142937959958,0.776835687246,0.550389795177,0.593938196443,0.0155424777756,0.479112479536,0.224326044045,0.951391409955,0.96669946977,0.952717379077,0.0221221323098,0.0422765225468,0.0,3.1485261503e-08,0.0,0.0,0.0,0.0,0.0,0.0,0.320250617077,0.0372556253099,0.95117042212,0.0,0.0,0.0,0.973770228918,0.0,0.0,0.0,7.11011282184e-05,0.642128741908,0.0,0.77672167488,0.0,0.0,0.0,0.911138716114,0.000475721962051,6.73283659245e-05,0.586641880498,0.666838308256,0.760243159368,0.0345557481554,0.067577079161,0.22112973304,0.0,0.000363107507656,0.0,0.0,0.000651000157246,0.998995976528,0.0,0.612891586343,0.994512459062,0.99920758162,0.0,0.0017657681938,0.0,0.000513330380007,0.390003206542,0.997969503899,0.940936203011,0.983659397667,0.0,0.00102290736773,0.00651386944491,0.0,0.0,0.0,0.817645176575,0.676942112432,0.0,0.947836778173,0.0944076645877,0.132067593907,0.117309731377,0.0250430015678,0.0184965254024,0.00678908915757,0.959889243906,0.0,0.00022419679451,0.0,0.0,0.998208018622,0.493020330033,0.429959645396,0.654384850434,0.655238227279,0.63469118785,0.999705092246,0.99961108616,0.192285374537,0.22112973304,0.214899519557,7.79367424537e-05,0.0100298812501,0.0991355740403,0.157565370118,0.232946006677,0.366133437068,0.22112973304,0.22112973304,0.366133437068,0.365304060147,0.248775087597,0.0121942178672,0.0167962380297,0.0314965075416,0.00525817519159,0.333041935746,0.353857107098,0.39879901485,0.0118694446624,0.522621866529,0.510751681601,0.932706598425,0.910922398311,0.613414706218,0.710485359618,0.321235285937,0.634486859093,0.55398701449,0.587170537314,0.754936487914,0.00556702844308,0.00405892025758,9.31807697317e-08,0.0,0.0,0.0,0.474051643829,0.376044531805,0.0,1.78343769207e-06,0.000111754206761,1.08496306316e-06,0.999100381408,0.867353733419,0.860315004926,0.977043430393,0.974256548519,0.0,0.0,0.996369274491,0.988960356865,0.99964635686,0.997207404519,0.995479021337,0.956922527805,0.42122055989,0.784886113263,0.999820702796,0.972329398333,0.0,0.0,0.858097775182,0.000740125132442,2.50066548359e-05,0.921482443537,0.226445211816,0.585768556406,0.512822043235,0.802337018071,0.768943341071,0.618596183094,0.0,0.0,0.395481928454,0.433681961644,0.22112973304,0.22112973304,0.0118635542347,0.0502535537683,0.0974379376013,0.0991355740403,0.0373137426532,0.0,0.201596578002,0.40730052729,0.353052057296,0.03747989565,0.423643111137,0.835604784866,0.960606927937,0.375588320216,0.237278512862,0.397575171669,0.363250587084,0.955684182152,0.60938544308,0.0,0.0,0.0,0.0,0.00253701271914,0.364827258499,0.376281410836,0.376281410836,0.376281410836,0.0,1.54172582532e-06,0.0371745128641,0.996631999677,0.439412796498,0.505087356439,0.590537752646,0.622221538776,0.250336358754,0.0,0.0684412772742,0.0179754900224,0.0,0.0569237850909,0.0,0.000131007554262,0.0753572055212,0.171659436076,0.0452953841735,0.00593486380651,0.01176535084,0.0,0.0,0.0352174071269,0.059796827581,0.00463271916858,0.000571332457193,4.71559829894e-05,8.60829838455e-05,0.0,0.000872577927462,0.189351020883,0.189351020883,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +3,0.999999979268,1.0,1.0,0.999946507583,0.999999973509,0.880415778093,0.718205364214,0.887231473841,0.897536707096,0.848471966078,0.988242580531,0.99357716894,0.996849492111,0.0,0.966591852561,0.990554373453,0.0,0.977106415903,0.965487837856,0.987482617357,0.950249292341,0.965389317358,0.98887920565,0.0934216197793,0.376610611528,0.9502098828,0.953854559654,0.948474486685,0.980509193941,0.970488932884,0.999850131282,0.995424250641,0.936563244129,0.958530533958,0.951604661287,0.983641305045,0.982873386151,0.922593155963,0.869708017382,0.0754242112374,0.0,8.63300203828e-07,9.11305692177e-06,0.0,0.0,0.896117746301,0.938813771497,0.932212234571,0.0,0.0,0.167505466559,0.143507074782,0.927215204439,5.64957272586e-05,0.690399481775,0.578682327309,0.931748587417,0.62656273374,0.539338016739,0.380410487666,0.964081859501,0.853687945334,0.975621037883,0.958010004941,0.973431930011,0.983956911688,0.519787555244,0.239549653094,0.231600699554,0.605911868717,0.538313149753,0.516873989926,0.637213947309,0.0303315229343,0.999922311216,0.998672513276,0.996051923179,0.178063552806,0.358219498014,0.214049642701,0.419859047196,0.448036068618,0.370393111775,0.316795792837,0.392143768485,0.118729605905,0.112426928006,9.39159815727e-05,0.0943276248492,0.0844804171612,0.0641107004547,0.138646421233,0.068885093907,0.11191709067,0.0448496378613,0.109066475961,0.0212571325787,0.0496435969777,0.38518542029,0.410585372937,0.332004318246,0.35020240586,0.343478951814,0.239446913872,0.340803671976,0.218188095105,0.0448496378613,0.0448534910079,0.202491125537,0.0642459981315,0.0632708413229,0.0382396271106,0.117432359248,0.00487994523056,0.223147867612,0.0467072554974,0.276078886352,0.0877499608771,0.902310287254,0.0448496378613,0.104804094347,0.449195723701,0.385048408446,0.303873432254,0.383625469552,0.326826850266,0.153878488916,0.153878488916,0.372691453704,0.143983047492,0.382583942326,0.237844660176,0.182019147759,0.991421687991,0.234777583988,0.997660875665,0.992970044971,0.998485423505,0.995677059451,0.976052861024,0.239589817815,0.298303546148,0.366941502065,0.233629059481,0.000233619992577,2.14072533542e-05,0.998320700587,0.239254178935,0.347498412721,0.368834784983,0.35807804678,0.419793598526,0.376561452002,0.235275943851,0.659135355723,0.498666419543,0.206435394787,0.208012141976,0.118477122958,0.585642486894,0.429542972588,0.873198485382,0.739048991472,0.0,0.0,0.000579616488468,2.6697850246e-05,0.000208317617633,0.0,0.9998321813,0.999928702067,0.71454492173,0.799076584238,0.755000297037,0.0,0.000369937668654,3.26238648854e-05,0.673900833046,0.0,0.000620380666643,2.6697850246e-05,0.000421423018377,0.000243879494353,0.0,0.0,0.0,0.0,0.000112206852275,5.09029666303e-05,0.0152090482999,0.000457400068499,0.000185428221513,0.419810146442,0.405231224342,1.14691884863e-05,5.5758256769e-08,0.0,5.68868078297e-05,0.394217922068,0.338691418541,0.0,0.0,0.746123817397,1.58618191019e-05,0.158193388901,0.186486710655,0.963984382276,0.934930163209,0.999324840717,0.998522981728,0.000217090444818,0.000657440698367,0.9502098828,0.778141252548,0.91136154342,0.13519773654,0.0649858590557,0.794608474632,0.188624003029,0.789864845759,0.0,0.647461536425,0.590404765501,0.625832426432,0.278708250329,0.18738806223,0.934318215167,0.871371184248,0.827013409638,0.866712931554,0.87479761169,0.86001808053,0.887771534808,0.185364892885,0.0122521354378,0.0,0.0,0.00434672319357,0.000508433996769,0.0406667917218,0.609073875025,0.000340928428612,0.000230116760465,0.00253715179106,0.0,0.0,0.0,0.423277464126,0.297844331114,0.709927558432,0.438145929683,0.446883004643,0.0,0.0,6.89713866729e-07,0.0,0.994287169874,0.0306877082154,0.0,0.000195783863155,0.811323723498,0.590150083213,0.610329242811,0.0150522593901,0.577891487487,0.229939317867,0.969726434548,0.980179095291,0.971007291358,0.0206874546919,0.0402425894703,6.36403978611e-05,5.62141192213e-07,1.32519956527e-05,5.7776661981e-06,0.0,0.0,0.0,0.0,0.333744474291,0.044921275188,0.969446598092,0.0,0.0,0.0,0.967987147967,0.0,0.0,0.0,6.06432375759e-05,0.617442582847,0.0,0.801469381387,0.0,0.0,0.0,0.941044554841,0.000475721962051,7.10420970962e-05,0.669359954877,0.726345567927,0.781105798871,0.029825945664,0.0627778102628,0.218188095105,0.0,0.000924799833292,0.0,0.0,0.000993202160934,0.975646175971,0.0,0.658879518491,0.985950489508,0.986712733982,0.0,0.00279416648015,0.0,0.00337003407646,0.541062695136,0.993250746186,0.932358716596,0.966722669503,0.0,0.000845646991739,0.00542267457517,0.0,0.0,0.0,0.843691544709,0.70973634678,0.0,0.975601130402,0.110566336453,0.144186108722,0.135125813735,0.0323005274616,0.0238568657629,0.00678908915757,0.975711982877,0.0,0.000354992708716,0.0,0.0,0.977533840654,0.507135516616,0.433074590556,0.691714576541,0.685709635805,0.586601742307,0.999781389087,0.99973606581,0.210830661968,0.218188095105,0.232552113212,0.000338733194492,0.0136307767401,0.118477122958,0.154498202259,0.235794194275,0.343478951814,0.218188095105,0.218188095105,0.343478951814,0.364485162444,0.2531363937,0.0174805185618,0.0225226223024,0.0412060060149,0.0179127592652,0.323207027066,0.339503491248,0.436719806761,0.0171246878325,0.544597226537,0.520790987931,0.947867765846,0.914371143889,0.610014759783,0.685456248865,0.41969366585,0.676393417913,0.627736421483,0.679123371472,0.790260619268,0.00713146627548,0.00520249508031,2.25280453399e-07,0.0,0.0,0.0,0.585215917252,0.481528244138,0.0,2.7930114542e-06,0.00017162817076,1.65844073294e-06,0.990474333381,0.868588354253,0.891844068386,0.985378393214,0.98573090179,1.30037806991e-05,0.0,0.998813095544,0.97144311656,0.995139932715,0.964508334633,0.97854647635,0.951935007153,0.427330912934,0.848154276292,0.999832688149,0.983872634314,0.0,2.17342317976e-05,0.890932497726,0.00101375840043,0.000150376728214,0.954487699167,0.213701784271,0.539342422852,0.470512402117,0.753492330906,0.728867345069,0.569588859854,0.0,0.0,0.372777761493,0.402623967182,0.218188095105,0.218188095105,0.0171253899787,0.0819110874809,0.116531703194,0.118477122958,0.0449726035763,0.0,0.208838576126,0.383625469552,0.349996688929,0.0497478232841,0.504272763563,0.882375072926,0.957813459079,0.417193168463,0.240281828159,0.375548059683,0.347498412721,0.966391180661,0.671312849767,0.0,0.0,0.0,0.0,0.00401377586592,0.376561452002,0.419810146442,0.419810146442,0.419810146442,0.0,1.7061665936e-06,0.0448496378613,0.962512664396,0.52874011069,0.524964923638,0.663035431883,0.653049028801,0.328260621316,0.0,0.0663565276912,0.0189719101196,0.0,0.0729351835389,0.0,0.000108268478169,0.0767494004977,0.171284298034,0.0454867198668,0.00559949572448,0.011048550811,0.0,0.0,0.0340351824869,0.0568807408344,0.000592562487824,0.000875751977003,0.0,0.0,0.0,0.000872577927462,0.208012141976,0.208012141976,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +4,0.999999979268,1.0,1.0,0.99955749418,0.999999973509,0.918689517378,0.776866137455,0.915999225019,0.935526917128,0.902884279496,0.982897896301,0.991854352398,0.997969234417,0.000482821718545,0.983351004368,0.99345037861,0.0,0.949579470367,0.912982427003,0.921230047459,0.882525571528,0.894281436973,0.994100878524,0.117758047686,0.406833273958,0.879019596438,0.885345471668,0.867917640824,0.987833911029,0.988218389547,0.999755508041,0.997882744609,0.957736475375,0.964744073735,0.96367097262,0.988763466292,0.987719562058,0.955503758059,0.909829044753,0.0987116173135,0.0,0.000877286459005,0.000717861316541,0.0,0.0,0.912943210196,0.894224870375,0.926031579473,0.0,0.000250915561426,0.294719506797,0.162719127499,0.925971329093,0.000139751681931,0.714571047749,0.601034100754,0.946741266308,0.636577174596,0.613572192379,0.451761114109,0.97855455646,0.886563570642,0.98123281458,0.89542191283,0.937235267778,0.982800081801,0.475108300928,0.25829356777,0.249884704856,0.560294136626,0.501845895346,0.520952743184,0.661898804698,0.0350239989996,0.999843115687,0.998541252073,0.995703644492,0.19351428095,0.342509912415,0.233317989881,0.444268787466,0.517757702074,0.355747312404,0.305860987089,0.442571593221,0.157136589019,0.150149294147,0.000163403537195,0.12937043416,0.111417963084,0.0839058133141,0.160496729176,0.0920574527975,0.133605283744,0.0834238233914,0.137364006409,0.0459776306761,0.0862144120642,0.389135669456,0.410207509656,0.321238269305,0.33111368097,0.324657401446,0.251374125907,0.322772854403,0.23639877885,0.0834238233914,0.0834268005494,0.223461400378,0.098410543717,0.0864182232701,0.0645096184405,0.155928564628,0.011498635721,0.248401979725,0.0756801406558,0.303732166681,0.124003618104,0.841978830261,0.0834238233914,0.142301783578,0.411137927231,0.3736184882,0.305063066276,0.363208589374,0.315022570444,0.167665654097,0.167665654097,0.353902397201,0.178319090478,0.362322121616,0.256489927624,0.210152958792,0.991291308826,0.279555740312,0.997640348341,0.995065642405,0.998211632636,0.995412709982,0.961696703173,0.258336056335,0.300421695752,0.350497485015,0.25203042133,0.000147452350124,1.35067226045e-05,0.998343785975,0.25798099811,0.335684273594,0.350619897818,0.342718476175,0.421204559312,0.362872674989,0.253772590892,0.653459510358,0.4823646304,0.234550130562,0.228823491984,0.156972205883,0.550120546024,0.407012491392,0.901954498056,0.797938412243,0.0,0.0,0.00259035231409,8.82598544724e-05,0.000853822657694,0.0,0.999676512091,0.999874064629,0.747694467586,0.792387554953,0.761107882129,0.000254866565528,0.00157340372554,0.000208234899687,0.73634240225,0.0,0.00318515289508,8.82598544724e-05,0.000588535943829,0.000151338071738,0.0,0.0,0.0,0.0,0.00031657830983,0.0237131764744,0.0288604904882,0.0052199645454,0.00337478368731,0.417632281112,0.434784779899,4.44467661689e-05,3.21415057453e-06,0.0,0.000137657642538,0.446264873632,0.378903872764,0.0,0.0,0.758594843821,0.000119982996576,0.19658900599,0.228095084576,0.917897000061,0.910805860776,0.999445047805,0.998932074624,0.000600775023592,0.000877039400177,0.879019596438,0.837316185053,0.945227802428,0.173819138861,0.0904659566173,0.818767039065,0.234869915144,0.794794750854,0.0,0.618852307914,0.635331523286,0.64677410126,0.291840405928,0.190222791706,0.961367362665,0.899662194459,0.879541618973,0.917700753483,0.920479201791,0.894302253035,0.930066449962,0.231343817279,0.0160804455526,0.0,0.0,0.021274012859,0.00714951578115,0.0440948390378,0.683254301481,0.000706375475074,0.000478845645015,0.0179089987971,8.69905876175e-09,0.0,0.0,0.52257512171,0.314169902857,0.734753887367,0.546884111007,0.609442658838,0.0,0.0,0.000521515007169,0.000366003510858,0.991204763796,0.0339762503264,0.000418245232906,0.0160962109679,0.83704962236,0.647445491068,0.644899010244,0.016043930843,0.730461379696,0.265755349239,0.980947267877,0.985775008524,0.976546215876,0.0227978666641,0.0399626485171,0.0031295070537,0.000545897288296,8.40284348622e-05,3.6635104667e-05,0.0,0.0,0.0,0.0,0.328819845701,0.0831251953426,0.980841709763,0.0010387473505,0.00104414288585,0.0,0.91487354149,0.0,0.0,0.0,6.5242635063e-05,0.577422266898,0.000951972949327,0.838577508276,5.88749150985e-08,0.0,0.0,0.962259886542,0.00668952449431,9.17751699153e-05,0.719779071999,0.771330392412,0.818256646622,0.0367468490003,0.0645262154182,0.23639877885,0.0,0.00121281852276,0.0,0.0,0.0335126995857,0.92688508286,0.0,0.692589950545,0.975611191392,0.913018768134,0.0,0.00176344085854,0.0,0.00472194477544,0.622780493327,0.987077203726,0.854550295558,0.897211205942,0.0,0.00102290736773,0.00597574295967,0.0,0.0,0.000931463303689,0.892844638832,0.764202569947,0.000916855755792,0.984574260698,0.139639788206,0.160877612431,0.166593387965,0.027116574347,0.020028046753,0.00432070593081,0.981095711851,0.0,0.000224058346205,0.0,0.0,0.9100040138,0.533388587504,0.442073655178,0.72546010786,0.721238671103,0.546241483667,0.999711802457,0.999691109752,0.231065092985,0.23639877885,0.248340410176,0.000969448104762,0.0210575747055,0.156972205883,0.168321222722,0.254320826172,0.324657401446,0.23639877885,0.23639877885,0.324657401446,0.348922740546,0.271608346184,0.0399842278239,0.0479869313419,0.0626396929629,0.0349873465956,0.318987998843,0.329953563046,0.43159902279,0.0394194620087,0.540210384768,0.501038647701,0.961109649848,0.933400425048,0.612573334564,0.712036316135,0.442777505994,0.729792576932,0.69034100203,0.742739146247,0.843774749742,0.00631746013068,0.00458863692314,1.18934289964e-07,0.0,0.0,0.0,0.650008694972,0.550273212298,0.0,0.00333674778756,0.00481602376532,0.00133136916786,0.985023567935,0.896966989508,0.933458891988,0.99166587315,0.990027623918,0.000603076115936,0.000919893525402,0.998532157738,0.928289654272,0.977236988603,0.945392588038,0.913126275393,0.899436944338,0.439323755493,0.919778913537,0.999707263097,0.988987164609,0.000192054453939,0.00026540386109,0.862832169548,0.00823735724986,0.000519079163476,0.969040958318,0.225223375406,0.501735955968,0.42803549796,0.782740425395,0.718255097035,0.532183220446,0.0,0.0,0.353975855743,0.375222584813,0.23639877885,0.23639877885,0.0394155131369,0.10740279238,0.154987887844,0.156972205883,0.08291122733,0.0,0.232599153698,0.363208589374,0.336539402092,0.0796572218151,0.531004723663,0.851659093921,0.895372472758,0.414318778006,0.258832085094,0.357132741989,0.335684273594,0.966501115178,0.714674769924,0.0,0.0,0.0,0.0,0.00297414226835,0.362872674989,0.417632281112,0.417632281112,0.417632281112,0.0,1.04840340651e-06,0.0834238233914,0.940997490052,0.683861520728,0.568800453038,0.766150935496,0.687289866149,0.390577921152,0.0,0.0721829095533,0.0206716378896,0.0,0.0645096793209,0.0,0.000131294381632,0.0826647813132,0.185413054237,0.0483294642266,0.00987923945503,0.0256758769916,0.0,0.0,0.0377900027964,0.061125847673,0.0131774424699,0.0241083584457,0.0,0.0,0.000508436504728,0.0122700482311,0.228823491984,0.228823491984,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +5,0.999999979268,1.0,1.0,0.989879142559,0.999999973509,0.937017615434,0.808675725016,0.928366277818,0.955469589174,0.932936665146,0.95872360783,0.971077031959,0.997999620117,0.00180687282289,0.989428138342,0.993058377249,0.0,0.889099932446,0.872545509555,0.867197157946,0.730231216078,0.799235309349,0.993953494534,0.137223838022,0.433485507191,0.636459930783,0.659469453563,0.608219035563,0.991484614169,0.996125363069,0.999173027628,0.997663792251,0.97636541213,0.966224815974,0.968498897966,0.989379454249,0.987415618977,0.974190418916,0.938553649695,0.119253410575,0.0,0.00224790576035,0.00170740842016,0.00109947527687,0.000200362276839,0.897052564024,0.701184962094,0.843822973166,0.0,0.000799231516181,0.421811443413,0.17326813763,0.857156439267,0.00374099115715,0.736029660525,0.594767027289,0.957214171333,0.629037667907,0.647193622871,0.508644262094,0.982575097111,0.896495404773,0.982715168538,0.685420088591,0.932779439146,0.987096627842,0.453693484387,0.277803517125,0.26973012078,0.535620636863,0.480736085338,0.540693344573,0.680062521963,0.0425613455131,0.999532702831,0.996204545355,0.991327526126,0.223131083965,0.373088941663,0.267460878592,0.441028333763,0.563359092667,0.377631068551,0.339662134242,0.467305161635,0.198074817813,0.189433583862,0.000339690252235,0.175523810798,0.153256936199,0.12355323228,0.19227639022,0.132255203924,0.165754015373,0.118644207732,0.172452140887,0.0716965791652,0.122983443727,0.398342974772,0.417394629887,0.312584125946,0.362631611354,0.356676614597,0.29034072583,0.354970726015,0.276785068699,0.118644207732,0.118646757449,0.248675006067,0.13147923324,0.114567479579,0.0923710533497,0.196780171213,0.0187788972566,0.274381671958,0.105673769358,0.320869956624,0.171282347053,0.604098890014,0.118644207732,0.186058330587,0.411261874831,0.390622949466,0.326984476897,0.392234585439,0.342764448227,0.190791149989,0.190791149989,0.382647579293,0.214739845373,0.391321368552,0.276071832127,0.239845261651,0.992523346103,0.323549712552,0.994523733285,0.992161921577,0.994846349552,0.987493672945,0.902505976729,0.277844310634,0.311458111317,0.367937529657,0.271790235175,0.0,0.0,0.996565710246,0.277503417245,0.339622332642,0.379266030623,0.342561172762,0.426217886801,0.382433529466,0.273462901922,0.654421739185,0.48267013926,0.259112936452,0.253858014761,0.197904868903,0.53040962903,0.415489045601,0.912831352208,0.830913379443,2.67607660565e-05,0.0,0.0278729226554,0.000145699227839,0.00142993679269,0.0,0.999331317042,0.999690151258,0.753451117169,0.663989760457,0.684141213471,0.0013365788229,0.00275094541152,0.000674516702887,0.784791436454,0.0,0.0117737867129,0.000145699227839,0.000605308818659,0.0,0.0,0.0,0.0,3.9008935105e-05,0.0297104329786,0.037119998244,0.040548960178,0.0392557648347,0.0395448199587,0.424166007485,0.437479632616,0.000151939684718,1.27951882357e-05,0.0,0.00503332737126,0.474896499853,0.423588468744,0.0,0.0,0.747891152074,0.000427998573968,0.246586174593,0.274605407121,0.684291279265,0.709329751262,0.999590575996,0.999075198976,0.00126668577107,0.00123187909097,0.636459930783,0.906050682367,0.962199207975,0.209286823415,0.110106065539,0.818929365889,0.277873042711,0.799899824022,0.0,0.590421250883,0.672856699026,0.669062647701,0.289757130638,0.207191666319,0.974738737393,0.912845504642,0.913421412864,0.945786311584,0.945359152982,0.904356608071,0.953429558939,0.273827081342,0.0442988128154,0.0,0.0,0.0336435763798,0.0122942239966,0.0529218991779,0.710361651739,0.00100586678483,0.000684379597545,0.0218746990858,4.27657061875e-09,0.0,0.0,0.577351195407,0.347739121018,0.754289695155,0.639709952366,0.726676716098,1.67019105892e-05,0.00258613994303,0.0402793139483,0.00218621989243,0.993494310435,0.0514720576292,0.0253461515598,0.0359440581171,0.847338169673,0.660397121932,0.64647497912,0.0350445225992,0.822156752713,0.282204260367,0.984410128884,0.986654070994,0.97771280726,0.0311558463167,0.0444635637502,0.0305465065772,0.00190684389985,0.00130318373584,0.000811735239339,6.88560634869e-05,0.0,0.0,0.0,0.349771189012,0.118144014628,0.98452792536,0.00250670516671,0.0025167409214,0.0,0.793886530684,0.0,0.0,0.0,0.0160166842039,0.552076027017,0.00239163420826,0.858422131938,2.89436752858e-08,0.0,0.0,0.969548228984,0.0242866856991,0.00471678550095,0.744039355244,0.793785126061,0.837549248598,0.0507696072559,0.0703632412881,0.276785068699,0.0,0.00122567693036,0.0,1.89045754713e-08,0.0568247080987,0.915807616168,0.0,0.70031675865,0.975535558485,0.863857864581,0.0,0.0,0.0,0.00487405706223,0.664745232697,0.981384693655,0.672060303093,0.791936933416,0.0,0.00175652512627,0.00888048795071,0.0,0.0,0.00236379547418,0.922118427272,0.81211710031,0.00234261704069,0.987048604152,0.180610040135,0.202089792065,0.207968742112,0.0177854982515,0.0131362017174,0.0,0.982057267897,0.0,0.0,7.22855392259e-05,0.00108043649076,0.83115550673,0.524472178474,0.444974317375,0.723694475701,0.733465052384,0.518940539962,0.999075191564,0.99911499703,0.256008083833,0.276785068699,0.272577993098,0.0118720787303,0.0530551746417,0.197904868903,0.191420565096,0.273989265683,0.356676614597,0.276785068699,0.276785068699,0.356676614597,0.373031339174,0.289605846008,0.0641820214214,0.07421537289,0.0886466873821,0.057827038578,0.325061629347,0.335045214062,0.435353489618,0.063473948967,0.525204503289,0.490975235973,0.95868454738,0.935122621666,0.596196197854,0.707575986131,0.438609162233,0.781438379818,0.74317268014,0.782639947576,0.882921378032,0.00505448695041,0.00361897218357,9.08428597352e-08,0.0,0.0,0.0,0.691246721583,0.601120333854,0.0,0.0348637593223,0.0373221740823,0.0335031393062,0.990571358297,0.910679190942,0.956122325825,0.995338984458,0.988650431714,0.00595417131627,0.0064987294944,0.997340648892,0.860571684696,0.949420875551,0.960232522017,0.829460715607,0.756959877215,0.448404303658,0.969236762358,0.999182884775,0.989593070997,0.00135982029466,0.000585140210943,0.688426545653,0.0336405950295,0.00681858641502,0.973763560843,0.220705804242,0.479462052843,0.420767163406,0.781391970652,0.704673875013,0.505473409734,0.0,0.000114333379679,0.382723254444,0.374728525389,0.276785068699,0.276785068699,0.0634689177521,0.146131912906,0.195926944222,0.197904868903,0.117785624568,1.50031203763e-06,0.257547187329,0.392234585439,0.362133184467,0.119448537616,0.527921461074,0.669086746043,0.728193045778,0.421640775743,0.27824048881,0.382501490919,0.339622332642,0.924071397407,0.73180965665,0.0,0.0,0.0,0.0,0.00234844457401,0.382433529466,0.424166007485,0.424166007485,0.424166007485,0.0,0.0,0.118644207732,0.956049430779,0.780287898327,0.566562944199,0.815695269663,0.744038193985,0.446154585873,0.0,0.0854657332191,0.0209624514134,1.08694878792e-09,0.0513506345006,0.0,0.00315775344585,0.0910283559004,0.184979585388,0.0602441446963,0.0319861691382,0.0381663488205,0.0,0.0,0.0571350194632,0.064782050176,0.0338618930995,0.0516930772005,0.0,0.0,0.00122550560342,0.0255304752784,0.253858014761,0.253858014761,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +6,0.999999979268,1.0,1.0,0.980982682091,0.999999973509,0.911738066829,0.773320486896,0.883247294342,0.941353391689,0.925224982318,0.863022450728,0.868512885005,0.990879007349,0.0315847237844,0.980109303308,0.981172579558,0.0338820044661,0.759746355126,0.813340953276,0.854401035061,0.588747372319,0.72654911804,0.973701393554,0.131398435882,0.446079248572,0.529387887524,0.567295086327,0.510378830885,0.985689753452,0.992182658574,0.995210624821,0.979057005006,0.983752888166,0.966412112706,0.978046011208,0.979901881655,0.974098917869,0.96983816814,0.932781283072,0.101231461177,0.0,0.0120754197906,0.0239700602357,0.00227840542776,0.000417354066552,0.790716271599,0.570635238724,0.70148182648,6.80378790239e-07,0.0375786821404,0.511994129764,0.274076571581,0.720875022949,0.047015781178,0.840023885158,0.70900731036,0.977545973608,0.664833376878,0.534397856587,0.40741903112,0.988391278471,0.904717243491,0.97466254237,0.598311550727,0.899716861096,0.962238916702,0.353148562399,0.201155430599,0.193688633378,0.414737249375,0.404736879435,0.483728359249,0.589351890801,0.0632115907451,0.99696191449,0.960572976568,0.958959220648,0.136717542646,0.226347746474,0.1551009569,0.319121701543,0.443716095783,0.230641959333,0.20046825234,0.325694612738,0.180349958487,0.168343364445,0.000380631768833,0.168029500069,0.150868442048,0.127767837178,0.119050499957,0.133639866415,0.108619030598,0.0938262928219,0.115857487075,0.0663486725984,0.104037794898,0.253863826909,0.26854240191,0.222328391454,0.215387037982,0.210519582395,0.171331555575,0.20951182613,0.163323530581,0.0938262928219,0.0938272418762,0.140797078081,0.098603738614,0.0792285649061,0.0750820596344,0.178881740522,0.0249685973092,0.226905504189,0.0819691583429,0.195720460771,0.164131673703,0.463189132366,0.0938262928219,0.172997736266,0.280372301251,0.242801931407,0.195514060887,0.239583719779,0.210292950938,0.120680786058,0.120680786058,0.233924944382,0.18454691451,0.239044689223,0.199553856717,0.191169439514,0.991648261959,0.204503834726,0.967321058603,0.968092357888,0.974379986612,0.945687758187,0.77164318105,0.201193159066,0.215038826794,0.233606036298,0.195593959933,0.0,0.0,0.9686013688,0.200877878891,0.226639789564,0.231928969401,0.230045035266,0.279931001941,0.235253720106,0.197140949933,0.589759041152,0.398291732725,0.157680457132,0.143105217985,0.180106038033,0.408500449368,0.275020844376,0.854863143265,0.805091713171,0.000330968055707,0.000366252712224,0.0663122712494,0.0457987131915,0.0411699793208,0.0,0.996599953201,0.999007973435,0.676835313264,0.500624150126,0.547900662254,0.00305864470753,0.031984797437,0.00347183115118,0.767898806977,0.0,0.0575063088134,0.0457987131915,0.0108344499366,0.0,0.0,0.0,0.0,0.000450280496337,0.071486899209,0.0651913598987,0.0433857907459,0.0839491934898,0.0878135492533,0.273718661722,0.308521347396,0.0134193031101,0.000385392143859,5.5596689761e-05,0.047532743538,0.434049523612,0.404186041207,0.0,0.0,0.664815376376,0.000250467284919,0.155043291017,0.172947883545,0.567725664582,0.56010659653,0.999065698757,0.997307310321,0.000915246782676,0.00169904652952,0.529387887524,0.920930059963,0.980272448094,0.140229748719,0.0934802038741,0.717735591142,0.228973842623,0.730647706183,0.0,0.507167402401,0.701685650217,0.687536170575,0.42750600847,0.284685603939,0.966160106799,0.857111483091,0.906091032554,0.938641176792,0.93561677459,0.836596422241,0.943780737221,0.224731994145,0.0790457041428,0.0,0.0,0.0771798875863,0.0315985050723,0.0845559558964,0.854613156082,0.0478243304867,0.0444117213,0.0464864886548,1.01044639196e-07,0.0,0.0,0.736015984897,0.387722962039,0.753592563423,0.696385136002,0.803327830404,0.0,0.00300933476055,0.0519740550987,0.00173425985706,0.995786198177,0.134844067767,0.0337342220964,0.0841834727769,0.913761245686,0.818399961536,0.618611557303,0.101614711702,0.890312876528,0.400554207021,0.985110028108,0.978863570858,0.973382091127,0.0694476738199,0.110617775626,0.0582073319408,0.0337698543581,0.00455290792996,0.00334476112938,0.000384402976765,0.0,0.0,0.0,0.211845019347,0.0933690332654,0.98552515414,0.0126075544496,0.0126576863429,0.0,0.738189649821,1.19424515595e-06,1.5344162895e-05,5.5596689761e-05,0.0433228145995,0.434353091203,0.0117414729816,0.812434355058,6.83866463803e-07,0.0,1.56709009803e-06,0.934845346532,0.0649844645954,0.0771299630967,0.664079880573,0.730024137064,0.788067171968,0.0973781956042,0.145333788083,0.163323530581,0.0,0.0242975697993,0.0,0.0243367388382,0.104190151165,0.909542634698,0.0,0.606995575517,0.957642478162,0.854119504728,0.0,0.0,0.0,0.0374865702954,0.555900075181,0.950375323885,0.523538779388,0.726889521951,0.0,0.00383777709138,0.0142691577279,0.00451420162009,0.0,0.0115375972703,0.914054931061,0.803467443304,0.0113907348383,0.989763773827,0.173034316489,0.199936784707,0.196036027268,0.00534406475102,0.00395027476406,0.0,0.975809950077,0.0,0.0,0.000150491647355,0.00225506104151,0.794175550237,0.671728635955,0.550917212805,0.596070504081,0.614143363193,0.42419038048,0.994882391979,0.9905573162,0.144646146247,0.163323530581,0.156521597981,0.0200645956732,0.0663855467512,0.180106038033,0.121262909711,0.19762776506,0.210519582395,0.163323530581,0.163323530581,0.210519582395,0.226224879865,0.212630303186,0.0619487819539,0.0678214487004,0.069901666731,0.0549754253133,0.22326476608,0.224754454918,0.28803084837,0.0615343368336,0.411004664046,0.35918795111,0.88786029572,0.862574039459,0.493810751721,0.619084649758,0.317447252596,0.773578292272,0.726030509894,0.756039729806,0.875519566503,0.00487991025584,0.00323953589267,5.10983781712e-08,0.000656221970732,0.00105594555462,0.00451420162009,0.619436946475,0.54664578746,0.0,0.0814447446093,0.0857798367881,0.0813765476419,0.98940659311,0.855326874821,0.946312672448,0.991169037799,0.971291586609,0.0461532319657,0.00742632303944,0.996502125837,0.748732322268,0.953258906419,0.949398045196,0.791170092615,0.595104444233,0.496591318916,0.972217781306,0.993299673764,0.980021259266,0.00418710216087,0.0396327283044,0.508036133928,0.0864802747753,0.0466446602619,0.982051730935,0.327746942635,0.402906934706,0.293500037553,0.704571702954,0.618488211454,0.427464344464,0.000227437263402,0.000887638440426,0.233969611996,0.250848992879,0.163323530581,0.163323530581,0.0615284930551,0.097464758045,0.178187335371,0.180106038033,0.0930414052379,7.7425786025e-05,0.195838972967,0.239583719779,0.218012032808,0.117653841045,0.412288948989,0.479995047228,0.646218567333,0.271391148568,0.201356346544,0.23691973763,0.226639789564,0.804405058559,0.643340419408,0.0,0.0,0.0,0.0,0.00174231089976,0.235253720106,0.273718661722,0.273718661722,0.273718661722,6.47617266174e-06,0.0,0.0938262928219,0.968690489785,0.868802085761,0.746590737319,0.916407287422,0.795457940251,0.400209348537,0.00180679590762,0.13325924817,0.0372203846405,0.001406163143,0.038309068733,0.0,0.00527839466258,0.181948030299,0.269751277582,0.132082448619,0.0626300126793,0.070245005832,5.5596689761e-05,0.0,0.101275895262,0.101460315176,0.0841079078742,0.0986391924715,0.0,0.0,0.00847960229931,0.0665101774346,0.143105217985,0.143105217985,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +7,0.999999979268,1.0,1.0,0.98029834114,0.999999973509,0.906650521672,0.837459610422,0.834052363944,0.943226757398,0.9459571822,0.892189174138,0.873776366925,0.986135485356,0.0502435422555,0.978047981894,0.976811030927,0.0378060396971,0.8127507323,0.88734377188,0.920382317097,0.62266412805,0.805018210207,0.962869350564,0.242778810665,0.576591606123,0.506828936253,0.552404648445,0.474578341634,0.984487952892,0.992173119382,0.996855822862,0.978903726084,0.985275518963,0.965599110994,0.973752430688,0.978301425662,0.9729556675,0.974035802947,0.944986249294,0.184050253286,0.0,0.00481565104942,0.0183854656167,0.0017959058592,0.00032778090997,0.640668408341,0.53192915871,0.589902655797,3.71804531579e-07,0.0494355187146,0.660985679973,0.360340326186,0.601119709141,0.0552855239784,0.8552082408,0.755831251357,0.972729804022,0.747680884836,0.62563582574,0.515150466545,0.993688645408,0.927810569813,0.980590708006,0.588199727381,0.940359792561,0.97021502454,0.475811965399,0.272770393665,0.264346588979,0.557245822303,0.496673686535,0.435207421756,0.45165099372,0.090682940044,0.999427595598,0.978259890476,0.979946957129,0.210369172781,0.354036964807,0.247175015818,0.410347970253,0.535381456316,0.36138409019,0.320716938961,0.401959054469,0.248266072127,0.236116164296,0.0315470853035,0.24398955501,0.210848255078,0.179291225274,0.186315454973,0.186481452223,0.17336082817,0.159834252229,0.188233281163,0.118356123675,0.171389842459,0.376361626496,0.39706297341,0.307553768807,0.343729515294,0.337832534244,0.271102419587,0.33611650778,0.257466202881,0.159834252229,0.159835034924,0.227922723186,0.163774263322,0.122889743653,0.126552388902,0.246784649071,0.0642328558416,0.298796959988,0.135619344275,0.288349460866,0.242839569695,0.384303132564,0.159834252229,0.245723806564,0.4185394783,0.374196149416,0.308725841771,0.373044086623,0.326055255764,0.181981442562,0.181981442562,0.363951118471,0.253254119417,0.372177929666,0.270963548618,0.261035387758,0.999253256715,0.269650052412,0.955449643601,0.965754237869,0.968673933711,0.936122151854,0.748419005241,0.272812957729,0.30166487251,0.351244235724,0.266496118193,0.0,0.0,0.977624796603,0.272457268598,0.325839220457,0.360743827906,0.334490021981,0.405222601985,0.366781769707,0.268241383429,0.56916324506,0.443706541295,0.234225272641,0.23216142057,0.248036222689,0.546141132654,0.409723833913,0.766764201765,0.859089087182,0.000562794402936,0.000854142451725,0.0721544510183,0.0468530484498,0.0433835422421,0.0,0.99934567222,0.999726103409,0.734951043292,0.347567941471,0.410969582851,0.00226436547893,0.0349900309281,0.0040819083782,0.844571563019,0.0,0.0603279643632,0.0468530484498,0.0147161808192,0.0,0.0,0.0,0.0,0.00714918499171,0.0889132645489,0.100449498477,0.0781034463929,0.135388110792,0.113906352972,0.404564718708,0.399153949052,0.0192959955673,0.00258178529402,0.000253728744488,0.0548313636837,0.579277130633,0.539001504343,0.0,0.0,0.72298191653,0.00395483762362,0.220370275508,0.242030427966,0.508426722013,0.466350182831,0.998173019369,0.995368631039,0.0320849411087,0.00881932607172,0.506828936253,0.944348020868,0.982097170766,0.203864167299,0.16137886048,0.551711068682,0.38621462342,0.687948026022,0.0,0.60525734851,0.763738917377,0.754162198736,0.543620825628,0.415948783918,0.968381242986,0.772087334817,0.932061172785,0.953526153761,0.947995938932,0.734636559677,0.954210834889,0.381343701532,0.147295342847,0.0,0.0,0.102773124555,0.0412932437327,0.126078829449,0.861241660715,0.07110529228,0.0629521197996,0.0722666162148,3.56453885444e-05,0.0,0.0,0.781295372592,0.492691892045,0.81319176963,0.781242584351,0.874971260893,0.00545148829598,0.021384054998,0.0977157749822,0.0126247421228,0.994677279078,0.221131871743,0.074814712454,0.0884985658994,0.908098930442,0.824953601702,0.714316253691,0.207821711996,0.930824884079,0.516736776042,0.984743933554,0.977389651313,0.972014683748,0.102349987946,0.147148442351,0.0986523817063,0.0535240266833,0.0240601981357,0.02116007945,0.00301644986309,0.0,0.0,0.0,0.332583845743,0.15890042643,0.985191208825,0.00362078703687,0.00363256531238,0.0,0.770127848003,5.45022240233e-06,7.00267444574e-05,0.000253728744488,0.073488693924,0.573202216782,0.00347043732443,0.826771736423,1.0161692399e-06,0.0,1.6406093872e-06,0.90084613202,0.0746408637455,0.165306614062,0.743811049278,0.79002545669,0.821560955153,0.146691113165,0.195745469349,0.257466202881,0.000102333617762,0.0330201785989,0.0,0.0342289922844,0.150987754237,0.93875773037,0.0,0.69398817463,0.9661660586,0.919687100053,0.0,0.0,0.0,0.0537613230655,0.650395350321,0.97833060952,0.544757292362,0.796796275098,0.0,0.0146232671564,0.0276296928156,0.00106023545358,0.0,0.00343436061974,0.931942294627,0.861047561343,0.00340708380805,0.994725804065,0.234606032713,0.252654655457,0.261770083842,0.00409374126431,0.00302359879191,0.0,0.974355759363,0.0,0.0,0.000118453469557,0.00177361057029,0.881541728662,0.706294779266,0.624845540046,0.41274956338,0.428424273538,0.543954539902,0.993952651025,0.988277166867,0.234412572742,0.257466202881,0.251761497812,0.0510092185245,0.120554213467,0.248036222689,0.182638176072,0.268790592873,0.337832534244,0.257466202881,0.257466202881,0.337832534244,0.356214268825,0.285173541847,0.111714981883,0.12057980323,0.114138087883,0.0959740881686,0.313930779605,0.321910517587,0.419293871185,0.111089374794,0.523398353957,0.49252403803,0.7839969939,0.749112268595,0.596733640148,0.691664799136,0.409195551478,0.839094930045,0.814080101962,0.833393108972,0.911838601009,0.0044176472028,0.00295285887135,6.74637423355e-08,0.000368824762785,0.000593207772371,0.00106023545358,0.708213709482,0.655345878441,2.84297575086e-05,0.110140625562,0.115570323305,0.109463623298,0.98578228303,0.773543501345,0.955693308399,0.990024455904,0.972984064237,0.0620844885454,0.0192257131003,0.998108332836,0.811070457451,0.977751118666,0.944824859428,0.873058063762,0.590070644789,0.590875478243,0.980528426957,0.99884322588,0.978416536144,0.00213333397252,0.0388161610075,0.405643263323,0.105392466218,0.0521139759476,0.981005513162,0.452841956968,0.498810327316,0.433697430464,0.736486002161,0.669960942408,0.53085873884,0.000483438272899,0.0157433350016,0.364022893927,0.375621950489,0.257466202881,0.257466202881,0.111075907561,0.144204748058,0.246275415466,0.248036222689,0.15823133707,0.00100434297555,0.266521951831,0.373044086623,0.344678031375,0.187819103151,0.502970009967,0.356033262024,0.655873401308,0.402278472919,0.273153011478,0.364349741225,0.325839220457,0.720887843918,0.725320449904,0.0,9.84941884548e-05,8.74730736906e-05,0.0,0.00181978331561,0.366781769707,0.404564718708,0.404564718708,0.404564718708,1.52103703764e-06,0.0,0.159834252229,0.96342078434,0.917200998985,0.764478363467,0.92720352728,0.851867574377,0.540341310077,0.000424356118725,0.206872519687,0.0522363770627,0.00196755075662,0.0382251570456,0.0,0.0302170526319,0.295645196767,0.394525240034,0.173111641904,0.100441116683,0.108988210145,0.000253728744488,0.0,0.173323402595,0.146037127557,0.120336874428,0.148405273531,0.0,0.0,0.00231280961765,0.0833485412298,0.23216142057,0.23216142057,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +8,0.999999979268,1.0,1.0,0.983377889472,0.999999973509,0.813945080766,0.818167213391,0.691262019928,0.892219034561,0.91447949877,0.899420320228,0.870750018692,0.970077462231,0.0573773874256,0.961203054044,0.949202363906,0.0345342277517,0.827532878949,0.889411867614,0.919808237182,0.65380662805,0.821940665263,0.92527705605,0.322111921069,0.646087912433,0.556469298842,0.59837083691,0.520922192311,0.961602702129,0.981699300041,0.995638214036,0.968168568211,0.97254670423,0.934997612277,0.950361977514,0.944807039166,0.940862711336,0.95818818171,0.911239922839,0.259313238932,0.0,0.00958560161504,0.0239027802546,0.00244532605928,0.000455290160501,0.49245132772,0.563049851273,0.504523688003,2.11300832925e-05,0.050722018465,0.735268210126,0.45005717937,0.503926576962,0.0668359612294,0.824971440998,0.702976676829,0.954750572081,0.665834111327,0.653311308622,0.53989697934,0.983271683114,0.939900048212,0.958277337528,0.633318875354,0.925036947901,0.947541210936,0.503398488775,0.325946806852,0.316889931291,0.595783328367,0.485970966676,0.311098631013,0.29229368462,0.123880701885,0.999173321067,0.980451107733,0.981747449149,0.26554543757,0.434012399964,0.31476619941,0.45833304268,0.561255754059,0.419888064741,0.387629981335,0.453694873149,0.282685096649,0.275828980189,0.0619917874053,0.286413387844,0.239303179062,0.203537484714,0.251668757233,0.213633940016,0.240015395203,0.224660441556,0.253608657627,0.16759497595,0.231778353344,0.439446454359,0.458933400587,0.384460655693,0.412904120497,0.403960586944,0.340290982316,0.402323264318,0.327280177063,0.224660441556,0.224660571411,0.292185812905,0.225314119468,0.182825900331,0.185981982966,0.28189095123,0.105515416688,0.337183511612,0.200393272608,0.35669382218,0.291041968792,0.400325509479,0.224660441556,0.286420201849,0.476302240622,0.43185051592,0.368756482583,0.457363454871,0.401132145081,0.228334829465,0.228334829465,0.446463626666,0.291828899663,0.456325184212,0.324004172691,0.30542053941,0.998397297312,0.343254055274,0.948162102798,0.956215229897,0.969096100664,0.941336540086,0.71207108211,0.325992569716,0.371530219258,0.435344966315,0.319201003227,0.0,0.0,0.975587182007,0.32561014962,0.409685156048,0.44261901668,0.418549273708,0.464684338014,0.423538414674,0.321077429694,0.457196456153,0.407897195097,0.301630686228,0.29650982196,0.282714733509,0.581588338217,0.463045043802,0.606741064378,0.828688042697,0.00235573760992,0.00138493200095,0.0817324290949,0.0521257014374,0.0497209057071,0.0,0.999231357262,0.999460439459,0.705239306943,0.278599946385,0.318000055064,0.00306036328195,0.0417796339827,0.0160085565204,0.812697072073,0.0,0.0674288177163,0.0521257014374,0.0154406444554,0.0,0.0,5.72696845867e-05,0.000325377397605,0.0143000513751,0.100859894921,0.131704308284,0.106099736674,0.171129786451,0.128994623361,0.465896064241,0.448285947911,0.0432704636476,0.0229698797212,0.000475539667425,0.0655776352883,0.573403720745,0.525189174592,1.08732578386e-05,0.000391929381819,0.686150025063,0.00573668456186,0.295699156619,0.324333824889,0.552400677964,0.480833025924,0.996768710243,0.991883860512,0.0626989643411,0.0412812164503,0.556469298842,0.919960188603,0.970752058629,0.280087841147,0.238109286256,0.38560002428,0.427190293522,0.537492126235,0.0,0.590159546756,0.82226102212,0.803610920499,0.609860801364,0.54477095523,0.94095407635,0.601512748224,0.894197917848,0.922867454258,0.910456781099,0.569621638337,0.917828951456,0.423861395687,0.196676426225,0.0,0.0,0.126631054866,0.0507528961677,0.170386881011,0.845647358597,0.0738661560219,0.0660891532392,0.090287394897,6.6988731629e-05,0.0,0.0,0.795763785403,0.591160167706,0.849203862232,0.843430123499,0.911176050683,0.0166657538028,0.0554819871254,0.136870711932,0.0227293683327,0.990240472318,0.344742745646,0.111706663798,0.0972797811527,0.883975791952,0.800141919062,0.681357193711,0.323268079095,0.944391829425,0.573177653154,0.970752791968,0.945490325208,0.940513205989,0.142678232165,0.195792023928,0.130524226822,0.0607607954803,0.0423634916026,0.037732732872,0.00551001002913,7.66369811458e-05,2.19055874416e-05,0.0,0.391269042452,0.22372460036,0.97200976078,0.00924167026626,0.00927667220226,0.0,0.798122199411,3.93284762858e-05,0.000152785739397,0.000475539667425,0.102340580158,0.605076319242,0.00867364674576,0.763475407754,1.19742527089e-06,0.0,2.89268547836e-06,0.783481668437,0.0887615330785,0.253717823383,0.744965178035,0.771846809864,0.778830731587,0.185113989199,0.295867042838,0.327280177063,0.000191255049192,0.0347427319564,0.0,0.032725178527,0.18565903774,0.913172233997,0.0,0.700059407947,0.949455445553,0.912935303464,0.0,0.0,0.0,0.0553106435609,0.675840292079,0.979159586855,0.58151277559,0.817005695975,0.0,0.0283066111139,0.0565249587221,0.00313261500804,0.0,0.0085394615777,0.889373942599,0.802665516484,0.00844243282395,0.98458036717,0.255727391538,0.25594240567,0.278412253112,0.00299104686939,0.00220915908382,0.0,0.942112156333,3.68701310254e-07,0.0,0.000172778607724,0.00242477366688,0.895895662316,0.673575851954,0.637452855733,0.260988140452,0.265542771041,0.557487548808,0.993198444587,0.9861395382,0.29863402305,0.327280177063,0.315004575874,0.0845897962816,0.17494748578,0.282714733509,0.22904091821,0.321667913658,0.403960586944,0.327280177063,0.327280177063,0.403960586944,0.416391811024,0.338589021343,0.158459894823,0.170655757637,0.173380880908,0.138760333748,0.389816394659,0.403484392743,0.477709543128,0.157599209882,0.555062772857,0.536443610224,0.622142884681,0.581404113868,0.611641292792,0.673091722541,0.457366737354,0.79735115618,0.783728996687,0.813566481568,0.867793017219,0.00335395092465,0.00227947751572,2.67790705105e-06,2.61772107452e-05,4.21027177128e-05,0.00313261500804,0.716749925412,0.667696694274,2.29281590869e-05,0.126032756389,0.132208392205,0.124153198259,0.970821605832,0.603726628616,0.919262788964,0.975144913308,0.951562034104,0.0681169086512,0.0295979555432,0.997680705343,0.823573678421,0.982539648734,0.898490917792,0.889089299129,0.607068529802,0.619988817365,0.973169799561,0.998673401898,0.944885659414,0.00370148312929,0.0441559169857,0.382768692636,0.117653784137,0.0612616477449,0.946855921508,0.517534456494,0.491239965576,0.494353555719,0.675663095151,0.611178125318,0.526261336185,0.000758632336379,0.0309060099463,0.446549664594,0.454948417324,0.327280177063,0.327280177063,0.157582425051,0.194492030635,0.281767483334,0.282714733509,0.223054066961,0.0382406380812,0.315004026701,0.457363454871,0.406148330995,0.239453017285,0.53765789917,0.333901305,0.692078103477,0.463333003376,0.326529284475,0.447946049937,0.409685156048,0.620991100737,0.726889646936,0.0,0.000185385836032,0.000164648366825,0.0,0.00342051822231,0.423538414674,0.465896064241,0.465896064241,0.465896064241,4.49411820344e-06,0.0,0.224660441556,0.9371001766,0.93142259392,0.739357749909,0.92363445947,0.803421137334,0.562623195299,0.00125381993385,0.297466117209,0.0667307878452,0.00188118331395,0.0330650470363,0.0,0.0686482625274,0.400953471882,0.477159431071,0.228563330513,0.136044152458,0.149321588925,0.000475539667425,0.0,0.243961489978,0.215063900171,0.142157619662,0.184290446319,0.0,0.0,0.00612441144414,0.10141147921,0.29650982196,0.29650982196,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.339140972718,0.0,0.0,0.559188145924,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.412254087726,0.329992421495,0.121335274651,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.00586966874805,0.0494496450909,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.375116199533,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.00885289379691,0.0740416549312,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.381192111887,0.0,0.0,0.3322031252,0.151516308166,0.0171172850443,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.510833744485,0.0,0.514894738776,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.000783615999779,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0883473956344,0.0352327570261,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.000437952371212,0.0,0.31880804415,0.434437249893,0.331086717388,0.311147532172,0.0136092625972,0.0,0.0303218780842,0.225334555475,0.102354917642,0.181757096341,0.0682214837258,0.342375172876,0.223003596071,0.125989196518,0.381643632554,0.0,0.0,0.139444794307,0.0,0.0,0.0,0.0,0.0,0.0525218827045,0.0797816321913,0.0,0.0,0.0,0.259185468863,0.200633605746,0.273889951164,0.436131989956,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.244258076634,0.0,0.0,0.0,0.0895653578555,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.161159613047,0.0,0.329367788679,0.370743494436,0.415275252967,0.397731236357,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.160364153647,0.0,0.337019024769,0.0,0.0,0.0,0.0,0.0,0.327016301435,0.356756234965,0.0,0.0,0.0,0.0,0.0,0.0,0.00541041092607,0.0,0.0,0.0,0.5619598228,0.563968578673,0.567182056105,0.0,0.0,0.0476937501844,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.541226913928,0.57795053196,0.517645829411,0.527972633777,0.538938926011,0.0,0.0,0.0,0.342765744669,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.386213700332,0.371015098542,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.00062859727681,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.388835989402,0.351587705504,0.0,0.562955462758,0.0,0.0,0.0,0.0,0.0,0.583727179509,0.282050308595,0.0,0.0,0.0127238809842,0.0,0.0,0.0,0.0,0.0,0.0,0.147488117259,0.558735378497,0.56678563128,0.0,0.295616338793,0.00112041911188,0.0331679141959,0.2053832572,0.297532347617,0.283821511313,0.0,0.0,0.0,0.235534525236,0.0,0.0,0.244483732641,0.133137365175,0.0,0.0,0.0,0.0 +9,0.999999979268,1.0,1.0,0.978355880805,0.999999973509,0.722764557029,0.764989920574,0.597982133307,0.809723156701,0.840447175363,0.868804902369,0.838795871116,0.944810009918,0.0453305176408,0.911132154856,0.919474019856,5.84471775605e-07,0.782629534059,0.83065540222,0.879418738507,0.624402352947,0.769981239596,0.871819262079,0.39518817769,0.642332391187,0.592568602925,0.629558553149,0.547044336534,0.931938070799,0.951840392272,0.974875317754,0.939896728194,0.94341007215,0.891027310728,0.907330959969,0.906742577836,0.904085457964,0.893823329925,0.816802732945,0.299037468926,0.0,0.019950638947,0.0311838405845,0.00294608953869,0.000567655786071,0.444554501633,0.597667247809,0.487153490716,5.37141304535e-05,0.0227536625926,0.772498097641,0.443147041439,0.481376595606,0.0642714215049,0.730654714283,0.619483260949,0.906049055036,0.553569546652,0.655368970609,0.553209978255,0.957704114533,0.938609129683,0.936914685574,0.662682843722,0.866058038689,0.910773160923,0.393831344344,0.361634898898,0.352053191683,0.5147952945,0.349853736668,0.222936962318,0.236661888233,0.120796292911,0.994080985329,0.958212340557,0.96320118387,0.300645682548,0.458193246364,0.348433389345,0.452657077692,0.574193578577,0.428739960057,0.413810139568,0.459495945234,0.301837773992,0.296109356582,0.062023454863,0.294842960497,0.247796639526,0.214662251177,0.278835947339,0.219912689863,0.270587617046,0.247334374765,0.283625469138,0.17313398046,0.249048245395,0.436965392173,0.453357707967,0.376796457174,0.4344483484,0.42545146905,0.380064368092,0.424284297873,0.370789569936,0.247334374765,0.247334764646,0.311467970606,0.249296995893,0.201025957637,0.202847646327,0.301081420739,0.106239669468,0.365606861363,0.223120388317,0.367224735376,0.297525713332,0.434729625404,0.247334374765,0.299928580345,0.43444008399,0.435332896315,0.380470440983,0.479172870872,0.425150798935,0.258366461384,0.258366461384,0.4686643507,0.314454689483,0.47817187453,0.359579692151,0.333244077286,0.988727612249,0.350148834933,0.924564169615,0.93648923326,0.947294240358,0.922729747608,0.669876348114,0.361683313648,0.393428491902,0.451858518708,0.354498186593,0.0,0.0,0.952699085089,0.36127873291,0.420027037981,0.464957763695,0.410903682377,0.458094121399,0.429493635126,0.356483349013,0.348405076283,0.300547467041,0.314978296838,0.315818430096,0.301855061656,0.513988330468,0.446492653091,0.516191261461,0.766661216174,0.0080566573634,0.00494839716687,0.0757083384389,0.0547622625069,0.0534184389335,8.95835938517e-06,0.996557799871,0.996500475146,0.617992935014,0.290458073942,0.294751971827,0.0171376497354,0.0478583185545,0.0254688340876,0.748012604467,0.0,0.0664625590211,0.0547622625069,0.0169644227047,0.0,0.0,0.000224056287627,0.000794110084254,0.0180783955018,0.0898739453649,0.126717942247,0.109647128886,0.1900833271,0.120449773606,0.459249935233,0.445921065227,0.0472188584668,0.0322316634397,0.00073379107878,0.0628236973182,0.546802216652,0.509210168374,2.68666877225e-05,0.0,0.585917782579,0.00697345159123,0.302704816555,0.331610174841,0.56792480654,0.486589603126,0.995207461705,0.9880974561,0.0628809271204,0.047515115995,0.592568602925,0.826095485714,0.936161589623,0.286981634076,0.262392282779,0.335519892045,0.424857071731,0.415441925219,0.0,0.465014367986,0.865496534635,0.816362910159,0.599438760475,0.550861789272,0.875383978184,0.509682131937,0.829962729978,0.855864222865,0.833667765988,0.476802590911,0.843669286121,0.424146031869,0.236777886197,3.78103187624e-06,1.90079654843e-06,0.174935792608,0.071240737176,0.240573296778,0.803896420701,0.0607888301761,0.045853651649,0.0998029878689,3.12340811578e-05,0.0,0.0,0.776967393959,0.63648372906,0.845957663283,0.893433042485,0.93242437542,0.0267996085162,0.0800614256113,0.138851157156,0.0280350462968,0.984907587702,0.368669818615,0.119705058375,0.102234735486,0.815943199627,0.755926928144,0.634360320996,0.318511282629,0.939550844503,0.56000379689,0.937467763138,0.907039363488,0.899505063359,0.164259819417,0.222974070512,0.116048060243,0.048186644192,0.0556824972673,0.049933737812,0.0074988727756,0.000292273414732,0.000393452816126,0.0,0.400616455389,0.246400584504,0.939327602239,0.0188884354815,0.0189646750167,0.0,0.79391547513,0.000538681057208,0.000589428606796,0.00073379107878,0.118605726448,0.526161168231,0.017550661641,0.686786412693,1.43910072714e-06,0.0,4.60341820084e-05,0.694556963095,0.114956705224,0.276655397665,0.696035191808,0.709255200255,0.70239910199,0.24428954965,0.352891334081,0.370789569936,8.8326687301e-05,0.038280130771,0.0,0.0260559825745,0.178469681276,0.860795503349,8.9585245025e-06,0.641145666943,0.913393120163,0.864055480851,0.0,0.0,8.92034207391e-06,0.0508887557838,0.680099378409,0.961267097044,0.56544639859,0.782643922711,0.0,0.0374371932082,0.0652055584621,0.00681685130654,0.0,0.0172360155171,0.809249592198,0.746724024498,0.0170105333069,0.963786941075,0.270666687529,0.283669652663,0.299000662652,0.0,0.0,0.0,0.902200190484,8.98701304788e-07,0.0,0.000222031313601,0.002926607135,0.853681738133,0.637078004274,0.607041707634,0.236759546305,0.236647773496,0.441597648429,0.976169306392,0.963034420623,0.317719332793,0.370789569936,0.332368994802,0.098848281312,0.188406924501,0.301855061656,0.259113466859,0.357108050593,0.42545146905,0.370789569936,0.370789569936,0.42545146905,0.428018100698,0.37440920137,0.16125788458,0.17711541322,0.1924078194,0.143292289414,0.406131302233,0.415704365678,0.464818259511,0.160138789006,0.502636948374,0.492502765904,0.551581287315,0.510222318282,0.538881762181,0.58790427836,0.451114175037,0.751247135114,0.725971239852,0.761157594537,0.806668728201,0.000423821890624,0.000283499694453,5.81121081608e-08,0.0,0.0,0.00681685130654,0.690769085861,0.649089867745,0.0,0.111011962789,0.119206297156,0.108642095397,0.956546794436,0.511819944603,0.844525428326,0.949577833069,0.923325478576,0.0377795413692,0.0273739119272,0.986301038644,0.775208024816,0.974630720244,0.846769821552,0.854396777502,0.588070497741,0.585986405743,0.927585796767,0.990683275921,0.906875097,0.00589212085606,0.0481001803049,0.411767037999,0.106113393853,0.0605289012908,0.895549539218,0.511556414896,0.35367680585,0.436563068756,0.57298373699,0.497103443833,0.387949390575,0.00119938784007,0.0390204659385,0.468747299834,0.420701502659,0.370789569936,0.370789569936,0.160123779318,0.203315215602,0.300746513918,0.301855061656,0.245731520606,0.0441415983023,0.346492524139,0.479172870872,0.420164705691,0.240483074578,0.514907484679,0.375682259958,0.703634593912,0.457449381872,0.36205746791,0.468458324014,0.420027037981,0.575078754693,0.667823657212,0.0,8.60741160486e-05,7.64480242208e-05,8.89510917626e-06,0.000561069839401,0.429493635126,0.459249935233,0.459249935233,0.459249935233,2.43163860896e-05,0.0,0.247334374765,0.901721049583,0.925131828409,0.712802710161,0.89494175354,0.687550590846,0.548401860271,0.00483066169582,0.382109653684,0.0864067449516,0.00149813037169,0.00420569701844,0.0,0.0949734783713,0.390572868697,0.464235704063,0.246575312491,0.151952223124,0.16241866158,0.00073379107878,0.0,0.308771515633,0.268322210009,0.162591491359,0.187208395676,0.0,0.0,0.0127321308252,0.139010465789,0.315818430096,0.315818430096,0.0222459265449,0.0132505318915,0.0295348689669,0.0282126712318,0.0176928354286,0.648338444544,0.578906619512,0.0258310787309,0.415588467559,0.031280151949,0.0203381880429,0.689257982169,0.668914071216,0.675486574204,0.667553358072,0.681582470896,0.675783952169,0.0815138837997,0.0284416752058,0.0377862406667,0.629197893457,0.629110485641,0.508692308666,0.0234653204811,0.0418424540554,0.491070283371,0.234204125783,0.0517728504529,0.0232846895058,0.0295625061619,0.0198891440725,0.0207723000382,0.0387595582577,0.0334857335501,0.0303323397606,0.298686810117,0.0670488133607,0.0657383544845,0.303582880937,0.326462612625,0.110480768949,0.611787093939,0.472788272126,0.387173366624,0.52398165974,0.307238737093,0.0640639473238,0.414796090549,0.0516849378712,0.246297231262,0.307737071161,0.036976406991,0.247379419912,0.0166945319849,0.0304548089833,0.0216390922259,0.0286613173357,0.0276020794237,0.632789402842,0.680680518873,0.647524108837,0.0181745304314,0.0261789846895,0.0245514252883,0.0182834487624,0.0170630898741,0.0181898371411,0.0222276348858,0.427127027137,0.590750918482,0.540861009226,0.586099165037,0.024110199657,0.0238784419825,0.025048596497,0.0192761465117,0.0284852209759,0.0244591157993,0.0243204718713,0.0328396310422,0.0355829874017,0.025926963379,0.143666625495,0.0510555416324,0.0494613306513,0.0559423254532,0.0233037324274,0.0536163029514,0.0222900622696,0.0243599631997,0.0240164805552,0.0361087637279,0.0253983683641,0.0221487435392,0.0207545987772,0.0245684744422,0.0239061539437,0.0239983025197,0.0247572491241,0.0240388464137,0.0248226672605,0.0243599631997,0.0243598984239,0.0255611179542,0.024195460844,0.0312758895403,0.0263784282819,0.0320815451425,0.181852882751,0.0298001188594,0.0225673176091,0.026541376118,0.0542415502113,0.407659826747,0.0243599631997,0.0453114619322,0.0225440610365,0.0242862198858,0.024914314049,0.0234470983842,0.0240260159273,0.0240789250635,0.0240789250635,0.0238386996476,0.0298255083985,0.0235288234874,0.0253260704932,0.0274495145154,0.669001260903,0.0311277020828,0.029603447198,0.0296145617213,0.143959838174,0.028320349373,0.588241992709,0.0262126604253,0.0253094480966,0.0238642413052,0.0246788814502,0.1077549331,0.13707877747,0.335423449116,0.0259597525343,0.0244458648189,0.0238841987683,0.0239688723222,0.020100490721,0.0245183251904,0.0248375257409,0.0189451851202,0.0180089074554,0.0256430889371,0.0257476616247,0.035531983266,0.019372202437,0.0240712542169,0.0240497671737,0.0141890984954,0.0505626902029,0.0626903841902,0.06041189929,0.0620190511205,0.0600168537355,0.355616030165,0.603090370725,0.642249971819,0.0136502936409,0.152088525041,0.0427034476371,0.0990912081956,0.0570486165535,0.0462565005249,0.0153004152451,0.467943717638,0.0643036680698,0.061530009222,0.404449739416,0.192168593401,0.177250450661,0.329905164795,0.310584677206,0.239215325543,0.0479991668691,0.0477771496812,0.0454710208095,0.0408170220062,0.0428176230122,0.0202766922983,0.0249256476408,0.0468430608547,0.162127064141,0.0555436785986,0.0564076754003,0.0190720119467,0.018434883445,0.362509578858,0.687512593897,0.0161665094712,0.605047636074,0.0357312816158,0.0380375312734,0.327201780771,0.152052501278,0.638238513832,0.633837975564,0.136080956224,0.048458663811,0.629197893457,0.176183458088,0.0554181581308,0.0480208225172,0.0360523579917,0.0374525964636,0.0243514070426,0.0193648617124,0.396028540817,0.0170774402606,0.0386680707458,0.0256399676419,0.233308908596,0.0791462582648,0.0238592460853,0.0338766672576,0.0174336694262,0.0171188893297,0.0223265559197,0.0281355091319,0.0211424990072,0.0276925348357,0.0297433942843,0.390075722402,0.355793411522,0.251424631013,0.395417006898,0.43660586068,0.0500275875528,0.416538375269,0.550246454547,0.408115839485,0.38014918198,0.371733757869,0.237104318036,0.0657609287372,0.0736123343251,0.0378305668797,0.0942945852545,0.186740035077,0.392440976699,0.266843067456,0.282728751657,0.43870503332,0.63643684917,0.0536801031121,0.131458022593,0.0661697482405,0.048629279046,0.0701963071249,0.0274273274532,0.0553285649942,0.0664820005082,0.0873282247045,0.026856837748,0.0207926870877,0.0213018630585,0.308990926272,0.191472546553,0.371756021315,0.53960019636,0.352846510225,0.352749546665,0.349171031898,0.348206896292,0.181847265693,0.0568917253248,0.0247345327823,0.0242965068221,0.0267603898291,0.184462836071,0.0673037565567,0.163332633889,0.660025055635,0.237838597297,0.152970542526,0.0555600388275,0.119244261569,0.0171821486427,0.126644324369,0.0166799005548,0.452029574257,0.405086629895,0.449495137371,0.0347492460754,0.157000621881,0.0442888255901,0.0129560813334,0.0139885891247,0.0158457745598,0.0953223543443,0.205339611867,0.0248226672605,0.395523599307,0.440863427592,0.527339417866,0.50139143464,0.0391952325173,0.677709288606,0.359093488828,0.0124115548906,0.661223910895,0.670277977482,0.317821974633,0.215443458425,0.409421164439,0.418053943315,0.0164150323469,0.664664929401,0.682098466077,0.672927017609,0.331354403479,0.390356023588,0.433199816085,0.0663024850682,0.322257106182,0.137075265591,0.0231995977908,0.0197305715437,0.181804534941,0.0213356864854,0.0419187755132,0.031678181265,0.0370104370661,0.755276666636,0.759631871737,0.764458674545,0.020778830463,0.203596688204,0.0994090266175,0.324685494966,0.307745349356,0.669936548145,0.307192109154,0.301896986579,0.0243698663834,0.026542244542,0.01775479213,0.50487017619,0.17843076266,0.0257151811343,0.0248226672605,0.0254848430512,0.33589415743,0.274718579366,0.035531983266,0.0240793792945,0.0249043988031,0.0239983025197,0.0248226672605,0.0248226672605,0.0239983025197,0.0243915730451,0.0263344130446,0.04249471501,0.0346213174833,0.0349884282328,0.0362147346638,0.0250028749507,0.02459369437,0.0199357514477,0.0433215597653,0.0152983956984,0.0182312318587,0.206686604934,0.165368972256,0.0131065720917,0.0126629950463,0.0185812566447,0.018158938677,0.0173108089579,0.0132929786776,0.0180893406926,0.738887963869,0.73272951543,0.653115522347,0.676429578643,0.722633913331,0.221499365434,0.014771261435,0.0172361927396,0.422688045186,0.0444795436304,0.0440529172538,0.0444649768882,0.658055783038,0.0341444681266,0.0214346357401,0.0332623563138,0.0248532101021,0.508475292493,0.446688580992,0.627304187891,0.681327734034,0.649630247477,0.676558077411,0.669110612221,0.658173043041,0.132035642985,0.0824823010461,0.52717791175,0.019863033166,0.289805523517,0.103420464987,0.549005679627,0.0540145320638,0.0587967732182,0.0249119289696,0.301231809526,0.0170282718808,0.0210105550247,0.0164309433673,0.0177588453142,0.0170407307192,0.0538010026404,0.334159781686,0.0238374597763,0.0220639375104,0.0248226672605,0.0248226672605,0.0429019520004,0.0336173707968,0.0316608280057,0.035531983266,0.0242507825136,0.0813907462909,0.0267191455814,0.0234470983842,0.0244221112879,0.0468174406076,0.0151565479901,0.468448639621,0.643987185557,0.0211827637963,0.0262027234731,0.0236331006566,0.0244458648189,0.463590646469,0.013020171704,0.315087152292,0.486103056186,0.425883624618,0.433809933471,0.743060773367,0.0245183251904,0.0202766922983,0.0202766922983,0.0202766922983,0.341208160012,0.708493589018,0.327465611536,0.0243599631997,0.663741704089,0.0716595955912,0.198640009751,0.0452613426308,0.344573810514,0.0209287878795,0.326181115665,0.168613113348,0.49521259901,0.687155977817,0.760077163143,0.0982057434043,0.348773913728,0.049146725984,0.0976700098499,0.123068631355,0.349865008489,0.345133588458,0.0565714599411,0.300506262804,0.0818548126313,0.395680415174,0.050917399515,0.0390345448265,0.328486405215,0.316560490021,0.288769561056,0.319572331415,0.0257476616247,0.0257476616247 +10,0.999825830045,1.0,1.0,0.974897773889,0.999999973509,0.597465202807,0.660732240234,0.499207267889,0.688425736025,0.702129535314,0.826603676287,0.788085147992,0.916148313684,0.031940448039,0.829386118236,0.891366389044,0.0,0.738424326676,0.78319433476,0.847230520245,0.592692289993,0.730234477978,0.801211811611,0.41250863712,0.605464349792,0.621649982004,0.654703518277,0.572735240451,0.90059049325,0.905582299493,0.936652526273,0.9024009207,0.908156877037,0.833825196419,0.862530028878,0.870611074674,0.868360986363,0.79318812595,0.688325199947,0.32834011344,0.0,0.0190629192198,0.0298223037368,0.0027235273172,0.000499457680405,0.396270462474,0.616823241633,0.455931425524,4.28104280215e-05,0.0160155454026,0.771720270473,0.44245833826,0.446397259198,0.062301810594,0.640098125537,0.53489589357,0.840335598096,0.442229451698,0.650889731975,0.562338694847,0.920934624217,0.920686726916,0.914297115135,0.684753933159,0.821157534281,0.874378864082,0.301221747569,0.371032942284,0.361085872886,0.404026776794,0.258858634363,0.182341688856,0.21470021289,0.104802240117,0.976791294491,0.93028145461,0.940863581311,0.305606809265,0.430072174682,0.350171676153,0.432884882498,0.58417148848,0.392646170753,0.390333549979,0.466708239862,0.336908293993,0.331007133325,0.0824370094314,0.328980243724,0.279447613959,0.246702278768,0.295869545066,0.24925874056,0.295920769225,0.288482132253,0.309992215137,0.199419366018,0.287109213388,0.405423543482,0.41611313203,0.342474818622,0.403207821333,0.394503753386,0.378245005331,0.394085644679,0.374922550199,0.288482132253,0.288481260917,0.310962368836,0.284095911466,0.229815351899,0.237380381274,0.336199248405,0.12697408952,0.391081144057,0.26231614712,0.363527410581,0.331185733733,0.46411566453,0.288482132253,0.334349537209,0.365629300994,0.397634536752,0.358514584128,0.446476744624,0.401992430896,0.263826753295,0.263826753295,0.437784147689,0.34408788958,0.445648725304,0.368899367997,0.354827677673,0.965775541132,0.366222122999,0.910337745893,0.920799581315,0.931092285955,0.906622130595,0.616456364134,0.371083203147,0.383165826231,0.421914184376,0.363624098434,0.0,0.0,0.919424057444,0.370663195253,0.393289574924,0.43471807689,0.373740977211,0.421296489959,0.392220439403,0.365684957519,0.269737331588,0.232269641995,0.321466913943,0.314063149245,0.336886093846,0.409380817085,0.391423205601,0.428468206406,0.648874648404,0.00986963255193,0.0185878283372,0.0713953100288,0.0521257014374,0.0499852606302,0.0,0.990056374966,0.982966571734,0.500281664695,0.305079582402,0.292994538482,0.0159324689116,0.0438507209968,0.0280586515285,0.619337332043,0.0,0.0637081498349,0.0521257014374,0.0177749022238,0.0,0.0,0.000477925087061,0.00155800027154,0.0303958647786,0.0971667149039,0.148222378212,0.12745183094,0.264477801488,0.14995275469,0.419810146442,0.432237041166,0.0509512568894,0.044691087483,0.00102242956643,0.0596830927827,0.502817524866,0.467786006693,0.0,0.0,0.455354104058,0.00548610634603,0.320204551523,0.348674318668,0.589682890077,0.499492929171,0.993729515988,0.984340745907,0.0832410317144,0.0608093402964,0.621649982004,0.717785153233,0.863951543355,0.306995029763,0.291116390569,0.30135794494,0.416219756146,0.327428248453,0.0,0.341662785911,0.858704365653,0.786947308151,0.598067011632,0.527592514675,0.769523664921,0.422466008816,0.709307574477,0.735111923884,0.707275337783,0.392848034687,0.726645519821,0.416447919463,0.31648646213,1.11619441087e-06,1.96862507432e-06,0.197836500483,0.0795091309305,0.270382726075,0.735598046412,0.0503484438812,0.0381804412483,0.10730415221,1.94779908001e-07,0.0,0.0,0.746820167699,0.621011718175,0.80307776942,0.885224626237,0.922843956796,0.0324954964597,0.0875145694546,0.136387386126,0.0262058254314,0.977637065559,0.403197229631,0.120066026041,0.107754125262,0.707329899443,0.689349360934,0.563338520995,0.34094514031,0.915586415597,0.570160754358,0.906383284509,0.869067043964,0.852850888851,0.178261709282,0.243295037825,0.104575415869,0.0340816515395,0.0780821677148,0.0706753742289,0.0108762960338,0.000618966327496,0.00102137075947,0.0,0.372365959131,0.287473064946,0.908581566273,0.0181901889814,0.0182639004592,0.0,0.7876795555,0.00137941611819,0.00128656495354,0.00102242956643,0.127162785582,0.410707932578,0.0168921004637,0.577273162187,1.31826337315e-06,0.0,4.8422379882e-05,0.589555684893,0.125035114438,0.341920079622,0.6226928098,0.615439411904,0.590312136659,0.299243615139,0.399083225839,0.374922550199,0.0,0.0400799580939,0.0,0.0222736564365,0.218701153799,0.817770746136,0.0,0.552532114119,0.880092392371,0.826770355957,0.0,0.0,0.0,0.0491866545582,0.679529910655,0.944561394994,0.542634493548,0.752264656578,0.0,0.0418048206493,0.0667280904201,0.00658658691968,0.0,0.0165868476573,0.686194693256,0.629737463228,0.0163682138899,0.92919998495,0.305386043269,0.332426949502,0.33764659148,0.0,0.0,0.0,0.859464479566,0.0,0.0,0.000180353308152,0.00270627530247,0.820045406279,0.585174977341,0.561271358459,0.234488877958,0.229605250448,0.331402726286,0.947905462073,0.93646623577,0.315370113271,0.374922550199,0.325442475877,0.122709327793,0.213396349732,0.336886093846,0.264602242999,0.366333479731,0.394503753386,0.374922550199,0.374922550199,0.394503753386,0.393053930241,0.384897025953,0.18516527658,0.204198929025,0.227407633001,0.167174210283,0.388504448926,0.391644310256,0.418647237189,0.183822035985,0.418347961648,0.412865503396,0.477998643969,0.438956747152,0.427686047522,0.465777069851,0.430272501247,0.643256800909,0.607247329644,0.650832524714,0.68643649384,0.000122360389238,8.18483748234e-05,2.304344659e-08,0.0,0.0,0.00658658691968,0.644997330363,0.613545846611,0.0,0.130166747481,0.143038086482,0.127331795272,0.942155465991,0.424153301146,0.727901295543,0.92038854745,0.895081227117,0.0329363339463,0.0204468925826,0.962570398096,0.726080127745,0.967851796412,0.807195521318,0.824175920113,0.55902084811,0.535145432649,0.851848276659,0.966798296979,0.870936264349,0.00558163843907,0.0458237751477,0.422568064977,0.117938201587,0.0569535274071,0.844358748551,0.530310913329,0.261200117785,0.359523246054,0.445623270484,0.38014997902,0.284156035103,0.0015738222432,0.0555857667513,0.43785276281,0.36369304721,0.374922550199,0.374922550199,0.183807203309,0.222819121865,0.335868454451,0.336886093846,0.286750064788,0.0573693638574,0.362400301726,0.446476744624,0.38830875776,0.269679027108,0.470458765385,0.402437808678,0.707283984842,0.418140693602,0.371225611071,0.437461241563,0.393289574924,0.519556551358,0.587249404303,0.0,0.0,0.0,0.0,0.00020717352708,0.392220439403,0.419810146442,0.419810146442,0.419810146442,9.5896067544e-06,0.0,0.288482132253,0.870978522465,0.899837642893,0.674240528403,0.836533101299,0.578841705001,0.529833335463,0.00473849905293,0.426928738366,0.0950716662759,0.00128065948388,0.00121421459245,0.0,0.104181498324,0.418624733709,0.487291967727,0.259050874422,0.163423720639,0.170991935395,0.00102242956643,0.0,0.369354464686,0.302668685387,0.225146739693,0.241814426103,0.0,0.0,0.0122727555093,0.154012297514,0.314063149245,0.314063149245,0.0932486674717,0.0222717523923,0.219935789644,0.051414629238,0.031199035003,0.744391222487,0.740617131784,0.0394130550158,0.472562549233,0.0588699026454,0.0302046422148,0.769712733146,0.751300836144,0.747881286304,0.734872391547,0.75571574085,0.749074669665,0.220316559513,0.0388738693647,0.0485191657307,0.718080362136,0.714993782036,0.614092236573,0.0348113820895,0.0787790182442,0.666503269315,0.291768810287,0.0624036832496,0.0319876009222,0.0362491998257,0.0298960694894,0.0308253656154,0.052133254741,0.0424420898251,0.0427375122122,0.363353308816,0.314515250181,0.258403680605,0.379386741151,0.387354109516,0.445147907993,0.712683630541,0.626992592067,0.427133671373,0.598387600539,0.0976744312667,0.0724570125359,0.583931982065,0.0746480983537,0.0965910855427,0.187733819896,0.0437642436491,0.171169635514,0.0189571930482,0.0341254287741,0.0292000483378,0.0326605517534,0.0363042670767,0.717961908637,0.752904322794,0.71489297231,0.0294022056016,0.0358690854003,0.03232258379,0.0270728415685,0.0335784218247,0.0540599544574,0.0687711083315,0.417594067197,0.734617154914,0.615975783294,0.699269786582,0.0313252451079,0.0310567606122,0.0319426139879,0.0209673894844,0.0322905742255,0.0293327356364,0.0313501663396,0.0399231479821,0.0634391181232,0.0420594840975,0.186117930043,0.0735842784507,0.069387418455,0.07336205581,0.0322523095893,0.0714242357642,0.0331469427603,0.0396395528799,0.0350948503291,0.0548268591509,0.0409481440208,0.026091798709,0.0241253634754,0.0321263989536,0.0308520174865,0.030894780658,0.0319675500415,0.0309520893645,0.0320600184358,0.0396395528799,0.0396376291017,0.0315696706872,0.0347539916269,0.0445750799964,0.0406176727571,0.056127431899,0.225735271708,0.0445237618487,0.0358564649498,0.0321579835665,0.0767722759622,0.512304200664,0.0396395528799,0.0704350932201,0.0298199464796,0.0288211010506,0.0299203489107,0.0306389846599,0.031238216138,0.0312929913788,0.0312929913788,0.0310443329394,0.0466238904044,0.0307235786924,0.0340105593344,0.0395992385017,0.749957357269,0.0431713905486,0.0631487541061,0.0441095455916,0.460911387429,0.177837037009,0.724850039204,0.0359424658563,0.0337725225669,0.031081202067,0.0326003146567,0.0833758649594,0.154542813753,0.403592313327,0.0353913718428,0.0316977870052,0.0310914292898,0.0314291875959,0.0230995912897,0.0291320301908,0.0329460054083,0.0511591488761,0.0349084020405,0.0331787801653,0.0315793438038,0.0634951457646,0.0265842701285,0.029006921806,0.0753798289811,0.0249249330329,0.0696605603926,0.0866464199001,0.0824581031034,0.0908386825502,0.0890939736198,0.41284268621,0.736632737838,0.734468653934,0.0237513096463,0.238142784586,0.08039356325,0.317093591948,0.133585569451,0.0725553604357,0.0237087554335,0.504512939429,0.0901708673113,0.0907392359865,0.461256484587,0.222342143445,0.212584995031,0.404053815521,0.375355729655,0.291571921296,0.0703290739377,0.0707300843918,0.0656575559089,0.0593692350315,0.0626264318632,0.0234499227922,0.0281202937754,0.0685028425899,0.203221337457,0.0759296307373,0.0806550940473,0.0236266580339,0.0219909251824,0.38460652626,0.771067798878,0.0338593479761,0.654797161355,0.05092128496,0.05450374654,0.41646874086,0.20372092745,0.700101270996,0.696039868928,0.179658709764,0.0730394152549,0.718080362136,0.0754061419083,0.0448689431126,0.0700339110758,0.0547085132679,0.298144742519,0.0280378134948,0.0490592836077,0.455530334177,0.030257277369,0.0392750568259,0.0295312627065,0.172287613564,0.0716886554842,0.0381062831728,0.291973108029,0.0281056579029,0.0289945400513,0.0394250072324,0.109775268329,0.0366570463494,0.0336082020373,0.0423891939604,0.440298498473,0.403192199369,0.296055101759,0.442118405123,0.498997376118,0.0609489712217,0.50539571864,0.627227112072,0.473606075651,0.428611657767,0.405357649972,0.298632698082,0.0698656564701,0.0504329835258,0.0355255558814,0.0705254488574,0.0759515332288,0.413567691145,0.288642151434,0.337973048787,0.468048507426,0.701110290252,0.067272451898,0.157104599679,0.0897513266729,0.0541036662902,0.0826293791403,0.0319531727029,0.0702229245522,0.0673745955538,0.0823828816229,0.0365084056022,0.030575081102,0.0306611417517,0.310186662687,0.157727044672,0.448432300246,0.610803275481,0.403441237848,0.405213941981,0.423918849956,0.428683832765,0.223412799551,0.0795041386339,0.0295624475836,0.0395056384813,0.0365910093049,0.331261875273,0.320770775668,0.200315967523,0.733464715086,0.285150105322,0.187512687213,0.0759700883487,0.132063779492,0.0257460569304,0.335491943625,0.0371796902679,0.504629147599,0.442877017987,0.496938977973,0.134536232355,0.189851052616,0.0609289137969,0.0191867043304,0.0239259375316,0.0279992643264,0.124190238729,0.0956604991719,0.0320600184358,0.451216202644,0.50107239083,0.592150801072,0.549289953877,0.059557712865,0.748866252013,0.397419809265,0.0191709274639,0.728522140406,0.738352208032,0.365750609537,0.259164551779,0.459088984489,0.48570866634,0.01876432939,0.735657022789,0.754137802674,0.74355201317,0.391226915835,0.425155610245,0.480878013659,0.0917106394167,0.369696487437,0.338079644473,0.0321829190607,0.0270090819598,0.350078063739,0.0287427696296,0.0639479937967,0.0509486108277,0.0606260578459,0.838822142309,0.834527879779,0.83763436367,0.030377888157,0.127391179626,0.127214375055,0.390412783948,0.382836652084,0.740299052035,0.318555778684,0.271082513204,0.0624805798391,0.0704739459977,0.0293704246362,0.709849391489,0.392923246684,0.0315146829478,0.0320600184358,0.0310561351404,0.377926072021,0.299053806656,0.0634951457646,0.0312939811621,0.0330917238519,0.030894780658,0.0320600184358,0.0320600184358,0.030894780658,0.0295616891574,0.0362719749097,0.0630818168574,0.0529041058442,0.0507139607439,0.052290244813,0.032949372656,0.0320529435194,0.0235607279212,0.0641506549218,0.0215253028085,0.0241146935509,0.431423682447,0.386170309895,0.0204618672039,0.0211035481823,0.0200789852033,0.0250189060266,0.0227382815306,0.020603420656,0.0279310340486,0.819566707821,0.814844552907,0.72984814344,0.730785245825,0.805495702662,0.26440022443,0.0192457676264,0.0204422285747,0.473895494168,0.0660584444632,0.0647505770294,0.0658700544791,0.725570053064,0.317610992519,0.0373277682589,0.0454634712308,0.0356653609642,0.592593300241,0.500707915319,0.742288985106,0.755947258968,0.715505777914,0.749747039898,0.738012699557,0.75343106422,0.140329238574,0.0686484166201,0.6942400997,0.0298819288637,0.376091713641,0.159261891061,0.690318293529,0.0748378303873,0.0865144044524,0.0326842046329,0.184602456967,0.0329374301957,0.0291751575653,0.0361654914982,0.0367064671275,0.0312641635826,0.0738625922109,0.395463959578,0.0310430495428,0.0300293740815,0.0320600184358,0.0320600184358,0.0634503815781,0.0460828478281,0.0548120601698,0.0634951457646,0.0394091447401,0.115382124733,0.037439937779,0.0306389846599,0.0298572657483,0.0676281426971,0.0194404783367,0.58967565701,0.724685258386,0.0246636995837,0.0359185925964,0.0308806368189,0.0316977870052,0.636354276248,0.0192128186868,0.361992218336,0.51770699584,0.471465042398,0.482198131346,0.825531133646,0.0291320301908,0.0234499227922,0.0234499227922,0.0234499227922,0.396915349875,0.866475449821,0.362926870867,0.0396395528799,0.733339214111,0.0675703939757,0.222686943227,0.054101455309,0.200844410599,0.0238618069128,0.374099346545,0.206731430071,0.569823873695,0.744708841559,0.845026283009,0.135120846082,0.368561435464,0.0594947920034,0.0739548708522,0.0897797879226,0.386928945059,0.372824394173,0.0784953214204,0.346324740124,0.107299860265,0.422898917762,0.0709005991174,0.0577440981943,0.396320960089,0.374426135415,0.339652586742,0.367275225526,0.0315793438038,0.0315793438038 +11,0.99892211997,1.0,1.0,0.976132812575,0.999999973509,0.458785973202,0.502076130057,0.398540712221,0.552780844324,0.528409476198,0.782689238903,0.728901979113,0.87870401219,0.0260181151765,0.743714480504,0.867428549292,0.0,0.694260782924,0.750198058845,0.819920306169,0.55129530021,0.695192192877,0.730993082084,0.38009765631,0.523518256934,0.610629070891,0.643578034647,0.571716920705,0.86134667609,0.858593240998,0.881419862257,0.855921251676,0.871801973395,0.791348678616,0.825688641298,0.840358577987,0.843300560148,0.703441011186,0.566046955232,0.32162121002,0.0,0.00528651728078,0.0193653360848,0.00227840542776,0.000418779832142,0.354714547712,0.603307889557,0.416509551156,4.69852019604e-05,0.0130209827688,0.747387996721,0.461570248668,0.406887805967,0.0638633028321,0.578523403474,0.434992402632,0.790794498955,0.339092491975,0.592366866245,0.537364304933,0.872073723633,0.875730187006,0.890526266224,0.673406973116,0.791795963187,0.84085904487,0.212085152818,0.355993754211,0.346175512704,0.280349137802,0.19169318456,0.168323346279,0.213925313729,0.0919722090151,0.948755084556,0.886773606973,0.911141262821,0.287763947659,0.352663128612,0.324750688522,0.372987917528,0.560693637128,0.312658446393,0.321579096689,0.458401908604,0.357350807682,0.350987187446,0.0971656892052,0.344497534889,0.2914461361,0.258289366042,0.295993255041,0.258655123383,0.301166402032,0.315264337453,0.316838670227,0.21371052413,0.31011501472,0.333558218795,0.334997429643,0.279361913042,0.321790704324,0.313854689193,0.343970533213,0.314629145908,0.350124668831,0.315264337453,0.315262037192,0.286728720238,0.303685051192,0.251080751588,0.258169905566,0.356744435926,0.140204021204,0.389478374221,0.286900240373,0.336803592921,0.346323762028,0.465181611265,0.315264337453,0.352438809668,0.268352628088,0.316846528077,0.301753306848,0.361241548691,0.334553981948,0.25017602916,0.25017602916,0.355955768015,0.357068120953,0.360738048005,0.353887812573,0.35666043274,0.921265365811,0.381994616487,0.885787933324,0.904575209872,0.895545619839,0.861374983505,0.556492940324,0.356043364129,0.336400143697,0.342168644469,0.348680864826,0.0,0.0,0.876488935332,0.3556287959,0.319941546439,0.354091356505,0.299288208405,0.339900559712,0.312384284666,0.350715033022,0.225628982045,0.183339919542,0.312906204381,0.287733125061,0.357341505046,0.290253299363,0.301152605323,0.339492822538,0.486085890046,0.0303708378058,0.0328884358376,0.0718112094996,0.0489618278691,0.0478216753801,0.0,0.973287123772,0.962915146634,0.356489660621,0.312183859335,0.301838029,0.00316479110238,0.0413522797811,0.0327931185109,0.456416019655,0.0,0.0623791154159,0.0489618278691,0.0198387705632,0.0,0.0,0.0254217709062,0.0146379425297,0.048627990042,0.108599950977,0.155555566159,0.138762032013,0.340173837016,0.18649389618,0.335123437748,0.384980994903,0.0580777732765,0.0603103501471,0.0277983801158,0.0608144100117,0.431145044061,0.396040345705,0.0,0.0,0.329599608669,0.0049184773105,0.334221000669,0.358428997049,0.596198115751,0.511731068661,0.991464043825,0.978027200878,0.097980409374,0.0745786432591,0.610629070891,0.641908251891,0.787921889593,0.312863554236,0.29304097092,0.284934147678,0.378890951206,0.275737510459,0.0,0.246172048407,0.812578132771,0.735797583373,0.587771247246,0.45952660506,0.648807592365,0.338179873897,0.547642447085,0.573493245913,0.55418747189,0.324292309712,0.579806919588,0.379108444372,0.385614312792,0.0281351287904,0.0403650489236,0.239713736507,0.116523406196,0.296308549047,0.641417712915,0.0485439244521,0.0362510384157,0.113590752889,2.34952112501e-07,0.0,0.0,0.681606411131,0.575601304099,0.747386710942,0.843259324453,0.888911364186,0.0346494759653,0.0954453145799,0.135063228314,0.0262004068896,0.969272736449,0.449506383927,0.12199940449,0.124259958655,0.628927690861,0.591779847929,0.460626199956,0.366345678643,0.874324910709,0.582842931485,0.862136826594,0.836683220643,0.816167306394,0.171794397166,0.25943122349,0.0968598888635,0.0277948038091,0.102680947477,0.095983415361,0.0432370164267,0.0342135226781,0.00179161808452,0.0,0.306122092604,0.314170501967,0.863650138712,0.00402790941587,0.00402739555056,0.0,0.767928799256,0.00297827511039,0.00943391229086,0.0277983801158,0.120457050049,0.285357499984,0.00387300642356,0.424412273245,1.59014740038e-06,0.0,0.0126193206633,0.484609568248,0.146658728723,0.406292708435,0.48652587366,0.46384170135,0.427674589734,0.378443018684,0.439959348717,0.350124668831,0.0,0.0446185013958,0.0,0.0215859633527,0.258297122461,0.787825698655,0.0,0.412390767736,0.847857862869,0.795704576347,0.0,0.0,0.0,0.0516450588265,0.624944161438,0.928248978902,0.504425594074,0.722910191786,0.0,0.0409750852772,0.0601462844385,0.00750764446712,0.0,0.00383865176949,0.538898966208,0.475565550248,0.00381281501441,0.890032855793,0.321227039045,0.353473896151,0.357540974871,0.0,0.0,0.0,0.825175723918,0.0,0.0,0.000150667207374,0.00225911992311,0.793303244448,0.486592914853,0.46836424211,0.244074351299,0.236013650568,0.234100349787,0.904916771265,0.89987991577,0.288145347373,0.350124668831,0.291322215682,0.143434753743,0.228883163455,0.357341505046,0.250941475233,0.351355156002,0.313854689193,0.350124668831,0.350124668831,0.313854689193,0.312921034632,0.36861745972,0.197458189124,0.219161146192,0.248623401398,0.180137175794,0.329812951495,0.322616321305,0.330552060338,0.195926570688,0.313615207461,0.307824158586,0.408322580026,0.369955999603,0.303466736903,0.32967509511,0.368749543943,0.485518858922,0.451705547592,0.491945736525,0.525371047263,0.0,0.0,0.0,0.0,0.0,0.00750764446712,0.5457423228,0.534143639287,0.0,0.151666648324,0.171167055726,0.149406830808,0.930710078601,0.339216820006,0.582159583763,0.87520993383,0.879640703686,0.0301071430882,0.0179672161561,0.925846016058,0.683473249227,0.957975086836,0.778602928696,0.798959336329,0.513555447747,0.44019827501,0.791172686479,0.922564267087,0.840801521085,0.00259976179614,0.0406096023702,0.404696550841,0.134495346924,0.0568919380471,0.789837894901,0.561955727253,0.192506099993,0.259257334324,0.322162456796,0.285707923327,0.206409984772,0.0317914237414,0.0779054359704,0.35599749139,0.27926796858,0.350124668831,0.350124668831,0.195912502847,0.24047681201,0.356347625827,0.357341505046,0.313386765121,0.072352785677,0.356372906038,0.361241548691,0.312023713768,0.281967523439,0.389459016856,0.397877999424,0.687543164968,0.333747491869,0.355811845673,0.355335822059,0.319941546439,0.458449423036,0.442513187072,0.0,0.0,0.0,0.0,0.0,0.312384284666,0.335123437748,0.335123437748,0.335123437748,1.1011036563e-05,0.0,0.315264337453,0.845065699362,0.852498121198,0.583032321613,0.756554426825,0.498419606798,0.476964805755,0.00660596505443,0.460367465983,0.103017684276,0.00124111947067,0.0,0.0,0.114305781587,0.485448884215,0.539197115909,0.267064177904,0.159484627057,0.16187270213,0.0277983801158,0.0,0.432901137591,0.320511322622,0.308399409763,0.296774208226,0.0,0.0,0.00581296683373,0.183177720745,0.287733125061,0.287733125061,0.13822648489,0.027735577158,0.272448632725,0.0641990012266,0.0343957641301,0.74906949799,0.748689041133,0.0817439629532,0.474277137005,0.101666354367,0.0345222364453,0.767161677549,0.757120835404,0.757401692628,0.743675602803,0.765486709646,0.759077353462,0.392732044821,0.0356893016058,0.0459460106402,0.737201519415,0.733622180067,0.715929890282,0.0360018014109,0.0989583983267,0.688786330611,0.302183839984,0.0633496913097,0.0325341489002,0.0364374857382,0.029758026505,0.0316115773147,0.0545684051876,0.044273286577,0.040708141258,0.386128506287,0.349875385822,0.287019571681,0.396036880331,0.38810397811,0.474148775447,0.736939213705,0.649570140825,0.423658502389,0.600501725074,0.077281213637,0.0653495459395,0.610112513465,0.0843407651792,0.0807774975953,0.0816447089257,0.0451368725921,0.0742130254159,0.0192371842856,0.0315797509363,0.0309325332481,0.0323594674124,0.0399057588014,0.736135265333,0.76130908421,0.721191985935,0.0475158156882,0.0325090976963,0.0303599057902,0.039074873694,0.0577808982813,0.0828553884438,0.156846996439,0.384229015258,0.758121481128,0.633201132495,0.713631381138,0.0297498587385,0.0316429317582,0.0294549421204,0.0207962820038,0.030342795222,0.0309067853934,0.0318366898206,0.0374066349216,0.0691137807894,0.0457732214424,0.0784686534955,0.0851058723333,0.0812747062509,0.0802303492904,0.0312453193855,0.0828257562779,0.033576736714,0.0439552248775,0.0362095844943,0.0631133143865,0.0455818944558,0.024902629499,0.0239159556084,0.0343031804081,0.0329429650734,0.0329724504199,0.030296895946,0.0328295188929,0.0300662739411,0.0439552248775,0.0439519475168,0.027721666536,0.0356321514824,0.048474135554,0.0444407348176,0.0612992289509,0.0888556121499,0.0439454178669,0.0382691434492,0.0268800401921,0.0884599665851,0.630869955493,0.0439552248775,0.079195342161,0.0355072095246,0.0302615637457,0.0296498983727,0.0327960782429,0.0300044061714,0.0297359693255,0.0297359693255,0.0308994465283,0.0467020502091,0.0324002612801,0.0313828246484,0.0373972826133,0.742298443451,0.0426615075609,0.261645877468,0.0616727505608,0.567599518359,0.237808736082,0.742693718706,0.0325535664995,0.0331324264621,0.0331677147526,0.0305282116297,0.091088849451,0.152925987203,0.459129176019,0.0322196016734,0.0336858885527,0.0306790818781,0.0344861699255,0.0233338247972,0.0306413703677,0.0307377013881,0.0756095748075,0.0549907743211,0.0297834682887,0.0275633730437,0.0690495077555,0.0344679464929,0.0313215021247,0.232332385649,0.0301574481736,0.0749928468055,0.0928103959086,0.0905218581621,0.0984700170421,0.0977988073107,0.39893508391,0.753774209045,0.729402007277,0.0344687973908,0.418538057596,0.176560683773,0.349554221967,0.148932114733,0.0820473050467,0.0290128189273,0.449030078852,0.0938281567531,0.0991378546288,0.467906849667,0.22891433484,0.233768545076,0.425604597569,0.393019629876,0.283720488842,0.0823568136825,0.0813543122558,0.0745859700993,0.0689748246032,0.0741148603277,0.0237005541585,0.0264454662064,0.0783018762054,0.100174486822,0.0847030549281,0.0905579912006,0.0230917041113,0.0233758069895,0.363969450714,0.761921193882,0.100857406678,0.618085307499,0.0532989134554,0.0583067732291,0.580514787461,0.439360366907,0.70719069573,0.703031804514,0.0834122975208,0.0789785755107,0.737201519415,0.0722751089799,0.0442964341516,0.0799764873618,0.0631582746837,0.362642396515,0.026085900427,0.0729295267397,0.422257038593,0.049120716462,0.037028424948,0.0302179215191,0.0874874254185,0.0712693123329,0.0428826360557,0.394586232425,0.0301214295627,0.030743185097,0.0457139470163,0.365928005457,0.0425566849601,0.0292603815406,0.0442852226833,0.423305856875,0.392678958616,0.29096095375,0.42966499857,0.48401051514,0.0507615205637,0.529386171341,0.604459130049,0.488737503582,0.421482616001,0.389991379873,0.396019872857,0.0580224192426,0.0400612407523,0.0326421385349,0.0514230900068,0.0633009786106,0.388063423711,0.27336921788,0.364626315905,0.428777997091,0.710457644747,0.0686115143501,0.157584730105,0.0915342131639,0.0510627024308,0.0580424342328,0.0331380099763,0.0701552350428,0.0566608958039,0.0729406948542,0.0372284229636,0.0305030885838,0.0306561135704,0.295102821015,0.160237636745,0.464764460504,0.614204670645,0.384151219826,0.389939673654,0.435581191315,0.44689085021,0.244129234607,0.087611766482,0.0301000324794,0.0437539479872,0.037401012798,0.355096708887,0.356907000317,0.329415933298,0.745545950868,0.307826352366,0.20351027517,0.0847069298129,0.135083692305,0.0368148379975,0.366734314959,0.0487595712359,0.482171762758,0.41954907776,0.467376860648,0.253200926387,0.18812333345,0.0653466265176,0.0261995626129,0.0328243371533,0.0380583012097,0.13061718102,0.0983072047625,0.0300662739411,0.456560841189,0.505279995637,0.578646621698,0.508686373794,0.0728827040712,0.758117363677,0.376617396382,0.0274109385732,0.734914904287,0.747298306878,0.409427547253,0.274546124881,0.435004816872,0.499489362788,0.0187662707638,0.741320421582,0.763387329624,0.753154687516,0.43032280572,0.419417476963,0.478176835988,0.0926231079743,0.404264613633,0.368461769636,0.0342732844262,0.0290356980519,0.377933343366,0.0307994424766,0.0747578275867,0.0627801801673,0.0709590940452,0.848091025724,0.84128502546,0.843222163464,0.0303236137502,0.106400708275,0.139629563006,0.403191643694,0.402257731331,0.750117541552,0.0886518395572,0.0888354283157,0.105004978832,0.241105328058,0.0475620135948,0.724317751726,0.523222173851,0.0276446972927,0.0300662739411,0.0282214149297,0.270588438466,0.102257362031,0.0690495077555,0.0297365691375,0.0308260072551,0.0329724504199,0.0300662739411,0.0300662739411,0.0329724504199,0.0312095559758,0.0327478900959,0.0735265755502,0.0606878556337,0.0579789537319,0.0601004498918,0.0334811610482,0.0335911460369,0.0247005039384,0.0748748661951,0.0289437371184,0.0296997112351,0.509532696074,0.473816528042,0.0297390648474,0.0309482902123,0.0201718341327,0.0275510705109,0.025725489062,0.0253825055826,0.0300962654669,0.822033003035,0.819902451436,0.659619215314,0.662145312145,0.802912065741,0.273610502493,0.0220310358227,0.0210790894992,0.466322691468,0.0811100982846,0.0786403847475,0.0812970737167,0.733539137639,0.392944762951,0.0435387932499,0.0482139483004,0.0394322623672,0.598896309129,0.496174187126,0.733288519532,0.762364079585,0.725518162542,0.757963745151,0.74731801751,0.763582152252,0.0674439518249,0.0655642106837,0.75525554394,0.0297414274023,0.394036339829,0.168630785401,0.721980669873,0.0846342675638,0.0956941631649,0.0347484904429,0.0934177575356,0.056988754755,0.0384347564809,0.111027080735,0.0723869729452,0.0539821017452,0.0803456186312,0.393495503403,0.0309054515632,0.0368653808508,0.0300662739411,0.0300662739411,0.0740025413925,0.0466315952711,0.060000668199,0.0690495077555,0.0436089153365,0.0902811842661,0.0345371860469,0.0327960782429,0.0312052667124,0.0789631908347,0.0234061250479,0.680370828158,0.740522028021,0.0251832085749,0.0325599349952,0.0328010676612,0.0336858885527,0.679794506142,0.027212718869,0.395826145954,0.456716337907,0.436140322138,0.454204401633,0.812625903645,0.0306413703677,0.0237005541585,0.0237005541585,0.0237005541585,0.387989599107,0.874132286066,0.347554537254,0.0439552248775,0.743190707522,0.0551761667352,0.0800207691064,0.0464649238453,0.0894578718279,0.0232056978673,0.365278812004,0.21007115718,0.566306791416,0.669072379836,0.854011517291,0.351765632771,0.276514871281,0.0573510714619,0.0703619807074,0.0903618355713,0.349511032709,0.254625380322,0.0852072585366,0.378665594219,0.115512633127,0.434096627634,0.0763844274027,0.0684433592875,0.420958143693,0.402664939057,0.352521263864,0.360150958951,0.0275633730437,0.0275633730437 +12,0.96845181092,1.0,1.0,0.981153774355,0.999999973509,0.323992830025,0.310369773615,0.328159104219,0.348770748485,0.325216769157,0.585990929345,0.516960126319,0.653377524034,0.0787212178625,0.493338771319,0.614292132046,0.0622667790175,0.519804698328,0.585880299862,0.673957059116,0.413118249875,0.542611387061,0.496266712965,0.141173185497,0.217059805748,0.623765728036,0.654159877561,0.609862708914,0.616665324055,0.654162683006,0.757003509951,0.687800215625,0.67821363761,0.513291480437,0.581655879632,0.572046517951,0.562671741526,0.456836537791,0.323844234829,0.143302479799,0.0,0.0417870559887,0.0661931183395,0.00380558928069,0.000727431116087,0.36557897852,0.601065420391,0.385452353591,0.000165236222059,0.0788932676323,0.646829825156,0.484907997451,0.384657890903,0.104540614387,0.411796928006,0.293041321543,0.57871543495,0.19977015053,0.361106853323,0.278696241183,0.614038105727,0.584703621988,0.628301121957,0.678609334529,0.629809708534,0.67918726681,0.159463575007,0.206754674578,0.20021221404,0.181018364526,0.175081080356,0.204018535163,0.276006677399,0.0898783569297,0.851317519377,0.740787635698,0.777191250774,0.154890869872,0.171784637305,0.172741137408,0.221466606741,0.315005089137,0.157963496941,0.159534285515,0.265850732415,0.265591092007,0.254601544185,0.063115371468,0.254266729259,0.220575595689,0.199212643296,0.169497548912,0.195155264331,0.177807331209,0.207497712422,0.191277123431,0.142527875866,0.208821903669,0.184925158875,0.185851771293,0.151640760084,0.155976426041,0.152418646987,0.180161028537,0.153132067924,0.185830150023,0.207497712422,0.207495194209,0.153977542179,0.194821277209,0.15468802789,0.16760438738,0.264554244418,0.0946739565096,0.264186378111,0.185069088117,0.188731005628,0.25217465722,0.506738806898,0.207497712422,0.259501377365,0.152933006767,0.164454409252,0.156824243433,0.173662555095,0.164848965154,0.13624222183,0.13624222183,0.171881663282,0.25303713174,0.173492915017,0.205351364215,0.234637317796,0.827041670098,0.211765348952,0.676561089115,0.634845501102,0.745043509432,0.629627370049,0.389869217106,0.206787732528,0.178958562799,0.165340024073,0.201881674644,0.0,0.0,0.731430820975,0.206511481809,0.155641149129,0.171253503432,0.151010947668,0.190506844814,0.159234297184,0.203237158224,0.248590538832,0.176618026752,0.178307337434,0.15364585361,0.265375522442,0.182476084319,0.162761518679,0.323508967356,0.302418782733,0.0527762281614,0.0457835075025,0.124460807234,0.118037366605,0.108868480089,0.000167585893686,0.899761901814,0.875911724357,0.284422688809,0.374766084956,0.366211082047,0.0379303693688,0.0911949798493,0.0611136460168,0.25339507664,1.5007815513e-05,0.126582630683,0.118037366605,0.0573243136637,0.0,0.0,0.049013992578,0.0315546047273,0.0514721691433,0.119633608743,0.124681659234,0.104508712721,0.169760532686,0.129114456206,0.185835474327,0.227248975216,0.0781854400886,0.0542663307256,0.0480891434098,0.104906191525,0.157230734485,0.14287819995,1.39198359303e-06,0.0,0.274566243583,0.00942460513199,0.183247080129,0.188642323857,0.652259092,0.575317362357,0.879745824405,0.857607301093,0.0634226671003,0.0677177036749,0.623765728036,0.410997292193,0.623899750923,0.168754387282,0.143338531453,0.343758990068,0.127676123923,0.309637733834,0.0,0.202643807343,0.547842745798,0.471773497754,0.412376759098,0.221883648319,0.402803031833,0.312415301993,0.324751739934,0.349863068488,0.341931337503,0.338458407289,0.358256308713,0.127840627687,0.169265354213,0.0563172505266,0.0807251935379,0.287359707892,0.172464471266,0.315740989197,0.507567642749,0.0960094523593,0.090843757205,0.127484518903,0.000245236770862,0.0,0.0,0.574041406756,0.321366645103,0.508799314594,0.655270501134,0.785120574671,0.0503600521565,0.128489147013,0.154243413203,0.0399668984266,0.887917877997,0.381943807645,0.142319528281,0.190991875711,0.479765970951,0.450787011766,0.213445314438,0.236451500736,0.792808646565,0.505401593546,0.596786535363,0.567426373663,0.543887624377,0.175523454966,0.272318836234,0.123894594502,0.0829472845554,0.0929174068401,0.089458481316,0.0613215436208,0.0564913827254,0.0262005351578,2.87790246106e-05,0.157814651717,0.20663277302,0.599970090697,0.0391660866899,0.0393256097598,0.0,0.70044987451,0.0261804608092,0.0318788454833,0.0480891434098,0.119968687399,0.184686535848,0.0361901403932,0.33804284587,0.00108647045091,0.0,0.0253323488707,0.330958440543,0.230073399702,0.224529036564,0.328621469948,0.333366825298,0.338672890332,0.296218755757,0.39744318988,0.185830150023,0.00198576372179,0.0691110172775,0.0,0.0612850787594,0.160046670969,0.613558077633,0.000167586016936,0.288834698061,0.678474071674,0.637250449223,0.0,0.0,0.000166874695931,0.0862280737783,0.376218542742,0.823251559161,0.384354782778,0.582830056921,0.0,0.070883676305,0.107838847084,0.0164879619543,0.0,0.0354924360436,0.304322028259,0.249104182527,0.0349934242019,0.643602597233,0.248410314077,0.290436816654,0.280394191748,0.0,1.15340301965e-05,0.0,0.554373516751,0.0,0.0,0.000251778362919,0.00377617436333,0.645822116403,0.325922171729,0.270110930157,0.315105248838,0.307792817998,0.178962851546,0.806388045559,0.769019470292,0.153739304837,0.185830150023,0.154459504194,0.106432954555,0.157921999292,0.265375522442,0.136752282692,0.203663709082,0.152418646987,0.185830150023,0.185830150023,0.152418646987,0.156746342379,0.216025046737,0.132129353345,0.146014165679,0.159311920515,0.119950834699,0.170521989339,0.159430587016,0.185664969026,0.131149476065,0.19955868755,0.184817253475,0.329040249441,0.338075165134,0.209318897059,0.258805722196,0.218717197097,0.242020618487,0.236020968056,0.28187667993,0.301742457603,0.00123981190345,0.000728721263502,0.000402437800304,0.00662950057876,0.00126914783951,0.0164879619543,0.332529446942,0.281117357079,0.0390964634101,0.122323817106,0.128452632343,0.12178124405,0.789958648152,0.310454280438,0.360672943088,0.653749707469,0.573040186835,0.0887701722517,0.0391135576904,0.879042657229,0.507667254387,0.90483267109,0.593554844391,0.65559469923,0.383997680419,0.216833944509,0.550091938171,0.800430704253,0.572562108358,0.0103384403833,0.103390898419,0.408164599718,0.143172439001,0.106868686359,0.560333435765,0.441349041192,0.174453689318,0.152975863517,0.287709423816,0.255379290606,0.180173332734,0.0528650715706,0.0696632950919,0.171895720774,0.150785470954,0.185830150023,0.185830150023,0.131139498437,0.143946010821,0.264043643364,0.265375522442,0.206013040997,0.0640248119843,0.221663457778,0.173662555095,0.156257913049,0.199066991894,0.245360194576,0.431159747963,0.646237793973,0.184225837418,0.206459733031,0.171803250121,0.155641149129,0.343110236443,0.310435044822,0.0,0.000252691691083,0.000208748529269,0.000166402658862,0.000436749271421,0.159234297184,0.185835474327,0.185835474327,0.185835474327,0.000195720636171,0.0,0.207497712422,0.682662680242,0.760931629242,0.429715131106,0.635720099463,0.31332678636,0.20603925135,0.0164680737734,0.402252584989,0.141836237465,0.0316985971349,0.0,0.0,0.146278918867,0.392977177517,0.465608548873,0.284933010925,0.164525394426,0.167258712975,0.0480891434098,0.0,0.310790482434,0.295641660129,0.192574271558,0.166253885726,0.0,0.0,0.0276085604698,0.260115872845,0.15364585361,0.15364585361,0.142156232496,0.0285856009417,0.271842807602,0.217116409681,0.0332700450129,0.679086250422,0.679829137097,0.133168228356,0.420773625996,0.104835617309,0.0357510189871,0.674016314458,0.687912217635,0.685083439248,0.675694344858,0.698037152263,0.69057106374,0.424586661064,0.0258971676578,0.0365618615853,0.679365765473,0.677816292168,0.677010983738,0.0299757286813,0.102510488014,0.636545292362,0.356001629322,0.0529853667999,0.0260281960956,0.0291348018405,0.026860279708,0.028570663225,0.0498024075585,0.0398512529777,0.0293178170766,0.315847949555,0.347347964628,0.284869132945,0.359428819613,0.356872790937,0.466293620912,0.680264570675,0.604283392273,0.317711094562,0.537460293774,0.0546190120203,0.0511178833696,0.57236715504,0.0827483289784,0.0753241509792,0.0777784122146,0.0387791278337,0.0687905111013,0.0176097076037,0.0245880049131,0.0258377429881,0.0271594406125,0.0343085247911,0.678909583608,0.686339905589,0.6564014117,0.0702464804602,0.0284363193122,0.02575063019,0.0525319846937,0.10410009667,0.293690151944,0.296391803757,0.328008402826,0.66550908142,0.623768744271,0.65551223255,0.0249823613808,0.0286173138951,0.0245753815029,0.0188674549593,0.024566335296,0.0299905683715,0.0288401388904,0.0292983911004,0.0484703023203,0.0323619690645,0.0660423860872,0.0731376578775,0.0734381003791,0.0790583741988,0.0243975704396,0.0807252914034,0.025030380494,0.0304417397407,0.0272738781011,0.0501618179809,0.0321526127372,0.0238838735366,0.0238606028264,0.0346016871067,0.0311144128837,0.0311480999804,0.0257111481934,0.0308576510925,0.0252425048771,0.0304417397407,0.0304404787902,0.0225605761985,0.0272394729064,0.0391226532025,0.0334536190371,0.0428551615929,0.0740096008907,0.0352755463871,0.0270608931325,0.0224976747643,0.0781198273617,0.673994684532,0.0304417397407,0.0639355744397,0.0387070381913,0.0295222768221,0.0274805176161,0.0309465942399,0.0254939772833,0.0249709420797,0.0249709420797,0.0272429314917,0.0362105664312,0.0301736595006,0.0270288976368,0.0311343686233,0.647370002715,0.0324244584466,0.297582591192,0.0701533314512,0.576936022124,0.382901839288,0.682278259697,0.0284918887477,0.0307471131926,0.0317638651208,0.0259609497809,0.076060108064,0.0805968903663,0.533111945836,0.028074557146,0.0329033881414,0.026812612659,0.0348091541795,0.0234299557739,0.0298418384091,0.0262227339257,0.0905573524677,0.128259719419,0.0238538686644,0.0225111834647,0.0484021782949,0.0423554206683,0.0315745529427,0.224425719313,0.0309383420568,0.0727275169487,0.088851728946,0.0886625643493,0.0951468345954,0.095377824287,0.383517812775,0.663945908033,0.638518643677,0.0411277713635,0.511505856628,0.399334348449,0.335982536245,0.146134104905,0.0807221145655,0.0278679359423,0.122258963472,0.0872215474275,0.0964458548451,0.401612006404,0.179758348479,0.211416306796,0.394125969044,0.366435659546,0.214854506891,0.0759332198358,0.0731685676431,0.0625532590446,0.059609753189,0.064742923566,0.0240019764509,0.0226141587089,0.0767141385933,0.0857098310045,0.0839663319353,0.0931376080503,0.0214668559067,0.0220813234628,0.128775431402,0.656789612719,0.10784877827,0.478971599158,0.041556797663,0.0458855705785,0.659737338809,0.596396143357,0.635787156232,0.633545528798,0.0696468782677,0.0663827016651,0.679365765473,0.0616095241317,0.0395967140017,0.0664819575118,0.0481979091721,0.374565248859,0.0219779279361,0.0823517700969,0.337085185408,0.06691233593,0.0306187195709,0.0250921202451,0.0686187714712,0.0560565960192,0.0430966379494,0.37900731692,0.0282946638736,0.028347329878,0.0873787146239,0.354241170656,0.0454999101283,0.02321172353,0.0339956022964,0.393815375109,0.37229019113,0.145979892631,0.379968214566,0.437250877865,0.0485759091568,0.466098064487,0.484863403541,0.425254361949,0.291714425731,0.302772534849,0.294390510781,0.0444175384362,0.0301968779893,0.0254793061854,0.0412035950309,0.0507639676548,0.13145436697,0.102939184325,0.123505970804,0.2298358834,0.64688613791,0.0600449836809,0.0987954903099,0.0809922568143,0.0528837796619,0.0560656964982,0.030549610963,0.0573914693743,0.0462599164077,0.0531578589517,0.030690975586,0.0270075839477,0.0261277748741,0.126782417012,0.142637420355,0.377467804551,0.547284154137,0.119155872671,0.133243466349,0.348079871431,0.404168921807,0.241255072189,0.0863644311797,0.0285525711195,0.0303220639498,0.0307686571153,0.355051441167,0.354958009118,0.207035210858,0.682426592097,0.30298061006,0.20048738705,0.0839500833254,0.111381039187,0.0488901966898,0.356080835496,0.0521801203382,0.417831944106,0.337266596112,0.419155435737,0.321930971853,0.13180867003,0.0536246045139,0.0276497325958,0.0362783515853,0.0426724072932,0.117184696,0.0728736838263,0.0252425048771,0.390625721945,0.442731687796,0.380825112655,0.325301583505,0.0614770538834,0.686125145503,0.245679008803,0.0314143526233,0.665667993022,0.678545310347,0.377081937841,0.227355169199,0.39262537487,0.43762601948,0.0164369709265,0.664915805983,0.696902836267,0.68537172906,0.395384054909,0.313819829561,0.412126388812,0.0837728627898,0.380186504567,0.35627820698,0.0314058575912,0.0279748933608,0.356820584279,0.0261504828511,0.0598890269452,0.04823853228,0.0508265243048,0.776865841868,0.768232921267,0.766813829664,0.0263989709919,0.0878829464803,0.120488020753,0.312164244694,0.358691206423,0.681815437699,0.0693014330412,0.0710721857714,0.362906475478,0.365195919406,0.0660101297497,0.647228346719,0.56571959161,0.0227048678462,0.0252425048771,0.024078396655,0.0978266202801,0.0880091933826,0.0484021782949,0.0249716916212,0.0263330833579,0.0311480999804,0.0252425048771,0.0252425048771,0.0311480999804,0.030160231175,0.0286511450879,0.06088050614,0.0476651883572,0.0478466618451,0.0499222401595,0.0319052295576,0.032534273586,0.025926421208,0.0622683461623,0.0352101298282,0.0355476026562,0.494512060799,0.449732848375,0.0366967436613,0.0373687536583,0.018497915333,0.0264043346029,0.0243247207529,0.0251336742425,0.0285600175749,0.672120724855,0.694629586,0.413854146152,0.445430264047,0.616044802077,0.272395790498,0.021053836484,0.0194799752667,0.352243086178,0.0710934175148,0.0694146761302,0.0716947204543,0.661236385017,0.378028891218,0.0468838316424,0.071001536251,0.0370724151487,0.521429773037,0.373413829448,0.643407297564,0.692198389773,0.663067700126,0.684681530008,0.679507669963,0.696557015848,0.0561470782622,0.0554026955548,0.664551638074,0.0268738183986,0.359126727707,0.164540405898,0.670100192997,0.0770397430977,0.0945200529566,0.0282854520059,0.0717305260607,0.0976518729959,0.0461288777386,0.11716092073,0.084102796514,0.0807197923702,0.0789185963335,0.21077455026,0.0272546578718,0.0413883864831,0.0252425048771,0.0252425048771,0.0615393959107,0.0365547770501,0.0421731716722,0.0484021782949,0.0302358300203,0.0779349077645,0.0295739970782,0.0309465942399,0.0298000135649,0.0675155900896,0.0253481464311,0.677672448337,0.680632015243,0.0252494518,0.0285167002531,0.0309439242211,0.0329033881414,0.633814930149,0.029448630275,0.369205353397,0.136958675424,0.165467086827,0.412667600931,0.680171066439,0.0298418384091,0.0240019764509,0.0240019764509,0.0240019764509,0.374116300052,0.806695565048,0.0967872555882,0.0304417397407,0.677045472499,0.0434198204327,0.0606326858899,0.0433063506089,0.0790274335635,0.0209657869597,0.355747851931,0.190829294691,0.497516708951,0.489376979873,0.781735855739,0.13735727741,0.197101110024,0.0475532232337,0.0544478815818,0.0770926302044,0.210245346502,0.210009712139,0.0833219949576,0.346873975243,0.102885171457,0.344311512644,0.0661203165239,0.0586843765762,0.362383198758,0.356399338889,0.35462820117,0.344793581505,0.0225111834647,0.0225111834647 +13,0.969058741694,1.0,1.0,0.986115260302,0.999999973509,0.436532442556,0.413472629816,0.412876269398,0.497331475029,0.476713843286,0.721367731537,0.660102388818,0.790220327012,0.100785100532,0.612267363825,0.766427595943,0.0695203647639,0.644349139058,0.688519384367,0.7056920034,0.51355473104,0.623962366054,0.64389040398,0.216690472834,0.270642486458,0.588057846996,0.619155380625,0.563041153654,0.776498240869,0.755391415688,0.721387015627,0.787818323303,0.788441991922,0.69115773947,0.74395184295,0.740613233564,0.726393457016,0.531646740607,0.38009342879,0.22320176458,0.0,0.0415439819415,0.0597922015938,0.00316865048367,0.000623778286317,0.434483830192,0.592127619269,0.45911856808,0.000189382035885,0.0942805171328,0.666196514284,0.535334016966,0.457202455816,0.0987829915351,0.478323971126,0.306130877125,0.710051742494,0.217148055418,0.451377670935,0.420796761501,0.775643824839,0.70617748369,0.791218591895,0.647588076163,0.71434113481,0.734094632968,0.211835435217,0.185528101706,0.181821448419,0.224175627215,0.226398088561,0.238575379789,0.301598246429,0.0951468971106,0.855318546261,0.82585822514,0.862736033222,0.169566494742,0.195792196986,0.200087813472,0.251291823209,0.453627649877,0.174286009579,0.182012105018,0.399105392766,0.275728796527,0.268660214016,0.068082020333,0.282648688067,0.245220448918,0.2271788133,0.230520123759,0.226368744969,0.222052615872,0.25144584311,0.228219806422,0.179298396934,0.251132060393,0.204422001518,0.199292136214,0.136224716989,0.178571069731,0.174610920676,0.20346658544,0.175352970718,0.20936320498,0.25144584311,0.251442101712,0.189955310631,0.232612013376,0.239813439956,0.215625283071,0.275433759001,0.111279536547,0.234680862917,0.237336702477,0.240928487903,0.287330395365,0.46743520049,0.25144584311,0.280919185233,0.193268866489,0.178649659665,0.179535234498,0.198257426353,0.185782733705,0.145579031595,0.145579031595,0.195750379682,0.256776444391,0.198018615892,0.184733051442,0.228438527365,0.777011320258,0.393335328861,0.720868762003,0.715622518312,0.744332294293,0.636712350503,0.522286170337,0.185546830799,0.166780447073,0.176459409176,0.182767287006,0.0,0.0,0.791393180978,0.185390320014,0.151056530884,0.194866088986,0.151664865589,0.203763927617,0.174211544606,0.183535240842,0.292337549183,0.216412071324,0.25851923642,0.186556140886,0.275779043712,0.213640049274,0.18586281584,0.389481855394,0.412897286105,0.0450359909337,0.0323453118267,0.117376948531,0.0992850732549,0.0929477832405,0.000423748245748,0.921030088746,0.876609899967,0.321907186652,0.364919154858,0.375880758483,0.040087809914,0.0802940559543,0.0556868165195,0.378617165854,0.0,0.114712362021,0.0992850732549,0.0694783279716,0.0,0.0,0.0487701895094,0.0254160238114,0.0514851800055,0.149193826169,0.174557615781,0.146670328423,0.316196009202,0.227320667806,0.196827741983,0.276141599896,0.0744622876658,0.048530671708,0.0354201334057,0.0968143908594,0.249072214263,0.244241071402,2.21775039672e-06,0.0,0.318096655804,0.0490638047554,0.341627979272,0.355757320885,0.6183318908,0.567829448344,0.888888583156,0.870679436977,0.0694627459777,0.0711466106881,0.588057846996,0.466346784762,0.710323440257,0.304043103399,0.231073116165,0.386524434829,0.233815035557,0.360287757956,0.0,0.249047256123,0.650965992191,0.597938899305,0.428464128293,0.243234981769,0.535945848496,0.381193723574,0.464313655875,0.493126347221,0.494509010277,0.407629520855,0.501937576319,0.233365426836,0.317117241182,0.0831047023588,0.119043552348,0.398607505918,0.234592646125,0.429814753046,0.497782370944,0.0783627152913,0.0799899706221,0.121139318607,0.000431574254823,1.43081109777e-07,0.0,0.54409197125,0.427758535821,0.626315542613,0.722504377388,0.803633561122,0.0447652680996,0.088693721432,0.110689244149,0.0677777627774,0.893563176254,0.511704049655,0.102341516326,0.223545423441,0.543707761666,0.442631245128,0.254336137363,0.310479283891,0.790760281759,0.509439463143,0.759609619734,0.73664040916,0.716999970801,0.189005754734,0.314031757744,0.100917237138,0.105295450805,0.119056324782,0.113293346379,0.0642658003917,0.0558107075068,0.0239911735268,4.71345723306e-05,0.177347471438,0.250901736994,0.762166002635,0.0391667349486,0.0393256097598,0.0,0.71544773111,0.0225309991752,0.0258834255305,0.0354201334057,0.132724498498,0.219036253813,0.0361339157047,0.396585362968,0.0104003138414,1.18420673427e-05,0.0541309438025,0.455260583846,0.274919449705,0.376123075462,0.386501562087,0.388724596417,0.389869604638,0.489094978569,0.516456860328,0.20936320498,0.00280547952574,0.0821829322511,0.0,0.0601739847083,0.262002789464,0.65127665309,0.000423747843103,0.323075793075,0.7369082981,0.672597605555,0.0,0.0,0.000430924877303,0.0832473721366,0.479527602451,0.876365781001,0.474676179708,0.642792339699,0.0,0.0427268853476,0.06869220801,0.0176392844704,0.0,0.0354237002427,0.395865178875,0.365412167036,0.0349162665407,0.806363005424,0.255317597956,0.255329362087,0.27130520153,0.0,5.97561147629e-05,2.85861709963e-05,0.725793147681,0.0,0.0,0.000209723484217,0.00314612593517,0.697014340567,0.311402914895,0.256555839231,0.327523786607,0.325114141222,0.229768120031,0.764255906791,0.766963524426,0.186349711732,0.20936320498,0.184758826868,0.137421872326,0.199577022985,0.275779043712,0.145868008312,0.183776904683,0.174610920676,0.20936320498,0.20936320498,0.174610920676,0.17435733108,0.188346364481,0.167751100345,0.183169849153,0.211444786474,0.155732158084,0.1583975597,0.153611908791,0.198204597643,0.166662970287,0.223338876645,0.205050026795,0.428627064792,0.419234265475,0.231258743883,0.288829171681,0.245651472354,0.385862403951,0.362100598803,0.398648708554,0.437026782676,0.000994425796587,0.000601407430739,0.0,0.00632038340935,0.000997536759062,0.0176392844704,0.424646564062,0.399040221602,0.0490088286369,0.188144254834,0.206918767295,0.187577532318,0.80523996412,0.379267921838,0.504209827115,0.795861472186,0.716750135437,0.0878631018857,0.0571592805469,0.811176517121,0.632469778887,0.895705323592,0.633119377977,0.700930571755,0.489536092781,0.221247912207,0.628939455844,0.819658235581,0.741045593299,0.00981877991459,0.0881256300826,0.429572921173,0.190914011008,0.0961791916327,0.718716420883,0.466013353039,0.226269889491,0.198260929585,0.332552697771,0.300046708739,0.23210589354,0.0423111732146,0.0837697434019,0.195770169086,0.174517710496,0.20936320498,0.20936320498,0.166653231286,0.249375503133,0.275527278299,0.275779043712,0.250511883111,0.063467205654,0.208457270278,0.198257426353,0.175570744227,0.241904882267,0.278777391552,0.418394628205,0.647929645258,0.195459236152,0.185325646895,0.191441749748,0.151056530884,0.456387799304,0.352669909863,0.0,0.000614203533726,0.000535127652248,0.000452366062091,8.72291343423e-05,0.174211544606,0.196827741983,0.196827741983,0.196827741983,0.000436015347751,0.0,0.25144584311,0.690379064687,0.746440710491,0.411576985922,0.62947005326,0.355008902529,0.327240800267,0.0202336414475,0.562501967399,0.186322182287,0.0323299934315,0.0,0.0,0.081153707306,0.49905615054,0.517167801554,0.323517322175,0.159084893705,0.167181229139,0.0354201334057,0.0,0.486791222921,0.377153069962,0.355692975943,0.288661918446,0.0,0.0,0.0281992562711,0.333799541608,0.186556140886,0.186556140886,0.0426973508314,0.023846184525,0.0601562791116,0.185397909789,0.0291409261693,0.505224430139,0.50745953729,0.122269056179,0.421583598508,0.0985934931875,0.0236114037417,0.523100535035,0.514257431187,0.509881948543,0.507875813654,0.527695516229,0.519593136987,0.36400062224,0.0158789834432,0.0243410599674,0.525851920289,0.522319397334,0.528135196468,0.0187101303865,0.0978017347547,0.459376607989,0.330874263405,0.0386992489446,0.0141636794273,0.017037326294,0.015931488356,0.0170685399246,0.0413437495542,0.034365599459,0.0164186944174,0.0762795542338,0.0873598468413,0.0828441009862,0.0916735178846,0.254851316898,0.396353276163,0.525009387289,0.474648268518,0.0752886276758,0.492639190351,0.0299542139655,0.0327198390536,0.453554843767,0.0529239462838,0.0511662195452,0.066124253475,0.0257942931784,0.0635820560789,0.0146997848092,0.0170588636206,0.0175735636286,0.0178733097794,0.0192560851607,0.524219040697,0.508599860895,0.475462571329,0.0569693946722,0.018676187642,0.0182430640724,0.0484850376746,0.0632513909199,0.0699728310214,0.149313767618,0.0853086552876,0.471343722148,0.483683762888,0.488207095318,0.0181408516739,0.0229097750135,0.0183318167769,0.0158007479188,0.0183100882577,0.0250988063543,0.0234167706872,0.0205330555706,0.0289837585961,0.0187631482435,0.0423676425768,0.0478816978533,0.0489282703738,0.0542683593073,0.016346399546,0.0550284706089,0.0154785268654,0.0169762431305,0.0173252657914,0.0325731543976,0.0182908973765,0.0195214657743,0.0200024417598,0.0278058007007,0.0263581984708,0.0264575285319,0.0192942995375,0.0260748597174,0.0186768580881,0.0169762431305,0.0169765061351,0.0171400709385,0.0176441607919,0.0250701085571,0.020342339885,0.0252344598416,0.0492105942174,0.0221774053868,0.015400882567,0.0164637741057,0.0515691719795,0.531069642303,0.0169762431305,0.0407609494017,0.0344195540616,0.0246730105897,0.0224568085746,0.0258633669844,0.0187959595335,0.0181173230494,0.0181173230494,0.021062429179,0.0226547433248,0.02486143677,0.0184492114462,0.0200441350152,0.450877835524,0.0205159448539,0.265535491029,0.0394642478312,0.423820301281,0.19448272961,0.511309954743,0.0186851493763,0.0226444915449,0.0261000738592,0.0182769825074,0.0504478549881,0.0537065132771,0.440902405221,0.0186178459228,0.0264301149009,0.0205046208436,0.0283530163151,0.0198467517088,0.0249242256372,0.0183192006786,0.070018615371,0.0571467578656,0.0165501452227,0.017264197765,0.0289246966329,0.0380085359713,0.0267433425075,0.0594853403737,0.0258723316713,0.0655904204257,0.0702959275049,0.0549575567386,0.0674920126321,0.0689177458626,0.349797767225,0.473349190189,0.450171833305,0.0349894584071,0.443298938319,0.196558357037,0.0862352057798,0.0722300414087,0.0631312125188,0.0236928767133,0.0632506627044,0.0571495029606,0.0691105677891,0.325925919386,0.0866282655595,0.0692218897674,0.259169429969,0.100110626416,0.0828272485184,0.0414143159678,0.0400285503739,0.0353517796995,0.0290623101338,0.0311349787669,0.020333369731,0.0177277884647,0.0565173840195,0.0602444198085,0.0751004080147,0.063127454661,0.0178087788122,0.0179413829894,0.0939308848853,0.467407954686,0.0487579776755,0.498837959133,0.0246315145691,0.0264832262554,0.527183989583,0.506242835713,0.469391437279,0.465836731036,0.0426432799936,0.0395244737888,0.525851920289,0.0448640537357,0.0288026023941,0.0362497124145,0.0230959385967,0.302602735045,0.0169144293738,0.0672779002632,0.0726592480911,0.054519120179,0.0198623767623,0.017598262084,0.0462851984068,0.0348288350755,0.0638715501193,0.0763703013045,0.0263716194007,0.0267752386651,0.0976649723966,0.232490391562,0.10635193577,0.0161841365838,0.0186920154725,0.305856506516,0.169301146024,0.0618647107227,0.276467372974,0.255092676965,0.0449726815501,0.294000479826,0.328045852442,0.207289087219,0.0865429362904,0.08207655118,0.0790140072108,0.0342804859098,0.0187250967881,0.0173979441239,0.0265295874734,0.0349151467315,0.104547077162,0.0740230772678,0.10069515451,0.230865705399,0.479353809647,0.0409487448997,0.0618030559322,0.0497089579399,0.0473055709518,0.0510917080465,0.0259368758059,0.0336809545524,0.0343028387166,0.0357069815572,0.0188227093407,0.0159327772249,0.0149177798116,0.0677969240638,0.0583174094233,0.118322415716,0.501589653011,0.0868828955582,0.0990706833038,0.286019951925,0.334843716614,0.0969007022607,0.0823120219758,0.0235770304073,0.0169204782457,0.0189300057446,0.0898325086817,0.0879526412757,0.0770245189356,0.519490630783,0.0951328469081,0.0848678598439,0.0751654654386,0.0620977765392,0.0447499784504,0.0888871247232,0.0361296465179,0.276856598362,0.200038608985,0.357198416987,0.17642197616,0.062398964781,0.0308029833262,0.0233160640111,0.0302778490132,0.0355333842567,0.049516989654,0.0550137368716,0.0186768580881,0.205290609945,0.258716988015,0.27573723641,0.246615014125,0.0287341351873,0.512328538003,0.211501115663,0.0276030606326,0.486602893893,0.509412964845,0.0916680964703,0.0631446706423,0.343808509437,0.272051624645,0.0133327728942,0.488285632049,0.528161872302,0.516345489928,0.0944371925465,0.0832268054218,0.203448771908,0.0533188865132,0.0900875743021,0.0890513891089,0.028656627023,0.0248858381937,0.0886664313979,0.0177187846532,0.0383142928907,0.0300489966213,0.0310904829588,0.619523677536,0.608176509804,0.603520879902,0.0152930863066,0.0588397185583,0.0551129708033,0.0850335471692,0.0888193573198,0.512502414968,0.0581516598174,0.0518488523101,0.0970455465118,0.242828111585,0.0559496886881,0.452698921083,0.441271218682,0.0174665845602,0.0186768580881,0.0189018272552,0.070098706074,0.0626076413252,0.0289246966329,0.0181174439286,0.0183369968337,0.0264575285319,0.0186768580881,0.0186768580881,0.0264575285319,0.0252979582496,0.0187248750901,0.0410507544544,0.0305985464563,0.0312159678544,0.0327411321327,0.0245326030429,0.0257820859086,0.0222062245969,0.0421484195139,0.0313554446252,0.0315694845322,0.305162141618,0.252457887782,0.0324160937691,0.0325853060964,0.0156510646279,0.0231222660612,0.020148664474,0.0209915793025,0.0262737582966,0.476632129984,0.491286822973,0.0942821719201,0.287500417038,0.391357015624,0.0788218568932,0.0171415438585,0.0152678977062,0.289430057606,0.0346828863133,0.0337050850093,0.0348187888166,0.489905175937,0.0758227094609,0.113302675413,0.059235438295,0.0219845081527,0.327909892231,0.412734988964,0.437560542384,0.518205963595,0.500720307442,0.508328120736,0.51135264416,0.527653345397,0.0422428617979,0.0408167356488,0.47191077238,0.0159502244595,0.0913081908909,0.0688394885659,0.519010254689,0.0413796637412,0.0672144162041,0.0178161558387,0.0489740765178,0.0630378924366,0.0417870598271,0.0590969187325,0.0562546878652,0.0609717441481,0.0725190692798,0.0903773220048,0.0210776297048,0.0358969464099,0.0186768580881,0.0186768580881,0.0415929857195,0.0225465906762,0.0248256938699,0.0289246966329,0.0168802961412,0.0489668007424,0.0192416928208,0.0258633669844,0.0249020824347,0.0456030711218,0.0222069620033,0.528002015409,0.52056433909,0.0213140400987,0.0187287092267,0.0255583392158,0.0264301149009,0.473364759998,0.0250286169193,0.0871226025814,0.0687681808421,0.069107754231,0.357463927509,0.452767958117,0.0249242256372,0.020333369731,0.020333369731,0.020333369731,0.287500308214,0.655194463112,0.0594277808303,0.0169762431305,0.501570687322,0.0315175162895,0.0497322445077,0.0395893287439,0.0595126818969,0.0159921858264,0.0913144528058,0.0685720619561,0.360329890607,0.37254588448,0.626852882606,0.0729835434498,0.081981234324,0.0315724140081,0.035981396001,0.0494706877814,0.0746000084988,0.0697918186255,0.0793736777667,0.0810930066231,0.0509792395523,0.229046727821,0.0358504816874,0.0279049673283,0.0787534027393,0.0799251553517,0.0899366149465,0.0916911589798,0.017264197765,0.017264197765 +14,0.934033704841,1.0,1.0,0.987141764703,0.999999973509,0.438414037704,0.408746783911,0.406521527802,0.489890272206,0.472919608166,0.685988103618,0.622769777173,0.749886103454,0.0689280765636,0.598510148599,0.723382166088,0.055038674331,0.615501012627,0.663063891242,0.683969740566,0.488360878139,0.602621021105,0.624231469471,0.169423171537,0.239513881341,0.520364178827,0.557815550203,0.51514686233,0.75902752196,0.745260206415,0.580708813941,0.746387334302,0.773871154852,0.700759630132,0.757124839147,0.712521382166,0.695288816363,0.512299918282,0.362739742572,0.160813710665,0.0,0.0368581144592,0.0423866628597,0.00380558928069,0.000729408159025,0.389311017501,0.524513049586,0.416949444264,0.00015783944045,0.0694018127575,0.620096634172,0.548090271541,0.41362878483,0.0691176825876,0.44918362302,0.277568634696,0.712079242373,0.179070143976,0.396615363418,0.383093389419,0.814307378541,0.738650175463,0.80741534581,0.588127416847,0.690745485813,0.739915465999,0.193543050601,0.117239466843,0.115629020755,0.194177750933,0.216328179876,0.22524726953,0.2774596656,0.0984478337455,0.72815023796,0.742971073066,0.814104974007,0.118006685077,0.124527594166,0.149269303183,0.208039228343,0.422767834074,0.113447350847,0.118722912384,0.400433658123,0.208572253692,0.208735564018,0.0752924093331,0.232700764168,0.200910726747,0.189290259561,0.227746150454,0.193436886966,0.205695921273,0.23324185198,0.198195271039,0.176585183573,0.230229321654,0.159093599223,0.150951301403,0.0953641877548,0.112979222669,0.110780085599,0.141747500595,0.111576441247,0.148075653692,0.23324185198,0.233237138983,0.158210213347,0.20951708796,0.278018701493,0.217522407669,0.209203976426,0.11688413261,0.154985266154,0.237961112443,0.212921369565,0.242868402446,0.413667833724,0.23324185198,0.223991995682,0.151367356283,0.120562658049,0.129994466241,0.123911386086,0.118434821523,0.0998826172271,0.0998826172271,0.122767819634,0.18956513837,0.123802454875,0.11689403783,0.16079440411,0.659416391429,0.433030858511,0.65347462875,0.679225641295,0.682702279026,0.625776182692,0.500336714382,0.117247604155,0.107014236633,0.112148323163,0.11603996344,0.0,0.0,0.685893125024,0.11717960422,0.0984399361401,0.122364458506,0.10489434916,0.154874874743,0.114058649863,0.116373619748,0.270126872998,0.203767074077,0.265270432133,0.151010693191,0.208857972589,0.1808596842,0.133662153034,0.363044706914,0.413305641656,0.0345872216967,0.0228537205181,0.0768282984964,0.05370763917,0.053049718818,0.00028025131989,0.854436552985,0.763626671006,0.28786486509,0.322443305111,0.353783389137,0.034517559854,0.0502812277952,0.0413821601352,0.389920825363,0.0,0.0688034049903,0.05370763917,0.0220658094207,0.0,0.0,0.0423792821776,0.0140293773726,0.0468552349658,0.111564041527,0.142193103448,0.136529224902,0.309655031757,0.195139976375,0.147202435213,0.243903667132,0.0617922756215,0.0456319584612,0.0258929460801,0.0659376856834,0.272133511261,0.26868267341,1.57357608003e-06,0.0,0.281079458808,0.0142184032038,0.3546434512,0.348618773341,0.579750454792,0.543446316892,0.887577266257,0.873190709626,0.0772120010538,0.0729192556524,0.520364178827,0.42680505871,0.702258008745,0.27403170111,0.168596920208,0.337744058857,0.204000064188,0.329413850129,0.0,0.229984132973,0.667939665146,0.651087845698,0.454919866589,0.231309301407,0.524297500938,0.364323745133,0.463318485361,0.486215273934,0.487472528901,0.368873722653,0.493679731324,0.202085894133,0.318174703357,0.074639846595,0.106963523418,0.362253753773,0.208929984526,0.420856186844,0.458680247635,0.0326654917541,0.0502475448846,0.0780640365509,0.000147610631436,0.0,0.0,0.507640550152,0.491060601456,0.691439117752,0.70963016699,0.763245514479,0.0166108420027,0.0445180566182,0.095465574643,0.0291251380527,0.893955034073,0.489852697748,0.0792636334318,0.169222019779,0.513053409167,0.417069664456,0.249847146631,0.304605169108,0.737045396131,0.51881980047,0.747677850406,0.713206488041,0.70725842996,0.208172428955,0.324113121937,0.0827702866008,0.0724436399074,0.122292570643,0.115404323421,0.0587422443638,0.0490039425925,0.00838133695145,7.46678939106e-05,0.123445664354,0.233423845948,0.7510567281,0.0363775084248,0.0365197357015,0.0,0.680670679066,0.0031234771156,0.00904574982133,0.0258929460801,0.156739071902,0.190025806991,0.0336319500928,0.379618356343,0.000970760345055,0.0,0.0336001484261,0.456477706162,0.229866711096,0.380323156399,0.353018981046,0.362201173552,0.368056340506,0.486335027949,0.513120210358,0.148075653692,1.18975628533e-05,0.0541721536332,0.0,1.10563512531e-06,0.215994688759,0.638867390213,0.00028025111221,0.285033588086,0.736750555001,0.650140886841,0.0,0.0,0.000279061994791,0.01945352375,0.423460625464,0.854066476054,0.444934405444,0.618855045509,0.0,0.0147792364526,0.0209488567811,0.0178695482754,0.0,0.0329893603098,0.39919009986,0.383034397584,0.0325298909179,0.84217680741,0.190998821828,0.153781462973,0.187475229762,0.0,2.02345751876e-05,8.62499919037e-06,0.709218263725,0.0,0.0,0.000251954106469,0.00378023748811,0.675204935855,0.307336032337,0.244925828829,0.30381035954,0.296347274903,0.21002972624,0.667203639554,0.650034015264,0.150392772698,0.148075653692,0.145630652892,0.149048466247,0.212880252727,0.208857972589,0.100008170223,0.116478616508,0.110780085599,0.148075653692,0.148075653692,0.110780085599,0.112861857264,0.117689754984,0.167517464597,0.179625606472,0.221704679174,0.160564216227,0.102137073692,0.0998333916753,0.150493803874,0.166662970287,0.188455898719,0.166857778415,0.420411076393,0.398650351032,0.198601497074,0.252662417805,0.201208648228,0.408316374341,0.376610801278,0.402477125571,0.441747769406,0.000478639975946,0.000287298808256,0.0,0.000301081626329,0.000479917956569,0.0178695482754,0.389162953841,0.37147993479,0.0,0.15056699933,0.169865437748,0.150210009799,0.805086741678,0.363964060208,0.495453379223,0.780808183869,0.686794660561,0.0679572426917,0.0350348009331,0.744774433515,0.605615179653,0.880992770276,0.631141371394,0.678053824735,0.459248677787,0.196606386529,0.595416657487,0.685016469661,0.71261774702,0.00982241436606,0.050501874564,0.368862366395,0.145178501931,0.0618825691644,0.746824272284,0.495800144014,0.216276904341,0.160908249081,0.295601144158,0.271446119021,0.220245113287,0.0307395815471,0.0838843324186,0.12277684639,0.130716770488,0.148075653692,0.148075653692,0.166657536494,0.288598736119,0.209931779149,0.208857972589,0.233554245244,0.0654236650603,0.140507978547,0.123911386086,0.115273893121,0.222315529183,0.236161944108,0.353927069085,0.598832691047,0.145196905161,0.117126991883,0.120241381247,0.0984399361401,0.439762023594,0.316088851213,0.0,1.15941425748e-05,1.02975125748e-05,0.00027827261444,4.19853858445e-05,0.114058649863,0.147202435213,0.147202435213,0.147202435213,0.000299475111534,0.0,0.23324185198,0.694123403138,0.677904032695,0.411119007283,0.573660707676,0.310827356685,0.301821402436,0.0199965193948,0.563500179296,0.164255810997,0.0101539900374,0.0,0.0,0.0464138549965,0.493573309415,0.534074162186,0.347498406989,0.144893364763,0.175801354387,0.0258929460801,0.0,0.496682678628,0.367916793346,0.344018704285,0.256964955068,0.0,0.0,0.0269510985459,0.291397656336,0.151010693191,0.151010693191,0.014608344606,0.0103107400438,0.0211237517607,0.0162273650065,0.009967100981,0.0374718522842,0.0403866507529,0.011820615972,0.0632588052438,0.0188516176915,0.00773627982614,0.173315399462,0.0398541661797,0.0364498317014,0.0372706345044,0.0446540473183,0.0410260047436,0.0333112720599,0.00606569736894,0.00821899073604,0.26556860835,0.234769238624,0.26257972549,0.00685902674507,0.0204812679096,0.00471352258179,0.026535597523,0.0126993801978,0.00404668791931,0.00517833609893,0.00530475912653,0.00537925728121,0.014100239148,0.0114068428304,0.00672190365783,0.0313456823646,0.0475562207302,0.0470521688179,0.0453120881029,0.0527394539114,0.0519970274253,0.260672386116,0.0449739033157,0.0344222708115,0.102337668732,0.00891661988306,0.0113511597204,0.0438823826022,0.0286199987438,0.015233324164,0.0221944461985,0.00767018620078,0.0203655775506,0.00747646306216,0.00736378295341,0.00455013400989,0.00473313812068,0.00391922521154,0.251435758635,0.0341498998512,0.0269618773497,0.0318841139065,0.0127465867457,0.0109606597509,0.0260000471022,0.0327019624738,0.0297900587066,0.0411437809179,0.0270540954205,0.00972021529962,0.0284869684387,0.0313129449056,0.0104363081861,0.0152481842381,0.00984600786554,0.00850387570765,0.00847630950723,0.0148330256413,0.0146327656929,0.00937121447797,0.00943127842189,0.00705285547524,0.0125437210731,0.0125645921594,0.0130763327254,0.0148594321637,0.0075515770721,0.0142953857959,0.00673974876683,0.00595879741363,0.00804133581196,0.00794568335293,0.00616008086316,0.0105520249218,0.0109211173805,0.0232869418128,0.0176252196067,0.0174107500621,0.0108665465459,0.0170611503769,0.0103024625961,0.00595879741363,0.0059598307781,0.00828559231014,0.00858309453439,0.00720189088013,0.00635691207813,0.00834825223597,0.0146767576894,0.0125433215949,0.00571464304158,0.00822755870652,0.0130971909998,0.277146877139,0.00595879741363,0.0113401539638,0.0209056917244,0.0143190636936,0.0125471653944,0.0186936402071,0.0111628200193,0.0104421834938,0.0104421834938,0.0135794514913,0.0114780340865,0.0176263361919,0.0118106808203,0.0123366011561,0.0,0.00839675002088,0.0149809133201,0.00652886762405,0.000405581550732,0.000619017612046,0.0434940913895,0.012783539256,0.0173308089512,0.019940317944,0.0111005178722,0.0209892635461,0.0226089150256,0.0249378012106,0.012506022485,0.0216785641163,0.0129852473566,0.0226740175011,0.0108887563367,0.0145018166677,0.0112745988435,0.0334647309291,0.0311897026371,0.00746120751739,0.00843365597418,0.00941599535836,0.0207455719796,0.0154327282031,0.0234263133907,0.0103755227425,0.0400611048329,0.0316755303653,0.0311131965497,0.0437956222777,0.0455155084768,0.0603302359033,0.0146973885073,0.0,0.0170538759755,0.191886987141,0.110822172907,0.045990868467,0.0463578607924,0.0426284597745,0.00906195939465,0.0235667165725,0.0340679767299,0.0453230306666,0.0479457118904,0.0252184052237,0.0296697692449,0.0401362115115,0.0379929108085,0.0311745316085,0.0177873825722,0.0162349239413,0.0146784939001,0.0107939153784,0.0113640232701,0.0111620781501,0.00901644535601,0.0312004161524,0.0231523707585,0.0395762567954,0.0373536205755,0.00624816556627,0.00684817152566,0.03179709115,0.271438176251,0.0239849614914,0.243409088466,0.00987720277974,0.01077865859,0.277011062945,0.286591284183,0.0250166420672,0.0242254520339,0.0148761842587,0.0153333961055,0.26556860835,0.0156127953666,0.00786778936463,0.0147878512326,0.00949029330432,0.117823381611,0.00629210797451,0.0330999881164,0.0306545279501,0.0271923846259,0.00533493298307,0.00464654344967,0.0140936557164,0.0115138187813,0.0128392977304,0.0271968838377,0.00837702831801,0.00838153729589,0.0125947560287,0.0355415515747,0.0125651915487,0.00638405804184,0.00666493672403,0.0568643482834,0.0506607919485,0.0280730393305,0.0515336152891,0.0488469595587,0.0138003006322,0.0436676050458,0.0442289171971,0.0426412935646,0.0375532185533,0.0354442630126,0.0328277897472,0.0103330784408,0.00526883156037,0.00482722590474,0.00732612650899,0.0101875714643,0.0326514282045,0.0243104722458,0.0285051084434,0.0749873167527,0.028245579248,0.0137799858497,0.0261468898286,0.0262094840163,0.0134562685063,0.0161510500221,0.00834645059002,0.0111273702883,0.0102695590273,0.0108700843653,0.00656948486081,0.00525327661836,0.00467153513014,0.0285658109702,0.0236721042298,0.0334434509635,0.157213150498,0.0326743257672,0.0330154941773,0.0375240052306,0.0386912032289,0.0472766851969,0.0495237550128,0.0134345885482,0.00595015479509,0.00665514558307,0.0479814061802,0.04769536615,0.0325605304347,0.0453679374368,0.0434940652254,0.04094273133,0.0397247698102,0.025860261861,0.024032470508,0.0470968152922,0.0161298994066,0.0537483401002,0.0461112279599,0.0603504649601,0.0210126506284,0.0323313778859,0.0101242401781,0.0115955528873,0.0142908523887,0.0162459610093,0.020255830408,0.0197798620966,0.0103024625961,0.0466475441109,0.0486875797718,0.114859565341,0.0376512433875,0.0108121560881,0.0355234656729,0.0453622554584,0.0141405112727,0.0283700352496,0.0370139131517,0.0413717907389,0.0255288551055,0.0594965444751,0.0465469751924,0.00655765487185,0.0304100454557,0.0460595717235,0.0408524607451,0.0424901878187,0.0294845680966,0.0374173639906,0.0304205604269,0.0405767864585,0.0469916014555,0.00906848366234,0.0082366668812,0.0458023811925,0.00430535631218,0.0112724966155,0.0104179429248,0.00998576529988,0.380076001109,0.362455306742,0.356218847827,0.00490597438781,0.0244797561913,0.0234156949152,0.0409891170715,0.0432919895785,0.0390419818318,0.019294756089,0.0158103510765,0.0513985426657,0.044630100181,0.0292718361996,0.00101586966005,0.0157229827737,0.00859398379751,0.0103024625961,0.0097309618154,0.0282431219698,0.0227118645741,0.00941599535836,0.0104426819232,0.0113479789038,0.0174107500621,0.0103024625961,0.0103024625961,0.0174107500621,0.0152108502713,0.0128877326236,0.00902561963065,0.00769412578331,0.0078066844108,0.00800983736033,0.01962766756,0.020934308512,0.0121604070912,0.00916544982475,0.0169423015215,0.017151511072,0.0298931195638,0.0318391820311,0.017206512193,0.0165413977463,0.00853213831378,0.00792723379532,0.0077485506431,0.00873305052786,0.00844702269097,0.165958918621,0.132487801694,0.0324323169961,0.0353310393921,0.0435972887096,0.0427915237525,0.00811238108946,0.00663202617392,0.0441310456654,0.0129533464385,0.0123706896476,0.0128686085322,0.0294734420976,0.0268797132256,0.0128963311116,0.0102559949969,0.00584333763256,0.0520215958196,0.090569800304,0.0,0.0401342190836,0.0373632285102,0.0329829827818,0.0393027650579,0.0462333390062,0.0130105723549,0.0139859316978,0.0219713440166,0.00531646283601,0.0453505372799,0.0404809724664,0.212304520456,0.019295883131,0.0434152422664,0.00466252190709,0.0148109376568,0.0328006129122,0.0254510830557,0.0241832984502,0.0280282588572,0.0316181417669,0.0422196667959,0.0322411218782,0.0135956438189,0.0246878982851,0.0103024625961,0.0103024625961,0.00909721908087,0.00854727298147,0.00818509600542,0.00941599535836,0.00594392724521,0.0181803138906,0.0126004397775,0.0186936402071,0.0150254625251,0.0102180397837,0.0116065872498,0.272183084337,0.11347061434,0.0118754925257,0.0128335673615,0.0186746191236,0.0216785641163,0.0417630448055,0.0124801727519,0.0389650885888,0.0258769343865,0.0273868632978,0.0608766592329,0.25812501596,0.0145018166677,0.0111620781501,0.0111620781501,0.0111620781501,0.0569711615799,0.417179498244,0.0236655306116,0.00595879741363,0.0329600357135,0.00980975264863,0.015101112331,0.0117638791968,0.019215533118,0.00660871576181,0.0491942147329,0.0263751211363,0.0554795249779,0.0421372755501,0.38418472461,0.0293871656212,0.0287566372487,0.0105332772781,0.0117180792167,0.0183554899075,0.0318851532333,0.029708282424,0.0486750526822,0.0356875237832,0.0197180102122,0.0417362474647,0.015366871999,0.0102481090588,0.0336816630441,0.0346622534887,0.0481838958033,0.0483076269516,0.00843365597418,0.00843365597418 +15,0.807179575381,1.0,1.0,0.982693548521,0.999999973509,0.340669455121,0.31040470761,0.351427028507,0.402705751993,0.347439869326,0.653179244093,0.599693296546,0.673329171666,0.00186023138582,0.533349281469,0.648721073001,0.0,0.575457943937,0.609874579634,0.642876763031,0.446107280056,0.55769303308,0.590800288838,0.103789077956,0.180112991971,0.45597732826,0.497059118075,0.450908574016,0.695997120922,0.62607243936,0.476563149147,0.652946509261,0.717081565152,0.638796888776,0.712335776144,0.641788150599,0.622489440437,0.442275678352,0.336581802589,0.0925989142484,0.0,0.0426867431878,0.0445650607437,0.00655014746533,0.00123792959383,0.361694230506,0.463901847182,0.384899011916,0.000162081264261,0.000799231516181,0.552793808724,0.462525292326,0.382201099328,0.0574607082516,0.401273967539,0.252371288979,0.668071825991,0.156282114623,0.391663684947,0.360772181535,0.764072842503,0.727248658434,0.730347793503,0.528074809769,0.645040169992,0.699199544514,0.191922095075,0.100428429967,0.0980822893492,0.184249732987,0.205678867624,0.195013637876,0.232295746706,0.0564322998127,0.590630692463,0.647855804714,0.739844310572,0.0852278254805,0.111534502067,0.101798637296,0.190573962372,0.397047649238,0.089115823591,0.0969730564094,0.345509852944,0.123318508756,0.125370821357,0.0536029887297,0.140569968532,0.118766131215,0.109928469123,0.164052738951,0.11529247379,0.138278127064,0.142863592204,0.127758495184,0.122664647791,0.142616078903,0.126932147503,0.12344012268,0.110706353206,0.0990686927148,0.0956235944748,0.100884863401,0.0957588928594,0.101959997223,0.142863592204,0.1428611989,0.107552858701,0.130815932493,0.205106917115,0.144786106553,0.123840808577,0.0892968282463,0.109042820728,0.154135344922,0.15839148175,0.148343331811,0.354045507296,0.142863592204,0.134592013159,0.146228132053,0.0942793954736,0.0936355784424,0.116194672474,0.106217109618,0.0751425106225,0.0751425106225,0.114240936294,0.118696585943,0.116008567986,0.0999251998102,0.111433517592,0.551687598534,0.349543230012,0.569136658076,0.608376534044,0.603649077753,0.564266447925,0.469660209515,0.10044028462,0.106241073337,0.113842538661,0.0986809615628,0.0,0.0,0.575803072698,0.10034122051,0.111101419205,0.113551810421,0.11937794025,0.13050470181,0.0876243356534,0.099167040931,0.241001061279,0.194356117686,0.199845836889,0.10156323271,0.123566992528,0.164738884434,0.112544050477,0.322786607279,0.300495552376,0.0285356354147,0.00560700798546,0.061367871102,0.0479073829268,0.0474530613273,0.000423748245748,0.747639734576,0.626419766119,0.243371229949,0.268016863101,0.294841864012,0.0399788480773,0.0462405755683,0.0349450798129,0.286551759516,0.0,0.0565211404136,0.0479073829268,0.0277443182451,0.0,0.0,0.0450426727615,0.0126274692581,0.0391576233279,0.0735130014182,0.0916116515683,0.0991387875504,0.195502420423,0.121742751464,0.121537865261,0.215815380154,0.0500066537471,0.0331728635553,0.00125104482476,0.0557461130071,0.26832609762,0.278276810355,2.03359936625e-06,0.0,0.249741523261,0.00191209784007,0.278982215961,0.267708658992,0.510136960354,0.477519855616,0.833010752847,0.814700120445,0.055201908486,0.0550638670527,0.45597732826,0.374773247493,0.642352021437,0.197148625276,0.0955482335559,0.300418703183,0.189590625284,0.294122626491,0.0,0.210761121166,0.67451829349,0.666438301135,0.389093020701,0.176168107989,0.463423063618,0.325346727866,0.379442363871,0.392184830465,0.386995499655,0.335504443094,0.411753964936,0.186490175935,0.204516465372,0.0800338301447,0.11463898207,0.305860197817,0.190175502343,0.386402035889,0.395416377662,0.00637710150954,0.00873385060614,0.0842443824466,0.000181064029921,7.84191175707e-08,0.0,0.436446014441,0.566927698285,0.707015890203,0.673684024214,0.679967703286,0.0105660562442,0.0320789670552,0.0780301601645,0.00818149146002,0.857352680429,0.383289369329,0.0641823093624,0.131449202547,0.450899043414,0.369651404733,0.223755687214,0.211538964323,0.639894422411,0.454831894687,0.694525300521,0.644536024946,0.641287253575,0.183900767868,0.278767066341,0.0550928339155,0.00207469256805,0.122573055408,0.116137637411,0.0594871251548,0.0496846182051,0.0109839890112,0.024321751742,0.0911652664616,0.143298277048,0.698506828518,0.0426730612845,0.0428329521119,0.0,0.635123439913,0.00296641779645,0.00252025427168,0.00125104482476,0.128775379153,0.175649196318,0.0396301492709,0.29717693699,0.0012255082059,6.49033595472e-06,0.0360329279163,0.397480095654,0.189568450081,0.264369571012,0.30703012897,0.298124435628,0.286803108779,0.401091203494,0.431892930143,0.101959997223,0.0,0.0678324093942,0.0,0.0,0.124793624976,0.604488183639,0.000423747742072,0.251459630686,0.692594822073,0.609645557269,0.0,0.0,0.00042686887995,0.0237943088679,0.420313363075,0.80724630786,0.401189942071,0.571939050599,0.0,0.0131054980886,0.0144707580916,0.021592804651,0.0,0.0389174618828,0.372893584091,0.343708046531,0.0384054636255,0.780430260128,0.109652183814,0.0717798158725,0.104465901352,0.0,0.0,0.0,0.641473779096,0.0,0.0,0.000432865076196,0.00648810525911,0.627355069415,0.291717706227,0.224159996835,0.243597146256,0.242256400548,0.200369282872,0.585177325471,0.549783612216,0.101330143295,0.101959997223,0.0995337961443,0.137548579723,0.179441941241,0.123566992528,0.0753254195599,0.0993200029988,0.0956235944748,0.101959997223,0.101959997223,0.0956235944748,0.0905443497161,0.101914999965,0.119431490876,0.123748293491,0.150468020125,0.117568960527,0.108262796571,0.110311538344,0.125641149911,0.119126846009,0.171179025727,0.146041795501,0.392280023701,0.369711581916,0.179388303597,0.220987615215,0.184655323909,0.345037808839,0.288462645315,0.30162673135,0.368355722643,0.0,0.0,0.0,0.0,0.0,0.021592804651,0.352530423378,0.341121683654,0.0,0.089383575817,0.101900692687,0.0890484152829,0.758899243761,0.324645699323,0.414206419519,0.725020749272,0.611236255571,0.0158886688761,0.00721463060694,0.666091890874,0.557919496139,0.855094580325,0.59658951879,0.629622904797,0.418768525608,0.164717113741,0.518234564855,0.55435288851,0.641797322042,0.0132227190043,0.0469523152101,0.325968751175,0.0935855082978,0.0530259873404,0.694843639124,0.422748189005,0.206168434677,0.160849918922,0.259189596827,0.24439660867,0.208652942899,0.0197978142912,0.0789834612216,0.114256358135,0.13923960812,0.101959997223,0.101959997223,0.119125457984,0.230753042928,0.124378230999,0.123566992528,0.143609730217,0.0495791597062,0.106312269937,0.116194672474,0.0921357366576,0.144037470213,0.221793801769,0.303244430095,0.54519219606,0.119485757993,0.100508653738,0.11513881761,0.111101419205,0.411148164942,0.275475317958,0.0,0.0,0.0,0.000438080845084,0.0,0.0876243356534,0.121537865261,0.121537865261,0.121537865261,0.000438439575432,0.0,0.142863592204,0.666455944744,0.578854831067,0.37948350269,0.488549440265,0.27447800141,0.292795501017,0.0231331641103,0.489553427546,0.173510429289,0.0,0.0,0.0,0.0429765757644,0.383925472994,0.456339552754,0.290996665483,0.137584759465,0.156835533566,0.00125104482476,0.0,0.408034423926,0.32941787646,0.246131334675,0.15439681305,0.0,0.0,0.0319355139499,0.245652471899,0.10156323271,0.10156323271,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.000504450756231,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.00214775985188,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,4.35662166938e-06,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.00450609342414,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0037164558184,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.00531048879569,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.00581302660657,0.0,0.0,0.0,0.00515875330302,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.000518818799843,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.00493175777251,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.00340542039739,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0044701374057,0.0,0.0,0.0,0.0,0.00418276203375,0.00437689045596,0.0,0.0,0.0,0.0 +16,0.678732395172,1.0,1.0,0.979956163639,0.999999973509,0.289948284603,0.269114468117,0.304981820721,0.351894240393,0.291437501821,0.62297915933,0.578715483978,0.576157405219,0.000271838644647,0.476508327741,0.566465270224,0.0,0.539896359388,0.565432845661,0.626094815104,0.415993710803,0.526283197455,0.556980127596,0.0773256408455,0.164490592494,0.423366971688,0.467073249988,0.413454642469,0.62615281815,0.571108617706,0.411300538931,0.56275149928,0.66160480675,0.577543350131,0.668909167068,0.572629963221,0.552947568839,0.384274966021,0.279931712336,0.0707739219414,0.0,0.0483117172775,0.0471074596737,0.0129541282735,0.00562014162836,0.331186062003,0.43663845342,0.360990662179,0.000171339764029,0.00014127066683,0.4920046544,0.409788445324,0.357763052015,0.0448478223859,0.373983269468,0.229668623094,0.632410901671,0.137275255021,0.375566686748,0.354423656651,0.69854070832,0.683644720282,0.659099336196,0.499683208311,0.601920895528,0.650373044027,0.184955686409,0.0882931207155,0.0856810817537,0.176217067557,0.191489457734,0.166186906578,0.187876264333,0.0405964237365,0.481790474016,0.557479677621,0.65704294648,0.0677418652881,0.103675874325,0.0822127525334,0.171477081977,0.379240690187,0.0763687469796,0.0849174113091,0.310842442585,0.064620573392,0.0673224926869,0.0349074288015,0.0746482445755,0.0658676656475,0.0611588495912,0.127647849824,0.065287504391,0.0964916851438,0.0769757483559,0.0812019104223,0.0663114941952,0.076520577916,0.113155944743,0.111074864735,0.119729289834,0.0902610287958,0.0861529558974,0.0813358268012,0.0860290789783,0.080351452406,0.0769757483559,0.0769751534211,0.0894873158654,0.0739809048143,0.156398146182,0.0882873034858,0.0648831819294,0.0540859828323,0.0810344082037,0.0959820135604,0.137453188455,0.0781402286039,0.314890996798,0.0769757483559,0.0706857193036,0.14049697444,0.081114901038,0.0787847025869,0.110682731867,0.0982556110115,0.0601414379037,0.0601414379037,0.108277393144,0.0696786105529,0.110453609667,0.087732857291,0.0770894390991,0.476650415896,0.293883658038,0.499577990249,0.548698809156,0.541932663989,0.512506200126,0.439261871006,0.0883063189076,0.103579775383,0.113312445223,0.0863476040991,0.0,0.0,0.480154930138,0.0881960274298,0.116377049117,0.107428977086,0.125264895305,0.117693838461,0.0741263463419,0.086888772961,0.209586848522,0.178843670306,0.167415098158,0.0849281639409,0.0647091201923,0.155168738438,0.100874096459,0.286692734881,0.256073417659,0.0309558619087,0.00911285944803,0.0451483125138,0.0431628778571,0.0435475789451,0.0159600512551,0.635902540982,0.514556872252,0.211628397285,0.222306842484,0.247083175146,0.0441847397645,0.0441862398609,0.0361766515406,0.246892631012,0.0,0.0451042450405,0.0431628778571,0.0366736450885,0.0,0.0,0.036397971678,0.00849088804168,0.0229143736602,0.0454170866128,0.0585574686695,0.0693363904971,0.13930226006,0.0817633576004,0.109739333002,0.195310445017,0.0386465486211,0.0201395879866,0.0252578011029,0.0441840452229,0.28450174559,0.28917544943,0.000168041389293,0.0,0.228098473454,0.000334808502111,0.22376880088,0.215996746699,0.465349110141,0.432184753531,0.789812162138,0.767033825741,0.0362296058057,0.0357994110697,0.423366971688,0.335788435237,0.596117529939,0.155951309266,0.067378531143,0.260519034505,0.207357819509,0.257019837046,0.0,0.199794241337,0.651813371887,0.645054823314,0.346584326209,0.153212031178,0.404012998003,0.2855278865,0.322068075227,0.331285124103,0.332637602955,0.301528147049,0.354586599385,0.203424605087,0.146516214426,0.103929522732,0.142171088271,0.275465339367,0.193924308545,0.376167197544,0.332305276027,0.0333351629494,0.0295664773334,0.111808091472,0.000252277900083,3.2269791284e-07,0.0,0.359536222303,0.582783774431,0.684386932403,0.63386154515,0.613389783883,0.0139632827254,0.043535825319,0.0939558360713,0.00789604364048,0.836165769823,0.298399784503,0.0791204799778,0.105662632814,0.413272236959,0.318594031908,0.226993717742,0.155315117674,0.562898278248,0.395770621481,0.640599172939,0.577290599299,0.576335846479,0.182543372542,0.26172787682,0.0690207230073,0.00080201107298,0.0997871936979,0.0945403334493,0.0454176871218,0.0368688749711,0.0253954293715,0.033426547186,0.0768703574762,0.0777087076063,0.644583859705,0.0490057217485,0.0491516803799,0.0,0.616994891236,0.0217536937389,0.0226651018808,0.0252578011029,0.118159833655,0.166622983148,0.0459564030617,0.245629234339,0.0164504875254,2.67080009454e-05,0.0722793465545,0.353487721987,0.169998417046,0.190058007468,0.273351669944,0.256556427935,0.235676093321,0.341659604713,0.372245186141,0.080351452406,0.0,0.089612939427,0.0,1.35261912174e-06,0.0810942504821,0.590059350509,0.015960082456,0.220459118328,0.647700641558,0.593844852281,0.0,0.0,0.0159125618954,0.0332290974482,0.406909798036,0.754512052436,0.373180981868,0.544538594905,0.0,0.0164226785974,0.0211675774465,0.0304963624944,0.0,0.0452453902524,0.310882945177,0.297621196739,0.0447295555993,0.710934646911,0.060708009169,0.0360454815871,0.0568867386853,0.0,0.0,0.0,0.574955838671,0.0,0.0,0.000855628747248,0.0128212091534,0.599833286333,0.274118481719,0.225445785017,0.190442364261,0.191213876326,0.192366985303,0.527383634798,0.478844291466,0.0847475334842,0.080351452406,0.0833554711003,0.0983777372412,0.121238128638,0.0647091201923,0.0603450767084,0.0870590708867,0.0861529558974,0.080351452406,0.080351452406,0.0861529558974,0.0785164867053,0.0907188275869,0.064604582639,0.066883673181,0.100062724497,0.0725099359583,0.110097002297,0.114297295563,0.11407613873,0.0644437429397,0.156771025186,0.135637800713,0.353658385563,0.332837723286,0.162885137109,0.196880016933,0.165731004826,0.304603016647,0.253139350402,0.263706575632,0.314178501528,0.0,0.0,0.0,0.0,0.0,0.0304963624944,0.333639092648,0.336556039753,0.0,0.048652081173,0.0605055725328,0.0479925127463,0.727840819444,0.284005163648,0.356972826383,0.651429968293,0.544517343351,0.0216910445985,0.00344287234189,0.609110936646,0.517916344718,0.849546799092,0.576919089852,0.603469752064,0.392183124344,0.164835078786,0.482191028548,0.44899596879,0.572562108358,0.0196097539576,0.0442660837981,0.304893671848,0.0558563362424,0.0439735906848,0.637350413941,0.367456385751,0.192352365826,0.157559903693,0.233039161408,0.223302244681,0.196621449088,0.0289488415551,0.0536526923294,0.108296379715,0.141467168583,0.080351452406,0.080351452406,0.0644509296573,0.188211987852,0.0651830251013,0.0647091201923,0.078233875402,0.030785419391,0.0823148413639,0.110682731867,0.0801060791648,0.0766746826535,0.203039003031,0.271106726855,0.523198265094,0.107584391608,0.0884863345559,0.110974109594,0.116377049117,0.381112908066,0.240499158859,0.0,0.0,0.0,0.015918656696,0.0,0.0741263463419,0.109739333002,0.109739333002,0.109739333002,0.0170117582044,0.0,0.0769757483559,0.66373640565,0.494021116685,0.338495180163,0.414097247731,0.245280574471,0.306191237816,0.0332824845505,0.437154988395,0.207048579513,7.77709987255e-08,0.0,0.0,0.0584625404023,0.3182883797,0.401459736481,0.261506165449,0.156150029834,0.164652237748,0.0252578011029,0.0,0.346526727408,0.316029541361,0.185661423927,0.10572530262,0.0,0.0,0.0395804109985,0.223735258108,0.0849281639409,0.0849281639409,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +17,0.56512594223,1.0,1.0,0.977861883598,0.999999973509,0.250140539754,0.221083295438,0.271767136168,0.298689953427,0.234187671583,0.593496130291,0.560014797301,0.498330852131,0.000447657964591,0.440923343609,0.495531857796,0.0,0.515682137597,0.540150740907,0.650143460631,0.421592934668,0.532454166398,0.536092267978,0.0620159496199,0.15782952126,0.409200044574,0.455381545601,0.389996540666,0.554046344881,0.513676988609,0.382632286587,0.504661541858,0.612095455647,0.52533351302,0.62285588674,0.512478583379,0.495951338442,0.349429999003,0.251252685423,0.0343664035189,0.0,0.0601887123268,0.0534060754462,0.0400576073066,0.0130561757596,0.302709192426,0.426113038729,0.34360816894,0.000215424181714,0.000232641459979,0.450747072042,0.368706993593,0.337224214028,0.0279524135946,0.366712212286,0.231088993176,0.592811718392,0.135384493922,0.357169476513,0.341119737656,0.626253919071,0.620934107306,0.590774611196,0.49017328731,0.569080628503,0.582120585524,0.165956587625,0.0836256569717,0.0809017838946,0.158236355134,0.166750648631,0.132493760885,0.147132776496,0.035659561266,0.419635417998,0.489474440859,0.581475751563,0.0596039629003,0.0982909863588,0.0715475050469,0.153965145012,0.358906183842,0.0665973913543,0.0779768600984,0.278222464409,0.0405707473201,0.0421528160751,0.000500786510225,0.0412922567686,0.0373030965945,0.0336728973097,0.0990513070808,0.0365250945979,0.0690904498307,0.0401087001101,0.0544444398507,0.0351220378546,0.0400667684308,0.101135104085,0.0991891083642,0.126076316221,0.0848606919831,0.0805533157114,0.0705082558924,0.0802949977304,0.0684555602958,0.0401087001101,0.0401092005319,0.0789753144015,0.0426277741687,0.11334231524,0.0534190656919,0.040610245471,0.021129046238,0.0704949125494,0.0591784624266,0.124284682651,0.0423265482299,0.289814342076,0.0401087001101,0.0413779758445,0.129095463974,0.0701772582891,0.0688583377284,0.106273155055,0.0934123370165,0.0542686634484,0.0542686634484,0.103798180482,0.0496777433057,0.106037399644,0.0830414059401,0.063189022317,0.437013727599,0.226240479645,0.448436105244,0.491786707841,0.499054226731,0.462839976859,0.429137753093,0.0836394202427,0.104322642284,0.113375607547,0.0815968433093,0.0,0.0,0.42029491371,0.0835244066493,0.121652632689,0.102925202323,0.128711763553,0.105084611086,0.0633988932499,0.082161182247,0.174758800462,0.152026802386,0.138530512974,0.0760582778799,0.0406174647703,0.137902917081,0.088534516375,0.253929171935,0.208604542391,0.0277473612926,0.00516660845751,0.0264210007321,0.0352553608272,0.034840948111,0.029578478286,0.542401574896,0.447030372015,0.178342042596,0.188144515112,0.206237229776,0.0515733547997,0.0366005910654,0.0307816331456,0.199709769237,0.0,0.0325762016241,0.0352553608272,0.0874489635999,0.0,0.0,0.00735297243145,0.00132148772058,0.0101070429546,0.0250668599416,0.036995182041,0.0361551619297,0.0910279612795,0.0500258402458,0.0979407741132,0.178775483901,0.0146444421794,0.00154481519708,0.0233523669563,0.0273511492719,0.261177955571,0.26615101513,0.000298560401525,0.0,0.19663726233,0.000687220398755,0.154536623064,0.151093729864,0.433748181361,0.395347146664,0.743037174646,0.712412452358,0.00165712881831,0.00243886889458,0.409200044574,0.323392971148,0.546257598363,0.106819079224,0.0237066184151,0.225139324972,0.208266037815,0.214756767521,3.35643812209e-09,0.176409052763,0.605431718411,0.584614299249,0.316616758129,0.153192935096,0.359563423207,0.2547655081,0.266370030729,0.275183067097,0.276886819097,0.267461152101,0.302424388296,0.204137121125,0.0984226503084,0.126490572,0.168620258423,0.240309959904,0.195470008485,0.364245377459,0.307740279067,0.0812825202462,0.0562191295749,0.167758926828,0.000569741796242,6.42456878747e-07,0.0,0.318873026615,0.527229552354,0.619511327192,0.596320084067,0.570620390389,0.0201467500121,0.064127092841,0.125261992289,0.0126135815453,0.81513206698,0.248635718852,0.105040816056,0.0846859699309,0.392174754045,0.303396412999,0.202424174383,0.124798939444,0.515784500271,0.342358571771,0.589313047232,0.518783912907,0.520419614756,0.194042716598,0.258225489973,0.102365186821,0.00122618485687,0.0516934074793,0.0473576576307,0.00748603349414,0.000558467698801,0.00944674035447,0.0460332034917,0.0666147865066,0.0408441080685,0.592486955207,0.062696946168,0.0628124126402,0.0,0.636250453593,0.00316196952641,0.00841343322121,0.0233523669563,0.11810648779,0.148344750949,0.0608037709191,0.204098535719,0.0272437522619,5.31727607841e-05,0.0993033360441,0.321382518172,0.149735864144,0.136377239037,0.235244786725,0.213625972762,0.193140502789,0.278391178871,0.345477335916,0.0684555602958,0.00254688340959,0.134039204354,0.0,0.0545518732095,0.0470690964756,0.596889741922,0.0295785290503,0.186591949628,0.595761076973,0.616585792559,0.0,0.0,0.0294932525572,0.103511678945,0.389896745137,0.678347886941,0.384354782778,0.562619759523,0.0,0.0281370007393,0.0445557613826,0.0399564053567,0.0,0.0603568081516,0.267970094661,0.246079860974,0.0600031293328,0.638636942394,0.0367561354084,0.0215631040663,0.0360263674643,0.0,0.0,0.0,0.51746250992,0.0,0.0,0.00264206712163,0.0395595557584,0.619316969146,0.27773953013,0.234206027302,0.145793402976,0.148675551447,0.171963839451,0.493984627314,0.44212595578,0.0758465847637,0.0684555602958,0.0742151321878,0.0515646355349,0.0673911470935,0.0406174647703,0.0544810210225,0.0823387714568,0.0805533157114,0.0684555602958,0.0684555602958,0.0805533157114,0.0696608677582,0.0869626994665,0.0343239066967,0.035389616126,0.0651647656833,0.0433668340679,0.11359400061,0.118836243052,0.101723710053,0.034248697581,0.137080918161,0.120531651,0.329658539202,0.304043483891,0.138709689658,0.168304801911,0.148226159848,0.246301786878,0.205885190756,0.215857419307,0.258956077367,0.0,0.0,0.0,0.0,0.0,0.0399564053567,0.304438446184,0.315989023457,0.044380382291,0.0260838078372,0.0345089331738,0.0256149076185,0.696852148859,0.25342342933,0.305143230913,0.578565455394,0.491029653427,0.0334930080393,0.00543469511135,0.568552684627,0.496220379138,0.871983079852,0.560303877304,0.626740949915,0.399735725351,0.168281969485,0.46030471486,0.387315821733,0.512333182794,0.0442341244906,0.0403661969079,0.290346715374,0.0337750662717,0.0297210751345,0.572519061091,0.316468199889,0.167829578111,0.145609295392,0.201184005581,0.192664682286,0.172866162248,0.028177738683,0.0237969125193,0.103817716724,0.137463714808,0.0684555602958,0.0684555602958,0.0342568215464,0.132969971303,0.040638648246,0.0406174647703,0.0413710303719,0.000144155996902,0.0727158876247,0.106273155055,0.0717150238174,0.0395705749024,0.181755007092,0.248428633079,0.529760329923,0.0958506453184,0.0838831963408,0.107894343326,0.121652632689,0.371793556984,0.203385497338,0.0,0.000562391767743,0.000490091161136,0.0295115728542,0.0,0.0633988932499,0.0979407741132,0.0979407741132,0.0979407741132,0.031269929347,0.0,0.0401087001101,0.673254854599,0.444926702166,0.329530146498,0.376713248344,0.243154641711,0.298928543798,0.0426666830931,0.388596619659,0.253723961355,0.00235621716112,0.0,0.0,0.0816238379264,0.261797041411,0.343071141273,0.244858459016,0.191857152727,0.187890578604,0.0233523669563,0.0,0.282886698053,0.327258082303,0.126084954512,0.0655508395736,0.0,0.0,0.051085943864,0.199312437078,0.0760582778799,0.0760582778799,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +18,0.388604013816,0.756339645386,0.823128318787,0.722560135227,0.997124976582,0.293053598268,0.254647738378,0.317277496135,0.323540238652,0.270183739609,0.361711750667,0.369543047656,0.316514885463,0.00260325827115,0.432413204033,0.288003261903,0.0,0.335899640951,0.327222965702,0.478690874357,0.341912421106,0.387092726519,0.459488514498,0.0454443994108,0.16671062316,0.403139199691,0.439793001508,0.354639780748,0.3196833431,0.476275154737,0.317064454847,0.331209907991,0.441308635016,0.239062136067,0.325257774859,0.270659042495,0.256300489054,0.407042444622,0.302497366169,0.0119557003229,0.0,0.126926325917,0.105543278859,0.0983662300754,0.028824364656,0.351735279518,0.42420381745,0.37355357655,0.000629371294446,0.00109293970744,0.199434074763,0.346001188003,0.371501070136,0.0407629849434,0.336103482203,0.270438670379,0.35207815279,0.200351826422,0.280609063616,0.228935240481,0.291401293266,0.268142493269,0.247428814232,0.469472468323,0.325150323561,0.304791055927,0.156202651042,0.0782125787628,0.0753915640401,0.163766778252,0.164816910329,0.150735655584,0.180363266351,0.0315654960304,0.275837040188,0.259715155898,0.301712257039,0.0507567371333,0.0885988414228,0.0544465350893,0.122306619997,0.236541280952,0.0584781882163,0.0704482017299,0.15531251121,0.0388871826286,0.0389997788576,0.000315103949977,0.0393701318721,0.0395218615281,0.0419162069024,0.0595066207258,0.0403945947984,0.0493351472321,0.0295456670138,0.0415036661649,0.0189759691941,0.0303337979819,0.0829944898368,0.0857839807264,0.105712553819,0.0781205721406,0.0744531668624,0.0588386481699,0.074051625108,0.0556478405151,0.0295456670138,0.029546312438,0.0534590110472,0.0327946686345,0.0570400263107,0.0341195007437,0.0387447897658,0.0145002941156,0.0673079028764,0.0399200896929,0.0784830459202,0.0393088419,0.284759493511,0.0295456670138,0.0390692744398,0.119380832785,0.0610400310449,0.0539014042377,0.0963516657663,0.0842864244721,0.0478086250552,0.0478086250552,0.0940414270205,0.0471641021305,0.0961316023801,0.0776074915523,0.0595220673366,0.390429389116,0.0871170919558,0.325843362671,0.304057211627,0.410129572807,0.354909385192,0.371437279833,0.0782268328741,0.0932436065122,0.100726955538,0.0761114113996,0.0,5.10703089422e-06,0.24877934932,0.0781077175442,0.10582581167,0.0932265548239,0.109825208788,0.0912212490781,0.0548169517686,0.07669587639,0.207934471323,0.145779613251,0.0751777340012,0.0526939759995,0.038877202955,0.14864735543,0.0823221954525,0.304999894885,0.245333217069,0.0500198234079,0.00978395226611,0.0393121200048,0.05370763917,0.0549002110811,0.090353537617,0.288197047101,0.296284333723,0.233265589547,0.197135608705,0.183269883514,0.113234485817,0.0633830519603,0.0537014197215,0.216345775932,0.0,0.0475853806777,0.05370763917,0.166375922399,0.0,0.0,0.0387810113431,0.0129444585409,0.0156499135594,0.0188136111157,0.0085427785642,0.00786446989058,0.020974803562,0.000191055185614,0.0865291510559,0.128067189388,0.0257189318841,0.00236141977894,0.0444002632024,0.0417431392113,0.223625647757,0.226072652288,0.000682999507795,0.0,0.247984672211,0.000580511267665,0.0520002010108,0.0489532395162,0.343002769866,0.274924413356,0.438324255923,0.421130732144,0.000515440024068,0.000551700096618,0.403139199691,0.373293326281,0.315089425074,0.0297025484173,0.00329987087536,0.279961609208,0.166454220203,0.266512732775,2.3056510987e-08,0.193259750279,0.23434727484,0.228853739687,0.303492262775,0.177491734603,0.369145338333,0.302162564856,0.284351002084,0.301713187608,0.302619908714,0.323188410991,0.320448795842,0.162939788871,0.0277517989874,0.203017004534,0.251999586315,0.238960735238,0.249053943753,0.381622721983,0.276283058978,0.167155628178,0.113973699282,0.282113085089,0.0174815671065,1.89258989946e-06,0.0,0.249702379857,0.172335268011,0.276743667855,0.228457735557,0.273221285843,0.017305981191,0.0811757405162,0.176703676249,0.0134099803362,0.546313519851,0.209613341821,0.142952942543,0.0930785200546,0.329225579882,0.288752487739,0.206662149782,0.127731815911,0.301639904311,0.292546398035,0.327117623137,0.271121078036,0.257147185436,0.226505502661,0.266840164697,0.173493992858,0.00353267683024,0.0978069757656,0.0924688535416,0.0428441828675,0.0342135226781,0.0409087342538,0.0929778600702,0.05427764724,0.0299457471447,0.330727169153,0.13216776672,0.132406326173,0.0,0.549477826595,0.036829753376,0.0387988209807,0.0444002632024,0.124495491644,0.162223727512,0.129401494613,0.26741425411,0.0525465711459,0.000156639664568,0.167920512818,0.343690047099,0.173790457494,0.068858730924,0.261068475255,0.262222973269,0.259811920205,0.194950350953,0.316895827445,0.0556478405151,0.0505379918016,0.238584534689,0.0,0.111400129634,0.000450718776128,0.378981224167,0.0903535212114,0.225342790422,0.33864056206,0.433901358065,0.0,0.0,0.0900888118962,0.191453326972,0.303707357417,0.336923792745,0.334492874657,0.431713332586,0.0,0.0417055196793,0.0629688149335,0.0702463293347,0.0,0.128732859775,0.281543106683,0.238230466594,0.128223667432,0.291637808375,0.0393135449203,0.0286827070346,0.0384098348039,0.000785655698344,0.000580277908885,0.00160549080489,0.262178770875,2.48870438574e-06,0.0,0.0065954120026,0.0979015422358,0.460099421617,0.2905313966,0.254106316238,0.170308753724,0.184223884587,0.174357363296,0.458800861579,0.350801558724,0.0527294768843,0.0556478405151,0.0530030710904,0.0772438642987,0.0883673909196,0.038877202955,0.0480285559499,0.0768797989742,0.0744531668624,0.0556478405151,0.0556478405151,0.0744531668624,0.0619848688834,0.0811785654139,0.0172841025862,0.0195430017196,0.0424452168194,0.025502103537,0.0999595998965,0.103781010064,0.092874320351,0.0171246878325,0.147109111827,0.124421132487,0.363403995705,0.35054962488,0.173257437234,0.217121284342,0.119556774706,0.23626510316,0.212061445052,0.238721622393,0.269074946107,0.000784860082194,0.000495081029966,0.000233882608421,0.000352201975667,0.000437378288093,0.0702463293347,0.283641543321,0.275619583306,0.101421254411,1.11685087154e-06,0.000104781190211,5.17995035975e-07,0.41437713228,0.300490465823,0.32305915558,0.359996289114,0.250505255898,0.0589702569006,0.00783045967697,0.595126497092,0.323585202102,0.741152936087,0.32382014032,0.474556952875,0.354772926849,0.191907347641,0.472192833093,0.235385969323,0.270906445109,0.104619103062,0.068305104098,0.313063877603,0.0186895855204,0.0463052134359,0.271903481797,0.286255665299,0.165357648147,0.133003243765,0.258446579237,0.229878709569,0.174187225651,0.0479939993553,0.0393484062822,0.0940596629185,0.118177291027,0.0556478405151,0.0556478405151,0.0171321518395,0.0515421053614,0.038708600873,0.038877202955,0.0302324059191,0.000115420549716,0.0682357236211,0.0963516657663,0.0632765430871,0.0304423480485,0.164211249333,0.273973325849,0.47969246733,0.0846102462591,0.0784038232132,0.0972514035607,0.10582581167,0.376755771811,0.242561845875,0.0,0.0448624657406,0.0398447391633,0.0901337129504,0.0003499771434,0.0548169517686,0.0865291510559,0.0865291510559,0.0865291510559,0.0897825522696,0.0,0.0295456670138,0.42214629675,0.283381832166,0.31884875699,0.287417923271,0.295062606873,0.25005191394,0.0746214890551,0.335059680217,0.357385433889,0.00462186208193,0.0036601280812,0.0,0.112444114765,0.243866201853,0.314647599591,0.238850587103,0.2879839866,0.251385349256,0.0444002632024,0.0,0.197220259343,0.348041098646,0.0580457104435,0.00735426574301,0.0,0.0,0.100514611535,0.220601695061,0.0526939759995,0.0526939759995,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +19,0.396342692168,0.708251714706,0.879230880737,0.687655828791,0.991329948107,0.430845879262,0.322858056538,0.441677676284,0.481284450712,0.409306230946,0.48475635036,0.499968493352,0.374089026604,0.0272985991664,0.556188815356,0.372276526727,8.68502864794e-06,0.435110948941,0.374238967132,0.47790560399,0.419523578776,0.433034651102,0.610270952834,0.0589649226341,0.20224818232,0.384956902318,0.416767296643,0.343570396443,0.417968290158,0.549141147922,0.293047081785,0.399265505058,0.521272021296,0.366212676843,0.473599591906,0.382651949985,0.364634300305,0.518955244312,0.409349321344,0.0155134801685,0.0,0.0732900410541,0.0514870461029,0.0739586050036,0.0229272456439,0.394339037419,0.416227629059,0.431712637416,0.000325748582882,0.0137709335705,0.309634529466,0.355369321804,0.426212102042,6.51769646293e-05,0.413855360691,0.348024678687,0.479314815378,0.270588639055,0.281806016579,0.237447488879,0.434127956848,0.395954630621,0.378209500901,0.445337699725,0.350162217533,0.300262109359,0.177316506534,0.0929545336088,0.0892561053075,0.171118493147,0.186266492587,0.164934719607,0.190152074682,0.0272416350066,0.298500701993,0.349673113486,0.410379943101,0.056634267899,0.0963485323584,0.0593951789312,0.115328981484,0.238900796599,0.0563400842948,0.0739355536102,0.160868434029,0.0400980532412,0.0407536104325,0.000289877980121,0.0363146444255,0.0369895024635,0.0381256552844,0.0695305148952,0.0362234610489,0.0560124657226,0.0313061702104,0.0458340177269,0.0172813980803,0.0303249091528,0.0790929672274,0.0805909621672,0.132788511298,0.0817411050698,0.0770279445643,0.0649713611083,0.0767178983971,0.0625076131349,0.0313061702104,0.0313069402095,0.0562580512032,0.0351822701477,0.0699635306962,0.0371488619299,0.0399134874551,0.012548194806,0.0810216003065,0.044801971494,0.0807531691532,0.0348454824591,0.280408297123,0.0313061702104,0.0377270811477,0.130487997394,0.05733851748,0.053002804802,0.105170768259,0.0922384834683,0.0530941096407,0.0530941096407,0.102692359146,0.0511854910597,0.104934685689,0.0921612475454,0.0678102790951,0.357932248712,0.103610901524,0.314483847886,0.348654660402,0.359191075746,0.333146224895,0.509046756691,0.0929732211429,0.109728712449,0.113759107412,0.0901998450992,0.0,0.0,0.301257498331,0.0928170576519,0.123767748678,0.101818169549,0.131869304634,0.0852904848246,0.0515987228368,0.0909660948618,0.214934391452,0.175239934392,0.0841777461874,0.054854514469,0.0400373821304,0.149426039743,0.0826360177385,0.374329383984,0.328684925406,0.0102827171245,7.68584949182e-05,0.000279308314518,9.26782634653e-05,0.00111439628539,0.0762265862271,0.357122928003,0.315204090328,0.250491808999,0.204393989795,0.202816685778,0.0636421545383,0.00976857128206,0.00987822167488,0.308180276443,0.00124877853828,0.000373877687965,9.26782634653e-05,0.15974937214,0.0,0.0,0.000203586967927,0.0001140191952,0.0139319439782,4.18224164444e-05,0.000145939274041,0.00698982328624,0.000920545576675,0.0,0.0808481512165,0.124786995636,0.000143821354669,0.00165989984387,0.000332981493762,4.07824255241e-05,0.255173223784,0.260807929556,0.000519993783407,0.000670415010172,0.247727483562,0.00127954381543,0.0625137914226,0.0567875134982,0.353651700988,0.303931921431,0.400751230884,0.384476625458,0.000697233160781,0.00143726630792,0.384956902318,0.461888410771,0.431100886324,0.0338902029548,0.00367009934476,0.284091398263,0.187491074693,0.268899790496,1.63473298307e-08,0.199256352507,0.352697141943,0.301002061197,0.388362335227,0.213416248604,0.515599892528,0.394699901347,0.42202829267,0.448991377846,0.452887670576,0.36140506287,0.47008003574,0.183652686802,0.0146775838732,0.1605577955,0.197222418199,0.133773193278,0.168746746117,0.283857853671,0.353768667333,0.174090312721,0.119804765059,0.280946199673,0.0170911286592,1.59741940148e-06,0.0,0.347777143077,0.218924245877,0.338166333685,0.362401305524,0.418745307413,0.0164314300129,0.0834962479898,0.203774910247,0.0167776325784,0.498918747349,0.206129426401,0.161029948779,0.028364671817,0.398775385843,0.369700093132,0.244885458723,0.151359381505,0.435871170844,0.376660545189,0.450208379106,0.385653011141,0.37691832481,0.230998793621,0.271020553622,0.198816309617,0.0300884810332,0.0847300564499,0.0780705877731,0.0118526007741,0.000268073947837,0.000255568955367,0.0621416997415,0.0524257992439,0.0318266234549,0.452895013871,0.0780504856092,0.0781214108603,0.0,0.55102668454,0.000346816782973,0.000343218264457,0.000332981493762,0.118477320505,0.163966454546,0.0777539462568,0.334204404677,0.0444747637966,0.000132209962281,0.136871474967,0.508835376796,0.0821259622897,0.0654470523457,0.28498371593,0.300073802411,0.313596330506,0.115305084289,0.304932661332,0.0625076131349,0.0500321015863,0.219291358698,0.000142902596654,0.113449434413,0.0,0.360689775238,0.0762265802089,0.235234348502,0.330274828798,0.430804636205,0.0,0.0,0.0760032972809,0.18986710862,0.306544121832,0.412866467329,0.401189942071,0.466828794701,0.0,0.0458744557823,0.0716440217298,0.049416445892,0.0,0.0776721780676,0.396530272992,0.340856041572,0.0775807636574,0.430686173441,0.040678232045,0.0387273763154,0.043276242176,0.00122673430297,0.000915921402919,0.0,0.379053429172,0.0,0.0,0.00489761424154,0.0734910289237,0.484705133693,0.377029597142,0.323137388346,0.183021398838,0.192413111979,0.187523282264,0.404338062623,0.321966934955,0.054800070379,0.0625076131349,0.054380486821,0.0677825184124,0.0738429629321,0.0400373821304,0.0533824451247,0.0912072224551,0.0770279445643,0.0625076131349,0.0625076131349,0.0770279445643,0.0608812934731,0.0975397013622,0.0150370374204,0.0180342348968,0.0471666159253,0.0235340166452,0.118142324504,0.121486189645,0.0875724116054,0.0148255195662,0.142416332509,0.121003982833,0.497894562576,0.44488508959,0.168752734217,0.219384337764,0.112117686048,0.341801145704,0.298510262741,0.312843799436,0.397516105119,0.0023393631671,0.00141313510359,0.00125163911668,0.00158352960635,0.00198470128367,0.049416445892,0.308383286234,0.306010274616,0.102473852091,0.0,0.0,0.0,0.376129061385,0.396379865675,0.473412014184,0.44419157153,0.345035578982,0.0666801387351,0.0108691965692,0.531173108236,0.404707281689,0.706401605046,0.297796399504,0.498169113465,0.441450617011,0.234657036346,0.556324147604,0.276417592171,0.382756932879,0.0747159461157,0.0145839176616,0.325680964544,0.00255147836167,0.00055494461461,0.41100948702,0.362023519055,0.1870294677,0.148673645793,0.264202374225,0.230805005637,0.192292447714,0.00219905410302,0.0348327928911,0.102711922499,0.140554132007,0.0625076131349,0.0625076131349,0.014833278556,0.0643286882493,0.0397787835581,0.0400373821304,0.0321995299746,0.000108176553004,0.0795324917753,0.105170768259,0.0630385964444,0.0251444214118,0.158614483515,0.276466463019,0.471504514672,0.0790782686576,0.0931707043147,0.107444410543,0.123767748678,0.508030607171,0.26027408933,0.0,0.0444174007127,0.0394320356167,0.0760412949715,0.00111537487234,0.0515987228368,0.0808481512165,0.0808481512165,0.0808481512165,0.0759885820958,0.0,0.0313061702104,0.377935073306,0.405859675123,0.415954491752,0.376539938963,0.375521665061,0.273807065696,0.0527094412553,0.262039923576,0.309811414071,0.00474087470655,0.00428802558895,0.0,0.115698160092,0.278481898126,0.377612893523,0.238714726793,0.303959740549,0.267413674745,0.000332981493762,0.0,0.143801862723,0.317161636659,0.0172859639447,0.00016431644132,0.0,0.0,0.0633940846372,0.117538805727,0.054854514469,0.054854514469,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +20,0.387056236682,0.801756000519,0.915171909332,0.771932940118,0.992875297864,0.466908292584,0.339802821109,0.456712331277,0.567249488215,0.486039850762,0.561164120152,0.583166972193,0.428073135027,0.0273519577957,0.702977869675,0.428748060823,1.04965009896e-06,0.472302826449,0.380550938279,0.459891476942,0.415789636687,0.42297958616,0.725488525699,0.0897391428625,0.244947795575,0.376875856684,0.409562832701,0.34400604963,0.477194033051,0.630836106149,0.277396657782,0.473834883175,0.571351123784,0.447405551823,0.547207075204,0.450867795605,0.432610355062,0.664729357991,0.570125060455,0.031552029795,0.0,0.0329098964923,0.0231300141865,0.0432075370606,0.0142930285984,0.379601271131,0.401094835979,0.417331893438,0.000170842933241,0.0137702752363,0.375687347317,0.291337050224,0.411158567018,0.00229972043188,0.470795805091,0.416550429452,0.539508277904,0.367544052882,0.268620153509,0.249595369515,0.520733223974,0.481421215407,0.479472224856,0.436705124441,0.353819782071,0.310294863061,0.156766698231,0.0688009623772,0.0655454357246,0.138219914401,0.165964746265,0.149778984168,0.176777344293,0.0212504296179,0.300991019634,0.400561926996,0.462724116431,0.0349323238347,0.0675445406662,0.0323763718617,0.086350611578,0.236250992876,0.0340465143362,0.0482844881254,0.14467887816,0.0270458823199,0.0271406046183,0.000304494228221,0.0227241202712,0.0254432342643,0.027082028737,0.0514376481803,0.02565125825,0.0369160435884,0.0172604429794,0.027502776374,0.00289825189367,0.0162711948132,0.0503491779593,0.0535981890262,0.117121915842,0.0553487950151,0.0513506098403,0.0393964446359,0.0510431974529,0.0369536257061,0.0172604429794,0.0172610244577,0.0279833116621,0.0201875472524,0.0608724806493,0.0224267145268,0.0268518982819,0.00867547497955,0.0610936786994,0.030009985548,0.0426971882277,0.0210700646964,0.279982891023,0.0172604429794,0.0243557134992,0.106540289031,0.0348979677864,0.0281909178737,0.075224233568,0.0648682238279,0.0337139843322,0.0337139843322,0.0732486962978,0.0357986791514,0.0750360524019,0.0681026755072,0.0489348182472,0.320092335976,0.121986189495,0.297222608886,0.378510698902,0.309531352186,0.302506639133,0.540879809115,0.0688174120039,0.0872316147172,0.0878946508914,0.0663761588845,0.0,0.0,0.346510677511,0.0686799497184,0.102660446632,0.0725518806992,0.112975621355,0.0587068572554,0.030080663329,0.0670506471594,0.190579849394,0.161150792382,0.0593286517554,0.0256987129923,0.0269899587491,0.117479260655,0.0588145381494,0.361665887538,0.353093207943,0.00643295723165,3.53530113151e-06,0.0,0.0,9.40425168858e-05,0.0343910915955,0.371835535727,0.31299429091,0.227039078686,0.200755614964,0.195671419102,0.0285527363092,0.00363594463416,0.00549569593036,0.353369466683,0.000879452389154,0.0,0.0,0.115296015318,0.0,0.0,0.000114656985275,8.69863408097e-07,0.0124404710728,8.92950842067e-06,0.0120023083438,0.0436977679809,0.000627378334288,6.38659950143e-05,0.0546291725473,0.0935127803203,0.0081430170323,0.00187144917462,0.0,1.56390755422e-05,0.324157219199,0.325804583091,0.000352921682593,0.000198101782309,0.214386799357,0.00157014011714,0.0933959658931,0.0923473151927,0.357453872267,0.305256779319,0.475359769877,0.455903320873,0.000819841662536,0.0287315402193,0.376875856684,0.57554366405,0.50242187165,0.0643441588961,0.00732480701373,0.266010048251,0.227956216588,0.241105539661,7.283914629e-09,0.169237699553,0.439953640478,0.36289240513,0.359010843031,0.213119073459,0.654971858035,0.391270152137,0.535025040643,0.564647862423,0.545228341966,0.339224327368,0.583388793588,0.223761714473,0.0181084580076,0.0956972430465,0.122348149061,0.0597482272175,0.0933459592109,0.164469277356,0.413855882714,0.132517496861,0.0919540792554,0.205454537838,0.0110910394214,7.98723311105e-07,0.0,0.391901473462,0.259825446944,0.391331603483,0.465994290303,0.51785207678,0.0159127073854,0.0765559013093,0.191636344755,0.0170292842806,0.562564753072,0.124196676137,0.151698826641,0.0,0.456348942581,0.424651194701,0.324038671482,0.122486369597,0.520371893524,0.367782292144,0.513144463767,0.454883978866,0.451327431521,0.179496607262,0.205484014542,0.173836313623,0.0299046143342,0.0687919378151,0.0634588274427,0.00959082813152,0.000154507777264,0.0,0.0453328325085,0.0289675061771,0.0177521174385,0.515189465099,0.0351237756655,0.0351167991317,0.0,0.526829443111,0.0,0.0,0.0,0.0917349742076,0.130543386606,0.0358309952551,0.330747986868,0.0304147727652,6.61061075989e-05,0.0894665044666,0.560618957541,0.0248365888168,0.0392614663449,0.275068709295,0.290259617803,0.304962208182,0.0530423304626,0.196468099121,0.0369536257061,0.0327734693081,0.155543314771,4.22264696705e-05,0.0861510688841,0.000427886314153,0.368372256679,0.0343911435396,0.216972921889,0.343680884925,0.417213937626,0.0,0.0,0.0342952441825,0.140640888136,0.297900730357,0.433911650141,0.389966469151,0.445715799383,0.0,0.0434501427886,0.0710568133988,0.0243751669134,0.0,0.0359899181096,0.540621597773,0.45379048021,0.0360660102639,0.524373564961,0.0284702969587,0.0286829859641,0.0302493830012,0.000344578019753,0.000254502084273,0.0,0.451156270934,6.33701285077e-07,0.0,0.00286946958437,0.0426847115576,0.460907440847,0.410227585603,0.333042430241,0.17318727158,0.182211971655,0.160884570445,0.346402312995,0.305806354057,0.0257719889649,0.0369536257061,0.026336703947,0.0557278568513,0.0563084691886,0.0269899587491,0.0339677904835,0.0672628987545,0.0513506098403,0.0369536257061,0.0369536257061,0.0513506098403,0.0378449505351,0.0730114295533,0.000599900166941,0.00366920555147,0.033692479968,0.0130999833917,0.0965440157184,0.100153024452,0.0608219214496,0.000383293522231,0.113419237972,0.0916109322925,0.515446785128,0.441655064971,0.141038899835,0.191234493193,0.0833319479936,0.436980915691,0.349716305586,0.339910251465,0.509097007894,0.00132178008611,0.000793334377349,0.000650854478008,0.00084602135752,0.00103343019122,0.0243751669134,0.309164438715,0.325580086963,0.0749487686961,1.47220328521e-06,6.99506899988e-05,6.19013220868e-07,0.433759726303,0.394463042797,0.587260684945,0.500810878518,0.414915561724,0.056184378145,0.0121162509895,0.444682430236,0.426074412322,0.690021061516,0.31831071834,0.471491515208,0.436890216191,0.266885800666,0.644855962638,0.291022551954,0.450859379654,0.0417100361537,0.00652296922543,0.312415097321,0.0,5.34403046815e-05,0.490177873491,0.334425529258,0.166560764886,0.125595226946,0.233368988684,0.199656510088,0.1689608776,0.00128420156326,0.0306691830573,0.0732642902256,0.120941612479,0.0369536257061,0.0369536257061,0.000394623682521,0.0793896167214,0.0266993712393,0.0269899587491,0.0181044038606,0.0204907670363,0.0581971667488,0.075224233568,0.0391945528638,0.0108768709953,0.131375046436,0.269691786026,0.452779564149,0.0531437425502,0.0690344450406,0.078843052507,0.102660446632,0.524381555148,0.246272555722,0.0,0.028770106866,0.0255193558106,0.0343247294589,0.000661656189828,0.030080663329,0.0546291725473,0.0546291725473,0.0546291725473,0.0360914030892,0.0,0.0172604429794,0.42209563106,0.478727875902,0.450604853095,0.44399185447,0.473177476665,0.307156969572,0.0268810735109,0.150412312593,0.213868690046,0.00363442482014,0.00327154874336,0.0,0.10499751949,0.229767069609,0.344330635441,0.185550753046,0.230719520012,0.21041486359,0.0,0.0,0.0739191233487,0.215130954324,0.0060856844581,0.000528399015403,0.0,0.0,0.0296057134355,0.0480789593794,0.0256987129923,0.0256987129923,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +21,0.385508537292,0.952939510345,0.924613761902,0.906615510202,0.996062556903,0.455990692731,0.333699899796,0.462143506562,0.545940274754,0.45817769039,0.619297000755,0.635646268816,0.380870322208,0.0304109569187,0.706918255503,0.39060756943,4.36704542026e-07,0.52601700644,0.443539531372,0.505864525628,0.446507565843,0.46873116875,0.74385985108,0.106258249356,0.234068575731,0.382936587419,0.41680685384,0.343807062281,0.439337933954,0.606148649253,0.225090319699,0.4163913488,0.565101863952,0.417191741155,0.525948001767,0.411028884224,0.393662867226,0.680537273194,0.593723342211,0.0685053356395,0.0,0.00349365464288,0.0025659961183,0.0111244228137,0.00632030664807,0.405279462395,0.410677411165,0.437471312944,2.76083755451e-05,0.015267184807,0.391179372939,0.217492894448,0.432357984625,0.00331908162082,0.505183530495,0.411532573813,0.53791452987,0.371555873369,0.268090018012,0.256488182537,0.509243693055,0.499768785091,0.455156642917,0.445012431166,0.422307539199,0.382132063233,0.171871795259,0.0799187259682,0.0761765444892,0.15252051649,0.178075729584,0.15870113026,0.188911198421,0.0142584784649,0.26518994181,0.369770185862,0.475019705666,0.0400893646212,0.0848860385911,0.0322405495028,0.0790553260312,0.237541519205,0.0397111368497,0.0598820568946,0.132400125985,0.0220296577696,0.0226790512597,7.94561117016e-05,0.0111939563375,0.00814978837759,0.00693889028879,0.0423257332354,0.00368706672858,0.0326402456582,0.0117850421152,0.0252937071806,0.00196239486255,0.0097460267302,0.0475709998655,0.0522887551321,0.1348430544,0.0700814133716,0.0650122959424,0.0450105703698,0.0644979331153,0.0409232423948,0.0117850421152,0.0117860246445,0.0212246373738,0.0167309975908,0.0443779584101,0.0165007632943,0.0218327928048,0.00061950972428,0.0656091329111,0.0220784871562,0.0317185792128,0.00894974540447,0.281340920134,0.0117850421152,0.0161803422911,0.122067780806,0.0378448777185,0.027728717565,0.0952805793875,0.0813523639984,0.0395867448759,0.0395867448759,0.092630023252,0.0341836464337,0.0950280988334,0.0791160551653,0.0523863665428,0.274374140093,0.112560274182,0.283606159237,0.360169154276,0.29099747376,0.298769945411,0.572491081321,0.0799376345797,0.103785868487,0.108436342628,0.0771314489193,0.0,0.0,0.2931993659,0.0797796236421,0.123767748678,0.0916951136007,0.133422063802,0.0571269209399,0.033912473334,0.0779067635764,0.207140282036,0.17256209494,0.0444765989199,0.0195735523971,0.0219774212383,0.126602893564,0.0657000810747,0.381781887338,0.342693292297,7.62957858459e-05,0.0,1.82800513267e-05,0.0,0.0,0.0204462556621,0.361184079873,0.283452547266,0.240443320923,0.205955845205,0.197902053925,0.00241599395689,0.000281727668857,0.000260179873596,0.335960908328,0.000793022843557,5.00994198039e-06,0.0,0.0985590280792,0.0,0.0,0.000171995201458,1.30486888137e-06,0.00679151358318,0.000275987555933,0.0470634651593,0.0515259975381,0.0368459331691,0.0028987143108,0.0539516189052,0.0834859879063,0.00857222857372,0.00114588852299,0.0,4.80835760385e-05,0.32168919748,0.329156401376,0.000221321926441,0.0,0.230442551009,0.00359385651716,0.0967146225256,0.102853759932,0.35062674835,0.29781070819,0.504265722886,0.48584069425,0.000505878510838,0.029273179355,0.382936587419,0.631372001374,0.517386614492,0.0863102665263,0.0483480364543,0.290276495849,0.240319840075,0.263174007266,0.0,0.182218504022,0.472231693669,0.392937619623,0.321233307088,0.21365190664,0.650108348966,0.407634961224,0.509827683188,0.537992621389,0.520894195016,0.363841356055,0.562129017087,0.236328695957,0.0508798964764,0.0572092126611,0.0731915800218,0.0259668634745,0.0538040547521,0.0970147760074,0.427995784995,0.130088248007,0.0901203558952,0.167384183924,0.0103303072345,3.87360063091e-07,0.0,0.395914527596,0.305471500541,0.415886410776,0.497663180564,0.54642159616,0.019312278261,0.0781824099829,0.187244262313,0.0222790430017,0.596151666391,0.0824337615528,0.151612988722,0.0,0.489374825395,0.426752345268,0.31453210849,0.104017411161,0.549169322754,0.329969643871,0.487345159448,0.416902602097,0.41602798452,0.146170750209,0.150856322583,0.168585441904,0.0329824188235,0.0412654926951,0.0383147996019,0.00594029178384,0.000231774769009,0.0,2.87790246106e-05,0.0302699235183,0.0121819994174,0.489578413673,0.00394665334333,0.00389578534334,0.0,0.549854406474,0.0,0.0,0.0,0.0727555850488,0.140953388469,0.00453388101573,0.340150027328,0.0199833474976,3.20597454139e-05,0.0560974776073,0.556459565655,0.0129509562259,0.0370347209498,0.282538632682,0.29857640818,0.315797148041,0.0331037224641,0.128009924622,0.0409232423948,0.0310565054073,0.118757236068,0.0,0.0842344618582,0.0273364775733,0.420025848051,0.0204462989502,0.227954215416,0.41071517676,0.465907416235,0.0,0.0,0.0203837838427,0.124616512384,0.298557796168,0.496504621676,0.414350518717,0.484672209191,0.0,0.0359344685929,0.0552722474249,0.0128037317645,0.0,0.00467587000495,0.540524834387,0.439773956065,0.00476687994509,0.509110228832,0.0170804359965,0.0226725585781,0.0248801192823,0.0,0.0,0.0,0.414166404713,0.0,0.0,0.000733544462673,0.0109818169509,0.505402624986,0.388742631686,0.317040107056,0.181081294913,0.194303387908,0.175420561164,0.294699188363,0.259575534741,0.0198133311023,0.0409232423948,0.021661230521,0.0285193575101,0.0248448514582,0.0219774212383,0.0398784914288,0.0781507437593,0.0650122959424,0.0409232423948,0.0409232423948,0.0650122959424,0.0452650150371,0.0845962847802,0.00039094468242,0.00249003382952,0.0215841554203,0.000364372568393,0.115143576703,0.120520386216,0.0608707275975,0.000242808019515,0.11735853901,0.0952710425317,0.528781577033,0.461182803163,0.150744750834,0.204283374561,0.0763025331615,0.418915476039,0.333854530278,0.329146523951,0.48602354956,0.000116232945203,7.37088774598e-05,0.000128288385585,8.12251803958e-05,5.05182865884e-05,0.0128037317645,0.313932641031,0.328955737164,0.0722807537993,0.00140192264544,0.0041809415903,2.25354808366e-06,0.469892393659,0.41004329053,0.566296681602,0.466446804881,0.381121290081,0.0547935862178,0.017892643644,0.368910719324,0.479930325282,0.725286651246,0.367859180177,0.512985911526,0.465066220099,0.252797328391,0.699575907174,0.25193267623,0.410937350508,0.00979227809651,0.000729178709135,0.325023428853,1.27569235423e-07,2.74262186411e-05,0.469421506038,0.286436337929,0.178776242024,0.143008737462,0.250382399208,0.215560056612,0.181685223067,7.27078725057e-07,0.0170134752829,0.0926509454495,0.140196848977,0.0409232423948,0.0409232423948,0.000242916329605,0.0714433503509,0.0215499933016,0.0219774212383,0.0124664206745,0.0204748615115,0.0652211881943,0.0952805793875,0.0467239408529,0.00145127282513,0.130943089752,0.277261306145,0.468309884423,0.0527390504482,0.0802187140651,0.0988525922035,0.123767748678,0.543782007035,0.256786073789,0.0,0.0272120190361,0.0241226762117,0.0203874716527,0.000128582604821,0.033912473334,0.0539516189052,0.0539516189052,0.0539516189052,0.0218641705318,0.0,0.0117850421152,0.454329242172,0.505893041148,0.430808628739,0.474554689756,0.516320444141,0.316061940462,0.0134946588487,0.0991961161471,0.148697562168,0.00356996690952,0.000735577797972,0.0,0.10015735382,0.163584833986,0.274495064433,0.139161676346,0.19134440996,0.179172607704,0.0,0.0,0.0492692325619,0.150980354729,0.0131515790067,0.0331679513601,0.0,0.0,0.00846608295798,0.0252625184633,0.0195735523971,0.0195735523971,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +22,0.394794966864,1.0,0.937203025818,0.947759685441,0.999058551259,0.450740936959,0.319205410585,0.465949037544,0.538543963459,0.442014536766,0.659601706272,0.667066818351,0.349885659361,0.0461367635044,0.711233842035,0.339480815928,0.0338799639326,0.568376961444,0.495144571118,0.532299149119,0.467066689856,0.501214962709,0.760396194421,0.12424616907,0.242728725695,0.368794703008,0.403409557253,0.336157106933,0.396381365098,0.607291313048,0.20172135584,0.402413135309,0.557018326275,0.371512806528,0.482954060609,0.350694594795,0.334908083448,0.673586570591,0.584726154644,0.0820638073599,0.0,0.00130134407426,0.00102510598158,0.0027235273172,0.000542785762077,0.406757841936,0.397822179244,0.437111454617,8.51808985945e-07,0.0446907456693,0.412118481851,0.17311031778,0.431983800206,0.00484997788407,0.508531563724,0.422608363983,0.502416903381,0.375111613746,0.252741926659,0.242068297452,0.470004619478,0.46922119913,0.427147427808,0.432102818303,0.47649533596,0.439063279995,0.186306189143,0.11161762396,0.106953446646,0.164418539209,0.190209152081,0.166204084364,0.191144207227,0.0104480006842,0.255266471269,0.363365276795,0.490385172092,0.0629029515085,0.117489400343,0.0519734490098,0.0789481461331,0.223304214858,0.0510555737187,0.0847320438012,0.129621200123,0.0336502683956,0.0354835825062,1.0971605997e-05,0.0151366755704,0.00922818574619,0.00321407748951,0.0538802234315,0.00232908654136,0.0503096566993,0.0260246599156,0.0432905031681,0.00407743724834,0.0203613296397,0.0539578973668,0.0584398329222,0.162286022928,0.0967386964622,0.0899412013338,0.0696317891483,0.0894189260616,0.0654815857931,0.0260246599156,0.0260259082881,0.0333653482261,0.0323088444423,0.0477619067556,0.0262195570006,0.0335481650672,0.000120908678223,0.0855830431558,0.0358425066838,0.038311303821,0.0125980223685,0.270220923529,0.0260246599156,0.0242858375983,0.141487737069,0.0456598254929,0.0378840950416,0.130529826895,0.113256293862,0.0613486799493,0.0613486799493,0.127237388601,0.0500797804389,0.130216203413,0.110617191733,0.0745713095082,0.253214506768,0.107490454418,0.265951398826,0.319627988184,0.276577893867,0.269843883958,0.594169714047,0.111641191261,0.139739428229,0.145655359632,0.108143619705,0.0,0.0,0.280920509471,0.111444249708,0.163282287077,0.126076072945,0.168877851715,0.0623693682055,0.042143544459,0.1091099558,0.217424891597,0.181656970315,0.0469654476806,0.0322667138221,0.0336564592315,0.136152322221,0.0753832421972,0.387382356384,0.327710174612,0.00015982646166,6.04813852231e-05,0.000453680241491,0.0,9.40425168858e-05,0.0108279891091,0.367902248375,0.2771676844,0.246115570884,0.198554123123,0.193584233338,0.000369458676812,0.000230883580621,0.000327100594927,0.321341246096,0.00123773271491,0.000310884200933,0.0,0.0913551486972,0.0,0.0,0.00033180393198,0.000364298283965,0.000651827384554,0.0133837184997,0.0663276560287,0.0642195331444,0.0607294365648,0.0330928976424,0.0600621664054,0.0839068214485,0.0107972391669,0.000511345542738,9.52230537188e-05,0.000205276192881,0.331541664697,0.327235137397,1.36255152954e-05,0.0,0.23924582203,0.00712327863221,0.0982125305919,0.107585656079,0.344881857877,0.291056508886,0.533236700952,0.513561961011,0.000438143756614,0.0351203023812,0.368794703008,0.629628753806,0.495410330654,0.09655421671,0.0635458589314,0.292719525785,0.23789335712,0.27223412112,0.0,0.193806073275,0.455147648626,0.371858230532,0.299267990912,0.216918622874,0.648229639982,0.413559074773,0.501307266245,0.528894303019,0.509321018643,0.370491992312,0.555578880356,0.234361008952,0.0720070986943,0.0331674595859,0.0428793254348,0.0136261318965,0.0305620644,0.0570982452688,0.44766226237,0.136581752057,0.106238230329,0.133661115504,0.0120315381482,1.71819940321e-07,0.0,0.410864348457,0.300714877806,0.398472718277,0.509293018374,0.576474220381,0.0281335885735,0.0972967448577,0.199138447706,0.0328316708881,0.637026358888,0.052034000974,0.163410676964,8.19618305613e-05,0.47994372256,0.443681615892,0.314554690834,0.0883547562343,0.576133180138,0.298468969841,0.445233632925,0.358923180514,0.36239825284,0.121888890626,0.120890586515,0.176125095694,0.0493934312281,0.020455485227,0.0194253899243,0.0033380619065,0.000437470403774,0.000393452816126,0.0,0.0396345099386,0.026403276205,0.448756453222,0.00156401348108,0.0015563518705,0.0,0.547888137409,0.000524964291445,0.000413189844413,9.52230537188e-05,0.0622482842145,0.150896723744,0.00165938229698,0.340401746071,0.0113603576753,1.42206284762e-05,0.0313173440562,0.559681167032,0.00668965108289,0.0374069251038,0.277927879402,0.296014558021,0.316613273335,0.0301865100492,0.0891092273987,0.0654815857931,0.0362406477441,0.0935155884467,0.0,0.0881127605631,0.0548358179362,0.449357782307,0.0108279552048,0.229616243245,0.46061292525,0.494782058546,0.0,0.0,0.0107928151017,0.114560434424,0.280038548989,0.548876848416,0.425817086162,0.509657747109,0.0,0.0375834591829,0.0538875425583,0.00405367284637,0.0,0.00168230861783,0.533432080965,0.433811530958,0.00169672207623,0.470279056134,0.0216444550564,0.0275224134906,0.0324966405045,0.0,0.0,0.0,0.358040009917,0.0,0.0,0.00017993196346,0.0026965339717,0.535470563415,0.400829768169,0.325132148751,0.177455432067,0.192353628215,0.188865899081,0.264629595565,0.242524411296,0.0324318764359,0.0654815857931,0.0337047329969,0.00325548148238,0.00186218509282,0.0336564592315,0.0617123067967,0.109414047635,0.0899412013338,0.0654815857931,0.0654815857931,0.0899412013338,0.059591389735,0.116038672742,0.000566401355039,0.00525647483945,0.0321045830098,0.000413940659698,0.151815581922,0.159456210886,0.0672351988887,0.000235414061947,0.122857148985,0.102897968103,0.536988528366,0.46725455739,0.157413966799,0.211561345933,0.0763025331615,0.406660442825,0.319597244007,0.312386993906,0.477911328596,3.46371974813e-05,1.98452591314e-05,0.000105080134175,7.26759212357e-05,4.11326033558e-05,0.00405367284637,0.300037373502,0.30978373233,0.0776321103085,0.0277418834785,0.0307355998967,0.0259840209607,0.496060048853,0.415962520558,0.560042130652,0.439279030042,0.331865973659,0.0827209893363,0.0302388031338,0.324067458855,0.524529756145,0.727799847639,0.399822137072,0.540220075974,0.47879323887,0.256970544672,0.70295797977,0.240483217628,0.350480300373,0.00250836112089,0.000290722639805,0.313487281852,0.0156768402999,0.000192164353644,0.432253295012,0.24287723229,0.191040827267,0.163195957093,0.259109152751,0.225225338942,0.194098854518,0.000130392069018,0.00256987307354,0.127263377503,0.167808478783,0.0654815857931,0.0654815857931,0.000235573123384,0.0682838415341,0.0331327054239,0.0336564592315,0.026674556064,0.0242119160031,0.0918404016679,0.130529826895,0.0627221004878,0.00117143854676,0.13095985326,0.26270622611,0.460261348953,0.0589779108073,0.111972362031,0.13460691851,0.163282287077,0.554826768852,0.256835561242,0.0,0.0320821294491,0.0284191059256,0.0107894971391,8.03001407055e-05,0.042143544459,0.0600621664054,0.0600621664054,0.0600621664054,0.00969870263283,6.79046547415e-07,0.0260246599156,0.470890047771,0.532509698272,0.443615718804,0.495735275605,0.522061254863,0.29955916035,0.00467849697418,0.0662763244757,0.101303599206,0.00370269616454,0.0,0.0,0.112077966857,0.120212574185,0.220431958157,0.111899296444,0.162005180874,0.156879205225,9.52230537188e-05,0.0,0.0394096290403,0.103960573563,0.0267466056341,0.0591023871701,0.0,0.0,0.00283762362039,0.0131504693665,0.0322667138221,0.0322667138221,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +23,0.410118984139,1.0,0.943497657776,0.947066951829,0.999817914433,0.447667634209,0.328138437835,0.45975963169,0.524710382874,0.436466956829,0.672575887009,0.671997554216,0.305917467523,0.0662767775214,0.679709844053,0.274851854028,0.04915647593,0.58826216617,0.514060177102,0.515456256996,0.469062960559,0.502880945272,0.740245400618,0.12809188083,0.228358664202,0.360713644642,0.397391376659,0.337471431305,0.342203540879,0.600871685798,0.206760079774,0.417918732781,0.551160783288,0.300643686864,0.415316591172,0.285330328194,0.270082891122,0.633683254124,0.538685000441,0.0729143035451,0.0,1.88509632224e-06,1.9899207728e-05,0.00109947527687,0.000204007730597,0.390458338475,0.383567168777,0.419506861803,0.0,0.064177708686,0.424792761255,0.134824352214,0.413752720015,0.0458296040178,0.499550025087,0.392705580097,0.451529614221,0.336789377486,0.255647874628,0.214863150117,0.37927788361,0.389507471357,0.349960399702,0.425877321003,0.493816564562,0.462670781149,0.196804993577,0.154584219499,0.150433015357,0.175854782641,0.20014577211,0.164632916726,0.180646621751,0.008541759069,0.270248418473,0.381506612014,0.497834030778,0.12109402383,0.174574028864,0.118211364806,0.0961853890305,0.218823164818,0.0872155575188,0.135986710893,0.155486211535,0.0585588233431,0.0651141607296,0.000231186318721,0.0252233950783,0.0143832020207,0.00428350513953,0.114344514566,0.00347349164241,0.114815957757,0.0782653656908,0.103496455202,0.0262326004819,0.0605004143679,0.0875386622533,0.0868848752015,0.181676099481,0.143745782551,0.134966030353,0.138945449611,0.13506836479,0.139758639034,0.0782653656908,0.0782664816966,0.0877819578564,0.083883229057,0.0984741943269,0.0715039250197,0.0589394710111,0.00488528334666,0.116446262884,0.0922148658405,0.0949819618338,0.0218555672084,0.262837039207,0.0782653656908,0.0422834134418,0.167145264732,0.0782846891003,0.0808020416833,0.187390940964,0.168311399669,0.109843928868,0.109843928868,0.183700358146,0.0788667538886,0.18703939195,0.153693816299,0.108998458203,0.254655359484,0.126208933916,0.227464754733,0.257096919619,0.250254977911,0.221821972649,0.592636336389,0.154605194833,0.178042244111,0.192142385054,0.151492291409,0.0,0.0,0.305447216852,0.154429913205,0.197679602269,0.182398608129,0.197917710537,0.087427318777,0.076271832622,0.15235234842,0.219263577522,0.181448720141,0.0998264302474,0.0867765019498,0.0586612919481,0.149156337644,0.103841641531,0.383313333313,0.335082316702,0.00207427515088,0.00079128250448,0.0321542842698,5.82972401573e-05,0.000913212707786,0.000179788025903,0.390729564983,0.2889689533,0.254933794442,0.18405232394,0.183305237964,8.34372246012e-08,0.00168194721664,0.00342710902068,0.321252332191,0.0017452651955,0.0108215399629,5.82972401573e-05,0.0913165751936,0.0,0.0,0.000470170164279,0.00131885152115,0.000741017254849,0.0393609904461,0.0671974417953,0.0554244501736,0.0935060017509,0.060785695272,0.0865291510559,0.10840345995,0.0301987197349,0.000536454266549,0.000776832888055,0.0436270076943,0.282267342943,0.277185050921,3.61477990041e-07,0.0,0.245510090555,0.0107698329654,0.104633474144,0.105104075012,0.345223181051,0.286387396564,0.559622814445,0.535460740812,0.00102889132757,0.00875635308179,0.360713644642,0.603805609432,0.434433780064,0.0910037063922,0.0599603674081,0.277166462862,0.167978380591,0.275110196913,0.0,0.203935845926,0.397743526474,0.314675079322,0.259760480882,0.210583719331,0.619161160193,0.407634961224,0.48233997797,0.507653378698,0.496791841809,0.365289299913,0.53476058305,0.165632500003,0.10222249152,5.47917788355e-05,5.18637881416e-07,0.0107810937594,0.000592204540237,0.0341671347119,0.428579162997,0.145646575838,0.118996757234,0.102084081729,0.0148257658574,0.0,0.0,0.399847095426,0.266320316042,0.339371370546,0.489100124893,0.580283162278,0.0422879222194,0.122140856175,0.208015602128,0.0438548242139,0.681474200997,0.0115512581859,0.176606011825,0.0142342045631,0.452648732608,0.422360736605,0.270547704672,0.0700692850374,0.572630972085,0.255252830403,0.392289829677,0.29370175542,0.294295607604,0.0925930061707,0.0859796067116,0.179424697739,0.0700358697376,0.00271813216402,0.00260793464983,0.000902176890308,0.000594766876286,0.00158154654817,0.0,0.0789403584819,0.0788033154175,0.39663535337,2.98215243414e-06,0.0,0.0,0.527558021069,0.00211864275905,0.00176964191113,0.000776832888055,0.0459442225023,0.160951955182,9.70543091902e-05,0.350761305956,0.00157334076726,0.0,4.44156570337e-06,0.547261622037,0.000553837842942,0.0462797810509,0.287242828792,0.308682757804,0.329703510475,0.0371258315209,0.0442819521481,0.139758639034,0.0444326753178,0.0771408340855,0.0,0.104110805254,0.0762282298129,0.446255759154,0.000179787537153,0.239045993557,0.475638051719,0.479883097886,0.0,0.0,0.000179025044977,0.113072882138,0.27754965293,0.581431041905,0.420086613515,0.499407929617,0.0,0.0405617404913,0.0511203391265,0.0,0.0,0.000118650824976,0.497834698051,0.416035233103,0.000132681570286,0.383413920383,0.0337704053979,0.0368343728093,0.0504540577287,0.0,0.0,0.0,0.290957090245,0.0,0.0,7.22855392259e-05,0.00108043649076,0.524585092535,0.372651095764,0.293555084585,0.162735913493,0.176391127183,0.200293043061,0.259747740111,0.238519307194,0.0866008404743,0.139758639034,0.0852470724724,0.000551565702718,0.000674534463775,0.0586612919481,0.110167563492,0.152622995782,0.134966030353,0.139758639034,0.139758639034,0.134966030353,0.097697302981,0.158586348448,0.0179093865234,0.0290285279543,0.0874406646977,0.0328392203651,0.186947275228,0.19448822993,0.0921994090873,0.0171246878325,0.133283889528,0.120390708099,0.524336760447,0.456914079616,0.162990733615,0.218829837702,0.092851343245,0.39479551984,0.315754219636,0.317655344308,0.460705080185,0.000535949513602,0.00030707036805,0.000334116792696,0.00743960051828,0.000557738977766,0.0,0.293861152425,0.281920233814,0.0882365483075,0.0462730499276,0.0525371028802,0.0451750854654,0.513404433812,0.410057988156,0.538876130264,0.408016987985,0.266308118122,0.0959771395342,0.0426238787201,0.314743704017,0.544505395556,0.705453607623,0.403694160375,0.525487193781,0.474070299893,0.226815351029,0.684302790732,0.263619052771,0.285166173588,0.0008969034756,4.74587174272e-05,0.294340658797,0.033950377761,0.0273450732075,0.350443276964,0.194540572751,0.201302293156,0.185610113674,0.264555463319,0.23257811167,0.204972837601,0.000880985208903,0.000725405486102,0.183729489807,0.19542565941,0.139758639034,0.139758639034,0.0171386889738,0.0906425317983,0.0583685523622,0.0586612919481,0.0791887582263,8.89530679719e-05,0.130244474651,0.187390940964,0.103339864809,0.00872937820177,0.143518861418,0.244916297341,0.444705461757,0.0859084808411,0.154881428024,0.188083027728,0.197679602269,0.54355531496,0.267302543056,0.0,0.039667399193,0.0351215817552,0.000178518638317,0.000306626006556,0.076271832622,0.0865291510559,0.0865291510559,0.0865291510559,0.000163386981836,2.57086519985e-06,0.0782653656908,0.487996642201,0.530412919526,0.420938398992,0.480504798362,0.502001621196,0.242979879835,0.0,0.0446704176421,0.0683090957633,0.0363056675762,0.0,0.0,0.134358250015,0.0752968338103,0.162423967909,0.0839643834367,0.124900653696,0.128343859093,0.000776832888055,0.0,0.0369881891752,0.0594439803552,0.0592672211757,0.0855140413832,0.0,0.0,0.0,0.00101587010534,0.0867765019498,0.0867765019498,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 diff --git a/PyPSA/source/examples/networks/scigrid-de/scigrid-de/generators.csv b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/generators.csv new file mode 100644 index 0000000000000000000000000000000000000000..6e75dd6139fb4627501f74d2b770e3e5e5703aca --- /dev/null +++ b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/generators.csv @@ -0,0 +1,1424 @@ +name,bus,p_nom,carrier,marginal_cost +1 Gas,1,121.0,Gas,50.0 +1 Hard Coal,1,272.0,Hard Coal,25.0 +102 Gas,102,67.9,Gas,50.0 +108 Run of River,108,63.1,Run of River,3.0 +108 Waste,108,38.0,Waste,6.0 +111 Gas,111,13.5,Gas,50.0 +112 Gas,112,13.5,Gas,50.0 +112 Run of River,112,11.1,Run of River,3.0 +114 Hard Coal,114,19.0,Hard Coal,25.0 +115 Brown Coal,115,4730.0,Brown Coal,10.0 +116 Gas,116,543.3,Gas,50.0 +118 Gas,118,95.0,Gas,50.0 +118 Waste,118,12.0,Waste,6.0 +119 Brown Coal,119,9.3,Brown Coal,10.0 +119 Gas,119,16.9,Gas,50.0 +119 Hard Coal,119,61.5,Hard Coal,25.0 +121 Brown Coal,121,24.6,Brown Coal,10.0 +123_220kV Brown Coal,123_220kV,3455.0,Brown Coal,10.0 +123_220kV Gas,123_220kV,96.5,Gas,50.0 +126 Gas,126,2.0,Gas,50.0 +126 Oil,126,60.0,Oil,100.0 +127 Brown Coal,127,1800.0,Brown Coal,10.0 +127 Waste,127,24.0,Waste,6.0 +128_220kV Run of River,128_220kV,36.4,Run of River,3.0 +129_220kV Hard Coal,129_220kV,61.5,Hard Coal,25.0 +12_220kV Gas,12_220kV,150.0,Gas,50.0 +12_220kV Waste,12_220kV,30.0,Waste,6.0 +130 Hard Coal,130,85.0,Hard Coal,25.0 +130 Waste,130,30.0,Waste,6.0 +131 Gas,131,586.3,Gas,50.0 +132 Storage Hydro,132,29.0,Storage Hydro,3.0 +133 Brown Coal,133,137.0,Brown Coal,10.0 +133 Waste,133,33.4,Waste,6.0 +134 Brown Coal,134,118.0,Brown Coal,10.0 +136 Gas,136,838.0,Gas,50.0 +136 Hard Coal,136,765.0,Hard Coal,25.0 +137 Gas,137,11.4,Gas,50.0 +139 Hard Coal,139,760.0,Hard Coal,25.0 +14 Run of River,14,27.0,Run of River,3.0 +140 Gas,140,11.1,Gas,50.0 +141 Gas,141,64.7,Gas,50.0 +142 Gas,142,9.8,Gas,50.0 +142 Hard Coal,142,449.0,Hard Coal,25.0 +145 Brown Coal,145,75.3,Brown Coal,10.0 +145 Gas,145,536.5,Gas,50.0 +145 Other,145,79.8,Other,32.0 +145 Waste,145,45.1,Waste,6.0 +146 Gas,146,84.0,Gas,50.0 +147 Gas,147,230.0,Gas,50.0 +148 Gas,148,10.2,Gas,50.0 +148 Oil,148,86.2,Oil,100.0 +148 Waste,148,53.7,Waste,6.0 +149 Run of River,149,128.7,Run of River,3.0 +150 Gas,150,15.8,Gas,50.0 +153 Waste,153,12.0,Waste,6.0 +155 Waste,155,12.6,Waste,6.0 +156_220kV Brown Coal,156_220kV,28.9,Brown Coal,10.0 +157_220kV Brown Coal,157_220kV,857.0,Brown Coal,10.0 +157_220kV Gas,157_220kV,22.0,Gas,50.0 +16 Gas,16,11.4,Gas,50.0 +160_220kV Hard Coal,160_220kV,103.0,Hard Coal,25.0 +160_220kV Waste,160_220kV,14.0,Waste,6.0 +161 Gas,161,58.58,Gas,50.0 +166 Gas,166,333.6,Gas,50.0 +166 Hard Coal,166,1324.5,Hard Coal,25.0 +167 Gas,167,26.0,Gas,50.0 +167 Run of River,167,46.9,Run of River,3.0 +169 Gas,169,33.7,Gas,50.0 +170 Gas,170,23.1,Gas,50.0 +170 Waste,170,61.5,Waste,6.0 +174 Gas,174,24.5,Gas,50.0 +174 Other,174,38.0,Other,32.0 +175 Hard Coal,175,95.0,Hard Coal,25.0 +175 Other,175,21.0,Other,32.0 +176 Gas,176,3.3,Gas,50.0 +177 Multiple,177,32.7,Multiple,28.0 +178 Gas,178,9.2,Gas,50.0 +178 Storage Hydro,178,15.0,Storage Hydro,3.0 +180 Gas,180,25.4,Gas,50.0 +180 Oil,180,333.5,Oil,100.0 +180 Waste,180,28.9,Waste,6.0 +183 Gas,183,22.5,Gas,50.0 +183 Hard Coal,183,766.0,Hard Coal,25.0 +183 Oil,183,38.0,Oil,100.0 +183 Waste,183,24.0,Waste,6.0 +184_220kV Gas,184_220kV,22.3,Gas,50.0 +185 Waste,185,33.1,Waste,6.0 +188 Gas,188,274.0,Gas,50.0 +18_220kV Gas,18_220kV,52.8,Gas,50.0 +19 Gas,19,10.6,Gas,50.0 +19 Hard Coal,19,370.0,Hard Coal,25.0 +190 Gas,190,13.4,Gas,50.0 +194 Hard Coal,194,1017.0,Hard Coal,25.0 +196 Hard Coal,196,695.0,Hard Coal,25.0 +196 Other,196,700.0,Other,32.0 +197 Run of River,197,40.1,Run of River,3.0 +198 Gas,198,15.0,Gas,50.0 +200 Brown Coal,200,27.5,Brown Coal,10.0 +200 Gas,200,7.4,Gas,50.0 +201_220kV Gas,201_220kV,58.1,Gas,50.0 +202 Gas,202,1870.1,Gas,50.0 +202 Nuclear,202,1329.0,Nuclear,8.0 +203 Gas,203,20.5,Gas,50.0 +204 Hard Coal,204,310.0,Hard Coal,25.0 +205 Gas,205,417.0,Gas,50.0 +209 Hard Coal,209,26.0,Hard Coal,25.0 +21 Gas,21,127.0,Gas,50.0 +21 Hard Coal,21,194.0,Hard Coal,25.0 +21 Waste,21,16.4,Waste,6.0 +210 Waste,210,14.7,Waste,6.0 +211 Hard Coal,211,721.0,Hard Coal,25.0 +211 Waste,211,11.6,Waste,6.0 +212 Run of River,212,24.0,Run of River,3.0 +213 Gas,213,104.6,Gas,50.0 +214 Gas,214,27.0,Gas,50.0 +215 Gas,215,47.2,Gas,50.0 +215 Waste,215,73.4,Waste,6.0 +217 Gas,217,108.0,Gas,50.0 +217 Hard Coal,217,508.0,Hard Coal,25.0 +217 Waste,217,17.0,Waste,6.0 +219 Gas,219,254.5,Gas,50.0 +219 Waste,219,34.0,Waste,6.0 +221 Geothermal,221,7.8,Geothermal,26.0 +222 Gas,222,99.0,Gas,50.0 +223 Gas,223,87.0,Gas,50.0 +224 Other,224,176.1,Other,32.0 +225 Gas,225,11.6,Gas,50.0 +225 Hard Coal,225,13.4,Hard Coal,25.0 +225 Waste,225,15.0,Waste,6.0 +226 Gas,226,20.0,Gas,50.0 +226 Run of River,226,0.1,Run of River,3.0 +227 Run of River,227,11.0,Run of River,3.0 +229 Gas,229,24.5,Gas,50.0 +229 Hard Coal,229,1958.0,Hard Coal,25.0 +22_220kV Oil,22_220kV,298.5,Oil,100.0 +233_220kV Gas,233_220kV,4.0,Gas,50.0 +233_220kV Hard Coal,233_220kV,20.7,Hard Coal,25.0 +235 Other,235,19.7,Other,32.0 +238 Run of River,238,12.1,Run of River,3.0 +239 Gas,239,800.0,Gas,50.0 +240 Gas,240,126.0,Gas,50.0 +245 Hard Coal,245,61.5,Hard Coal,25.0 +246 Hard Coal,246,510.0,Hard Coal,25.0 +247_220kV Oil,247_220kV,34.5,Oil,100.0 +247_220kV Run of River,247_220kV,633.2,Run of River,3.0 +248_220kV Hard Coal,248_220kV,895.0,Hard Coal,25.0 +249 Waste,249,14.6,Waste,6.0 +250_220kV Other,250_220kV,396.6,Other,32.0 +254 Gas,254,10.7,Gas,50.0 +255 Run of River,255,84.8,Run of River,3.0 +258 Gas,258,32.0,Gas,50.0 +258 Other,258,28.0,Other,32.0 +260 Hard Coal,260,138.5,Hard Coal,25.0 +266 Gas,266,59.9,Gas,50.0 +266 Oil,266,92.8,Oil,100.0 +266 Run of River,266,214.0,Run of River,3.0 +266 Waste,266,9.0,Waste,6.0 +26_220kV Nuclear,26_220kV,1410.0,Nuclear,8.0 +270 Gas,270,50.0,Gas,50.0 +275 Gas,275,42.0,Gas,50.0 +275 Other,275,100.0,Other,32.0 +275 Waste,275,12.5,Waste,6.0 +276 Hard Coal,276,110.0,Hard Coal,25.0 +276 Run of River,276,16.0,Run of River,3.0 +279_220kV Brown Coal,279_220kV,90.8,Brown Coal,10.0 +28 Hard Coal,28,260.0,Hard Coal,25.0 +28 Oil,28,101.0,Oil,100.0 +28 Run of River,28,311.8,Run of River,3.0 +281_220kV Gas,281_220kV,74.0,Gas,50.0 +282_220kV Oil,282_220kV,17.0,Oil,100.0 +285 Gas,285,18.1,Gas,50.0 +287 Gas,287,64.0,Gas,50.0 +288 Hard Coal,288,18.5,Hard Coal,25.0 +289 Gas,289,33.4,Gas,50.0 +289 Hard Coal,289,794.0,Hard Coal,25.0 +290 Waste,290,15.7,Waste,6.0 +291 Brown Coal,291,1105.0,Brown Coal,10.0 +292_220kV Brown Coal,292_220kV,20.3,Brown Coal,10.0 +292_220kV Gas,292_220kV,216.0,Gas,50.0 +292_220kV Waste,292_220kV,59.0,Waste,6.0 +294 Gas,294,13.5,Gas,50.0 +294 Waste,294,11.2,Waste,6.0 +295 Gas,295,119.0,Gas,50.0 +295 Hard Coal,295,43.3,Hard Coal,25.0 +298 Gas,298,37.5,Gas,50.0 +299 Hard Coal,299,690.0,Hard Coal,25.0 +3 Run of River,3,18.8,Run of River,3.0 +301 Gas,301,13.0,Gas,50.0 +302 Brown Coal,302,33.5,Brown Coal,10.0 +302 Gas,302,128.0,Gas,50.0 +302 Other,302,56.0,Other,32.0 +302 Storage Hydro,302,79.0,Storage Hydro,3.0 +302 Waste,302,14.7,Waste,6.0 +304 Gas,304,34.2,Gas,50.0 +304 Oil,304,24.8,Oil,100.0 +305 Nuclear,305,1310.0,Nuclear,8.0 +305 Oil,305,136.0,Oil,100.0 +308_220kV Gas,308_220kV,27.4,Gas,50.0 +308_220kV Nuclear,308_220kV,1275.0,Nuclear,8.0 +30_220kV Run of River,30_220kV,3.0,Run of River,3.0 +31 Gas,31,195.4,Gas,50.0 +310 Hard Coal,310,778.0,Hard Coal,25.0 +313 Brown Coal,313,875.0,Brown Coal,10.0 +315_220kV Run of River,315_220kV,77.4,Run of River,3.0 +316 Gas,316,75.8,Gas,50.0 +316 Waste,316,10.0,Waste,6.0 +318 Run of River,318,62.7,Run of River,3.0 +320 Gas,320,9.8,Gas,50.0 +321 Gas,321,0.2,Gas,50.0 +321 Waste,321,13.6,Waste,6.0 +322_220kV Gas,322_220kV,85.1,Gas,50.0 +323 Gas,323,12.9,Gas,50.0 +326 Gas,326,319.8,Gas,50.0 +327 Gas,327,26.9,Gas,50.0 +328 Run of River,328,36.5,Run of River,3.0 +329 Run of River,329,50.0,Run of River,3.0 +329 Storage Hydro,329,500.0,Storage Hydro,3.0 +32_220kV Gas,32_220kV,109.1,Gas,50.0 +33 Gas,33,31.8,Gas,50.0 +330 Nuclear,330,1410.0,Nuclear,8.0 +330 Run of River,330,35.8,Run of River,3.0 +331 Gas,331,227.0,Gas,50.0 +331 Run of River,331,217.5,Run of River,3.0 +331 Waste,331,12.5,Waste,6.0 +332_220kV Gas,332_220kV,97.9,Gas,50.0 +332_220kV Run of River,332_220kV,25.2,Run of River,3.0 +335 Gas,335,54.0,Gas,50.0 +336_220kV Gas,336_220kV,52.8,Gas,50.0 +336_220kV Run of River,336_220kV,89.0,Run of River,3.0 +337 Gas,337,13.4,Gas,50.0 +337 Run of River,337,80.1,Run of River,3.0 +338_220kV Waste,338_220kV,54.0,Waste,6.0 +34 Hard Coal,34,757.0,Hard Coal,25.0 +34 Oil,34,56.0,Oil,100.0 +340 Gas,340,299.6,Gas,50.0 +340 Storage Hydro,340,82.5,Storage Hydro,3.0 +341 Brown Coal,341,134.0,Brown Coal,10.0 +341 Gas,341,159.0,Gas,50.0 +341 Oil,341,111.0,Oil,100.0 +341 Other,341,42.7,Other,32.0 +341 Waste,341,58.0,Waste,6.0 +342 Gas,342,19.5,Gas,50.0 +344 Gas,344,69.95,Gas,50.0 +347 Gas,347,211.0,Gas,50.0 +347 Hard Coal,347,89.0,Hard Coal,25.0 +347 Oil,347,327.0,Oil,100.0 +348 Gas,348,458.9,Gas,50.0 +350 Gas,350,18.6,Gas,50.0 +351_220kV Other,351_220kV,23.3,Other,32.0 +351_220kV Run of River,351_220kV,63.7,Run of River,3.0 +351_220kV Waste,351_220kV,25.5,Waste,6.0 +352 Hard Coal,352,472.0,Hard Coal,25.0 +352 Oil,352,46.0,Oil,100.0 +352 Run of River,352,73.3,Run of River,3.0 +353_220kV Gas,353_220kV,1391.0,Gas,50.0 +353_220kV Run of River,353_220kV,23.3,Run of River,3.0 +354 Gas,354,540.5,Gas,50.0 +354 Hard Coal,354,333.0,Hard Coal,25.0 +354 Waste,354,40.0,Waste,6.0 +358 Hard Coal,358,318.0,Hard Coal,25.0 +36 Gas,36,11.06,Gas,50.0 +36 Hard Coal,36,280.0,Hard Coal,25.0 +360_220kV Gas,360_220kV,42.0,Gas,50.0 +360_220kV Hard Coal,360_220kV,779.0,Hard Coal,25.0 +360_220kV Waste,360_220kV,16.0,Waste,6.0 +362_220kV Run of River,362_220kV,79.5,Run of River,3.0 +363 Other,363,16.0,Other,32.0 +364 Waste,364,11.6,Waste,6.0 +365 Gas,365,81.8,Gas,50.0 +365 Hard Coal,365,688.0,Hard Coal,25.0 +365 Waste,365,36.0,Waste,6.0 +367 Nuclear,367,1402.0,Nuclear,8.0 +370 Brown Coal,370,56.8,Brown Coal,10.0 +370 Other,370,85.0,Other,32.0 +373 Run of River,373,25.0,Run of River,3.0 +374 Gas,374,100.2,Gas,50.0 +374 Hard Coal,374,725.0,Hard Coal,25.0 +376 Geothermal,376,0.6,Geothermal,26.0 +376 Hard Coal,376,875.0,Hard Coal,25.0 +377 Waste,377,11.0,Waste,6.0 +378 Gas,378,260.44,Gas,50.0 +378 Hard Coal,378,769.0,Hard Coal,25.0 +37_220kV Gas,37_220kV,52.0,Gas,50.0 +37_220kV Nuclear,37_220kV,1360.0,Nuclear,8.0 +37_220kV Waste,37_220kV,58.4,Waste,6.0 +380 Gas,380,250.0,Gas,50.0 +380 Waste,380,22.5,Waste,6.0 +382_220kV Gas,382_220kV,122.0,Gas,50.0 +382_220kV Waste,382_220kV,20.7,Waste,6.0 +383 Waste,383,24.4,Waste,6.0 +385_220kV Gas,385_220kV,58.4,Gas,50.0 +385_220kV Hard Coal,385_220kV,24.8,Hard Coal,25.0 +385_220kV Oil,385_220kV,0.5,Oil,100.0 +386 Gas,386,23.0,Gas,50.0 +39 Gas,39,2299.3,Gas,50.0 +390 Gas,390,1001.3,Gas,50.0 +390 Hard Coal,390,17.4,Hard Coal,25.0 +390 Waste,390,18.0,Waste,6.0 +394 Gas,394,26.5,Gas,50.0 +395 Gas,395,490.0,Gas,50.0 +396 Nuclear,396,2572.0,Nuclear,8.0 +398 Oil,398,87.0,Oil,100.0 +399 Gas,399,35.1,Gas,50.0 +399 Waste,399,9.5,Waste,6.0 +40 Run of River,40,48.0,Run of River,3.0 +401 Gas,401,31.0,Gas,50.0 +402 Oil,402,88.0,Oil,100.0 +402 Run of River,402,60.0,Run of River,3.0 +402 Storage Hydro,402,158.0,Storage Hydro,3.0 +403_220kV Run of River,403_220kV,19.0,Run of River,3.0 +404_220kV Run of River,404_220kV,24.0,Run of River,3.0 +405 Gas,405,56.0,Gas,50.0 +405 Run of River,405,84.6,Run of River,3.0 +409 Gas,409,8.5,Gas,50.0 +409 Hard Coal,409,112.6,Hard Coal,25.0 +409 Oil,409,69.9,Oil,100.0 +409 Waste,409,19.5,Waste,6.0 +410 Run of River,410,20.1,Run of River,3.0 +414 Gas,414,193.7,Gas,50.0 +417 Run of River,417,137.3,Run of River,3.0 +418 Run of River,418,66.0,Run of River,3.0 +421_220kV Waste,421_220kV,67.3,Waste,6.0 +422 Gas,422,13.5,Gas,50.0 +423 Run of River,423,36.8,Run of River,3.0 +424 Oil,424,70.0,Oil,100.0 +425 Other,425,288.5,Other,32.0 +426 Gas,426,32.4,Gas,50.0 +428 Brown Coal,428,74.0,Brown Coal,10.0 +429 Gas,429,112.1,Gas,50.0 +429 Hard Coal,429,12.0,Hard Coal,25.0 +43 Gas,43,196.0,Gas,50.0 +43 Waste,43,17.0,Waste,6.0 +430 Brown Coal,430,38.5,Brown Coal,10.0 +433 Gas,433,15.6,Gas,50.0 +434 Run of River,434,12.8,Run of River,3.0 +434 Storage Hydro,434,124.0,Storage Hydro,3.0 +435 Brown Coal,435,164.0,Brown Coal,10.0 +438 Run of River,438,146.0,Run of River,3.0 +439 Gas,439,11.7,Gas,50.0 +44 Waste,44,26.0,Waste,6.0 +440 Other,440,17.0,Other,32.0 +441 Gas,441,182.0,Gas,50.0 +441 Hard Coal,441,61.5,Hard Coal,25.0 +442 Gas,442,26.5,Gas,50.0 +444 Gas,444,430.0,Gas,50.0 +445 Run of River,445,217.1,Run of River,3.0 +446 Gas,446,14.7,Gas,50.0 +447 Gas,447,75.0,Gas,50.0 +45 Gas,45,296.0,Gas,50.0 +45 Geothermal,45,23.1,Geothermal,26.0 +451 Run of River,451,104.4,Run of River,3.0 +453 Run of River,453,30.0,Run of River,3.0 +455 Gas,455,16.9,Gas,50.0 +456 Run of River,456,0.1,Run of River,3.0 +457 Waste,457,29.6,Waste,6.0 +463 Gas,463,25.8,Gas,50.0 +464 Gas,464,51.9,Gas,50.0 +464 Oil,464,66.3,Oil,100.0 +464 Run of River,464,25.4,Run of River,3.0 +465 Gas,465,35.0,Gas,50.0 +465 Oil,465,80.0,Oil,100.0 +468 Gas,468,17.5,Gas,50.0 +468 Hard Coal,468,79.0,Hard Coal,25.0 +468 Oil,468,4.8,Oil,100.0 +468 Waste,468,16.0,Waste,6.0 +47 Gas,47,444.0,Gas,50.0 +47 Run of River,47,31.7,Run of River,3.0 +474 Gas,474,390.0,Gas,50.0 +475 Oil,475,250.6,Oil,100.0 +478 Gas,478,95.0,Gas,50.0 +484 Gas,484,65.0,Gas,50.0 +484 Other,484,6.0,Other,32.0 +484 Run of River,484,66.7,Run of River,3.0 +484 Storage Hydro,484,45.5,Storage Hydro,3.0 +485 Run of River,485,26.5,Run of River,3.0 +486 Hard Coal,486,138.5,Hard Coal,25.0 +486 Oil,486,11.4,Oil,100.0 +487 Brown Coal,487,875.0,Brown Coal,10.0 +487 Oil,487,11.4,Oil,100.0 +49 Brown Coal,49,352.0,Brown Coal,10.0 +49 Hard Coal,49,66.0,Hard Coal,25.0 +49 Waste,49,37.5,Waste,6.0 +492 Hard Coal,492,54.0,Hard Coal,25.0 +492 Waste,492,56.9,Waste,6.0 +495 Hard Coal,495,147.0,Hard Coal,25.0 +51 Storage Hydro,51,20.0,Storage Hydro,3.0 +52_220kV Gas,52_220kV,390.0,Gas,50.0 +52_220kV Hard Coal,52_220kV,1347.0,Hard Coal,25.0 +56_220kV Gas,56_220kV,11.7,Gas,50.0 +58 Gas,58,52.5,Gas,50.0 +58 Hard Coal,58,26.9,Hard Coal,25.0 +59 Hard Coal,59,324.0,Hard Coal,25.0 +5_220kV Run of River,5_220kV,4.6,Run of River,3.0 +61_220kV Gas,61_220kV,334.0,Gas,50.0 +61_220kV Hard Coal,61_220kV,350.0,Hard Coal,25.0 +62 Hard Coal,62,119.0,Hard Coal,25.0 +62 Run of River,62,9.9,Run of River,3.0 +62 Waste,62,44.0,Waste,6.0 +63 Brown Coal,63,465.0,Brown Coal,10.0 +63 Run of River,63,22.0,Run of River,3.0 +64 Run of River,64,11.2,Run of River,3.0 +65 Run of River,65,164.5,Run of River,3.0 +66 Run of River,66,5.1,Run of River,3.0 +67 Gas,67,14.8,Gas,50.0 +67 Hard Coal,67,300.0,Hard Coal,25.0 +67 Oil,67,88.0,Oil,100.0 +67 Other,67,150.0,Other,32.0 +67 Storage Hydro,67,392.0,Storage Hydro,3.0 +67 Waste,67,33.0,Waste,6.0 +68_220kV Run of River,68_220kV,4.2,Run of River,3.0 +69_220kV Gas,69_220kV,11.5,Gas,50.0 +69_220kV Waste,69_220kV,18.8,Waste,6.0 +70 Gas,70,423.0,Gas,50.0 +70 Waste,70,15.6,Waste,6.0 +71 Brown Coal,71,49.0,Brown Coal,10.0 +71 Gas,71,161.1,Gas,50.0 +71 Waste,71,9.9,Waste,6.0 +72 Brown Coal,72,900.0,Brown Coal,10.0 +72 Gas,72,151.0,Gas,50.0 +72 Oil,72,120.0,Oil,100.0 +74_220kV Gas,74_220kV,122.0,Gas,50.0 +74_220kV Waste,74_220kV,23.3,Waste,6.0 +75 Brown Coal,75,49.0,Brown Coal,10.0 +77 Gas,77,5.3,Gas,50.0 +79 Brown Coal,79,750.0,Brown Coal,10.0 +79 Waste,79,18.7,Waste,6.0 +81 Brown Coal,81,2835.0,Brown Coal,10.0 +81 Other,81,124.5,Other,32.0 +83 Brown Coal,83,750.0,Brown Coal,10.0 +83 Other,83,53.6,Other,32.0 +84 Hard Coal,84,323.0,Hard Coal,25.0 +87_220kV Gas,87_220kV,138.7,Gas,50.0 +87_220kV Hard Coal,87_220kV,175.9,Hard Coal,25.0 +87_220kV Multiple,87_220kV,120.0,Multiple,28.0 +88 Gas,88,39.8,Gas,50.0 +89 Other,89,606.0,Other,32.0 +92 Gas,92,66.0,Gas,50.0 +94_220kV Geothermal,94_220kV,0.2,Geothermal,26.0 +95_220kV Gas,95_220kV,24.3,Gas,50.0 +99_220kV Gas,99_220kV,113.6,Gas,50.0 +99_220kV Hard Coal,99_220kV,655.6,Hard Coal,25.0 +218 Wind Offshore,218,48.3,Wind Offshore,0.0 +221 Wind Offshore,221,400.0,Wind Offshore,0.0 +270 Wind Offshore,270,60.0,Wind Offshore,0.0 +271 Wind Offshore,271,1018.0,Wind Offshore,0.0 +22_220kV Wind Offshore,22_220kV,1447.2,Wind Offshore,0.0 +1 Wind Onshore,1,153.510391124,Wind Onshore,0.0 +3 Wind Onshore,3,50.1645064777,Wind Onshore,0.0 +4 Wind Onshore,4,84.2627790573,Wind Onshore,0.0 +6 Wind Onshore,6,49.8864693272,Wind Onshore,0.0 +7 Wind Onshore,7,67.2230767625,Wind Onshore,0.0 +8 Wind Onshore,8,94.6840441287,Wind Onshore,0.0 +9 Wind Onshore,9,85.7985253848,Wind Onshore,0.0 +11 Wind Onshore,11,132.724113719,Wind Onshore,0.0 +14 Wind Onshore,14,0.0641793580921,Wind Onshore,0.0 +16 Wind Onshore,16,204.187623515,Wind Onshore,0.0 +17 Wind Onshore,17,52.7005296089,Wind Onshore,0.0 +19 Wind Onshore,19,6.03553849736,Wind Onshore,0.0 +21 Wind Onshore,21,36.1635315924,Wind Onshore,0.0 +23 Wind Onshore,23,61.8362042365,Wind Onshore,0.0 +25 Wind Onshore,25,106.787062956,Wind Onshore,0.0 +27 Wind Onshore,27,13.0295812923,Wind Onshore,0.0 +28 Wind Onshore,28,71.6568833062,Wind Onshore,0.0 +29 Wind Onshore,29,293.755374959,Wind Onshore,0.0 +31 Wind Onshore,31,52.7816062429,Wind Onshore,0.0 +33 Wind Onshore,33,304.429780208,Wind Onshore,0.0 +34 Wind Onshore,34,20.6686903142,Wind Onshore,0.0 +35 Wind Onshore,35,16.3680870699,Wind Onshore,0.0 +36 Wind Onshore,36,273.855776496,Wind Onshore,0.0 +38 Wind Onshore,38,2.8560689855,Wind Onshore,0.0 +39 Wind Onshore,39,603.943953043,Wind Onshore,0.0 +40 Wind Onshore,40,426.979978559,Wind Onshore,0.0 +41 Wind Onshore,41,1055.73517403,Wind Onshore,0.0 +43 Wind Onshore,43,414.871332023,Wind Onshore,0.0 +44 Wind Onshore,44,10.3516315814,Wind Onshore,0.0 +45 Wind Onshore,45,161.862596706,Wind Onshore,0.0 +47 Wind Onshore,47,2.87856145665,Wind Onshore,0.0 +48 Wind Onshore,48,8.67091627581,Wind Onshore,0.0 +49 Wind Onshore,49,209.786523699,Wind Onshore,0.0 +50 Wind Onshore,50,339.421642565,Wind Onshore,0.0 +51 Wind Onshore,51,134.399019048,Wind Onshore,0.0 +53 Wind Onshore,53,32.6535257613,Wind Onshore,0.0 +54 Wind Onshore,54,5.00902220665,Wind Onshore,0.0 +55 Wind Onshore,55,3.19075884669,Wind Onshore,0.0 +57 Wind Onshore,57,0.788599328058,Wind Onshore,0.0 +58 Wind Onshore,58,4.32869590283,Wind Onshore,0.0 +59 Wind Onshore,59,112.606432525,Wind Onshore,0.0 +60 Wind Onshore,60,77.81765048,Wind Onshore,0.0 +62 Wind Onshore,62,153.592985662,Wind Onshore,0.0 +63 Wind Onshore,63,15.7834481687,Wind Onshore,0.0 +64 Wind Onshore,64,2.29550964638,Wind Onshore,0.0 +65 Wind Onshore,65,26.1200405137,Wind Onshore,0.0 +66 Wind Onshore,66,34.4132463542,Wind Onshore,0.0 +67 Wind Onshore,67,72.3279923602,Wind Onshore,0.0 +70 Wind Onshore,70,132.149983123,Wind Onshore,0.0 +71 Wind Onshore,71,405.642114881,Wind Onshore,0.0 +72 Wind Onshore,72,510.054159155,Wind Onshore,0.0 +73 Wind Onshore,73,633.681488492,Wind Onshore,0.0 +75 Wind Onshore,75,602.719370584,Wind Onshore,0.0 +76 Wind Onshore,76,113.714399398,Wind Onshore,0.0 +77 Wind Onshore,77,225.599169646,Wind Onshore,0.0 +79 Wind Onshore,79,116.66596966,Wind Onshore,0.0 +80 Wind Onshore,80,36.7824924762,Wind Onshore,0.0 +81 Wind Onshore,81,384.449592248,Wind Onshore,0.0 +82 Wind Onshore,82,89.1408968614,Wind Onshore,0.0 +83 Wind Onshore,83,45.7938996393,Wind Onshore,0.0 +84 Wind Onshore,84,54.0020322251,Wind Onshore,0.0 +85 Wind Onshore,85,155.605297616,Wind Onshore,0.0 +88 Wind Onshore,88,3.75903786416,Wind Onshore,0.0 +89 Wind Onshore,89,0.20495144136,Wind Onshore,0.0 +90 Wind Onshore,90,84.5061198676,Wind Onshore,0.0 +91 Wind Onshore,91,4.60112152773,Wind Onshore,0.0 +92 Wind Onshore,92,71.0254994175,Wind Onshore,0.0 +93 Wind Onshore,93,290.937197757,Wind Onshore,0.0 +97 Wind Onshore,97,0.0266961326966,Wind Onshore,0.0 +98 Wind Onshore,98,83.9306653156,Wind Onshore,0.0 +101 Wind Onshore,101,350.979987644,Wind Onshore,0.0 +102 Wind Onshore,102,245.381700655,Wind Onshore,0.0 +103 Wind Onshore,103,0.206918168218,Wind Onshore,0.0 +108 Wind Onshore,108,2.17859329378,Wind Onshore,0.0 +109 Wind Onshore,109,2.28979314118,Wind Onshore,0.0 +110 Wind Onshore,110,76.2216146438,Wind Onshore,0.0 +111 Wind Onshore,111,320.42275108,Wind Onshore,0.0 +112 Wind Onshore,112,0.778210370106,Wind Onshore,0.0 +113 Wind Onshore,113,1.65455712572,Wind Onshore,0.0 +114 Wind Onshore,114,89.637993199,Wind Onshore,0.0 +115 Wind Onshore,115,69.5342899494,Wind Onshore,0.0 +116 Wind Onshore,116,3.53636055719,Wind Onshore,0.0 +118 Wind Onshore,118,13.4468885109,Wind Onshore,0.0 +119 Wind Onshore,119,50.5752781935,Wind Onshore,0.0 +121 Wind Onshore,121,32.6011423992,Wind Onshore,0.0 +122 Wind Onshore,122,90.7925410198,Wind Onshore,0.0 +126 Wind Onshore,126,8.72501236524,Wind Onshore,0.0 +127 Wind Onshore,127,43.2540231528,Wind Onshore,0.0 +130 Wind Onshore,130,2.41600145539,Wind Onshore,0.0 +131 Wind Onshore,131,0.540589207054,Wind Onshore,0.0 +132 Wind Onshore,132,1.46288325395,Wind Onshore,0.0 +133 Wind Onshore,133,1.46433600229,Wind Onshore,0.0 +134 Wind Onshore,134,1.28828304343,Wind Onshore,0.0 +135 Wind Onshore,135,25.8188993015,Wind Onshore,0.0 +136 Wind Onshore,136,29.1548929719,Wind Onshore,0.0 +137 Wind Onshore,137,105.296621064,Wind Onshore,0.0 +139 Wind Onshore,139,3.06138955244,Wind Onshore,0.0 +140 Wind Onshore,140,4.73874713717,Wind Onshore,0.0 +141 Wind Onshore,141,1.02575387048,Wind Onshore,0.0 +142 Wind Onshore,142,2.8970783132,Wind Onshore,0.0 +144 Wind Onshore,144,0.359496708924,Wind Onshore,0.0 +145 Wind Onshore,145,0.719596947326,Wind Onshore,0.0 +146 Wind Onshore,146,0.508453122526,Wind Onshore,0.0 +147 Wind Onshore,147,8.11781937714,Wind Onshore,0.0 +148 Wind Onshore,148,0.402699751377,Wind Onshore,0.0 +149 Wind Onshore,149,3.669011551,Wind Onshore,0.0 +150 Wind Onshore,150,1.25427791149,Wind Onshore,0.0 +151 Wind Onshore,151,19.8227688711,Wind Onshore,0.0 +152 Wind Onshore,152,28.389456755,Wind Onshore,0.0 +153 Wind Onshore,153,119.464559856,Wind Onshore,0.0 +154 Wind Onshore,154,1.21573343643,Wind Onshore,0.0 +155 Wind Onshore,155,25.239356261,Wind Onshore,0.0 +158 Wind Onshore,158,52.5273816031,Wind Onshore,0.0 +161 Wind Onshore,161,175.889627208,Wind Onshore,0.0 +162 Wind Onshore,162,0.861317127693,Wind Onshore,0.0 +163 Wind Onshore,163,45.7037068059,Wind Onshore,0.0 +164 Wind Onshore,164,17.6455290289,Wind Onshore,0.0 +166 Wind Onshore,166,8.55678386233,Wind Onshore,0.0 +167 Wind Onshore,167,3.62255952466,Wind Onshore,0.0 +168 Wind Onshore,168,13.0361134479,Wind Onshore,0.0 +169 Wind Onshore,169,0.116517735693,Wind Onshore,0.0 +170 Wind Onshore,170,0.0687883675101,Wind Onshore,0.0 +171 Wind Onshore,171,0.160624563218,Wind Onshore,0.0 +172 Wind Onshore,172,0.609451340471,Wind Onshore,0.0 +173 Wind Onshore,173,16.0223344728,Wind Onshore,0.0 +174 Wind Onshore,174,4.35414698006,Wind Onshore,0.0 +175 Wind Onshore,175,1.04303990751,Wind Onshore,0.0 +176 Wind Onshore,176,13.4980870092,Wind Onshore,0.0 +177 Wind Onshore,177,238.377761372,Wind Onshore,0.0 +178 Wind Onshore,178,25.2633910944,Wind Onshore,0.0 +179 Wind Onshore,179,502.409178069,Wind Onshore,0.0 +180 Wind Onshore,180,330.031939735,Wind Onshore,0.0 +181 Wind Onshore,181,285.625411508,Wind Onshore,0.0 +182 Wind Onshore,182,57.1885789685,Wind Onshore,0.0 +183 Wind Onshore,183,83.2771628571,Wind Onshore,0.0 +185 Wind Onshore,185,5.05377980991,Wind Onshore,0.0 +186 Wind Onshore,186,1.34577448661,Wind Onshore,0.0 +187 Wind Onshore,187,2.72184505996,Wind Onshore,0.0 +188 Wind Onshore,188,0.265989546307,Wind Onshore,0.0 +189 Wind Onshore,189,9.02988478213,Wind Onshore,0.0 +190 Wind Onshore,190,3.58404269875,Wind Onshore,0.0 +192 Wind Onshore,192,254.253327175,Wind Onshore,0.0 +193 Wind Onshore,193,1.26008164224,Wind Onshore,0.0 +194 Wind Onshore,194,0.855454008484,Wind Onshore,0.0 +196 Wind Onshore,196,0.232219951161,Wind Onshore,0.0 +197 Wind Onshore,197,27.8740104664,Wind Onshore,0.0 +198 Wind Onshore,198,3.16799906488,Wind Onshore,0.0 +199 Wind Onshore,199,1.30629298198,Wind Onshore,0.0 +200 Wind Onshore,200,0.688544563034,Wind Onshore,0.0 +202 Wind Onshore,202,85.7395569744,Wind Onshore,0.0 +203 Wind Onshore,203,99.3195792397,Wind Onshore,0.0 +204 Wind Onshore,204,38.2225747563,Wind Onshore,0.0 +205 Wind Onshore,205,4.21900521504,Wind Onshore,0.0 +206 Wind Onshore,206,11.3439712953,Wind Onshore,0.0 +207 Wind Onshore,207,38.9163677331,Wind Onshore,0.0 +208 Wind Onshore,208,9.81457352728,Wind Onshore,0.0 +209 Wind Onshore,209,179.712741143,Wind Onshore,0.0 +210 Wind Onshore,210,20.2618052811,Wind Onshore,0.0 +211 Wind Onshore,211,220.533673307,Wind Onshore,0.0 +212 Wind Onshore,212,73.0794126745,Wind Onshore,0.0 +213 Wind Onshore,213,21.4545687721,Wind Onshore,0.0 +214 Wind Onshore,214,2.39995325631,Wind Onshore,0.0 +215 Wind Onshore,215,8.98603573294,Wind Onshore,0.0 +216 Wind Onshore,216,0.202358992327,Wind Onshore,0.0 +217 Wind Onshore,217,160.697955702,Wind Onshore,0.0 +218 Wind Onshore,218,90.5922662203,Wind Onshore,0.0 +219 Wind Onshore,219,39.8191571163,Wind Onshore,0.0 +221 Wind Onshore,221,201.421956418,Wind Onshore,0.0 +222 Wind Onshore,222,56.0124084149,Wind Onshore,0.0 +223 Wind Onshore,223,25.1666668423,Wind Onshore,0.0 +224 Wind Onshore,224,49.892379932,Wind Onshore,0.0 +225 Wind Onshore,225,182.623255886,Wind Onshore,0.0 +226 Wind Onshore,226,47.5418211266,Wind Onshore,0.0 +227 Wind Onshore,227,0.548842797786,Wind Onshore,0.0 +228 Wind Onshore,228,3.80689419028,Wind Onshore,0.0 +229 Wind Onshore,229,3.57545259247,Wind Onshore,0.0 +230 Wind Onshore,230,2.24203138868,Wind Onshore,0.0 +234 Wind Onshore,234,1.26826084243,Wind Onshore,0.0 +235 Wind Onshore,235,9.02599008132,Wind Onshore,0.0 +237 Wind Onshore,237,18.0052285695,Wind Onshore,0.0 +238 Wind Onshore,238,91.1718882891,Wind Onshore,0.0 +239 Wind Onshore,239,57.3427040317,Wind Onshore,0.0 +240 Wind Onshore,240,4.50590653785,Wind Onshore,0.0 +241 Wind Onshore,241,2.25296531835,Wind Onshore,0.0 +242 Wind Onshore,242,67.7544693336,Wind Onshore,0.0 +245 Wind Onshore,245,35.8716168147,Wind Onshore,0.0 +246 Wind Onshore,246,0.980482439128,Wind Onshore,0.0 +249 Wind Onshore,249,12.3395302249,Wind Onshore,0.0 +253 Wind Onshore,253,30.5978493741,Wind Onshore,0.0 +254 Wind Onshore,254,593.230436463,Wind Onshore,0.0 +255 Wind Onshore,255,211.057769317,Wind Onshore,0.0 +256 Wind Onshore,256,3.806101238,Wind Onshore,0.0 +257 Wind Onshore,257,160.523777952,Wind Onshore,0.0 +258 Wind Onshore,258,34.9314811298,Wind Onshore,0.0 +259 Wind Onshore,259,3.98285835666,Wind Onshore,0.0 +260 Wind Onshore,260,17.4828066828,Wind Onshore,0.0 +262 Wind Onshore,262,3.02136730179,Wind Onshore,0.0 +265 Wind Onshore,265,51.2442219887,Wind Onshore,0.0 +266 Wind Onshore,266,1.68127574154,Wind Onshore,0.0 +268 Wind Onshore,268,33.5547803098,Wind Onshore,0.0 +269 Wind Onshore,269,75.0750875274,Wind Onshore,0.0 +270 Wind Onshore,270,575.368279546,Wind Onshore,0.0 +271 Wind Onshore,271,101.139399875,Wind Onshore,0.0 +273 Wind Onshore,273,9.0373397227,Wind Onshore,0.0 +274 Wind Onshore,274,82.2250352232,Wind Onshore,0.0 +275 Wind Onshore,275,14.6054088834,Wind Onshore,0.0 +276 Wind Onshore,276,188.691049163,Wind Onshore,0.0 +277 Wind Onshore,277,2.26770583626,Wind Onshore,0.0 +278 Wind Onshore,278,339.510510359,Wind Onshore,0.0 +280 Wind Onshore,280,385.361595031,Wind Onshore,0.0 +283 Wind Onshore,283,27.074037541,Wind Onshore,0.0 +284 Wind Onshore,284,83.0798418272,Wind Onshore,0.0 +285 Wind Onshore,285,323.472536333,Wind Onshore,0.0 +286 Wind Onshore,286,8.0808958712,Wind Onshore,0.0 +287 Wind Onshore,287,57.1278247463,Wind Onshore,0.0 +288 Wind Onshore,288,21.8333957759,Wind Onshore,0.0 +289 Wind Onshore,289,91.9582753388,Wind Onshore,0.0 +290 Wind Onshore,290,76.1351770479,Wind Onshore,0.0 +291 Wind Onshore,291,30.8305188023,Wind Onshore,0.0 +293 Wind Onshore,293,23.4043450629,Wind Onshore,0.0 +294 Wind Onshore,294,14.887727285,Wind Onshore,0.0 +295 Wind Onshore,295,64.502694254,Wind Onshore,0.0 +296 Wind Onshore,296,82.2786953613,Wind Onshore,0.0 +297 Wind Onshore,297,3.81841924681,Wind Onshore,0.0 +298 Wind Onshore,298,15.0918417045,Wind Onshore,0.0 +299 Wind Onshore,299,88.9528798809,Wind Onshore,0.0 +300 Wind Onshore,300,235.487328921,Wind Onshore,0.0 +301 Wind Onshore,301,109.288718785,Wind Onshore,0.0 +302 Wind Onshore,302,20.1687166138,Wind Onshore,0.0 +304 Wind Onshore,304,201.699554501,Wind Onshore,0.0 +305 Wind Onshore,305,0.524108115665,Wind Onshore,0.0 +306 Wind Onshore,306,0.352420710612,Wind Onshore,0.0 +309 Wind Onshore,309,82.0353612917,Wind Onshore,0.0 +310 Wind Onshore,310,6.65472673495,Wind Onshore,0.0 +311 Wind Onshore,311,119.710848522,Wind Onshore,0.0 +313 Wind Onshore,313,39.469005702,Wind Onshore,0.0 +314 Wind Onshore,314,3.4294999088,Wind Onshore,0.0 +316 Wind Onshore,316,5.10582944022,Wind Onshore,0.0 +318 Wind Onshore,318,38.9505114335,Wind Onshore,0.0 +319 Wind Onshore,319,47.3717915205,Wind Onshore,0.0 +320 Wind Onshore,320,1.23166024502,Wind Onshore,0.0 +321 Wind Onshore,321,2.16920079779,Wind Onshore,0.0 +323 Wind Onshore,323,53.0956287692,Wind Onshore,0.0 +324 Wind Onshore,324,99.0752999943,Wind Onshore,0.0 +325 Wind Onshore,325,25.6556410363,Wind Onshore,0.0 +326 Wind Onshore,326,65.61125218,Wind Onshore,0.0 +327 Wind Onshore,327,170.437781001,Wind Onshore,0.0 +328 Wind Onshore,328,0.00434449024199,Wind Onshore,0.0 +329 Wind Onshore,329,0.626493907051,Wind Onshore,0.0 +330 Wind Onshore,330,2.51245968177,Wind Onshore,0.0 +331 Wind Onshore,331,4.36303730827,Wind Onshore,0.0 +333 Wind Onshore,333,1297.76442825,Wind Onshore,0.0 +335 Wind Onshore,335,28.4263216809,Wind Onshore,0.0 +337 Wind Onshore,337,2.93704756536,Wind Onshore,0.0 +339 Wind Onshore,339,17.8722009047,Wind Onshore,0.0 +340 Wind Onshore,340,93.1996278632,Wind Onshore,0.0 +341 Wind Onshore,341,148.402692339,Wind Onshore,0.0 +342 Wind Onshore,342,184.14923543,Wind Onshore,0.0 +343 Wind Onshore,343,4.90197409828,Wind Onshore,0.0 +344 Wind Onshore,344,98.2933506333,Wind Onshore,0.0 +345 Wind Onshore,345,41.6820737696,Wind Onshore,0.0 +346 Wind Onshore,346,2.40210740929,Wind Onshore,0.0 +347 Wind Onshore,347,5.39742965672,Wind Onshore,0.0 +348 Wind Onshore,348,5.87366595854,Wind Onshore,0.0 +349 Wind Onshore,349,27.3564890353,Wind Onshore,0.0 +350 Wind Onshore,350,177.778741548,Wind Onshore,0.0 +352 Wind Onshore,352,4.01460326021,Wind Onshore,0.0 +354 Wind Onshore,354,2.15820669618,Wind Onshore,0.0 +355 Wind Onshore,355,23.74405048,Wind Onshore,0.0 +356 Wind Onshore,356,60.8633036939,Wind Onshore,0.0 +357 Wind Onshore,357,4.87729945362,Wind Onshore,0.0 +358 Wind Onshore,358,4.33551577107,Wind Onshore,0.0 +359 Wind Onshore,359,9.59431935716,Wind Onshore,0.0 +361 Wind Onshore,361,0.00456719955131,Wind Onshore,0.0 +363 Wind Onshore,363,1.82701375341,Wind Onshore,0.0 +364 Wind Onshore,364,0.298940300013,Wind Onshore,0.0 +365 Wind Onshore,365,3.27832133929,Wind Onshore,0.0 +366 Wind Onshore,366,3.34909004921,Wind Onshore,0.0 +367 Wind Onshore,367,1.64588987762,Wind Onshore,0.0 +368 Wind Onshore,368,11.8540035349,Wind Onshore,0.0 +369 Wind Onshore,369,262.264924035,Wind Onshore,0.0 +370 Wind Onshore,370,30.8367475842,Wind Onshore,0.0 +371 Wind Onshore,371,12.0039872819,Wind Onshore,0.0 +372 Wind Onshore,372,7.87902661745,Wind Onshore,0.0 +373 Wind Onshore,373,2.75749675502,Wind Onshore,0.0 +374 Wind Onshore,374,23.9585741942,Wind Onshore,0.0 +375 Wind Onshore,375,1.95633185604,Wind Onshore,0.0 +376 Wind Onshore,376,17.7082761196,Wind Onshore,0.0 +377 Wind Onshore,377,20.7585904677,Wind Onshore,0.0 +378 Wind Onshore,378,0.161123144974,Wind Onshore,0.0 +379 Wind Onshore,379,0.929763183455,Wind Onshore,0.0 +380 Wind Onshore,380,199.111908709,Wind Onshore,0.0 +381 Wind Onshore,381,13.0632583963,Wind Onshore,0.0 +383 Wind Onshore,383,90.4975589655,Wind Onshore,0.0 +386 Wind Onshore,386,56.928865333,Wind Onshore,0.0 +387 Wind Onshore,387,50.7968954699,Wind Onshore,0.0 +388 Wind Onshore,388,16.0633432056,Wind Onshore,0.0 +389 Wind Onshore,389,218.675423603,Wind Onshore,0.0 +390 Wind Onshore,390,30.1310283897,Wind Onshore,0.0 +392 Wind Onshore,392,0.289238434706,Wind Onshore,0.0 +393 Wind Onshore,393,26.2930875243,Wind Onshore,0.0 +394 Wind Onshore,394,45.6502535882,Wind Onshore,0.0 +395 Wind Onshore,395,1.21534084768,Wind Onshore,0.0 +396 Wind Onshore,396,3.306939311,Wind Onshore,0.0 +397 Wind Onshore,397,29.7730350488,Wind Onshore,0.0 +398 Wind Onshore,398,121.31240995,Wind Onshore,0.0 +399 Wind Onshore,399,0.832524410153,Wind Onshore,0.0 +400 Wind Onshore,400,10.0864820071,Wind Onshore,0.0 +401 Wind Onshore,401,70.0983272434,Wind Onshore,0.0 +402 Wind Onshore,402,205.513321072,Wind Onshore,0.0 +405 Wind Onshore,405,0.734589478908,Wind Onshore,0.0 +406 Wind Onshore,406,1.10210758817,Wind Onshore,0.0 +409 Wind Onshore,409,0.226056957693,Wind Onshore,0.0 +410 Wind Onshore,410,3.97118800864,Wind Onshore,0.0 +411 Wind Onshore,411,214.719214758,Wind Onshore,0.0 +412 Wind Onshore,412,414.115976321,Wind Onshore,0.0 +414 Wind Onshore,414,22.7079271365,Wind Onshore,0.0 +415 Wind Onshore,415,42.3523424512,Wind Onshore,0.0 +416 Wind Onshore,416,0.0949711377878,Wind Onshore,0.0 +417 Wind Onshore,417,0.0159577330889,Wind Onshore,0.0 +418 Wind Onshore,418,0.837260326848,Wind Onshore,0.0 +422 Wind Onshore,422,2.1470467446e-08,Wind Onshore,0.0 +423 Wind Onshore,423,0.147716007334,Wind Onshore,0.0 +424 Wind Onshore,424,1.68832442006,Wind Onshore,0.0 +425 Wind Onshore,425,73.4862311837,Wind Onshore,0.0 +426 Wind Onshore,426,56.4599127019,Wind Onshore,0.0 +427 Wind Onshore,427,0.247292713667,Wind Onshore,0.0 +428 Wind Onshore,428,70.6335891308,Wind Onshore,0.0 +429 Wind Onshore,429,17.1359833072,Wind Onshore,0.0 +430 Wind Onshore,430,63.2071733464,Wind Onshore,0.0 +431 Wind Onshore,431,79.1072159825,Wind Onshore,0.0 +432 Wind Onshore,432,0.0361579458313,Wind Onshore,0.0 +433 Wind Onshore,433,0.0610682185031,Wind Onshore,0.0 +434 Wind Onshore,434,0.000455522242349,Wind Onshore,0.0 +435 Wind Onshore,435,1.22999224722,Wind Onshore,0.0 +436 Wind Onshore,436,43.8638910737,Wind Onshore,0.0 +437 Wind Onshore,437,3.29824335081,Wind Onshore,0.0 +438 Wind Onshore,438,7.96885916892,Wind Onshore,0.0 +439 Wind Onshore,439,0.343307573159,Wind Onshore,0.0 +440 Wind Onshore,440,81.7672084659,Wind Onshore,0.0 +441 Wind Onshore,441,192.966587575,Wind Onshore,0.0 +442 Wind Onshore,442,35.2002569935,Wind Onshore,0.0 +443 Wind Onshore,443,108.220145939,Wind Onshore,0.0 +444 Wind Onshore,444,61.2238159146,Wind Onshore,0.0 +445 Wind Onshore,445,176.213655222,Wind Onshore,0.0 +446 Wind Onshore,446,234.376272084,Wind Onshore,0.0 +447 Wind Onshore,447,246.029905672,Wind Onshore,0.0 +448 Wind Onshore,448,0.805984486427,Wind Onshore,0.0 +449 Wind Onshore,449,1.21335617518,Wind Onshore,0.0 +451 Wind Onshore,451,1.01257029068,Wind Onshore,0.0 +452 Wind Onshore,452,138.839906457,Wind Onshore,0.0 +453 Wind Onshore,453,109.821439285,Wind Onshore,0.0 +454 Wind Onshore,454,4.66913458007,Wind Onshore,0.0 +455 Wind Onshore,455,0.124454538855,Wind Onshore,0.0 +456 Wind Onshore,456,0.673857688536,Wind Onshore,0.0 +457 Wind Onshore,457,3.4935608962,Wind Onshore,0.0 +459 Wind Onshore,459,0.928187582185,Wind Onshore,0.0 +460 Wind Onshore,460,0.213536185243,Wind Onshore,0.0 +461 Wind Onshore,461,3.73281623771,Wind Onshore,0.0 +462 Wind Onshore,462,0.521719224928,Wind Onshore,0.0 +463 Wind Onshore,463,19.3063857702,Wind Onshore,0.0 +464 Wind Onshore,464,1.37650939265,Wind Onshore,0.0 +465 Wind Onshore,465,1.0778860584,Wind Onshore,0.0 +466 Wind Onshore,466,0.073776456668,Wind Onshore,0.0 +467 Wind Onshore,467,0.0261115323765,Wind Onshore,0.0 +468 Wind Onshore,468,25.0845450748,Wind Onshore,0.0 +469 Wind Onshore,469,2.77964085133,Wind Onshore,0.0 +470 Wind Onshore,470,17.8681570236,Wind Onshore,0.0 +471 Wind Onshore,471,1.29719736092,Wind Onshore,0.0 +472 Wind Onshore,472,89.339497458,Wind Onshore,0.0 +473 Wind Onshore,473,26.3289024862,Wind Onshore,0.0 +474 Wind Onshore,474,119.651672188,Wind Onshore,0.0 +475 Wind Onshore,475,134.800517856,Wind Onshore,0.0 +476 Wind Onshore,476,29.5508022611,Wind Onshore,0.0 +477 Wind Onshore,477,10.763986748,Wind Onshore,0.0 +478 Wind Onshore,478,0.211614489114,Wind Onshore,0.0 +479 Wind Onshore,479,41.0786982606,Wind Onshore,0.0 +480 Wind Onshore,480,12.0197862477,Wind Onshore,0.0 +481 Wind Onshore,481,37.466518157,Wind Onshore,0.0 +482 Wind Onshore,482,39.3953665018,Wind Onshore,0.0 +483 Wind Onshore,483,5.98760147471,Wind Onshore,0.0 +484 Wind Onshore,484,5.67979087386,Wind Onshore,0.0 +485 Wind Onshore,485,25.9924891673,Wind Onshore,0.0 +486 Wind Onshore,486,7.45450093216,Wind Onshore,0.0 +487 Wind Onshore,487,7.95800152789,Wind Onshore,0.0 +488 Wind Onshore,488,0.00131066478912,Wind Onshore,0.0 +489 Wind Onshore,489,88.2646125587,Wind Onshore,0.0 +490 Wind Onshore,490,33.260903116,Wind Onshore,0.0 +491 Wind Onshore,491,1.39975698126,Wind Onshore,0.0 +492 Wind Onshore,492,6.97236853525,Wind Onshore,0.0 +493 Wind Onshore,493,4.73049465924,Wind Onshore,0.0 +494 Wind Onshore,494,1.93101668749,Wind Onshore,0.0 +495 Wind Onshore,495,219.092115221,Wind Onshore,0.0 +2_220kV Wind Onshore,2_220kV,21.3664022656,Wind Onshore,0.0 +5_220kV Wind Onshore,5_220kV,68.762601706,Wind Onshore,0.0 +10_220kV Wind Onshore,10_220kV,480.698670974,Wind Onshore,0.0 +12_220kV Wind Onshore,12_220kV,334.885742705,Wind Onshore,0.0 +13_220kV Wind Onshore,13_220kV,13.279826168,Wind Onshore,0.0 +15_220kV Wind Onshore,15_220kV,0.0342484524703,Wind Onshore,0.0 +18_220kV Wind Onshore,18_220kV,227.66202248,Wind Onshore,0.0 +20_220kV Wind Onshore,20_220kV,12.0589307388,Wind Onshore,0.0 +22_220kV Wind Onshore,22_220kV,1289.24118792,Wind Onshore,0.0 +24_220kV Wind Onshore,24_220kV,99.4400143411,Wind Onshore,0.0 +26_220kV Wind Onshore,26_220kV,86.7132172475,Wind Onshore,0.0 +30_220kV Wind Onshore,30_220kV,98.4341164973,Wind Onshore,0.0 +32_220kV Wind Onshore,32_220kV,238.321957679,Wind Onshore,0.0 +37_220kV Wind Onshore,37_220kV,392.815157911,Wind Onshore,0.0 +42_220kV Wind Onshore,42_220kV,99.1031638333,Wind Onshore,0.0 +46_220kV Wind Onshore,46_220kV,0.833608678014,Wind Onshore,0.0 +52_220kV Wind Onshore,52_220kV,0.171578405746,Wind Onshore,0.0 +56_220kV Wind Onshore,56_220kV,0.715522464707,Wind Onshore,0.0 +61_220kV Wind Onshore,61_220kV,103.00507623,Wind Onshore,0.0 +68_220kV Wind Onshore,68_220kV,8.98169605456,Wind Onshore,0.0 +69_220kV Wind Onshore,69_220kV,25.0367994661,Wind Onshore,0.0 +74_220kV Wind Onshore,74_220kV,758.547933152,Wind Onshore,0.0 +78_220kV Wind Onshore,78_220kV,89.4774107804,Wind Onshore,0.0 +86_220kV Wind Onshore,86_220kV,7.52357781345,Wind Onshore,0.0 +87_220kV Wind Onshore,87_220kV,95.4376729474,Wind Onshore,0.0 +94_220kV Wind Onshore,94_220kV,59.2568089536,Wind Onshore,0.0 +95_220kV Wind Onshore,95_220kV,68.1988383832,Wind Onshore,0.0 +96_220kV Wind Onshore,96_220kV,24.0689205783,Wind Onshore,0.0 +99_220kV Wind Onshore,99_220kV,64.9377498058,Wind Onshore,0.0 +100_220kV Wind Onshore,100_220kV,155.126607127,Wind Onshore,0.0 +104_220kV Wind Onshore,104_220kV,0.654438393951,Wind Onshore,0.0 +105_220kV Wind Onshore,105_220kV,50.0548224713,Wind Onshore,0.0 +106_220kV Wind Onshore,106_220kV,2.00267243918,Wind Onshore,0.0 +107_220kV Wind Onshore,107_220kV,0.246199309224,Wind Onshore,0.0 +117_220kV Wind Onshore,117_220kV,5.30905864691,Wind Onshore,0.0 +120_220kV Wind Onshore,120_220kV,101.498472549,Wind Onshore,0.0 +123_220kV Wind Onshore,123_220kV,17.9517371195,Wind Onshore,0.0 +124_220kV Wind Onshore,124_220kV,2.40268588876,Wind Onshore,0.0 +125_220kV Wind Onshore,125_220kV,0.711206857594,Wind Onshore,0.0 +128_220kV Wind Onshore,128_220kV,218.884211419,Wind Onshore,0.0 +129_220kV Wind Onshore,129_220kV,5.2999386857,Wind Onshore,0.0 +138_220kV Wind Onshore,138_220kV,5.68501661608,Wind Onshore,0.0 +143_220kV Wind Onshore,143_220kV,2.09615495407,Wind Onshore,0.0 +156_220kV Wind Onshore,156_220kV,23.7891713059,Wind Onshore,0.0 +157_220kV Wind Onshore,157_220kV,65.0074668763,Wind Onshore,0.0 +159_220kV Wind Onshore,159_220kV,78.9652647132,Wind Onshore,0.0 +160_220kV Wind Onshore,160_220kV,159.165074052,Wind Onshore,0.0 +165_220kV Wind Onshore,165_220kV,11.9801821685,Wind Onshore,0.0 +184_220kV Wind Onshore,184_220kV,8.36260839263,Wind Onshore,0.0 +191_220kV Wind Onshore,191_220kV,1.39894761524,Wind Onshore,0.0 +195_220kV Wind Onshore,195_220kV,0.390879959804,Wind Onshore,0.0 +201_220kV Wind Onshore,201_220kV,250.480113476,Wind Onshore,0.0 +220_220kV Wind Onshore,220_220kV,12.1229743482,Wind Onshore,0.0 +231_220kV Wind Onshore,231_220kV,0.0358333813701,Wind Onshore,0.0 +232_220kV Wind Onshore,232_220kV,5.59260608432,Wind Onshore,0.0 +233_220kV Wind Onshore,233_220kV,18.681964382,Wind Onshore,0.0 +236_220kV Wind Onshore,236_220kV,0.0531461245219,Wind Onshore,0.0 +247_220kV Wind Onshore,247_220kV,17.5108302983,Wind Onshore,0.0 +248_220kV Wind Onshore,248_220kV,3.59964866908,Wind Onshore,0.0 +250_220kV Wind Onshore,250_220kV,0.0299096483521,Wind Onshore,0.0 +251_220kV Wind Onshore,251_220kV,0.563547347789,Wind Onshore,0.0 +252_220kV Wind Onshore,252_220kV,2.89934039102,Wind Onshore,0.0 +261_220kV Wind Onshore,261_220kV,1.14291745916,Wind Onshore,0.0 +264_220kV Wind Onshore,264_220kV,5.56985592836,Wind Onshore,0.0 +267_220kV Wind Onshore,267_220kV,0.118937830652,Wind Onshore,0.0 +272_220kV Wind Onshore,272_220kV,782.463701225,Wind Onshore,0.0 +279_220kV Wind Onshore,279_220kV,84.6228380201,Wind Onshore,0.0 +281_220kV Wind Onshore,281_220kV,229.927503412,Wind Onshore,0.0 +282_220kV Wind Onshore,282_220kV,83.5571752494,Wind Onshore,0.0 +292_220kV Wind Onshore,292_220kV,404.486732631,Wind Onshore,0.0 +303_220kV Wind Onshore,303_220kV,66.9207172853,Wind Onshore,0.0 +307_220kV Wind Onshore,307_220kV,0.000282085714083,Wind Onshore,0.0 +308_220kV Wind Onshore,308_220kV,89.0700055742,Wind Onshore,0.0 +312_220kV Wind Onshore,312_220kV,105.800801794,Wind Onshore,0.0 +315_220kV Wind Onshore,315_220kV,2.28667590888,Wind Onshore,0.0 +317_220kV Wind Onshore,317_220kV,0.15481733758,Wind Onshore,0.0 +322_220kV Wind Onshore,322_220kV,8.16533291814,Wind Onshore,0.0 +332_220kV Wind Onshore,332_220kV,0.938928397166,Wind Onshore,0.0 +334_220kV Wind Onshore,334_220kV,45.9721869349,Wind Onshore,0.0 +336_220kV Wind Onshore,336_220kV,188.822835863,Wind Onshore,0.0 +338_220kV Wind Onshore,338_220kV,26.7659131433,Wind Onshore,0.0 +351_220kV Wind Onshore,351_220kV,56.406717084,Wind Onshore,0.0 +353_220kV Wind Onshore,353_220kV,15.4380419165,Wind Onshore,0.0 +360_220kV Wind Onshore,360_220kV,0.065259330574,Wind Onshore,0.0 +362_220kV Wind Onshore,362_220kV,0.0051931708536,Wind Onshore,0.0 +382_220kV Wind Onshore,382_220kV,107.008141438,Wind Onshore,0.0 +384_220kV Wind Onshore,384_220kV,155.221920322,Wind Onshore,0.0 +385_220kV Wind Onshore,385_220kV,64.1300521047,Wind Onshore,0.0 +391_220kV Wind Onshore,391_220kV,23.0517859404,Wind Onshore,0.0 +403_220kV Wind Onshore,403_220kV,0.0277114755375,Wind Onshore,0.0 +404_220kV Wind Onshore,404_220kV,0.00632964140695,Wind Onshore,0.0 +413_220kV Wind Onshore,413_220kV,0.0288099331934,Wind Onshore,0.0 +421_220kV Wind Onshore,421_220kV,1.28680397051,Wind Onshore,0.0 +450_220kV Wind Onshore,450_220kV,0.670542049842,Wind Onshore,0.0 +458_220kV Wind Onshore,458_220kV,0.0465395074554,Wind Onshore,0.0 +1 Solar,1,79.6742560609,Solar,0.0 +3 Solar,3,31.1791155392,Solar,0.0 +4 Solar,4,40.212665684,Solar,0.0 +6 Solar,6,20.3045209459,Solar,0.0 +7 Solar,7,26.1592506043,Solar,0.0 +8 Solar,8,68.4254026128,Solar,0.0 +9 Solar,9,44.4785577653,Solar,0.0 +11 Solar,11,103.998682344,Solar,0.0 +14 Solar,14,41.3867255852,Solar,0.0 +16 Solar,16,84.3780121085,Solar,0.0 +17 Solar,17,79.0551550636,Solar,0.0 +19 Solar,19,183.821408516,Solar,0.0 +21 Solar,21,37.3790334569,Solar,0.0 +23 Solar,23,37.369338741,Solar,0.0 +25 Solar,25,29.5166073047,Solar,0.0 +27 Solar,27,8.93177879641,Solar,0.0 +28 Solar,28,39.2614503805,Solar,0.0 +29 Solar,29,99.7401656585,Solar,0.0 +31 Solar,31,130.774402129,Solar,0.0 +33 Solar,33,201.766962889,Solar,0.0 +34 Solar,34,4.16018945038,Solar,0.0 +35 Solar,35,3.42991713565,Solar,0.0 +36 Solar,36,50.2879465505,Solar,0.0 +38 Solar,38,14.7886688836,Solar,0.0 +39 Solar,39,121.437125869,Solar,0.0 +40 Solar,40,185.545127919,Solar,0.0 +41 Solar,41,255.443429575,Solar,0.0 +43 Solar,43,362.597919288,Solar,0.0 +44 Solar,44,21.273778898,Solar,0.0 +45 Solar,45,77.0174857744,Solar,0.0 +47 Solar,47,5.21471479441,Solar,0.0 +48 Solar,48,9.57649096897,Solar,0.0 +49 Solar,49,54.0354709189,Solar,0.0 +50 Solar,50,92.4802821362,Solar,0.0 +51 Solar,51,155.333413197,Solar,0.0 +53 Solar,53,103.86577437,Solar,0.0 +54 Solar,54,40.3763462646,Solar,0.0 +55 Solar,55,25.9902423186,Solar,0.0 +57 Solar,57,24.6223726015,Solar,0.0 +58 Solar,58,112.609207037,Solar,0.0 +59 Solar,59,86.3959417736,Solar,0.0 +60 Solar,60,19.1273788624,Solar,0.0 +62 Solar,62,61.8883512149,Solar,0.0 +63 Solar,63,106.233906737,Solar,0.0 +64 Solar,64,58.2728200809,Solar,0.0 +65 Solar,65,12.8393796824,Solar,0.0 +66 Solar,66,358.676350566,Solar,0.0 +67 Solar,67,29.5317709447,Solar,0.0 +70 Solar,70,89.3005966177,Solar,0.0 +71 Solar,71,328.718571189,Solar,0.0 +72 Solar,72,243.241909161,Solar,0.0 +73 Solar,73,203.443402523,Solar,0.0 +75 Solar,75,232.127956348,Solar,0.0 +76 Solar,76,80.4037717732,Solar,0.0 +77 Solar,77,144.715972126,Solar,0.0 +79 Solar,79,153.606375759,Solar,0.0 +80 Solar,80,34.2620170352,Solar,0.0 +81 Solar,81,217.054487558,Solar,0.0 +82 Solar,82,16.3482224843,Solar,0.0 +83 Solar,83,35.1643325868,Solar,0.0 +84 Solar,84,21.8924923001,Solar,0.0 +85 Solar,85,156.277964167,Solar,0.0 +88 Solar,88,8.1510919734,Solar,0.0 +89 Solar,89,1.79196759744,Solar,0.0 +90 Solar,90,81.2499299671,Solar,0.0 +91 Solar,91,3.68518233687,Solar,0.0 +92 Solar,92,61.3154903889,Solar,0.0 +93 Solar,93,191.635296053,Solar,0.0 +97 Solar,97,59.4243426049,Solar,0.0 +98 Solar,98,48.5382676619,Solar,0.0 +101 Solar,101,119.077524519,Solar,0.0 +102 Solar,102,142.844699621,Solar,0.0 +103 Solar,103,9.84236066,Solar,0.0 +108 Solar,108,7.18318255245,Solar,0.0 +109 Solar,109,9.5608896996,Solar,0.0 +110 Solar,110,54.7900098014,Solar,0.0 +111 Solar,111,138.730049031,Solar,0.0 +112 Solar,112,2.85898664312,Solar,0.0 +113 Solar,113,4.79549373064,Solar,0.0 +114 Solar,114,86.7035713777,Solar,0.0 +115 Solar,115,25.3565007917,Solar,0.0 +116 Solar,116,7.64365313712,Solar,0.0 +118 Solar,118,45.6581688249,Solar,0.0 +119 Solar,119,26.209248478,Solar,0.0 +121 Solar,121,16.3630315106,Solar,0.0 +122 Solar,122,43.4771775464,Solar,0.0 +126 Solar,126,42.0249070995,Solar,0.0 +127 Solar,127,19.3952363453,Solar,0.0 +130 Solar,130,19.4912494116,Solar,0.0 +131 Solar,131,6.05227179856,Solar,0.0 +132 Solar,132,18.5716563093,Solar,0.0 +133 Solar,133,6.42055374372,Solar,0.0 +134 Solar,134,10.8112029419,Solar,0.0 +135 Solar,135,27.9762174524,Solar,0.0 +136 Solar,136,28.1838273805,Solar,0.0 +137 Solar,137,193.234776211,Solar,0.0 +139 Solar,139,6.86558614609,Solar,0.0 +140 Solar,140,10.6310697811,Solar,0.0 +141 Solar,141,4.92036804098,Solar,0.0 +142 Solar,142,6.68963671768,Solar,0.0 +144 Solar,144,1.91202440048,Solar,0.0 +145 Solar,145,8.05638783349,Solar,0.0 +146 Solar,146,5.69359712972,Solar,0.0 +147 Solar,147,20.7171111431,Solar,0.0 +148 Solar,148,8.93845403313,Solar,0.0 +149 Solar,149,65.1824653363,Solar,0.0 +150 Solar,150,11.8788620772,Solar,0.0 +151 Solar,151,10.1964958266,Solar,0.0 +152 Solar,152,66.6139926948,Solar,0.0 +153 Solar,153,121.671046677,Solar,0.0 +154 Solar,154,12.4525836658,Solar,0.0 +155 Solar,155,32.4862488557,Solar,0.0 +158 Solar,158,25.8748930976,Solar,0.0 +161 Solar,161,67.4333290023,Solar,0.0 +162 Solar,162,9.64304372622,Solar,0.0 +163 Solar,163,19.1661348612,Solar,0.0 +164 Solar,164,25.1814057028,Solar,0.0 +166 Solar,166,29.114828087,Solar,0.0 +167 Solar,167,12.1621880467,Solar,0.0 +168 Solar,168,29.1835928188,Solar,0.0 +169 Solar,169,2.23765189247,Solar,0.0 +170 Solar,170,5.02370478794,Solar,0.0 +171 Solar,171,11.7306227274,Solar,0.0 +172 Solar,172,3.41768401337,Solar,0.0 +173 Solar,173,11.7659600244,Solar,0.0 +174 Solar,174,11.1448823949,Solar,0.0 +175 Solar,175,3.73270979541,Solar,0.0 +176 Solar,176,17.4348273992,Solar,0.0 +177 Solar,177,109.345623408,Solar,0.0 +178 Solar,178,23.4815557252,Solar,0.0 +179 Solar,179,176.928964154,Solar,0.0 +180 Solar,180,161.276649117,Solar,0.0 +181 Solar,181,84.0705616914,Solar,0.0 +182 Solar,182,18.431240768,Solar,0.0 +183 Solar,183,47.5302099481,Solar,0.0 +185 Solar,185,10.7907104783,Solar,0.0 +186 Solar,186,2.68010525828,Solar,0.0 +187 Solar,187,5.63130043544,Solar,0.0 +188 Solar,188,1.87938058935,Solar,0.0 +189 Solar,189,91.7766704254,Solar,0.0 +190 Solar,190,186.94217135,Solar,0.0 +192 Solar,192,138.604086709,Solar,0.0 +193 Solar,193,3.04032494127,Solar,0.0 +194 Solar,194,1.60064915912,Solar,0.0 +196 Solar,196,1.57820666119,Solar,0.0 +197 Solar,197,48.6597169142,Solar,0.0 +198 Solar,198,2.87787743383,Solar,0.0 +199 Solar,199,5.22728175119,Solar,0.0 +200 Solar,200,3.92704342258,Solar,0.0 +202 Solar,202,72.6779347262,Solar,0.0 +203 Solar,203,111.043024571,Solar,0.0 +204 Solar,204,62.4049705273,Solar,0.0 +205 Solar,205,10.5799252948,Solar,0.0 +206 Solar,206,4.09103357811,Solar,0.0 +207 Solar,207,47.970380957,Solar,0.0 +208 Solar,208,31.23366266,Solar,0.0 +209 Solar,209,83.1733681904,Solar,0.0 +210 Solar,210,13.8872934732,Solar,0.0 +211 Solar,211,226.804107757,Solar,0.0 +212 Solar,212,71.3804125043,Solar,0.0 +213 Solar,213,52.1481022426,Solar,0.0 +214 Solar,214,17.3006198894,Solar,0.0 +215 Solar,215,39.8924684097,Solar,0.0 +216 Solar,216,9.18976482456,Solar,0.0 +217 Solar,217,40.5756460295,Solar,0.0 +218 Solar,218,74.9224344102,Solar,0.0 +219 Solar,219,81.0354832754,Solar,0.0 +221 Solar,221,112.018080105,Solar,0.0 +222 Solar,222,18.1587294633,Solar,0.0 +223 Solar,223,116.696893662,Solar,0.0 +224 Solar,224,116.477465463,Solar,0.0 +225 Solar,225,228.386530018,Solar,0.0 +226 Solar,226,26.5015733691,Solar,0.0 +227 Solar,227,86.2365746488,Solar,0.0 +228 Solar,228,53.6568323407,Solar,0.0 +229 Solar,229,23.1327737351,Solar,0.0 +230 Solar,230,53.342089814,Solar,0.0 +234 Solar,234,136.821485252,Solar,0.0 +235 Solar,235,25.6063102247,Solar,0.0 +237 Solar,237,5.35610593423,Solar,0.0 +238 Solar,238,55.8258151624,Solar,0.0 +239 Solar,239,37.2971956496,Solar,0.0 +240 Solar,240,25.1498061516,Solar,0.0 +241 Solar,241,48.5877681047,Solar,0.0 +242 Solar,242,127.647586328,Solar,0.0 +245 Solar,245,52.0517875922,Solar,0.0 +246 Solar,246,6.76177981575,Solar,0.0 +249 Solar,249,11.7078004149,Solar,0.0 +253 Solar,253,26.2886918355,Solar,0.0 +254 Solar,254,226.257418351,Solar,0.0 +255 Solar,255,183.631946845,Solar,0.0 +256 Solar,256,7.21385449034,Solar,0.0 +257 Solar,257,75.6388526054,Solar,0.0 +258 Solar,258,90.7750732185,Solar,0.0 +259 Solar,259,42.3125380802,Solar,0.0 +260 Solar,260,155.279397297,Solar,0.0 +262 Solar,262,156.979011577,Solar,0.0 +265 Solar,265,82.9282512593,Solar,0.0 +266 Solar,266,301.614349192,Solar,0.0 +268 Solar,268,19.4889665285,Solar,0.0 +269 Solar,269,54.0671688866,Solar,0.0 +270 Solar,270,82.1519469094,Solar,0.0 +271 Solar,271,21.3295659833,Solar,0.0 +273 Solar,273,2.85527347592,Solar,0.0 +274 Solar,274,36.2687832585,Solar,0.0 +275 Solar,275,55.7508443105,Solar,0.0 +276 Solar,276,84.3085014161,Solar,0.0 +277 Solar,277,0.456443333038,Solar,0.0 +278 Solar,278,65.2833137834,Solar,0.0 +280 Solar,280,220.758669251,Solar,0.0 +283 Solar,283,51.3671639748,Solar,0.0 +284 Solar,284,151.902129991,Solar,0.0 +285 Solar,285,383.858473404,Solar,0.0 +286 Solar,286,60.6556523832,Solar,0.0 +287 Solar,287,77.3752768096,Solar,0.0 +288 Solar,288,153.887449491,Solar,0.0 +289 Solar,289,89.4394256267,Solar,0.0 +290 Solar,290,194.473407228,Solar,0.0 +291 Solar,291,116.049525596,Solar,0.0 +293 Solar,293,33.5259472314,Solar,0.0 +294 Solar,294,66.6389544354,Solar,0.0 +295 Solar,295,35.869318087,Solar,0.0 +296 Solar,296,25.6191621951,Solar,0.0 +297 Solar,297,0.986841253211,Solar,0.0 +298 Solar,298,3.77532544167,Solar,0.0 +299 Solar,299,31.2155911865,Solar,0.0 +300 Solar,300,172.123236393,Solar,0.0 +301 Solar,301,25.9637060018,Solar,0.0 +302 Solar,302,67.2193133171,Solar,0.0 +304 Solar,304,218.703564343,Solar,0.0 +305 Solar,305,22.1918482816,Solar,0.0 +306 Solar,306,18.4162831018,Solar,0.0 +309 Solar,309,136.190504425,Solar,0.0 +310 Solar,310,79.2700055257,Solar,0.0 +311 Solar,311,408.37422005,Solar,0.0 +313 Solar,313,46.2770010595,Solar,0.0 +314 Solar,314,93.8077865618,Solar,0.0 +316 Solar,316,197.087429677,Solar,0.0 +318 Solar,318,311.473266956,Solar,0.0 +319 Solar,319,156.546089273,Solar,0.0 +320 Solar,320,114.687410687,Solar,0.0 +321 Solar,321,48.4439464501,Solar,0.0 +323 Solar,323,107.077621671,Solar,0.0 +324 Solar,324,90.1366736759,Solar,0.0 +325 Solar,325,21.9947694651,Solar,0.0 +326 Solar,326,112.249862957,Solar,0.0 +327 Solar,327,99.8112078997,Solar,0.0 +328 Solar,328,95.5908999584,Solar,0.0 +329 Solar,329,159.818571632,Solar,0.0 +330 Solar,330,235.581664439,Solar,0.0 +331 Solar,331,479.248155829,Solar,0.0 +333 Solar,333,276.640075051,Solar,0.0 +335 Solar,335,272.215344887,Solar,0.0 +337 Solar,337,533.823158688,Solar,0.0 +339 Solar,339,88.5771395366,Solar,0.0 +340 Solar,340,197.787397172,Solar,0.0 +341 Solar,341,111.203655583,Solar,0.0 +342 Solar,342,199.45798322,Solar,0.0 +343 Solar,343,80.0706265568,Solar,0.0 +344 Solar,344,78.4850441808,Solar,0.0 +345 Solar,345,97.8729744075,Solar,0.0 +346 Solar,346,7.06706321303,Solar,0.0 +347 Solar,347,9.94901011501,Solar,0.0 +348 Solar,348,10.7390924222,Solar,0.0 +349 Solar,349,240.920274276,Solar,0.0 +350 Solar,350,275.412619907,Solar,0.0 +352 Solar,352,195.868212622,Solar,0.0 +354 Solar,354,123.060645601,Solar,0.0 +355 Solar,355,9.48628233579,Solar,0.0 +356 Solar,356,24.0580395312,Solar,0.0 +357 Solar,357,1.41216689344,Solar,0.0 +358 Solar,358,1.17103382504,Solar,0.0 +359 Solar,359,8.91430562005,Solar,0.0 +361 Solar,361,41.7228166946,Solar,0.0 +363 Solar,363,6.61764092982,Solar,0.0 +364 Solar,364,3.33585971783,Solar,0.0 +365 Solar,365,10.4345225167,Solar,0.0 +366 Solar,366,63.4117651656,Solar,0.0 +367 Solar,367,14.8357577621,Solar,0.0 +368 Solar,368,51.1022503929,Solar,0.0 +369 Solar,369,79.2867662718,Solar,0.0 +370 Solar,370,21.0067488094,Solar,0.0 +371 Solar,371,11.3051921841,Solar,0.0 +372 Solar,372,15.1825713303,Solar,0.0 +373 Solar,373,399.133201245,Solar,0.0 +374 Solar,374,23.2049103442,Solar,0.0 +375 Solar,375,21.4693567656,Solar,0.0 +376 Solar,376,28.1564825237,Solar,0.0 +377 Solar,377,118.262711802,Solar,0.0 +378 Solar,378,33.0312281458,Solar,0.0 +379 Solar,379,74.1394082622,Solar,0.0 +380 Solar,380,53.4082985092,Solar,0.0 +381 Solar,381,47.3452596402,Solar,0.0 +383 Solar,383,180.894957129,Solar,0.0 +386 Solar,386,62.5726417247,Solar,0.0 +387 Solar,387,61.9531425507,Solar,0.0 +388 Solar,388,30.4451691568,Solar,0.0 +389 Solar,389,280.614896696,Solar,0.0 +390 Solar,390,173.515675268,Solar,0.0 +392 Solar,392,1.5383477261,Solar,0.0 +393 Solar,393,79.7135796057,Solar,0.0 +394 Solar,394,196.679379837,Solar,0.0 +395 Solar,395,170.831583767,Solar,0.0 +396 Solar,396,131.456153348,Solar,0.0 +397 Solar,397,56.5650540612,Solar,0.0 +398 Solar,398,81.5919504805,Solar,0.0 +399 Solar,399,74.1012522785,Solar,0.0 +400 Solar,400,30.3035516899,Solar,0.0 +401 Solar,401,43.2989213414,Solar,0.0 +402 Solar,402,69.2632549986,Solar,0.0 +405 Solar,405,27.7247677283,Solar,0.0 +406 Solar,406,69.8900097171,Solar,0.0 +409 Solar,409,26.5606245666,Solar,0.0 +410 Solar,410,81.3619621142,Solar,0.0 +411 Solar,411,102.529569026,Solar,0.0 +412 Solar,412,109.809789706,Solar,0.0 +414 Solar,414,6.41796892856,Solar,0.0 +415 Solar,415,12.6293305345,Solar,0.0 +416 Solar,416,25.5809127568,Solar,0.0 +417 Solar,417,183.749132895,Solar,0.0 +418 Solar,418,132.975197331,Solar,0.0 +422 Solar,422,22.9826318392,Solar,0.0 +423 Solar,423,7.46863292826,Solar,0.0 +424 Solar,424,19.2646857571,Solar,0.0 +425 Solar,425,32.3842943932,Solar,0.0 +426 Solar,426,31.4360021066,Solar,0.0 +427 Solar,427,3.74984023399,Solar,0.0 +428 Solar,428,120.215573965,Solar,0.0 +429 Solar,429,9.51469636955,Solar,0.0 +430 Solar,430,37.6190969078,Solar,0.0 +431 Solar,431,48.5405800132,Solar,0.0 +432 Solar,432,7.95644447902,Solar,0.0 +433 Solar,433,37.6069157472,Solar,0.0 +434 Solar,434,30.1167105203,Solar,0.0 +435 Solar,435,2.24116723731,Solar,0.0 +436 Solar,436,121.687485113,Solar,0.0 +437 Solar,437,63.6543584058,Solar,0.0 +438 Solar,438,41.2295969441,Solar,0.0 +439 Solar,439,27.3744152868,Solar,0.0 +440 Solar,440,25.3943741814,Solar,0.0 +441 Solar,441,84.1636035878,Solar,0.0 +442 Solar,442,26.86114381,Solar,0.0 +443 Solar,443,32.5128258133,Solar,0.0 +444 Solar,444,30.0637291945,Solar,0.0 +445 Solar,445,138.236316083,Solar,0.0 +446 Solar,446,68.0977716531,Solar,0.0 +447 Solar,447,98.2891409367,Solar,0.0 +448 Solar,448,2.04128815414,Solar,0.0 +449 Solar,449,6.45337371893,Solar,0.0 +451 Solar,451,2.75944832878,Solar,0.0 +452 Solar,452,54.5646653518,Solar,0.0 +453 Solar,453,52.6484439662,Solar,0.0 +454 Solar,454,1.68385355098,Solar,0.0 +455 Solar,455,9.02528346733,Solar,0.0 +456 Solar,456,3.55441515269,Solar,0.0 +457 Solar,457,7.83757575517,Solar,0.0 +459 Solar,459,4.93667191188,Solar,0.0 +460 Solar,460,1.13571664617,Solar,0.0 +461 Solar,461,8.37432949145,Solar,0.0 +462 Solar,462,1.75288097417,Solar,0.0 +463 Solar,463,38.4674700625,Solar,0.0 +464 Solar,464,4.53579937256,Solar,0.0 +465 Solar,465,5.12064063763,Solar,0.0 +466 Solar,466,6.94095668978,Solar,0.0 +467 Solar,467,5.86546800139,Solar,0.0 +468 Solar,468,47.5101752353,Solar,0.0 +469 Solar,469,5.25539819324,Solar,0.0 +470 Solar,470,16.9376711255,Solar,0.0 +471 Solar,471,4.14121117669,Solar,0.0 +472 Solar,472,91.3351836439,Solar,0.0 +473 Solar,473,24.8426974,Solar,0.0 +474 Solar,474,30.8363184682,Solar,0.0 +475 Solar,475,38.1469896962,Solar,0.0 +476 Solar,476,46.5682001194,Solar,0.0 +477 Solar,477,31.3475721608,Solar,0.0 +478 Solar,478,0.14062786392,Solar,0.0 +479 Solar,479,45.2572344968,Solar,0.0 +480 Solar,480,14.824594689,Solar,0.0 +481 Solar,481,18.2704986262,Solar,0.0 +482 Solar,482,12.0004959782,Solar,0.0 +483 Solar,483,78.1145093775,Solar,0.0 +484 Solar,484,133.539659181,Solar,0.0 +485 Solar,485,281.819662447,Solar,0.0 +486 Solar,486,135.492385271,Solar,0.0 +487 Solar,487,79.1844275783,Solar,0.0 +488 Solar,488,9.16060720929,Solar,0.0 +489 Solar,489,72.3565226403,Solar,0.0 +490 Solar,490,28.8936368193,Solar,0.0 +491 Solar,491,50.4778661943,Solar,0.0 +492 Solar,492,32.8746763897,Solar,0.0 +493 Solar,493,20.2594855058,Solar,0.0 +494 Solar,494,8.18979902657,Solar,0.0 +495 Solar,495,101.236915064,Solar,0.0 +2_220kV Solar,2_220kV,6.78252247482,Solar,0.0 +5_220kV Solar,5_220kV,15.9445613219,Solar,0.0 +10_220kV Solar,10_220kV,159.570248092,Solar,0.0 +12_220kV Solar,12_220kV,277.793547711,Solar,0.0 +13_220kV Solar,13_220kV,186.387377279,Solar,0.0 +15_220kV Solar,15_220kV,120.486097306,Solar,0.0 +18_220kV Solar,18_220kV,61.1612999846,Solar,0.0 +20_220kV Solar,20_220kV,9.95471989703,Solar,0.0 +22_220kV Solar,22_220kV,230.400934825,Solar,0.0 +24_220kV Solar,24_220kV,66.1161367928,Solar,0.0 +26_220kV Solar,26_220kV,21.8361627105,Solar,0.0 +30_220kV Solar,30_220kV,36.5183261461,Solar,0.0 +32_220kV Solar,32_220kV,135.324360757,Solar,0.0 +37_220kV Solar,37_220kV,96.6101777695,Solar,0.0 +42_220kV Solar,42_220kV,65.6688094022,Solar,0.0 +46_220kV Solar,46_220kV,1.50988418606,Solar,0.0 +52_220kV Solar,52_220kV,4.80483205253,Solar,0.0 +56_220kV Solar,56_220kV,11.0049726358,Solar,0.0 +61_220kV Solar,61_220kV,38.0755601028,Solar,0.0 +68_220kV Solar,68_220kV,46.7223785366,Solar,0.0 +69_220kV Solar,69_220kV,44.2391882093,Solar,0.0 +74_220kV Solar,74_220kV,383.418198179,Solar,0.0 +78_220kV Solar,78_220kV,62.3356431164,Solar,0.0 +86_220kV Solar,86_220kV,6.00797041814,Solar,0.0 +87_220kV Solar,87_220kV,134.60573289,Solar,0.0 +94_220kV Solar,94_220kV,89.4256193232,Solar,0.0 +95_220kV Solar,95_220kV,103.003683059,Solar,0.0 +96_220kV Solar,96_220kV,18.9808285849,Solar,0.0 +99_220kV Solar,99_220kV,104.495096936,Solar,0.0 +100_220kV Solar,100_220kV,70.7548700462,Solar,0.0 +104_220kV Solar,104_220kV,3.65255829765,Solar,0.0 +105_220kV Solar,105_220kV,93.133764568,Solar,0.0 +106_220kV Solar,106_220kV,10.6514426275,Solar,0.0 +107_220kV Solar,107_220kV,1.30943921024,Solar,0.0 +117_220kV Solar,117_220kV,17.9962458899,Solar,0.0 +120_220kV Solar,120_220kV,83.0754127741,Solar,0.0 +123_220kV Solar,123_220kV,10.1932288932,Solar,0.0 +124_220kV Solar,124_220kV,0.866492729284,Solar,0.0 +125_220kV Solar,125_220kV,7.9175966632,Solar,0.0 +128_220kV Solar,128_220kV,67.7239512483,Solar,0.0 +129_220kV Solar,129_220kV,8.92855603065,Solar,0.0 +138_220kV Solar,138_220kV,12.7268921642,Solar,0.0 +143_220kV Solar,143_220kV,6.62525480382,Solar,0.0 +156_220kV Solar,156_220kV,38.7248883709,Solar,0.0 +157_220kV Solar,157_220kV,131.682322011,Solar,0.0 +159_220kV Solar,159_220kV,59.7917587743,Solar,0.0 +160_220kV Solar,160_220kV,35.9869279893,Solar,0.0 +165_220kV Solar,165_220kV,13.5798950324,Solar,0.0 +184_220kV Solar,184_220kV,17.8414810649,Solar,0.0 +191_220kV Solar,191_220kV,3.15017898174,Solar,0.0 +195_220kV Solar,195_220kV,0.731379679998,Solar,0.0 +201_220kV Solar,201_220kV,66.5941206082,Solar,0.0 +220_220kV Solar,220_220kV,18.7854907646,Solar,0.0 +231_220kV Solar,231_220kV,2.67419933099,Solar,0.0 +232_220kV Solar,232_220kV,99.0742349063,Solar,0.0 +233_220kV Solar,233_220kV,201.733891319,Solar,0.0 +236_220kV Solar,236_220kV,15.0481177122,Solar,0.0 +247_220kV Solar,247_220kV,236.871780957,Solar,0.0 +248_220kV Solar,248_220kV,14.4044085501,Solar,0.0 +250_220kV Solar,250_220kV,0.0283784055798,Solar,0.0 +251_220kV Solar,251_220kV,0.534696196047,Solar,0.0 +252_220kV Solar,252_220kV,2.75090688335,Solar,0.0 +261_220kV Solar,261_220kV,81.9291796662,Solar,0.0 +263_220kV Solar,263_220kV,2.17499037282,Solar,0.0 +264_220kV Solar,264_220kV,226.383636562,Solar,0.0 +267_220kV Solar,267_220kV,1.33159165748,Solar,0.0 +272_220kV Solar,272_220kV,189.722791697,Solar,0.0 +279_220kV Solar,279_220kV,100.619409992,Solar,0.0 +281_220kV Solar,281_220kV,144.682716774,Solar,0.0 +282_220kV Solar,282_220kV,134.937779598,Solar,0.0 +292_220kV Solar,292_220kV,155.990147453,Solar,0.0 +303_220kV Solar,303_220kV,94.4706110276,Solar,0.0 +307_220kV Solar,307_220kV,41.9758348816,Solar,0.0 +308_220kV Solar,308_220kV,298.346979291,Solar,0.0 +312_220kV Solar,312_220kV,393.914066526,Solar,0.0 +315_220kV Solar,315_220kV,251.944938615,Solar,0.0 +317_220kV Solar,317_220kV,10.3782881081,Solar,0.0 +322_220kV Solar,322_220kV,50.3514256534,Solar,0.0 +332_220kV Solar,332_220kV,849.576943945,Solar,0.0 +334_220kV Solar,334_220kV,233.477991242,Solar,0.0 +336_220kV Solar,336_220kV,242.698642978,Solar,0.0 +338_220kV Solar,338_220kV,255.834575646,Solar,0.0 +351_220kV Solar,351_220kV,262.446697862,Solar,0.0 +353_220kV Solar,353_220kV,91.306831625,Solar,0.0 +360_220kV Solar,360_220kV,26.5925612809,Solar,0.0 +362_220kV Solar,362_220kV,12.1265109683,Solar,0.0 +382_220kV Solar,382_220kV,199.184531465,Solar,0.0 +384_220kV Solar,384_220kV,579.835050797,Solar,0.0 +385_220kV Solar,385_220kV,122.315702959,Solar,0.0 +391_220kV Solar,391_220kV,30.6064358953,Solar,0.0 +403_220kV Solar,403_220kV,23.6450711466,Solar,0.0 +404_220kV Solar,404_220kV,17.7333803642,Solar,0.0 +413_220kV Solar,413_220kV,72.0714562574,Solar,0.0 +421_220kV Solar,421_220kV,59.6608875033,Solar,0.0 +450_220kV Solar,450_220kV,1.68150652412,Solar,0.0 +458_220kV Solar,458_220kV,0.116706305643,Solar,0.0 diff --git a/PyPSA/source/examples/networks/scigrid-de/scigrid-de/investment_periods.csv b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/investment_periods.csv new file mode 100644 index 0000000000000000000000000000000000000000..34dd42197768b92fdb696fcfe70396544f01bb62 --- /dev/null +++ b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/investment_periods.csv @@ -0,0 +1 @@ +period,objective,years diff --git a/PyPSA/source/examples/networks/scigrid-de/scigrid-de/lines.csv b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/lines.csv new file mode 100644 index 0000000000000000000000000000000000000000..d6041d55594921a60987b36aa69fb0d0c81b1239 --- /dev/null +++ b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/lines.csv @@ -0,0 +1,853 @@ +name,bus0,bus1,type,s_nom,length,num_parallel,wkt_srid_4326,voltage,from_relation,r_ohmkm,ref,cables,x_ohmkm,wires,osm_name,operator,c_nfkm,frequency,length_m,i_th_max_a +1,1,2_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,43.379,1.0,"SRID=4326;LINESTRING(9.52257596986262 52.3604090557601,9.11321007472722 52.5438533223737)",220000.0,67129.0,0.08,2016,3.0,0.32,2.0,Leitung 2016,TenneT,11.5,50.0,43379.0,1.3 +2,3,4,Al/St 240/40 4-bundle 380.0,3422.53239576,72.686,2.0,"SRID=4326;LINESTRING(9.38974509624863 52.0263130660355,9.12526570294975 52.5382640870749)",380000.0,67131.0,0.0125,,6.0,0.125,4.0,Leitung 3011,TenneT,27.4,50.0,72686.0,5.2 +3,5_220kV,6,Al/St 240/40 2-bundle 220.0,990.733061929,33.943,2.0,"SRID=4326;LINESTRING(10.3662749375017 52.2846467462009,9.91814864613865 52.3799963125719)",220000.0,67151.0,0.04,1,6.0,0.16,2.0,Leitung 2024,TenneT,23.0,50.0,33943.0,2.6 +4,7,5,Al/St 240/40 4-bundle 380.0,1711.26619788,33.471,1.0,"SRID=4326;LINESTRING(9.91717971972185 52.2781686139394,10.3662749375017 52.2846467462009)",380000.0,67153.0,0.025,3026,3.0,0.25,4.0,Leitung 3026,TenneT,13.7,50.0,33471.0,2.6 +5,8,9,Al/St 240/40 4-bundle 380.0,3422.53239576,28.163,2.0,"SRID=4326;LINESTRING(10.4149923381504 53.4126068830249,10.3787705903765 53.2197927685849)",380000.0,67585.0,0.0125,,6.0,0.125,4.0,KKW Krümmel - UW Lüneburg,transpower,27.4,50.0,28163.0,5.2 +6,10_220kV,11,Al/St 240/40 2-bundle 220.0,495.366530965,27.565,1.0,"SRID=4326;LINESTRING(12.9416466019288 52.5621144045848,13.2061029867985 52.6571362773373)",220000.0,69042.0,0.08,295,3.0,0.32,2.0,295,50Hertz,11.5,50.0,27565.0,1.3 +7,11,12_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,45.809,1.0,"SRID=4326;LINESTRING(13.2061029867985 52.6571362773373,13.7096697058228 52.5397952580643)",220000.0,69043.0,0.08,293,3.0,0.32,2.0,293,50Hertz,11.5,50.0,45809.0,1.3 +8,10_220kV,12_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,68.39,1.0,"SRID=4326;LINESTRING(12.9416466019288 52.5621144045848,13.7096697058228 52.5397952580643)",220000.0,69045.0,0.08,294,3.0,0.32,2.0,294,50Hertz,11.5,50.0,68390.0,1.3 +9,13_220kV,14,Al/St 240/40 2-bundle 220.0,495.366530965,39.282,1.0,"SRID=4326;LINESTRING(11.3704262486892 48.2914922329548,11.8132328973194 48.2212664654998)",220000.0,69488.0,0.08,263,3.0,0.32,2.0,Oba-Nfi I,TenneT TSO GmbH,11.5,50.0,39282.0,1.3 +10,13,15,Al/St 240/40 4-bundle 380.0,855.633098939,44.323,0.5,"SRID=4326;LINESTRING(11.3704262486892 48.2914922329548,11.8681562938884 48.2068029185353)",380000.0,69489.0,0.05,461,3.0,0.5,2.0,Oba-Oh,transpower,6.85,,44323.0,1.3 +11,16,5,Al/St 240/40 4-bundle 380.0,1711.26619788,36.253,1.0,"SRID=4326;LINESTRING(10.7610403785613 52.3677293804096,10.3662749375017 52.2846467462009)",380000.0,71281.0,0.025,2,3.0,0.25,4.0,Leitung 3023,TenneT,13.7,50.0,36253.0,2.6 +12,17,18,Al/St 240/40 4-bundle 380.0,1283.44964841,200.291,0.75,"SRID=4326;LINESTRING(13.4947633747219 52.5897644435618,13.6834769666546 54.1390906406635)",380000.0,109962.0,0.033333,518,3.0,0.333333,3.0,518,50Hertz,10.275,50.0,200291.0,1.95 +13,17,12,Al/St 240/40 4-bundle 380.0,1283.44964841,24.268,0.75,"SRID=4326;LINESTRING(13.4947633747219 52.5897644435618,13.7096697058228 52.5397952580643)",380000.0,109969.0,0.033333,520,3.0,0.333333,3.0,520,50Hertz,10.275,50.0,24268.0,1.95 +14,13,15,Al/St 240/40 4-bundle 380.0,855.633098939,44.371,0.5,"SRID=4326;LINESTRING(11.3704262486892 48.2914922329548,11.8681562938884 48.2068029185353)",380000.0,112804.0,0.05,462,3.0,0.5,2.0,Oba-Oh,transpower,6.85,,44371.0,1.3 +15,14,15_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,5.432,2.0,"SRID=4326;LINESTRING(11.8132328973194 48.2212664654998,11.8681562938884 48.2068029185353)",220000.0,112810.0,0.04,286;287,6.0,0.16,2.0,Nfi-Oh,,23.0,50.0,5432.0,2.6 +16,13_220kV,19,Al/St 240/40 2-bundle 220.0,990.733061929,30.996,2.0,"SRID=4326;LINESTRING(11.3704262486892 48.2914922329548,11.2939727001158 48.0520427719624)",220000.0,114252.0,0.04,269;270,6.0,0.16,2.0,OBR-OBA,TenneT,23.0,50.0,30996.0,2.6 +17,20,21,Al/St 240/40 4-bundle 380.0,855.633098939,31.287,0.5,"SRID=4326;LINESTRING(9.98460982000897 53.7411287759659,10.1575015165126 53.5551316685834)",380000.0,114696.0,0.05,A,3.0,0.5,2.0,Hamburg/Ost - Hamburg/Nord,50Hertz,6.85,50.0,31287.0,1.3 +18,20,22,Al/St 240/40 4-bundle 380.0,1711.26619788,57.378,1.0,"SRID=4326;LINESTRING(9.98460982000897 53.7411287759659,9.20210420095253 53.8951574100045)",380000.0,114718.0,0.025,G,3.0,0.25,4.0,Brunsbüttel - Hamburg/Nord,50Hertz,13.7,,57378.0,2.6 +19,20_220kV,23,Al/St 240/40 2-bundle 220.0,495.366530965,3.313,1.0,"SRID=4326;LINESTRING(9.98460982000897 53.7411287759659,9.98853660865951 53.766277668594)",220000.0,114723.0,,,3.0,,2.0,UW HH-Nord - UW Ulzburg,,,,3313.0, +20,23,24_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,62.919,2.0,"SRID=4326;LINESTRING(9.98853660865951 53.766277668594,9.72699196993402 54.2914209867842)",220000.0,114724.0,,,6.0,,2.0,UW Hamburg Nord - UW Audorf,TenneT,,50.0,62919.0, +21,25,26,Al/St 240/40 4-bundle 380.0,1711.26619788,8.193,1.0,"SRID=4326;LINESTRING(9.34476098807139 53.9218370583068,9.34400177215961 53.8517774635399)",380000.0,114738.0,,308,6.0,,2.0,308 AKW Brokdorf - UW Wilster,TenneT,,50.0,8193.0, +22,25,22,Al/St 240/40 4-bundle 380.0,855.633098939,10.275,0.5,"SRID=4326;LINESTRING(9.34476098807139 53.9218370583068,9.20210420095253 53.8951574100045)",380000.0,114742.0,,309,3.0,,2.0,309 AKW Brunsbüttel - UW Wilster,TenneT,,50.0,10275.0, +23,23,27,Al/St 240/40 2-bundle 220.0,495.366530965,41.954,1.0,"SRID=4326;LINESTRING(9.98853660865951 53.766277668594,9.52960512906833 53.6189906941937)",220000.0,114747.0,,,3.0,,2.0,AKW Stade - UW Hamburg Nord,TenneT,,,41954.0, +24,28,23,Al/St 240/40 2-bundle 220.0,495.366530965,19.423,1.0,"SRID=4326;LINESTRING(9.77654483874895 53.6906352606291,9.98853660865951 53.766277668594)",220000.0,114752.0,,,3.0,,2.0,UW Kummerfeld - UW Hamburg Nord,TenneT,,,19423.0, +25,8,21,Al/St 240/40 4-bundle 380.0,855.633098939,28.576,0.5,"SRID=4326;LINESTRING(10.4149923381504 53.4126068830249,10.1575015165126 53.5551316685834)",380000.0,114782.0,,991;992,3.0,,2.0,991;992,50Hertz,,50.0,28576.0, +26,9,29,Al/St 240/40 4-bundle 380.0,1711.26619788,25.571,1.0,"SRID=4326;LINESTRING(10.3787705903765 53.2197927685849,10.3821666628198 52.9932540372914)",380000.0,114789.0,,,3.0,,4.0,3021 Lüneburg - Stadorf,TenneT,,50.0,25571.0, +27,30,25,Al/St 240/40 4-bundle 380.0,855.633098939,57.641,0.5,"SRID=4326;LINESTRING(9.5017896678173 53.5428143723363,9.34476098807139 53.9218370583068)",380000.0,114795.0,,307,3.0,,2.0,307 UW Wilster - UW Dollern,TenneT,,50.0,57641.0, +28,31,32,Al/St 240/40 4-bundle 380.0,1283.44964841,129.863,0.75,"SRID=4326;LINESTRING(9.74615081640288 50.9168399490798,11.1228545605428 50.9989090670144)",380000.0,129420.0,0.033333,449,3.0,0.333333,3.0,449 Mecklar - Vieselbach,50Hertz,10.275,50.0,129863.0,1.95 +29,32,33,Al/St 240/40 4-bundle 380.0,1283.44964841,72.614,0.75,"SRID=4326;LINESTRING(11.1228545605428 50.9989090670144,10.3859411643633 51.0018133014781)",380000.0,129422.0,0.033333,454,3.0,0.333333,3.0,454 Eisenach - Vieselbach,50Hertz,10.275,50.0,72614.0,1.95 +30,34,35,Al/St 240/40 2-bundle 220.0,495.366530965,5.845,1.0,"SRID=4326;LINESTRING(8.12264230304551 53.5605160490331,8.08565059715704 53.605834192587)",220000.0,138889.0,0.08,209,3.0,0.32,2.0,209 weiß,TenneT,11.5,50.0,5845.0,1.3 +31,35,36,Al/St 240/40 2-bundle 220.0,495.366530965,2.65,1.0,"SRID=4326;LINESTRING(8.08565059715704 53.605834192587,8.06005695814032 53.6202025750861)",220000.0,138892.0,0.08,211,3.0,0.32,2.0,211 rot,TenneT,11.5,50.0,2650.0,1.3 +32,2_220kV,6,Al/St 240/40 2-bundle 220.0,495.366530965,74.645,1.0,"SRID=4326;LINESTRING(9.11321007472722 52.5438533223737,9.91814864613865 52.3799963125719)",220000.0,142684.0,0.08,2008,3.0,0.32,2.0,Leitung 2008,TenneT,11.5,50.0,74645.0,1.3 +33,37,10,Al/St 240/40 4-bundle 380.0,1283.44964841,104.169,0.75,"SRID=4326;LINESTRING(11.6389127631156 52.2687346818467,12.9416466019288 52.5621144045848)",380000.0,145483.0,0.033333,494,3.0,0.333333,3.0,494,50Hertz,10.275,50.0,104169.0,1.95 +34,10,38,Al/St 240/40 4-bundle 380.0,1283.44964841,29.669,0.75,"SRID=4326;LINESTRING(12.9416466019288 52.5621144045848,13.2115276454389 52.5806278102291)",380000.0,145484.0,0.033333,498,3.0,0.333333,3.0,498,50Hertz,10.275,50.0,29669.0,1.95 +35,37,38,Al/St 240/40 4-bundle 380.0,2566.89929682,124.574,1.5,"SRID=4326;LINESTRING(11.6389127631156 52.2687346818467,13.2115276454389 52.5806278102291)",380000.0,145485.0,0.016667,493,6.0,0.166667,3.0,493,50Hertz,20.55,50.0,124574.0,3.9 +36,39,40,Al/St 240/40 4-bundle 380.0,1283.44964841,195.877,0.75,"SRID=4326;LINESTRING(11.7273534150745 52.5689469938591,13.1433929102518 53.9081469774219)",380000.0,145508.0,0.033333,513,3.0,0.333333,3.0,513,50Hertz,10.275,50.0,195877.0,1.95 +37,39,41,Al/St 240/40 4-bundle 380.0,1283.44964841,75.667,0.75,"SRID=4326;LINESTRING(11.7273534150745 52.5689469938591,11.8531057080136 53.0865444750041)",380000.0,145509.0,0.033333,516,3.0,0.333333,3.0,516,50Hertz,10.275,50.0,75667.0,1.95 +38,42,41,Al/St 240/40 4-bundle 380.0,1283.44964841,134.922,0.75,"SRID=4326;LINESTRING(12.1869848227216 53.8157751724384,11.8531057080136 53.0865444750041)",380000.0,145510.0,0.033333,514,3.0,0.333333,3.0,514,50Hertz,10.275,50.0,134922.0,1.95 +39,18,42,Al/St 240/40 4-bundle 380.0,1283.44964841,149.01,0.75,"SRID=4326;LINESTRING(13.6834769666546 54.1390906406635,12.1869848227216 53.8157751724384)",380000.0,145513.0,0.033333,512,3.0,0.333333,3.0,512,50Hertz,10.275,50.0,149010.0,1.95 +40,10_220kV,43,Al/St 240/40 2-bundle 220.0,495.366530965,55.092,1.0,"SRID=4326;LINESTRING(12.9416466019288 52.5621144045848,12.4993659669539 52.4052284251973)",220000.0,240489.0,0.08,319,3.0,0.32,2.0,319,50Hertz,11.5,50.0,55092.0,1.3 +41,10_220kV,43,Al/St 240/40 2-bundle 220.0,495.366530965,55.03,1.0,"SRID=4326;LINESTRING(12.9416466019288 52.5621144045848,12.4993659669539 52.4052284251973)",220000.0,240490.0,0.08,324,3.0,0.32,2.0,324,50Hertz,11.5,50.0,55030.0,1.3 +42,44,45,Al/St 240/40 2-bundle 220.0,495.366530965,38.755,1.0,"SRID=4326;LINESTRING(13.5061534469385 52.473822309174,13.3057532949594 52.2307818451873)",220000.0,241440.0,0.08,291,3.0,0.32,2.0,291,50Hertz,11.5,50.0,38755.0,1.3 +43,44,46_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,9.168,1.0,"SRID=4326;LINESTRING(13.5061534469385 52.473822309174,13.5241987309997 52.5356342713481)",220000.0,241442.0,0.08,301,3.0,0.32,2.0,301 Wuhlheide - Marzahn,50Hertz,11.5,,9168.0,1.3 +44,46,12,Al/St 240/40 4-bundle 380.0,1711.26619788,17.01,1.0,"SRID=4326;LINESTRING(13.5241987309997 52.5356342713481,13.7096697058228 52.5397952580643)",380000.0,252583.0,0.025,,3.0,0.25,4.0,495,50Hertz,13.7,,17010.0,2.6 +45,46,12,Al/St 240/40 4-bundle 380.0,1711.26619788,17.029,1.0,"SRID=4326;LINESTRING(13.5241987309997 52.5356342713481,13.7096697058228 52.5397952580643)",380000.0,252584.0,0.025,,3.0,0.25,4.0,496,50Hertz,13.7,,17029.0,2.6 +46,47,48,Al/St 240/40 4-bundle 380.0,427.81654947,7.717,0.25,"SRID=4326;LINESTRING(13.4564306927748 52.5214517451981,13.5370442127043 52.5374565238016)",380000.0,252589.0,0.1,,3.0,1.0,1.0,836 (BIN-FRH),Vattenfall_Europe_Berlin,3.425,50.0,7717.0,0.65 +47,49,50,Al/St 240/40 4-bundle 380.0,855.633098939,5.531,0.5,"SRID=4326;LINESTRING(10.99933301239 52.2001419745622,10.9772332275028 52.1694333176707)",380000.0,3883857.0,,3845,3.0,,2.0,,,,50.0,5531.0, +48,31,33,Al/St 240/40 4-bundle 380.0,1283.44964841,58.378,0.75,"SRID=4326;LINESTRING(9.74615081640288 50.9168399490798,10.3859411643633 51.0018133014781)",380000.0,270536.0,0.033333,450,3.0,0.333333,3.0,450 Mecklar - Eisenach,50Hertz,10.275,50.0,58378.0,1.95 +49,31,51,Al/St 240/40 4-bundle 380.0,3422.53239576,40.647,2.0,"SRID=4326;LINESTRING(9.74615081640288 50.9168399490798,9.25938382024592 51.0603668474477)",380000.0,270963.0,0.0125,3009,6.0,0.125,4.0,3009 Borken - Mecklar,TenneT,27.4,,40647.0,5.2 +50,52,53,Al/St 240/40 4-bundle 380.0,1711.26619788,120.968,1.0,"SRID=4326;LINESTRING(8.3133401659254 49.0090434643525,7.75349546540486 48.0993557006259)",380000.0,272103.0,0.025,7510,3.0,0.25,4.0,Daxlanden - Eichstetten,EnBW,13.7,,120968.0,2.6 +51,52,54,Al/St 240/40 4-bundle 380.0,1283.44964841,32.978,0.75,"SRID=4326;LINESTRING(8.3133401659254 49.0090434643525,8.43846751211921 49.2524597288652)",380000.0,272126.0,0.033333,7520,3.0,0.333333,3.0,Daxlanden - Philippsburg,EnBW,10.275,,32978.0,1.95 +52,52_220kV,55,Al/St 240/40 2-bundle 220.0,990.733061929,38.219,2.0,"SRID=4326;LINESTRING(8.3133401659254 49.0090434643525,8.51211132935593 49.293965649573)",220000.0,272681.0,0.04,5100,3.0,0.16,4.0,Altlußheim - Daxlanden,EnBW,23.0,,38219.0,2.6 +53,52_220kV,56_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,66.788,1.0,"SRID=4326;LINESTRING(8.3133401659254 49.0090434643525,8.62320967451897 49.3565822492832)",220000.0,272682.0,0.08,5100,3.0,0.32,2.0,Daxlanden - Neurott,EnBW,11.5,,66788.0,1.3 +54,57,52_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,14.179,2.0,"SRID=4326;LINESTRING(8.44554248929306 48.9860881753906,8.3133401659254 49.0090434643525)",220000.0,272728.0,0.04,,3.0,0.16,4.0,Seehof (220 kV Daxlanden-Oberwald),EnBW,23.0,,14179.0,2.6 +55,57,58,Al/St 240/40 2-bundle 220.0,495.366530965,21.835,1.0,"SRID=4326;LINESTRING(8.44554248929306 48.9860881753906,8.64158406338239 48.8900524832355)",220000.0,272729.0,0.08,,3.0,0.32,2.0,Edelberg (220 kV Oberwald - Birkenfeld),EnBW,11.5,,21835.0,1.3 +56,58,52_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,29.944,1.0,"SRID=4326;LINESTRING(8.64158406338239 48.8900524832355,8.3133401659254 49.0090434643525)",220000.0,272732.0,0.08,,3.0,0.32,2.0,Pfinztal (220 kV Daxlanden-Birkenfeld),EnBW,11.5,,29944.0,1.3 +57,59,60,Al/St 240/40 4-bundle 380.0,855.633098939,56.049,0.5,"SRID=4326;LINESTRING(8.56374399744213 53.0317215835514,8.47470704898602 53.4286511943138)",380000.0,289000.0,0.05,,3.0,0.5,2.0,307,TenneT,6.85,50.0,56049.0,1.3 +58,61_220kV,62,Al/St 240/40 2-bundle 220.0,247.683265482,38.653,0.5,"SRID=4326;LINESTRING(8.51888180951659 53.2026352836834,8.82002487241632 53.1073882918815)",220000.0,300029.0,0.16,,3.0,0.64,1.0,2144,TenneT,5.75,50.0,38653.0,0.65 +59,30,61,Al/St 240/40 4-bundle 380.0,855.633098939,90.79,0.5,"SRID=4326;LINESTRING(9.5017896678173 53.5428143723363,8.51888180951659 53.2026352836834)",380000.0,309969.0,0.05,3103,3.0,0.5,2.0,Dollern-(Niedervieland/Unterweser)-Alfstedt,TenneT,6.85,50.0,90790.0,1.3 +60,63,52_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,41.529,1.0,"SRID=4326;LINESTRING(8.12076909802284 48.7048354259965,8.3133401659254 49.0090434643525)",220000.0,339243.0,0.08,BUEHL-DAXLA,3.0,0.32,2.0,Bühl - Daxlanden,EnBW,11.5,50.0,41529.0,1.3 +61,13,64,Al/St 240/40 4-bundle 380.0,1711.26619788,17.129,1.0,"SRID=4326;LINESTRING(11.3704262486892 48.2914922329548,11.4407746483177 48.1718035473026)",380000.0,377770.0,0.025,439;440,3.0,0.25,4.0,Oberbachern - Menzing,Stadtwerke München,13.7,,17129.0,2.6 +62,65,66,Al/St 240/40 4-bundle 380.0,1283.44964841,163.671,0.75,"SRID=4326;LINESTRING(13.3264049984725 50.3463721850552,12.1147644054101 49.6311550521523)",380000.0,395087.0,0.033333,441,3.0,0.333333,3.0,Etzenricht - Hradec,CEPS;TenneT TSO GmbH,10.275,,163671.0,1.95 +63,59,67,Al/St 240/40 4-bundle 380.0,855.633098939,14.983,0.5,"SRID=4326;LINESTRING(8.56374399744213 53.0317215835514,8.69516075602312 53.0925360847799)",380000.0,445559.0,0.05,,3.0,0.5,2.0,303/307,TenneT,6.85,50.0,14983.0,1.3 +64,61,67,Al/St 240/40 4-bundle 380.0,855.633098939,37.346,0.5,"SRID=4326;LINESTRING(8.51888180951659 53.2026352836834,8.69516075602312 53.0925360847799)",380000.0,445560.0,0.05,,3.0,0.5,2.0,307;303,TenneT,6.85,50.0,37346.0,1.3 +65,68,69,Al/St 240/40 4-bundle 380.0,1711.26619788,64.4,1.0,"SRID=4326;LINESTRING(8.77244296019056 49.974668665137,8.41937105938048 49.6337356823792)",380000.0,576361.0,0.025,,3.0,0.25,4.0,Ried Ost,Amprion,13.7,50.0,64400.0,2.6 +66,70,69,Al/St 240/40 4-bundle 380.0,1711.26619788,55.596,1.0,"SRID=4326;LINESTRING(8.38855636787547 49.9823050841271,8.41937105938048 49.6337356823792)",380000.0,576364.0,0.025,,3.0,0.25,4.0,Ried West,Amprion,13.7,50.0,55596.0,2.6 +67,71,72,Al/St 240/40 4-bundle 380.0,1711.26619788,51.283,1.0,"SRID=4326;LINESTRING(12.2621734868101 51.7410202755519,11.8942661378135 51.4057031412478)",380000.0,902595.0,0.025,504,3.0,0.25,4.0,504,50Hertz,13.7,50.0,51283.0,2.6 +68,73,74,Al/St 240/40 4-bundle 380.0,1283.44964841,54.278,0.75,"SRID=4326;LINESTRING(13.1570904871893 51.8284516615019,13.9024346886892 51.8747235190459)",380000.0,902597.0,0.033333,501,3.0,0.333333,3.0,501,50Hertz,10.275,50.0,54278.0,1.95 +69,37,75,Al/St 240/40 4-bundle 380.0,1283.44964841,118.953,0.75,"SRID=4326;LINESTRING(11.6389127631156 52.2687346818467,11.5165549672107 51.5843592098734)",380000.0,904158.0,0.033333,536,3.0,0.333333,3.0,536 Klostermansfeld - Wolmirstedt,50Hertz,10.275,50.0,118953.0,1.95 +70,72,75,Al/St 240/40 4-bundle 380.0,1711.26619788,38.634,1.0,"SRID=4326;LINESTRING(11.8942661378135 51.4057031412478,11.5165549672107 51.5843592098734)",380000.0,904159.0,0.025,538,3.0,0.25,4.0,538 Lauchstädt - Klostermansfeld,50Hertz,13.7,50.0,38634.0,2.6 +71,37,72,Al/St 240/40 4-bundle 380.0,1283.44964841,156.914,0.75,"SRID=4326;LINESTRING(11.6389127631156 52.2687346818467,11.8942661378135 51.4057031412478)",380000.0,904160.0,0.033333,535,3.0,0.333333,3.0,535 Lauchstädt - Wolmirstedt,50Hertz,10.275,50.0,156914.0,1.95 +72,76,77,Al/St 240/40 4-bundle 380.0,1711.26619788,54.107,1.0,"SRID=4326;LINESTRING(8.70221844882815 51.7363525828065,8.99279240395088 51.343009397907)",380000.0,906772.0,0.025,3016,3.0,0.25,4.0,Leitung 3016,TenneT,13.7,,54107.0,2.6 +73,76,77,Al/St 240/40 4-bundle 380.0,1711.26619788,54.107,1.0,"SRID=4326;LINESTRING(8.70221844882815 51.7363525828065,8.99279240395088 51.343009397907)",380000.0,906773.0,0.025,3014,3.0,0.25,4.0,Leitung 3014,TenneT,13.7,,54107.0,2.6 +74,77,51,Al/St 240/40 4-bundle 380.0,1711.26619788,41.955,1.0,"SRID=4326;LINESTRING(8.99279240395088 51.343009397907,9.25938382024592 51.0603668474477)",380000.0,906782.0,0.025,3012,3.0,0.25,4.0,Leitung 3212,TenneT,13.7,,41955.0,2.6 +75,77,51,Al/St 240/40 4-bundle 380.0,1711.26619788,41.955,1.0,"SRID=4326;LINESTRING(8.99279240395088 51.343009397907,9.25938382024592 51.0603668474477)",380000.0,906784.0,0.025,3013,3.0,0.25,4.0,Leitung 3213,TenneT,13.7,,41955.0,2.6 +76,73,72,Al/St 240/40 4-bundle 380.0,1283.44964841,110.358,0.75,"SRID=4326;LINESTRING(13.1570904871893 51.8284516615019,11.8942661378135 51.4057031412478)",380000.0,907513.0,0.033333,503,3.0,0.333333,3.0,503,50Hertz,10.275,50.0,110358.0,1.95 +77,18,40,Al/St 240/40 4-bundle 380.0,1283.44964841,56.436,0.75,"SRID=4326;LINESTRING(13.6834769666546 54.1390906406635,13.1433929102518 53.9081469774219)",380000.0,912407.0,0.033333,509,3.0,0.333333,3.0,509,50Hertz,10.275,50.0,56436.0,1.95 +78,10_220kV,45,Al/St 240/40 2-bundle 220.0,743.049796447,70.037,1.5,"SRID=4326;LINESTRING(12.9416466019288 52.5621144045848,13.3057532949594 52.2307818451873)",220000.0,918562.0,0.053333,526,3.0,0.213333,3.0,526,50Hertz,17.25,50.0,70037.0,1.95 +79,10,74,Al/St 240/40 4-bundle 380.0,1283.44964841,130.685,0.75,"SRID=4326;LINESTRING(12.9416466019288 52.5621144045848,13.9024346886892 51.8747235190459)",380000.0,918563.0,0.033333,521,3.0,0.333333,3.0,521,50Hertz,10.275,50.0,130685.0,1.95 +80,45,74_220kV,Al/St 240/40 2-bundle 220.0,247.683265482,62.038,0.5,"SRID=4326;LINESTRING(13.3057532949594 52.2307818451873,13.9024346886892 51.8747235190459)",220000.0,918564.0,0.16,522,3.0,0.64,1.0,522,50Hertz,5.75,50.0,62038.0,0.65 +81,78,32,Al/St 240/40 4-bundle 380.0,1711.26619788,77.738,1.0,"SRID=4326;LINESTRING(11.6463786274424 50.534876971042,11.1228545605428 50.9989090670144)",380000.0,918567.0,0.025,416,3.0,0.25,4.0,416,50Hertz,13.7,50.0,77738.0,2.6 +82,79,80,Al/St 240/40 4-bundle 380.0,855.633098939,22.016,0.5,"SRID=4326;LINESTRING(14.4719654742998 51.5538127185923,14.5081091811329 51.4137103812427)",380000.0,919059.0,,566,3.0,,2.0,566,50Hertz,,50.0,22016.0, +83,79,80,Al/St 240/40 4-bundle 380.0,855.633098939,21.999,0.5,"SRID=4326;LINESTRING(14.4719654742998 51.5538127185923,14.5081091811329 51.4137103812427)",380000.0,919060.0,,565,3.0,,2.0,565,50Hertz,,50.0,21999.0, +84,81,79,Al/St 240/40 4-bundle 380.0,855.633098939,62.18,0.5,"SRID=4326;LINESTRING(14.4281804852225 51.8888641394403,14.4719654742998 51.5538127185923)",380000.0,919119.0,,542,3.0,,2.0,542,50Hertz,,50.0,62180.0, +85,81,79,Al/St 240/40 4-bundle 380.0,855.633098939,62.195,0.5,"SRID=4326;LINESTRING(14.4281804852225 51.8888641394403,14.4719654742998 51.5538127185923)",380000.0,919121.0,,541,3.0,,2.0,541,50Hertz,,50.0,62195.0, +86,34,82,Al/St 240/40 2-bundle 220.0,495.366530965,2.344,1.0,"SRID=4326;LINESTRING(8.12264230304551 53.5605160490331,8.14191382633093 53.5640011680095)",220000.0,941141.0,0.08,300,3.0,0.32,2.0,300 Wilhelmshaven - Maade,,11.5,50.0,2344.0,1.3 +87,83,84,Al/St 240/40 2-bundle 220.0,495.366530965,13.652,1.0,"SRID=4326;LINESTRING(10.1562824575947 54.2755482549913,10.1773541034729 54.3395212638642)",220000.0,943999.0,0.08,211,3.0,0.32,2.0,211,TenneT,11.5,50.0,13652.0,1.3 +88,83,24_220kV,Al/St 240/40 2-bundle 220.0,247.683265482,31.608,0.5,"SRID=4326;LINESTRING(10.1562824575947 54.2755482549913,9.72699196993402 54.2914209867842)",220000.0,945143.0,0.16,207,3.0,0.64,1.0,207,TenneT,5.75,50.0,31608.0,0.65 +89,85,86,Al/St 240/40 4-bundle 380.0,1711.26619788,46.344,1.0,"SRID=4326;LINESTRING(7.03496796515083 52.2028607381056,7.30820430094578 52.4768353862806)",380000.0,962084.0,0.025,,3.0,0.25,4.0,Gronau West,Amprion,13.7,50.0,46344.0,2.6 +90,87,86,Al/St 240/40 4-bundle 380.0,1711.26619788,110.031,1.0,"SRID=4326;LINESTRING(7.06030699176008 51.6964760130648,7.30820430094578 52.4768353862806)",380000.0,962095.0,0.025,,3.0,0.25,4.0,Münsterland,Amprion,13.7,50.0,110031.0,2.6 +91,88,89,Al/St 240/40 2-bundle 220.0,495.366530965,5.752,1.0,"SRID=4326;LINESTRING(6.6746753206169 51.3249574728255,6.72376714521247 51.3556543809247)",220000.0,1190715.0,0.08,,3.0,0.32,2.0,Stratum West,Amprion,11.5,50.0,5752.0,1.3 +92,90,86,Al/St 240/40 4-bundle 380.0,1711.26619788,69.877,1.0,"SRID=4326;LINESTRING(7.54571772261949 51.9703244223875,7.30820430094578 52.4768353862806)",380000.0,962096.0,0.025,,3.0,0.25,4.0,Roxel Ost,Amprion,13.7,50.0,69877.0,2.6 +93,91,86,Al/St 240/40 4-bundle 380.0,1711.26619788,1.071,1.0,"SRID=4326;LINESTRING(7.30561448538174 52.4801491895706,7.30820430094578 52.4768353862806)",380000.0,962108.0,0.025,,3.0,0.25,4.0,Emsland-D0,Amprion,13.7,50.0,1071.0,2.6 +94,86,92,Al/St 240/40 4-bundle 380.0,1711.26619788,1.07,1.0,"SRID=4326;LINESTRING(7.30820430094578 52.4768353862806,7.30433875539085 52.4824259958004)",380000.0,962109.0,0.025,,3.0,0.25,4.0,Emsland-D2,Amprion,13.7,50.0,1070.0,2.6 +95,91,86,Al/St 240/40 4-bundle 380.0,1711.26619788,1.07,1.0,"SRID=4326;LINESTRING(7.30561448538174 52.4801491895706,7.30820430094578 52.4768353862806)",380000.0,962110.0,0.025,,3.0,0.25,4.0,Emsland-D1,Amprion,13.7,50.0,1070.0,2.6 +96,86,93,Al/St 240/40 4-bundle 380.0,1711.26619788,47.087,1.0,"SRID=4326;LINESTRING(7.30820430094578 52.4768353862806,7.27715391322869 52.7532425946802)",380000.0,962120.0,0.025,,3.0,0.25,4.0,Meppen,Amprion,13.7,50.0,47087.0,2.6 +97,94,86,Al/St 240/40 4-bundle 380.0,1711.26619788,74.417,1.0,"SRID=4326;LINESTRING(8.30775689679575 52.3456356447783,7.30820430094578 52.4768353862806)",380000.0,962121.0,0.025,,3.0,0.25,4.0,Niedersachsen Süd,Amprion,13.7,50.0,74417.0,2.6 +98,86,95,Al/St 240/40 4-bundle 380.0,1711.26619788,60.192,1.0,"SRID=4326;LINESTRING(7.30820430094578 52.4768353862806,7.88957671235007 52.2773967624078)",380000.0,962122.0,0.025,,3.0,0.25,4.0,Bramgau,Amprion,13.7,50.0,60192.0,2.6 +99,86_220kV,96_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,1.321,1.0,"SRID=4326;LINESTRING(7.30820430094578 52.4768353862806,7.31784994377907 52.4691114902799)",220000.0,962172.0,0.08,,3.0,0.32,2.0,Benteler,Amprion,11.5,50.0,1321.0,1.3 +100,97,66,Al/St 240/40 4-bundle 380.0,855.633098939,110.113,0.5,"SRID=4326;LINESTRING(13.3352803846608 49.5896898612644,12.1147644054101 49.6311550521523)",380000.0,965662.0,,442,3.0,,2.0,Etzenricht - Přeštice,ČEPS,,,110113.0, +101,42,98,Al/St 240/40 4-bundle 380.0,1711.26619788,35.731,1.0,"SRID=4326;LINESTRING(12.1869848227216 53.8157751724384,12.2147735096776 54.1003234403618)",380000.0,1000670.0,0.025,544,3.0,0.25,4.0,544,50Hertz,13.7,50.0,35731.0,2.6 +102,99,100,Al/St 240/40 4-bundle 380.0,1711.26619788,94.562,1.0,"SRID=4326;LINESTRING(6.99647659179489 49.3754093063084,6.47473977006816 49.9224692993999)",380000.0,1020855.0,0.025,,3.0,0.25,4.0,Uchtelfangen West,Amprion,13.7,,94562.0,2.6 +103,42,98,Al/St 240/40 4-bundle 380.0,1711.26619788,35.716,1.0,"SRID=4326;LINESTRING(12.1869848227216 53.8157751724384,12.2147735096776 54.1003234403618)",380000.0,1021991.0,0.025,543,3.0,0.25,4.0,543,50Hertz,13.7,50.0,35716.0,2.6 +104,42,101,Al/St 240/40 4-bundle 380.0,1711.26619788,47.163,1.0,"SRID=4326;LINESTRING(12.1869848227216 53.8157751724384,11.6993926965362 53.5722744892702)",380000.0,1022003.0,0.025,424,3.0,0.25,4.0,424,50Hertz,13.7,50.0,47163.0,2.6 +105,102,42,Al/St 240/40 4-bundle 380.0,1711.26619788,71.64,1.0,"SRID=4326;LINESTRING(11.3706373688866 53.6009910848999,12.1869848227216 53.8157751724384)",380000.0,1022004.0,0.025,423,3.0,0.25,4.0,423,50Hertz,13.7,50.0,71640.0,2.6 +106,103,87,Al/St 240/40 4-bundle 380.0,1711.26619788,40.854,1.0,"SRID=4326;LINESTRING(6.94821343461115 51.4445789430072,7.06030699176008 51.6964760130648)",380000.0,1082800.0,0.025,,3.0,0.25,4.0,Kusenhorst,Amprion,13.7,50.0,40854.0,2.6 +107,104,103,Al/St 240/40 4-bundle 380.0,1711.26619788,16.665,1.0,"SRID=4326;LINESTRING(6.92675896360373 51.4992056838496,6.94821343461115 51.4445789430072)",380000.0,1082801.0,0.025,,3.0,0.25,4.0,Büscherhof West,Amprion,13.7,50.0,16665.0,2.6 +108,105,87,Al/St 240/40 4-bundle 380.0,1711.26619788,37.005,1.0,"SRID=4326;LINESTRING(6.68078798893986 51.6462959356851,7.06030699176008 51.6964760130648)",380000.0,1082805.0,0.025,,3.0,0.25,4.0,Lippe Nord,Amprion,13.7,50.0,37005.0,2.6 +109,106,107,Al/St 240/40 4-bundle 380.0,1283.44964841,24.754,0.75,"SRID=4326;LINESTRING(7.16721460965932 51.3899067582106,7.1255729106725 51.4489772588145)",380000.0,1082806.0,0.033333,,3.0,0.333333,3.0,Weitmar West,Amprion,10.275,50.0,24754.0,1.95 +110,108,107,Al/St 240/40 4-bundle 380.0,1711.26619788,14.687,1.0,"SRID=4326;LINESTRING(6.99606888010598 51.5141695891404,7.1255729106725 51.4489772588145)",380000.0,1082807.0,0.025,,3.0,0.25,4.0,Eiberg Nord,Amprion,13.7,50.0,14687.0,2.6 +111,109,106,Al/St 240/40 4-bundle 380.0,1711.26619788,17.504,1.0,"SRID=4326;LINESTRING(7.31318284290137 51.4466113896128,7.16721460965932 51.3899067582106)",380000.0,1082808.0,0.025,,3.0,0.25,4.0,Kemnade Süd,Amprion,13.7,50.0,17504.0,2.6 +112,110,111,Al/St 240/40 4-bundle 380.0,1711.26619788,114.491,1.0,"SRID=4326;LINESTRING(7.99288723332679 51.6837077155955,8.62480052815036 51.4278883849265)",380000.0,1082809.0,0.025,,3.0,0.25,4.0,Sauerland Nord,Amprion,13.7,50.0,114491.0,2.6 +113,87,112,Al/St 240/40 4-bundle 380.0,1711.26619788,35.441,1.0,"SRID=4326;LINESTRING(7.06030699176008 51.6964760130648,7.35311186939787 51.5758105020818)",380000.0,1082810.0,0.025,,3.0,0.25,4.0,Recklinghausen,Amprion,13.7,50.0,35441.0,2.6 +114,113,87,Al/St 240/40 4-bundle 380.0,1711.26619788,22.998,1.0,"SRID=4326;LINESTRING(7.12323882950453 51.5256121803004,7.06030699176008 51.6964760130648)",380000.0,1082811.0,0.025,,3.0,0.25,4.0,Westfalen West,Amprion,13.7,50.0,22998.0,2.6 +115,87,85,Al/St 240/40 4-bundle 380.0,1711.26619788,63.803,1.0,"SRID=4326;LINESTRING(7.06030699176008 51.6964760130648,7.03496796515083 52.2028607381056)",380000.0,1082814.0,0.025,,3.0,0.25,4.0,Hamaland,Amprion,13.7,50.0,63803.0,2.6 +116,110,114,Al/St 240/40 4-bundle 380.0,1711.26619788,67.379,1.0,"SRID=4326;LINESTRING(7.99288723332679 51.6837077155955,8.21170597594605 51.1779854801194)",380000.0,1082816.0,0.025,,3.0,0.25,4.0,Arpe West,Amprion,13.7,50.0,67379.0,2.6 +117,115,116,Al/St 240/40 2-bundle 220.0,990.733061929,21.323,2.0,"SRID=4326;LINESTRING(6.57648208812114 51.0555954583116,6.77252309438106 51.1471054906194)",220000.0,1082942.0,0.04,,3.0,0.16,4.0,Frimmersdorf 5e,Amprion,23.0,50.0,21323.0,2.6 +118,117,118,Al/St 240/40 4-bundle 380.0,1711.26619788,34.584,1.0,"SRID=4326;LINESTRING(6.97257742700703 50.795693017514,7.13689469738717 50.6428608767133)",380000.0,1082943.0,0.025,,3.0,0.25,4.0,Ließem Ost,Amprion,13.7,50.0,34584.0,2.6 +119,117,119,Al/St 240/40 4-bundle 380.0,1711.26619788,44.751,1.0,"SRID=4326;LINESTRING(6.97257742700703 50.795693017514,6.45655246642153 50.8713380368705)",380000.0,1082944.0,0.025,,3.0,0.25,4.0,Sechtem Süd,Amprion,13.7,50.0,44751.0,2.6 +120,117,120,Al/St 240/40 4-bundle 380.0,1711.26619788,68.003,1.0,"SRID=4326;LINESTRING(6.97257742700703 50.795693017514,7.86394571365263 50.7682419129404)",380000.0,1082946.0,0.025,,3.0,0.25,4.0,Siegtal Süd,Amprion,13.7,50.0,68003.0,2.6 +121,121,122,Al/St 240/40 4-bundle 380.0,1711.26619788,12.228,1.0,"SRID=4326;LINESTRING(6.32166491493505 50.8383473627586,6.16738169006425 50.8020442927138)",380000.0,1082948.0,0.025,,3.0,0.25,4.0,Röhe Nord,Amprion,13.7,50.0,12228.0,2.6 +122,123,124,Al/St 240/40 4-bundle 380.0,1711.26619788,10.62,1.0,"SRID=4326;LINESTRING(6.70220337779745 51.0088609096753,6.61240231530956 51.0359242594819)",380000.0,1082949.0,0.025,,3.0,0.25,4.0,Neurath 3c,Amprion,13.7,50.0,10620.0,2.6 +123,125,126,Al/St 240/40 4-bundle 380.0,1711.26619788,28.54,1.0,"SRID=4326;LINESTRING(6.96568442352005 51.0693295322353,7.23319684331545 51.2325069075148)",380000.0,1082952.0,0.025,,3.0,0.25,4.0,Leichlingen Ost,Amprion,13.7,50.0,28540.0,2.6 +124,127,119,Al/St 240/40 4-bundle 380.0,1711.26619788,16.766,1.0,"SRID=4326;LINESTRING(6.30090123786765 50.8289532122665,6.45655246642153 50.8713380368705)",380000.0,1082953.0,0.025,,3.0,0.25,4.0,Weisweiler Süd,Amprion,13.7,50.0,16766.0,2.6 +125,118,128,Al/St 240/40 4-bundle 380.0,1711.26619788,42.726,1.0,"SRID=4326;LINESTRING(7.13689469738717 50.6428608767133,7.44948529010545 50.4025276621126)",380000.0,1082954.0,0.025,,3.0,0.25,4.0,Weißenthurm Ost,Amprion,13.7,50.0,42726.0,2.6 +126,121,119,Al/St 240/40 4-bundle 380.0,1711.26619788,14.743,1.0,"SRID=4326;LINESTRING(6.32166491493505 50.8383473627586,6.45655246642153 50.8713380368705)",380000.0,1082955.0,0.025,,3.0,0.25,4.0,Weisweiler Nord,Amprion,13.7,50.0,14743.0,2.6 +127,129_220kV,124_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,34.374,1.0,"SRID=4326;LINESTRING(6.62582790177014 51.2606098249047,6.61240231530956 51.0359242594819)",220000.0,1082956.0,0.08,,3.0,0.32,2.0,Neurath 1a,Amprion,11.5,50.0,34374.0,1.3 +128,125,130,Al/St 240/40 4-bundle 380.0,1711.26619788,16.932,1.0,"SRID=4326;LINESTRING(6.96568442352005 51.0693295322353,7.12308195236379 51.1715887782054)",380000.0,1082957.0,0.025,,3.0,0.25,4.0,Leichlingen West,Amprion,13.7,50.0,16932.0,2.6 +129,125,123,Al/St 240/40 4-bundle 380.0,1711.26619788,22.039,1.0,"SRID=4326;LINESTRING(6.96568442352005 51.0693295322353,6.70220337779745 51.0088609096753)",380000.0,1082958.0,0.025,,3.0,0.25,4.0,Opladen Nord,Amprion,13.7,50.0,22039.0,2.6 +130,131,132,Al/St 240/40 4-bundle 380.0,1711.26619788,20.646,1.0,"SRID=4326;LINESTRING(6.80109333956604 51.1225390518584,7.00877914033175 51.1742549180397)",380000.0,1082959.0,0.025,,3.0,0.25,4.0,St. Peter Nord,Amprion,13.7,50.0,20646.0,2.6 +131,133,123,Al/St 240/40 4-bundle 380.0,1711.26619788,22.321,1.0,"SRID=4326;LINESTRING(6.8426506615144 50.8635385008881,6.70220337779745 51.0088609096753)",380000.0,1082960.0,0.025,,3.0,0.25,4.0,Brauweiler West,Amprion,13.7,50.0,22321.0,2.6 +132,117,120,Al/St 240/40 4-bundle 380.0,1711.26619788,68.003,1.0,"SRID=4326;LINESTRING(6.97257742700703 50.795693017514,7.86394571365263 50.7682419129404)",380000.0,1082961.0,0.025,,3.0,0.25,4.0,Siegtal Nord,Amprion,13.7,50.0,68003.0,2.6 +133,117_220kV,134,Al/St 240/40 2-bundle 220.0,990.733061929,24.479,2.0,"SRID=4326;LINESTRING(6.97257742700703 50.795693017514,6.80461158012622 50.9662452961379)",220000.0,1082962.0,0.04,,3.0,0.16,4.0,Godorf West,Amprion,23.0,50.0,24479.0,2.6 +134,135,136,Al/St 240/40 4-bundle 380.0,1711.26619788,30.283,1.0,"SRID=4326;LINESTRING(7.7186272145384 51.5342075115271,7.93077008561427 51.6729710197069)",380000.0,1082963.0,0.025,,3.0,0.25,4.0,Haarstrang West,Amprion,13.7,50.0,30283.0,2.6 +135,125,123,Al/St 240/40 4-bundle 380.0,1711.26619788,22.039,1.0,"SRID=4326;LINESTRING(6.96568442352005 51.0693295322353,6.70220337779745 51.0088609096753)",380000.0,1082964.0,0.025,,3.0,0.25,4.0,Opladen Süd,Amprion,13.7,50.0,22039.0,2.6 +136,123,131,Al/St 240/40 4-bundle 380.0,1711.26619788,18.127,1.0,"SRID=4326;LINESTRING(6.70220337779745 51.0088609096753,6.80109333956604 51.1225390518584)",380000.0,1082965.0,0.025,,3.0,0.25,4.0,St. Peter Süd,Amprion,13.7,50.0,18127.0,2.6 +137,117,119,Al/St 240/40 4-bundle 380.0,1711.26619788,44.751,1.0,"SRID=4326;LINESTRING(6.97257742700703 50.795693017514,6.45655246642153 50.8713380368705)",380000.0,1082966.0,0.025,,3.0,0.25,4.0,Sechtem Nord,Amprion,13.7,50.0,44751.0,2.6 +138,117,128,Al/St 240/40 4-bundle 380.0,1711.26619788,62.381,1.0,"SRID=4326;LINESTRING(6.97257742700703 50.795693017514,7.44948529010545 50.4025276621126)",380000.0,1082967.0,0.025,,3.0,0.25,4.0,Mittelrhein,Amprion,13.7,50.0,62381.0,2.6 +139,137,105_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,41.846,1.0,"SRID=4326;LINESTRING(6.22183144595687 51.6946456422206,6.68078798893986 51.6462959356851)",220000.0,1082973.0,0.08,,3.0,0.32,2.0,Pfalzdorf Süd,Amprion,11.5,50.0,41846.0,1.3 +140,138_220kV,87_220kV,Al/St 240/40 2-bundle 220.0,743.049796447,13.5,1.5,"SRID=4326;LINESTRING(7.00674987859998 51.5999114220203,7.06030699176008 51.6964760130648)",220000.0,1082974.0,0.053333,,3.0,0.213333,3.0,Scholven D,Amprion,17.25,50.0,13500.0,1.95 +141,139,105_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,32.152,1.0,"SRID=4326;LINESTRING(7.03761988504233 51.5985496311832,6.68078798893986 51.6462959356851)",220000.0,1082975.0,0.08,,3.0,0.32,2.0,Schermbeck Nord,Amprion,11.5,50.0,32152.0,1.3 +142,140,87_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,28.023,1.0,"SRID=4326;LINESTRING(7.2640834625924 51.5735734103802,7.06030699176008 51.6964760130648)",220000.0,1082976.0,0.08,,3.0,0.32,2.0,Hochlarmark Ost,Amprion,11.5,50.0,28023.0,1.3 +143,106_220kV,141,Al/St 240/40 2-bundle 220.0,495.366530965,18.405,1.0,"SRID=4326;LINESTRING(7.16721460965932 51.3899067582106,7.2789471130269 51.4722816773138)",220000.0,1082983.0,0.08,,3.0,0.32,2.0,Laer West,Amprion,11.5,50.0,18405.0,1.3 +144,142,107_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,17.736,1.0,"SRID=4326;LINESTRING(7.18667755311932 51.550589070577,7.1255729106725 51.4489772588145)",220000.0,1082984.0,0.08,,3.0,0.32,2.0,Herne 1A,Amprion,11.5,50.0,17736.0,1.3 +145,129_220kV,123_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,30.676,1.0,"SRID=4326;LINESTRING(6.62582790177014 51.2606098249047,6.70220337779745 51.0088609096753)",220000.0,1082985.0,0.08,,3.0,0.32,2.0,Frixheim Nord,Amprion,11.5,50.0,30676.0,1.3 +146,140,143_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,7.062,1.0,"SRID=4326;LINESTRING(7.2640834625924 51.5735734103802,7.35135784054859 51.568368608134)",220000.0,1082986.0,0.08,,3.0,0.32,2.0,Knepper C,Amprion,11.5,50.0,7062.0,1.3 +147,142,144,Al/St 240/40 2-bundle 220.0,743.049796447,15.151,1.5,"SRID=4326;LINESTRING(7.18667755311932 51.550589070577,7.17937912538938 51.4722019484347)",220000.0,1082987.0,0.053333,,3.0,0.213333,3.0,Herne 4,Amprion,17.25,50.0,15151.0,1.95 +148,140,106_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,30.931,1.0,"SRID=4326;LINESTRING(7.2640834625924 51.5735734103802,7.16721460965932 51.3899067582106)",220000.0,1082989.0,0.08,,3.0,0.32,2.0,Pöppinghausen Ost,Amprion,11.5,50.0,30931.0,1.3 +149,145,146,Al/St 240/40 2-bundle 220.0,495.366530965,43.148,1.0,"SRID=4326;LINESTRING(6.93488869659333 51.0335223350589,6.82813596908193 51.1626762413706)",220000.0,1082990.0,0.08,,3.0,0.32,2.0,Frixheim Süd,Amprion,11.5,50.0,43148.0,1.3 +150,147,107_220kV,Al/St 240/40 2-bundle 220.0,247.683265482,37.515,0.5,"SRID=4326;LINESTRING(7.50597329671546 51.401592550355,7.1255729106725 51.4489772588145)",220000.0,1082992.0,0.16,,3.0,0.64,1.0,Herdecke Nord,Amprion,5.75,50.0,37515.0,0.65 +151,144,107_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,12.825,1.0,"SRID=4326;LINESTRING(7.17937912538938 51.4722019484347,7.1255729106725 51.4489772588145)",220000.0,1082996.0,0.08,,3.0,0.32,2.0,Bochum West,Amprion,11.5,50.0,12825.0,1.3 +152,139,107_220kV,Al/St 240/40 2-bundle 220.0,247.683265482,22.117,0.5,"SRID=4326;LINESTRING(7.03761988504233 51.5985496311832,7.1255729106725 51.4489772588145)",220000.0,1082997.0,0.16,,3.0,0.64,1.0,Gladbeck West,Amprion,5.75,50.0,22117.0,0.65 +153,140,141,Al/St 240/40 2-bundle 220.0,495.366530965,12.649,1.0,"SRID=4326;LINESTRING(7.2640834625924 51.5735734103802,7.2789471130269 51.4722816773138)",220000.0,1082998.0,0.08,,3.0,0.32,2.0,Pöppinghausen West,Amprion,11.5,50.0,12649.0,1.3 +154,142,107_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,21.357,1.0,"SRID=4326;LINESTRING(7.18667755311932 51.550589070577,7.1255729106725 51.4489772588145)",220000.0,1082999.0,0.08,,3.0,0.32,2.0,Herne 2B,Amprion,11.5,50.0,21357.0,1.3 +155,147,107_220kV,Al/St 240/40 2-bundle 220.0,247.683265482,37.227,0.5,"SRID=4326;LINESTRING(7.50597329671546 51.401592550355,7.1255729106725 51.4489772588145)",220000.0,1083001.0,0.16,,3.0,0.64,1.0,Herdecke Süd,Amprion,5.75,50.0,37227.0,0.65 +156,125_220kV,123_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,23.276,1.0,"SRID=4326;LINESTRING(6.96568442352005 51.0693295322353,6.70220337779745 51.0088609096753)",220000.0,1083933.0,0.08,,3.0,0.32,2.0,Stommeln Nord,Amprion,11.5,50.0,23276.0,1.3 +157,125_220kV,148,Al/St 240/40 2-bundle 220.0,495.366530965,21.638,1.0,"SRID=4326;LINESTRING(6.96568442352005 51.0693295322353,6.84699931064759 51.2055917698058)",220000.0,1083953.0,0.08,,3.0,0.32,2.0,Eller Ost,Amprion,11.5,50.0,21638.0,1.3 +158,149,150,Al/St 240/40 2-bundle 220.0,495.366530965,20.577,1.0,"SRID=4326;LINESTRING(7.19316578267403 50.798335064694,7.0473845241055 50.9055286094924)",220000.0,1083959.0,0.08,,3.0,0.32,2.0,Gremberghoven West,Amprion,11.5,50.0,20577.0,1.3 +159,145,125_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,4.849,1.0,"SRID=4326;LINESTRING(6.93488869659333 51.0335223350589,6.96568442352005 51.0693295322353)",220000.0,1083962.0,0.08,,3.0,0.32,2.0,Kasselberg Süd,Amprion,11.5,50.0,4849.0,1.3 +160,129_220kV,151,Al/St 240/40 2-bundle 220.0,495.366530965,19.881,1.0,"SRID=4326;LINESTRING(6.62582790177014 51.2606098249047,6.70967369516713 51.0909407427733)",220000.0,1083967.0,0.08,,3.0,0.32,2.0,Osterath West,Amprion,11.5,50.0,19881.0,1.3 +161,117_220kV,152,Al/St 240/40 2-bundle 220.0,495.366530965,22.08,1.0,"SRID=4326;LINESTRING(6.97257742700703 50.795693017514,7.01293742640329 50.6176584938251)",220000.0,1083975.0,0.08,,3.0,0.32,2.0,Berggeist West,Amprion,11.5,50.0,22080.0,1.3 +162,149,120_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,51.114,1.0,"SRID=4326;LINESTRING(7.19316578267403 50.798335064694,7.86394571365263 50.7682419129404)",220000.0,1083988.0,0.08,,3.0,0.32,2.0,Leuscheid Nord,Amprion,11.5,50.0,51114.0,1.3 +163,139,87_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,11.834,1.0,"SRID=4326;LINESTRING(7.03761988504233 51.5985496311832,7.06030699176008 51.6964760130648)",220000.0,1128368.0,0.08,,3.0,0.32,2.0,Polsum Süd,Amprion,11.5,50.0,11834.0,1.3 +164,126,120,Al/St 240/40 4-bundle 380.0,1711.26619788,73.819,1.0,"SRID=4326;LINESTRING(7.23319684331545 51.2325069075148,7.86394571365263 50.7682419129404)",380000.0,1083990.0,0.025,,3.0,0.25,4.0,Dauersberg Ost,Amprion,13.7,50.0,73819.0,2.6 +165,129_220kV,153,Al/St 240/40 2-bundle 220.0,495.366530965,34.1,1.0,"SRID=4326;LINESTRING(6.62582790177014 51.2606098249047,6.33006858948983 51.2429161503794)",220000.0,1083998.0,0.08,,3.0,0.32,2.0,Fichtenhain Ost,Amprion,11.5,50.0,34100.0,1.3 +166,125_220kV,147,Al/St 240/40 2-bundle 220.0,247.683265482,119.387,0.5,"SRID=4326;LINESTRING(6.96568442352005 51.0693295322353,7.50597329671546 51.401592550355)",220000.0,1084000.0,0.16,,3.0,0.64,1.0,Bigge West,Amprion,5.75,50.0,119387.0,0.65 +167,125_220kV,154,Al/St 240/40 2-bundle 220.0,247.683265482,9.874,0.5,"SRID=4326;LINESTRING(6.96568442352005 51.0693295322353,7.0235444991641 50.999068409611)",220000.0,1084001.0,0.16,,3.0,0.64,1.0,Dünnwald West,Amprion,5.75,50.0,9874.0,0.65 +168,149,120_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,51.114,1.0,"SRID=4326;LINESTRING(7.19316578267403 50.798335064694,7.86394571365263 50.7682419129404)",220000.0,1084007.0,0.08,,3.0,0.32,2.0,Leuscheid Süd,Amprion,11.5,50.0,51114.0,1.3 +169,130,120,Al/St 240/40 4-bundle 380.0,1711.26619788,83.781,1.0,"SRID=4326;LINESTRING(7.12308195236379 51.1715887782054,7.86394571365263 50.7682419129404)",380000.0,1084017.0,0.025,,3.0,0.25,4.0,Dauersberg West,Amprion,13.7,50.0,83781.0,2.6 +170,125_220kV,155,Al/St 240/40 2-bundle 220.0,247.683265482,121.485,0.5,"SRID=4326;LINESTRING(6.96568442352005 51.0693295322353,7.72438401522458 51.4093341305984)",220000.0,1084020.0,0.16,,3.0,0.64,1.0,Bigge Ost,Amprion,5.75,50.0,121485.0,0.65 +171,125_220kV,154,Al/St 240/40 2-bundle 220.0,247.683265482,9.874,0.5,"SRID=4326;LINESTRING(6.96568442352005 51.0693295322353,7.0235444991641 50.999068409611)",220000.0,1084021.0,0.16,,3.0,0.64,1.0,Dünnwald Ost,Amprion,5.75,50.0,9874.0,0.65 +172,117_220kV,156_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,13.409,2.0,"SRID=4326;LINESTRING(6.97257742700703 50.795693017514,6.84223115147656 50.8598647243573)",220000.0,1084023.0,0.04,,3.0,0.16,4.0,Knapsack 2b,Amprion,23.0,50.0,13409.0,2.6 +173,117_220kV,149,Al/St 240/40 2-bundle 220.0,495.366530965,17.034,1.0,"SRID=4326;LINESTRING(6.97257742700703 50.795693017514,7.19316578267403 50.798335064694)",220000.0,1084027.0,0.08,,3.0,0.32,2.0,Troisdorf Ost,Amprion,11.5,50.0,17034.0,1.3 +174,154,150,Al/St 240/40 2-bundle 220.0,247.683265482,11.403,0.5,"SRID=4326;LINESTRING(7.0235444991641 50.999068409611,7.0473845241055 50.9055286094924)",220000.0,1084032.0,0.16,,3.0,0.64,1.0,Merheim Ost,Amprion,5.75,50.0,11403.0,0.65 +175,154,150,Al/St 240/40 2-bundle 220.0,247.683265482,11.403,0.5,"SRID=4326;LINESTRING(7.0235444991641 50.999068409611,7.0473845241055 50.9055286094924)",220000.0,1084040.0,0.16,,3.0,0.64,1.0,Merheim West,Amprion,5.75,50.0,11403.0,0.65 +176,117_220kV,149,Al/St 240/40 2-bundle 220.0,495.366530965,17.065,1.0,"SRID=4326;LINESTRING(6.97257742700703 50.795693017514,7.19316578267403 50.798335064694)",220000.0,1084046.0,0.08,,3.0,0.32,2.0,Troisdorf West,Amprion,11.5,50.0,17065.0,1.3 +177,157,110,Al/St 240/40 4-bundle 380.0,1711.26619788,57.541,1.0,"SRID=4326;LINESTRING(8.364229530696 51.9271204904611,7.99288723332679 51.6837077155955)",380000.0,1084345.0,0.025,,3.0,0.25,4.0,Gütersloh Süd,Amprion,13.7,50.0,57541.0,2.6 +178,119,158,Al/St 240/40 4-bundle 380.0,1711.26619788,4.315,1.0,"SRID=4326;LINESTRING(6.45655246642153 50.8713380368705,6.48812326381716 50.8810703125704)",380000.0,1084358.0,0.025,,3.0,0.25,4.0,Hambach 3c,Amprion,13.7,50.0,4315.0,2.6 +179,159,60,Al/St 240/40 4-bundle 380.0,855.633098939,31.674,0.5,"SRID=4326;LINESTRING(8.05408735442182 53.3323026933898,8.47470704898602 53.4286511943138)",380000.0,1086366.0,0.05,302,3.0,0.5,2.0,302,transpower,6.85,50.0,31674.0,1.3 +180,160_220kV,161,Al/St 240/40 2-bundle 220.0,495.366530965,32.238,1.0,"SRID=4326;LINESTRING(8.47791063608626 53.428485468955,8.05283751634017 53.3338809598955)",220000.0,1086367.0,0.08,302,3.0,0.32,2.0,302,TenneT,11.5,50.0,32238.0,1.3 +181,115,151,Al/St 240/40 2-bundle 220.0,990.733061929,11.477,2.0,"SRID=4326;LINESTRING(6.57648208812114 51.0555954583116,6.70967369516713 51.0909407427733)",220000.0,1101991.0,0.04,,3.0,0.16,4.0,Frimmersdorf 4d,Amprion,23.0,50.0,11477.0,2.6 +182,162,134,Al/St 240/40 2-bundle 220.0,495.366530965,4.813,1.0,"SRID=4326;LINESTRING(6.85153113794826 50.9841704680083,6.80461158012622 50.9662452961379)",220000.0,1101996.0,0.08,,3.0,0.32,2.0,Bocklemünd Süd,Amprion,11.5,50.0,4813.0,1.3 +183,115,123_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,5.73,1.0,"SRID=4326;LINESTRING(6.57648208812114 51.0555954583116,6.70220337779745 51.0088609096753)",220000.0,1101999.0,0.08,,3.0,0.32,2.0,Frimmersdorf 3c,Amprion,11.5,50.0,5730.0,1.3 +184,138,87,Al/St 240/40 4-bundle 380.0,1711.26619788,12.948,1.0,"SRID=4326;LINESTRING(7.00674987859998 51.5999114220203,7.06030699176008 51.6964760130648)",380000.0,1128370.0,0.025,,3.0,0.25,4.0,Scholven F,Amprion,13.7,50.0,12948.0,2.6 +185,162,134,Al/St 240/40 2-bundle 220.0,495.366530965,4.813,1.0,"SRID=4326;LINESTRING(6.85153113794826 50.9841704680083,6.80461158012622 50.9662452961379)",220000.0,1102004.0,0.08,,3.0,0.32,2.0,Bocklemünd Nord,Amprion,11.5,50.0,4813.0,1.3 +186,123,163,Al/St 240/40 4-bundle 380.0,1711.26619788,13.157,1.0,"SRID=4326;LINESTRING(6.70220337779745 51.0088609096753,6.59601979462215 50.9525274624278)",380000.0,1102892.0,0.025,,3.0,0.25,4.0,Paffendorf Süd,Amprion,13.7,50.0,13157.0,2.6 +187,112,164,Al/St 240/40 4-bundle 380.0,1711.26619788,10.045,1.0,"SRID=4326;LINESTRING(7.35311186939787 51.5758105020818,7.33861258614081 51.6319781250427)",380000.0,1109593.0,0.025,,3.0,0.25,4.0,Datteln West,Amprion,13.7,50.0,10045.0,2.6 +188,112,165,Al/St 240/40 4-bundle 380.0,1711.26619788,34.595,1.0,"SRID=4326;LINESTRING(7.35311186939787 51.5758105020818,7.70831848872651 51.6762033088437)",380000.0,1109595.0,0.025,,3.0,0.25,4.0,Mengede Nord,Amprion,13.7,50.0,34595.0,2.6 +189,166,165,Al/St 240/40 4-bundle 380.0,1711.26619788,15.207,1.0,"SRID=4326;LINESTRING(7.61702099509812 51.6371337640588,7.70831848872651 51.6762033088437)",380000.0,1109596.0,0.025,,3.0,0.25,4.0,Bergkamen A,Amprion,13.7,50.0,15207.0,2.6 +190,112,164,Al/St 240/40 4-bundle 380.0,1711.26619788,10.019,1.0,"SRID=4326;LINESTRING(7.35311186939787 51.5758105020818,7.33861258614081 51.6319781250427)",380000.0,1109599.0,0.025,,3.0,0.25,4.0,Datteln Ost,Amprion,13.7,50.0,10019.0,2.6 +191,167,165_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,25.175,1.0,"SRID=4326;LINESTRING(7.52228010104186 51.5321980552231,7.70831848872651 51.6762033088437)",220000.0,1109600.0,0.08,,3.0,0.32,2.0,Wambel Ost,Amprion,11.5,50.0,25175.0,1.3 +192,168,104,Al/St 240/40 4-bundle 380.0,1711.26619788,14.743,1.0,"SRID=4326;LINESTRING(6.90660779028721 51.5652424465435,6.92675896360373 51.4992056838496)",380000.0,1109607.0,0.025,,3.0,0.25,4.0,Essen West,Amprion,13.7,50.0,14743.0,2.6 +193,138_220kV,104_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,24.437,1.0,"SRID=4326;LINESTRING(7.00674987859998 51.5999114220203,6.92675896360373 51.4992056838496)",220000.0,1109609.0,0.08,,3.0,0.32,2.0,Scholven E,Amprion,11.5,50.0,24437.0,1.3 +194,139,104_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,16.157,1.0,"SRID=4326;LINESTRING(7.03761988504233 51.5985496311832,6.92675896360373 51.4992056838496)",220000.0,1109610.0,0.08,,3.0,0.32,2.0,Buer Süd,Amprion,11.5,50.0,16157.0,1.3 +195,104_220kV,169,Al/St 240/40 2-bundle 220.0,495.366530965,3.433,1.0,"SRID=4326;LINESTRING(6.92675896360373 51.4992056838496,6.96734393053748 51.4908618864265)",220000.0,1109616.0,0.08,,3.0,0.32,2.0,LMG West,Amprion,11.5,50.0,3433.0,1.3 +196,104_220kV,169,Al/St 240/40 2-bundle 220.0,495.366530965,3.427,1.0,"SRID=4326;LINESTRING(6.92675896360373 51.4992056838496,6.96734393053748 51.4908618864265)",220000.0,1109619.0,0.08,,3.0,0.32,2.0,LMG Ost,Amprion,11.5,50.0,3427.0,1.3 +197,168,105,Al/St 240/40 4-bundle 380.0,1711.26619788,42.401,1.0,"SRID=4326;LINESTRING(6.90660779028721 51.5652424465435,6.68078798893986 51.6462959356851)",380000.0,1116357.0,0.025,,3.0,0.25,4.0,Lippe Süd,Amprion,13.7,50.0,42401.0,2.6 +198,104_220kV,170,Al/St 240/40 2-bundle 220.0,495.366530965,8.909,1.0,"SRID=4326;LINESTRING(6.92675896360373 51.4992056838496,6.87287359352087 51.4457833372124)",220000.0,1123975.0,0.08,,3.0,0.32,2.0,Osterfeld West,Amprion,11.5,50.0,8909.0,1.3 +199,104_220kV,170,Al/St 240/40 2-bundle 220.0,247.683265482,8.837,0.5,"SRID=4326;LINESTRING(6.92675896360373 51.4992056838496,6.87287359352087 51.4457833372124)",220000.0,1123976.0,0.16,,3.0,0.64,1.0,Osterfeld Ost,Amprion,5.75,50.0,8837.0,0.65 +200,138,87,Al/St 240/40 4-bundle 380.0,1711.26619788,13.676,1.0,"SRID=4326;LINESTRING(7.00674987859998 51.5999114220203,7.06030699176008 51.6964760130648)",380000.0,1128366.0,0.025,,3.0,0.25,4.0,Scholven B,Amprion,13.7,50.0,13676.0,2.6 +201,171,89,Al/St 240/40 2-bundle 220.0,990.733061929,16.654,2.0,"SRID=4326;LINESTRING(6.86804496307922 51.3744747805902,6.72376714521247 51.3556543809247)",220000.0,1141433.0,0.04,,3.0,0.16,4.0,Meerbusch Ost,Amprion,23.0,50.0,16654.0,2.6 +202,119,158,Al/St 240/40 4-bundle 380.0,1711.26619788,4.315,1.0,"SRID=4326;LINESTRING(6.45655246642153 50.8713380368705,6.48812326381716 50.8810703125704)",380000.0,1167325.0,0.025,,3.0,0.25,4.0,Hambach 1a,Amprion,13.7,50.0,4315.0,2.6 +203,105_220kV,172,Al/St 240/40 2-bundle 220.0,990.733061929,19.072,2.0,"SRID=4326;LINESTRING(6.68078798893986 51.6462959356851,6.78752251671348 51.505550100013)",220000.0,1167326.0,0.04,,3.0,0.16,4.0,Dinslaken West,Amprion,23.0,50.0,19072.0,2.6 +204,123,173,Al/St 240/40 4-bundle 380.0,1711.26619788,30.309,1.0,"SRID=4326;LINESTRING(6.70220337779745 51.0088609096753,6.62284323546559 51.258713368098)",380000.0,1167329.0,0.025,,3.0,0.25,4.0,Knechtsteden West,Amprion,13.7,50.0,30309.0,2.6 +205,119,158,Al/St 240/40 4-bundle 380.0,1711.26619788,4.315,1.0,"SRID=4326;LINESTRING(6.45655246642153 50.8713380368705,6.48812326381716 50.8810703125704)",380000.0,1167330.0,0.025,,3.0,0.25,4.0,Hambach 2b,Amprion,13.7,50.0,4315.0,2.6 +206,105_220kV,174,Al/St 240/40 2-bundle 220.0,495.366530965,20.117,1.0,"SRID=4326;LINESTRING(6.68078798893986 51.6462959356851,6.81216588075166 51.5196200118095)",220000.0,1167333.0,0.08,,3.0,0.32,2.0,Sterkrade Nord,Amprion,11.5,50.0,20117.0,1.3 +207,37,39,Al/St 240/40 4-bundle 380.0,1711.26619788,37.603,1.0,"SRID=4326;LINESTRING(11.6389127631156 52.2687346818467,11.7273534150745 52.5689469938591)",380000.0,1173397.0,0.025,489,3.0,0.25,4.0,489,50Hertz,13.7,50.0,37603.0,2.6 +208,129_220kV,175,Al/St 240/40 2-bundle 220.0,495.366530965,19.857,1.0,"SRID=4326;LINESTRING(6.62582790177014 51.2606098249047,6.72031000055597 51.3915325380171)",220000.0,1174140.0,0.08,,3.0,0.32,2.0,Stratum Ost,Amprion,11.5,50.0,19857.0,1.3 +209,129_220kV,176,Al/St 240/40 2-bundle 220.0,495.366530965,8.599,1.0,"SRID=4326;LINESTRING(6.62582790177014 51.2606098249047,6.54240844257145 51.2994458149911)",220000.0,1174142.0,0.08,,3.0,0.32,2.0,Fichtenhain West,Amprion,11.5,50.0,8599.0,1.3 +210,88,129_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,9.099,1.0,"SRID=4326;LINESTRING(6.6746753206169 51.3249574728255,6.62582790177014 51.2606098249047)",220000.0,1174143.0,0.08,,3.0,0.32,2.0,Gellep West,Amprion,11.5,50.0,9099.0,1.3 +211,177,18_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,56.416,1.0,"SRID=4326;LINESTRING(13.0786421047586 54.2848461697305,13.6834769666546 54.1390906406635)",220000.0,1177425.0,0.08,313,3.0,0.32,2.0,313,50Hertz_Transmission,11.5,50.0,56416.0,1.3 +212,177,18_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,56.362,1.0,"SRID=4326;LINESTRING(13.0786421047586 54.2848461697305,13.6834769666546 54.1390906406635)",220000.0,1177426.0,0.08,314,3.0,0.32,2.0,314,50Hertz_Transmission,11.5,50.0,56362.0,1.3 +213,178,120_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,46.991,1.0,"SRID=4326;LINESTRING(8.02029033797883 50.9236168949394,7.86394571365263 50.7682419129404)",220000.0,1182294.0,0.08,,3.0,0.32,2.0,Heller Ost,Amprion,11.5,50.0,46991.0,1.3 +214,179,12_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,86.333,1.0,"SRID=4326;LINESTRING(13.9570055475871 53.252171625753,13.7096697058228 52.5397952580643)",220000.0,1188567.0,0.08,303,3.0,0.32,2.0,303,50Hertz_Transmission,11.5,50.0,86333.0,1.3 +215,180,181,Al/St 240/40 2-bundle 220.0,495.366530965,62.054,1.0,"SRID=4326;LINESTRING(14.270834670665 53.0962865535819,14.0336963684666 53.5078685980791)",220000.0,1188568.0,0.08,306,3.0,0.32,2.0,306,50Hertz Transmission,11.5,50.0,62054.0,1.3 +216,182,180,Al/St 240/40 2-bundle 220.0,495.366530965,26.246,1.0,"SRID=4326;LINESTRING(14.4762521006827 53.1967495480613,14.270834670665 53.0962865535819)",220000.0,1188569.0,0.08,507,3.0,0.32,2.0,220kV Krajnik - Vierraden,PSE Operator;50Hertz,11.5,50.0,26246.0,1.3 +217,179,181,Al/St 240/40 2-bundle 220.0,495.366530965,29.603,1.0,"SRID=4326;LINESTRING(13.9570055475871 53.252171625753,14.0336963684666 53.5078685980791)",220000.0,1188570.0,0.08,305,3.0,0.32,2.0,305,50Hertz Transmission,11.5,50.0,29603.0,1.3 +218,180,12_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,100.983,1.0,"SRID=4326;LINESTRING(14.270834670665 53.0962865535819,13.7096697058228 52.5397952580643)",220000.0,1188571.0,0.08,304,3.0,0.32,2.0,304,50Hertz_Transmission,11.5,50.0,100983.0,1.3 +219,183,30,Al/St 240/40 4-bundle 380.0,1711.26619788,28.675,1.0,"SRID=4326;LINESTRING(9.90884752815356 53.4880466403676,9.5017896678173 53.5428143723363)",380000.0,1188840.0,0.025,E,3.0,0.25,4.0,E,50Hertz Transmission,13.7,50.0,28675.0,2.6 +220,183,21,Al/St 240/40 4-bundle 380.0,3422.53239576,29.674,2.0,"SRID=4326;LINESTRING(9.90884752815356 53.4880466403676,10.1575015165126 53.5551316685834)",380000.0,1188841.0,0.0125,C,6.0,0.125,4.0,C,50Hertz Transmission,27.4,50.0,29674.0,5.2 +221,183,21,Al/St 240/40 4-bundle 380.0,1711.26619788,29.628,1.0,"SRID=4326;LINESTRING(9.90884752815356 53.4880466403676,10.1575015165126 53.5551316685834)",380000.0,1188844.0,0.025,D,3.0,0.25,4.0,D,50Hertz Transmission,13.7,50.0,29628.0,2.6 +222,183,30,Al/St 240/40 4-bundle 380.0,1711.26619788,28.654,1.0,"SRID=4326;LINESTRING(9.90884752815356 53.4880466403676,9.5017896678173 53.5428143723363)",380000.0,1188848.0,0.025,F,3.0,0.25,4.0,F,50Hertz Transmission,13.7,50.0,28654.0,2.6 +223,184_220kV,185,Al/St 240/40 2-bundle 220.0,247.683265482,14.09,0.5,"SRID=4326;LINESTRING(6.61738109142906 51.4757909635544,6.65673519501182 51.379523634379)",220000.0,1190720.0,0.16,,3.0,0.64,1.0,Ürdingen West,Amprion,5.75,50.0,14090.0,0.65 +224,184_220kV,89,Al/St 240/40 2-bundle 220.0,495.366530965,21.946,1.0,"SRID=4326;LINESTRING(6.61738109142906 51.4757909635544,6.72376714521247 51.3556543809247)",220000.0,1190723.0,0.08,,3.0,0.32,2.0,Mündelheim Nord,Amprion,11.5,50.0,21946.0,1.3 +225,184,186,Al/St 240/40 4-bundle 380.0,1711.26619788,9.562,1.0,"SRID=4326;LINESTRING(6.61738109142906 51.4757909635544,6.7111452744645 51.5261875490663)",380000.0,1208415.0,0.025,,3.0,0.25,4.0,Lohheide West,Amprion,13.7,50.0,9562.0,2.6 +226,184,187,Al/St 240/40 4-bundle 380.0,1711.26619788,15.588,1.0,"SRID=4326;LINESTRING(6.61738109142906 51.4757909635544,6.68934277038931 51.5764228881728)",380000.0,1208417.0,0.025,,3.0,0.25,4.0,Zensenbusch Süd,Amprion,13.7,50.0,15588.0,2.6 +227,137,105_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,41.843,1.0,"SRID=4326;LINESTRING(6.22183144595687 51.6946456422206,6.68078798893986 51.6462959356851)",220000.0,1208426.0,0.08,,3.0,0.32,2.0,Pfalzdorf Nord,Amprion,11.5,50.0,41843.0,1.3 +228,104_220kV,172,Al/St 240/40 2-bundle 220.0,495.366530965,10.353,1.0,"SRID=4326;LINESTRING(6.92675896360373 51.4992056838496,6.78752251671348 51.505550100013)",220000.0,1224880.0,0.08,,3.0,0.32,2.0,Emscher Nord,Amprion,11.5,50.0,10353.0,1.3 +229,104_220kV,172,Al/St 240/40 2-bundle 220.0,495.366530965,10.342,1.0,"SRID=4326;LINESTRING(6.92675896360373 51.4992056838496,6.78752251671348 51.505550100013)",220000.0,1224881.0,0.08,,3.0,0.32,2.0,Emscher Süd,Amprion,11.5,50.0,10342.0,1.3 +230,89,175,Al/St 240/40 2-bundle 220.0,495.366530965,5.643,1.0,"SRID=4326;LINESTRING(6.72376714521247 51.3556543809247,6.72031000055597 51.3915325380171)",220000.0,1225767.0,0.08,,3.0,0.32,2.0,Rheinhausen West,Amprion,11.5,50.0,5643.0,1.3 +231,185,89,Al/St 240/40 2-bundle 220.0,495.366530965,8.792,1.0,"SRID=4326;LINESTRING(6.65673519501182 51.379523634379,6.72376714521247 51.3556543809247)",220000.0,1225768.0,0.08,,3.0,0.32,2.0,Mündelheim Süd,Amprion,11.5,50.0,8792.0,1.3 +232,89,188,Al/St 240/40 2-bundle 220.0,495.366530965,4.702,1.0,"SRID=4326;LINESTRING(6.72376714521247 51.3556543809247,6.72365237186396 51.375845810902)",220000.0,1225769.0,0.08,,3.0,0.32,2.0,Huckingen 2b,Amprion,11.5,50.0,4702.0,1.3 +233,189,190,Al/St 240/40 4-bundle 380.0,1283.44964841,37.253,0.75,"SRID=4326;LINESTRING(8.61664767275337 48.0659537906797,8.87294280487974 48.3100912582216)",380000.0,1460038.0,0.033333,,3.0,0.333333,3.0,ENGST-TRSNG,EnBW,10.275,,37253.0,1.95 +234,191_220kV,172,Al/St 240/40 2-bundle 220.0,495.366530965,6.769,1.0,"SRID=4326;LINESTRING(6.71525259691926 51.5267908666491,6.78752251671348 51.505550100013)",220000.0,1491474.0,0.08,,3.0,0.32,2.0,Walsum Süd,Amprion,11.5,50.0,6769.0,1.3 +235,191_220kV,172,Al/St 240/40 2-bundle 220.0,495.366530965,6.525,1.0,"SRID=4326;LINESTRING(6.71525259691926 51.5267908666491,6.78752251671348 51.505550100013)",220000.0,1491477.0,0.08,,3.0,0.32,2.0,Walsum Nord,Amprion,11.5,50.0,6525.0,1.3 +236,42_220kV,192,Al/St 240/40 2-bundle 220.0,495.366530965,0.929,1.0,"SRID=4326;LINESTRING(12.1869848227216 53.8157751724384,12.1857651811846 53.8108597989599)",220000.0,1521278.0,0.08,,3.0,0.32,2.0,,50Hertz,11.5,50.0,929.0,1.3 +237,42_220kV,192,Al/St 240/40 2-bundle 220.0,495.366530965,0.933,1.0,"SRID=4326;LINESTRING(12.1869848227216 53.8157751724384,12.1857651811846 53.8108597989599)",220000.0,1521279.0,0.08,,3.0,0.32,2.0,,50Hertz,11.5,50.0,933.0,1.3 +238,105,186,Al/St 240/40 4-bundle 380.0,1711.26619788,18.907,1.0,"SRID=4326;LINESTRING(6.68078798893986 51.6462959356851,6.7111452744645 51.5261875490663)",380000.0,1526991.0,0.025,,3.0,0.25,4.0,Niederrhein West,Amprion,13.7,50.0,18907.0,2.6 +239,105,187,Al/St 240/40 4-bundle 380.0,1711.26619788,12.541,1.0,"SRID=4326;LINESTRING(6.68078798893986 51.6462959356851,6.68934277038931 51.5764228881728)",380000.0,1527013.0,0.025,,3.0,0.25,4.0,Zensenbusch Nord,Amprion,13.7,50.0,12541.0,2.6 +240,191_220kV,193,Al/St 240/40 2-bundle 220.0,495.366530965,8.483,1.0,"SRID=4326;LINESTRING(6.71525259691926 51.5267908666491,6.72256980950278 51.4797876727124)",220000.0,1527967.0,0.08,,3.0,0.32,2.0,Beeck Tr212,Amprion,11.5,50.0,8483.0,1.3 +241,187,194,Al/St 240/40 4-bundle 380.0,1711.26619788,0.71,1.0,"SRID=4326;LINESTRING(6.68934277038931 51.5764228881728,6.68306568260248 51.577104777735)",380000.0,1527969.0,0.025,,3.0,0.25,4.0,Voerde B,Amprion,13.7,50.0,710.0,2.6 +242,187,194,Al/St 240/40 4-bundle 380.0,1711.26619788,0.562,1.0,"SRID=4326;LINESTRING(6.68934277038931 51.5764228881728,6.68306568260248 51.577104777735)",380000.0,1527971.0,0.025,,3.0,0.25,4.0,Voerde A,Amprion,13.7,50.0,562.0,2.6 +243,191_220kV,195_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,6.647,1.0,"SRID=4326;LINESTRING(6.71525259691926 51.5267908666491,6.68310697663871 51.5756455059265)",220000.0,1527975.0,0.08,,3.0,0.32,2.0,Möllen Süd,Amprion,11.5,50.0,6647.0,1.3 +244,191_220kV,196,Al/St 240/40 2-bundle 220.0,495.366530965,5.026,1.0,"SRID=4326;LINESTRING(6.71525259691926 51.5267908666491,6.74541676675317 51.4986419480434)",220000.0,1527977.0,0.08,,3.0,0.32,2.0,Schwelgern Tr22,Amprion,11.5,50.0,5026.0,1.3 +245,197,195_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,7.165,1.0,"SRID=4326;LINESTRING(6.61427855504049 51.6186788033284,6.68310697663871 51.5756455059265)",220000.0,1527982.0,0.08,,3.0,0.32,2.0,Möllen Nord,Amprion,11.5,50.0,7165.0,1.3 +246,110,198,Al/St 240/40 4-bundle 380.0,1711.26619788,2.167,1.0,"SRID=4326;LINESTRING(7.99288723332679 51.6837077155955,7.9706568937083 51.6768446667846)",380000.0,1703680.0,0.025,,3.0,0.25,4.0,Westfalen D,Amprion,13.7,50.0,2167.0,2.6 +247,197,191_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,13.873,1.0,"SRID=4326;LINESTRING(6.61427855504049 51.6186788033284,6.71525259691926 51.5267908666491)",220000.0,1527984.0,0.08,,3.0,0.32,2.0,Löhnen West,Amprion,11.5,50.0,13873.0,1.3 +248,199,165_220kV,Al/St 240/40 2-bundle 220.0,247.683265482,27.322,0.5,"SRID=4326;LINESTRING(7.44506930371379 51.6185000869374,7.70831848872651 51.6762033088437)",220000.0,1547542.0,0.16,,3.0,0.64,1.0,Cappenberg Süd,Amprion,5.75,50.0,27322.0,0.65 +249,134,123_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,11.754,1.0,"SRID=4326;LINESTRING(6.80461158012622 50.9662452961379,6.70220337779745 51.0088609096753)",220000.0,1548412.0,0.08,,3.0,0.32,2.0,Rommerskirchen Ost,Amprion,11.5,50.0,11754.0,1.3 +250,200,193,Al/St 240/40 2-bundle 220.0,495.366530965,2.82,1.0,"SRID=4326;LINESTRING(6.73212495035583 51.4594437365975,6.72256980950278 51.4797876727124)",220000.0,1578736.0,0.08,,3.0,0.32,2.0,Stockum West,Amprion,11.5,50.0,2820.0,1.3 +251,200,193,Al/St 240/40 2-bundle 220.0,495.366530965,2.82,1.0,"SRID=4326;LINESTRING(6.73212495035583 51.4594437365975,6.72256980950278 51.4797876727124)",220000.0,1578739.0,0.08,,3.0,0.32,2.0,Stockum Ost,Amprion,11.5,50.0,2820.0,1.3 +252,4,201,Al/St 240/40 4-bundle 380.0,855.633098939,79.19,0.5,"SRID=4326;LINESTRING(9.12526570294975 52.5382640870749,9.25103425143703 53.1144323051271)",380000.0,1584792.0,0.05,3003,3.0,0.5,2.0,3003 schwarz,TenneT,6.85,50.0,79190.0,1.3 +253,4,201,Al/St 240/40 4-bundle 380.0,855.633098939,79.282,0.5,"SRID=4326;LINESTRING(9.12526570294975 52.5382640870749,9.25103425143703 53.1144323051271)",380000.0,1584793.0,0.05,3003,3.0,0.5,2.0,3003 grün,TenneT,6.85,50.0,79282.0,1.3 +254,202,86,Al/St 240/40 4-bundle 380.0,1711.26619788,0.772,1.0,"SRID=4326;LINESTRING(7.32089257541703 52.4714572265383,7.30820430094578 52.4768353862806)",380000.0,1596307.0,0.025,,3.0,0.25,4.0,Kernkraftwerk Emsland,Amprion,13.7,50.0,772.0,2.6 +255,85,203,Al/St 240/40 4-bundle 380.0,1711.26619788,21.968,1.0,"SRID=4326;LINESTRING(7.03496796515083 52.2028607381056,6.75917485282445 52.2488578798871)",380000.0,1596713.0,0.025,,3.0,0.25,4.0,Gronau - Hengelo weiß,Amprion,13.7,50.0,21968.0,2.6 +256,85,203,Al/St 240/40 4-bundle 380.0,1711.26619788,21.962,1.0,"SRID=4326;LINESTRING(7.03496796515083 52.2028607381056,6.75917485282445 52.2488578798871)",380000.0,1596714.0,0.025,,3.0,0.25,4.0,Gronau - Hengelo schwarz,Amprion,13.7,50.0,21962.0,2.6 +257,147,204,Al/St 240/40 2-bundle 220.0,495.366530965,22.344,1.0,"SRID=4326;LINESTRING(7.50597329671546 51.401592550355,7.70632726933662 51.278583155205)",220000.0,1605946.0,0.08,S,3.0,0.32,2.0,Südleitung,markE,11.5,50.0,22344.0,1.3 +258,147,204,Al/St 240/40 2-bundle 220.0,495.366530965,22.347,1.0,"SRID=4326;LINESTRING(7.50597329671546 51.401592550355,7.70632726933662 51.278583155205)",220000.0,1605949.0,0.08,N,3.0,0.32,2.0,Nordleitung,markE,11.5,50.0,22347.0,1.3 +259,147,205,Al/St 240/40 2-bundle 220.0,247.683265482,4.507,0.5,"SRID=4326;LINESTRING(7.50597329671546 51.401592550355,7.45034204531003 51.413608212821)",220000.0,1606843.0,0.16,,3.0,0.64,1.0,Koepchenwerk Maschine 5,Amprion,5.75,50.0,4507.0,0.65 +260,123,206,Al/St 240/40 4-bundle 380.0,1711.26619788,6.7,1.0,"SRID=4326;LINESTRING(6.70220337779745 51.0088609096753,6.63448162267058 51.0359799381303)",380000.0,1610217.0,0.025,,3.0,0.25,4.0,Neurath 4d,Amprion,13.7,50.0,6700.0,2.6 +261,123,206,Al/St 240/40 4-bundle 380.0,1711.26619788,6.646,1.0,"SRID=4326;LINESTRING(6.70220337779745 51.0088609096753,6.63448162267058 51.0359799381303)",380000.0,1610218.0,0.025,,3.0,0.25,4.0,Neurath 5e,Amprion,13.7,50.0,6646.0,2.6 +262,165_220kV,207,Al/St 240/40 2-bundle 220.0,495.366530965,27.915,1.0,"SRID=4326;LINESTRING(7.70831848872651 51.6762033088437,7.60711612154456 51.8986046029193)",220000.0,1610616.0,0.08,,3.0,0.32,2.0,Werse West,Amprion,11.5,50.0,27915.0,1.3 +263,165,208,Al/St 240/40 4-bundle 380.0,1711.26619788,23.948,1.0,"SRID=4326;LINESTRING(7.70831848872651 51.6762033088437,7.4035444340631 51.644449487967)",380000.0,1610620.0,0.025,,3.0,0.25,4.0,Waltrop Süd,Amprion,13.7,50.0,23948.0,2.6 +264,4,94,Al/St 240/40 4-bundle 380.0,1711.26619788,79.06,1.0,"SRID=4326;LINESTRING(9.12526570294975 52.5382640870749,8.30775689679575 52.3456356447783)",380000.0,1630765.0,0.025,,3.0,0.25,4.0,Dümmersee Süd 1,Amprion,13.7,50.0,79060.0,2.6 +265,4,2,Al/St 240/40 4-bundle 380.0,3422.53239576,1.268,2.0,"SRID=4326;LINESTRING(9.12526570294975 52.5382640870749,9.11321007472722 52.5438533223737)",380000.0,1630850.0,0.0125,,6.0,0.125,4.0,Robert Frank - Landesbergen,transpower,27.4,50.0,1268.0,5.2 +266,4,2,Al/St 240/40 4-bundle 380.0,1711.26619788,1.105,1.0,"SRID=4326;LINESTRING(9.12526570294975 52.5382640870749,9.11321007472722 52.5438533223737)",380000.0,1630851.0,0.025,3004,3.0,0.25,4.0,Leitung 3004,transpower,13.7,50.0,1105.0,2.6 +267,4,2,Al/St 240/40 4-bundle 380.0,1711.26619788,0.902,1.0,"SRID=4326;LINESTRING(9.12526570294975 52.5382640870749,9.11321007472722 52.5438533223737)",380000.0,1630852.0,0.025,3005,3.0,0.25,4.0,Leitung 3005,transpower,13.7,50.0,902.0,2.6 +268,209,4,Al/St 240/40 4-bundle 380.0,3422.53239576,25.841,2.0,"SRID=4326;LINESTRING(8.93969252741257 52.4030332405173,9.12526570294975 52.5382640870749)",380000.0,1630911.0,0.0125,3017,6.0,0.125,4.0,Leitung 3017,TenneT,27.4,50.0,25841.0,5.2 +269,119,163,Al/St 240/40 4-bundle 380.0,1711.26619788,20.419,1.0,"SRID=4326;LINESTRING(6.45655246642153 50.8713380368705,6.59601979462215 50.9525274624278)",380000.0,1632259.0,0.025,,3.0,0.25,4.0,Oberzier Süd,Amprion,13.7,50.0,20419.0,2.6 +270,210,3,Al/St 240/40 4-bundle 380.0,1711.26619788,1.82,1.0,"SRID=4326;LINESTRING(9.40952032742215 52.034316665873,9.38974509624863 52.0263130660355)",380000.0,1633120.0,0.025,3013,3.0,0.25,4.0,Leitung 3013,TenneT,13.7,50.0,1820.0,2.6 +271,99,211,Al/St 240/40 4-bundle 380.0,1711.26619788,20.242,1.0,"SRID=4326;LINESTRING(6.99647659179489 49.3754093063084,7.23579344536789 49.3693923629482)",380000.0,1636702.0,0.025,,3.0,0.25,4.0,Bliestal Süd,Amprion,13.7,,20242.0,2.6 +272,99,69,Al/St 240/40 4-bundle 380.0,1711.26619788,125.443,1.0,"SRID=4326;LINESTRING(6.99647659179489 49.3754093063084,8.41937105938048 49.6337356823792)",380000.0,1636703.0,0.025,,3.0,0.25,4.0,Bürstadt Ltg,Amprion,13.7,50.0,125443.0,2.6 +273,212,99,Al/St 240/40 4-bundle 380.0,1711.26619788,48.829,1.0,"SRID=4326;LINESTRING(6.81368431456473 49.7248340737293,6.99647659179489 49.3754093063084)",380000.0,1636708.0,0.025,,3.0,0.25,4.0,Uchtelfangen Ost,Amprion,13.7,,48829.0,2.6 +274,213,214,Al/St 240/40 2-bundle 220.0,247.683265482,45.076,0.5,"SRID=4326;LINESTRING(8.58869049603326 49.8155140579794,8.5471609252134 49.4405396661002)",220000.0,1636720.0,0.16,,3.0,0.64,1.0,Rheinau Ost,Amprion,5.75,50.0,45076.0,0.65 +275,69_220kV,215,Al/St 240/40 2-bundle 220.0,495.366530965,13.606,1.0,"SRID=4326;LINESTRING(8.41937105938048 49.6337356823792,8.40826973550855 49.5285689245626)",220000.0,1636721.0,0.08,,3.0,0.32,2.0,Anilin 1A,Amprion,11.5,50.0,13606.0,1.3 +276,128,69,Al/St 240/40 4-bundle 380.0,1711.26619788,123.721,1.0,"SRID=4326;LINESTRING(7.44948529010545 50.4025276621126,8.41937105938048 49.6337356823792)",380000.0,1636722.0,0.025,,3.0,0.25,4.0,Soonwald West,Amprion,13.7,50.0,123721.0,2.6 +277,128,69,Al/St 240/40 4-bundle 380.0,1711.26619788,123.741,1.0,"SRID=4326;LINESTRING(7.44948529010545 50.4025276621126,8.41937105938048 49.6337356823792)",380000.0,1636723.0,0.025,,3.0,0.25,4.0,Soonwald Ost,Amprion,13.7,50.0,123741.0,2.6 +278,69_220kV,215,Al/St 240/40 2-bundle 220.0,495.366530965,13.604,1.0,"SRID=4326;LINESTRING(8.41937105938048 49.6337356823792,8.40826973550855 49.5285689245626)",220000.0,1636726.0,0.08,,3.0,0.32,2.0,Anilin 2B,Amprion,11.5,50.0,13604.0,1.3 +279,216,69,Al/St 240/40 4-bundle 380.0,1711.26619788,111.236,1.0,"SRID=4326;LINESTRING(9.1907874369222 48.914416873799,8.41937105938048 49.6337356823792)",380000.0,1636729.0,0.025,,3.0,0.25,4.0,Kugelberg West,Amprion,13.7,50.0,111236.0,2.6 +280,216,69,Al/St 240/40 4-bundle 380.0,1711.26619788,111.228,1.0,"SRID=4326;LINESTRING(9.1907874369222 48.914416873799,8.41937105938048 49.6337356823792)",380000.0,1636730.0,0.025,,3.0,0.25,4.0,Kugelberg Ost,Amprion,13.7,50.0,111228.0,2.6 +281,213,214,Al/St 240/40 2-bundle 220.0,495.366530965,45.076,1.0,"SRID=4326;LINESTRING(8.58869049603326 49.8155140579794,8.5471609252134 49.4405396661002)",220000.0,1636737.0,0.08,,3.0,0.32,2.0,Rheinau West,Amprion,11.5,50.0,45076.0,1.3 +282,217,98,Al/St 240/40 4-bundle 380.0,1711.26619788,7.386,1.0,"SRID=4326;LINESTRING(12.1320699705054 54.14245987564,12.2147735096776 54.1003234403618)",380000.0,1636750.0,0.025,546,3.0,0.25,4.0,546,50Hertz,13.7,50.0,7386.0,2.6 +283,42_220kV,218,Al/St 240/40 2-bundle 220.0,495.366530965,41.565,1.0,"SRID=4326;LINESTRING(12.1869848227216 53.8157751724384,12.2135605440795 54.1119998545154)",220000.0,1636782.0,0.08,275,3.0,0.32,2.0,275,50Hertz Transmission,11.5,50.0,41565.0,1.3 +284,177,42_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,106.704,1.0,"SRID=4326;LINESTRING(13.0786421047586 54.2848461697305,12.1869848227216 53.8157751724384)",220000.0,1636783.0,0.08,276;317,3.0,0.32,2.0,276/317,50Hertz Transmission,11.5,50.0,106704.0,1.3 +285,219,209,Al/St 240/40 4-bundle 380.0,1711.26619788,45.492,1.0,"SRID=4326;LINESTRING(8.5712588278729 52.1053649602273,8.93969252741257 52.4030332405173)",380000.0,1636863.0,0.025,3018,3.0,0.25,4.0,,TenneT,13.7,50.0,45492.0,2.6 +286,219,220,Al/St 240/40 4-bundle 380.0,1711.26619788,15.054,1.0,"SRID=4326;LINESTRING(8.5712588278729 52.1053649602273,8.65937290290079 51.9996101318907)",380000.0,1636864.0,0.025,,3.0,0.25,4.0,Eickum - Bechterdissen,TenneT,13.7,50.0,15054.0,2.6 +287,219,220,Al/St 240/40 4-bundle 380.0,1711.26619788,15.048,1.0,"SRID=4326;LINESTRING(8.5712588278729 52.1053649602273,8.65937290290079 51.9996101318907)",380000.0,1636865.0,0.025,,3.0,0.25,4.0,Eickum - Bechterdissen,TenneT,13.7,50.0,15048.0,2.6 +288,219,209,Al/St 240/40 4-bundle 380.0,1711.26619788,45.501,1.0,"SRID=4326;LINESTRING(8.5712588278729 52.1053649602273,8.93969252741257 52.4030332405173)",380000.0,1636866.0,0.025,3018,3.0,0.25,4.0,Overstädt - Eickum,TenneT,13.7,50.0,45501.0,2.6 +289,94,95,Al/St 240/40 4-bundle 380.0,1711.26619788,61.846,1.0,"SRID=4326;LINESTRING(8.30775689679575 52.3456356447783,7.88957671235007 52.2773967624078)",380000.0,1636917.0,0.025,,3.0,0.25,4.0,Niedersachsen Nord,Amprion,13.7,50.0,61846.0,2.6 +290,159,221,Al/St 240/40 4-bundle 380.0,855.633098939,57.405,0.5,"SRID=4326;LINESTRING(8.05408735442182 53.3323026933898,7.31136229105814 53.1257665563186)",380000.0,1637160.0,0.05,304,3.0,0.5,2.0,,TenneT,6.85,50.0,57405.0,1.3 +291,159,221,Al/St 240/40 4-bundle 380.0,855.633098939,57.397,0.5,"SRID=4326;LINESTRING(8.05408735442182 53.3323026933898,7.31136229105814 53.1257665563186)",380000.0,1637161.0,0.05,304,3.0,0.5,2.0,,TenneT,6.85,50.0,57397.0,1.3 +292,34,161,Al/St 240/40 2-bundle 220.0,495.366530965,33.166,1.0,"SRID=4326;LINESTRING(8.12264230304551 53.5605160490331,8.05283751634017 53.3338809598955)",220000.0,1637162.0,0.08,204,3.0,0.32,2.0,204 rot,TenneT,11.5,50.0,33166.0,1.3 +293,222,221,Al/St 240/40 4-bundle 380.0,1711.26619788,26.834,1.0,"SRID=4326;LINESTRING(6.94948397153705 53.1235378223082,7.31136229105814 53.1257665563186)",380000.0,1637163.0,0.025,306,6.0,0.25,2.0,Diele (DE) - Meeden (NL),TenneT,13.7,50.0,26834.0,2.6 +294,34,161,Al/St 240/40 2-bundle 220.0,495.366530965,33.17,1.0,"SRID=4326;LINESTRING(8.12264230304551 53.5605160490331,8.05283751634017 53.3338809598955)",220000.0,1637164.0,0.08,204,3.0,0.32,2.0,204 blau,TenneT,11.5,50.0,33170.0,1.3 +295,211,52,Al/St 240/40 4-bundle 380.0,855.633098939,152.12,0.5,"SRID=4326;LINESTRING(7.23579344536789 49.3693923629482,8.3133401659254 49.0090434643525)",380000.0,1638847.0,,,3.0,,2.0,Kaiserslautern Süd,Amprion,,50.0,152120.0, +296,215,223,Al/St 240/40 2-bundle 220.0,495.366530965,73.707,1.0,"SRID=4326;LINESTRING(8.40826973550855 49.5285689245626,8.27327814618118 49.0293980916396)",220000.0,1638848.0,,,3.0,,2.0,Weingarten Ltg,Amprion,,50.0,73707.0, +297,224,215,Al/St 240/40 2-bundle 220.0,495.366530965,28.868,1.0,"SRID=4326;LINESTRING(8.3523338799966 49.4212743386606,8.40826973550855 49.5285689245626)",220000.0,1638849.0,,,3.0,,2.0,Roxheim Süd,Amprion,,50.0,28868.0, +298,225,224,Al/St 240/40 2-bundle 220.0,495.366530965,63.539,1.0,"SRID=4326;LINESTRING(7.72920197913641 49.4826474297682,8.3523338799966 49.4212743386606)",220000.0,1638851.0,,,3.0,,2.0,Eisbach Süd,Amprion,,50.0,63539.0, +299,224,223,Al/St 240/40 2-bundle 220.0,495.366530965,46.981,1.0,"SRID=4326;LINESTRING(8.3523338799966 49.4212743386606,8.27327814618118 49.0293980916396)",220000.0,1638852.0,,,3.0,,2.0,Bienwald West,Amprion,,50.0,46981.0, +300,226,6,Al/St 240/40 2-bundle 220.0,247.683265482,48.552,0.5,"SRID=4326;LINESTRING(9.79733602695253 52.0008392513306,9.91814864613865 52.3799963125719)",220000.0,1639492.0,0.16,2001/2030,3.0,0.64,1.0,Leitung 2001/2030,TenneT,5.75,50.0,48552.0,0.65 +301,7,3,Al/St 240/40 4-bundle 380.0,1711.26619788,53.907,1.0,"SRID=4326;LINESTRING(9.91717971972185 52.2781686139394,9.38974509624863 52.0263130660355)",380000.0,1639494.0,0.025,3027,3.0,0.25,4.0,Leitung 3027,TenneT,13.7,50.0,53907.0,2.6 +302,216,227,Al/St 240/40 4-bundle 380.0,1711.26619788,114.939,1.0,"SRID=4326;LINESTRING(9.1907874369222 48.914416873799,10.0821573264928 48.2660901578788)",380000.0,1641461.0,0.025,,3.0,0.25,4.0,Donau Ost,Amprion,13.7,50.0,114939.0,2.6 +303,228,229,Al/St 240/40 2-bundle 220.0,495.366530965,15.95,1.0,"SRID=4326;LINESTRING(8.62716125806072 49.5435952670263,8.50180851652818 49.4449115114399)",220000.0,1641462.0,,,3.0,,2.0,Bergstraße,Amprion,,50.0,15950.0, +304,227,230,Al/St 240/40 4-bundle 380.0,1711.26619788,46.07,1.0,"SRID=4326;LINESTRING(10.0821573264928 48.2660901578788,10.391776056934 48.5184609117668)",380000.0,1641463.0,0.025,,3.0,0.25,4.0,Günzburg Süd,Amprion,13.7,50.0,46070.0,2.6 +305,231,53,Al/St 240/40 4-bundle 380.0,3422.53239576,83.096,2.0,"SRID=4326;LINESTRING(7.95805402328778 47.5954722640613,7.75349546540486 48.0993557006259)",380000.0,1641466.0,0.0125,,6.0,0.125,4.0,Rheingraben,Amprion,27.4,50.0,83096.0,5.2 +306,232_220kV,233_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,19.756,1.0,"SRID=4326;LINESTRING(10.0837512706734 48.268867515302,9.88551509756969 48.3040743584705)",220000.0,1641467.0,,,3.0,,2.0,Iller Ltg.,Amprion,,50.0,19756.0, +307,234,235,Al/St 240/40 2-bundle 220.0,495.366530965,69.709,1.0,"SRID=4326;LINESTRING(9.01340238045655 47.8453486310857,8.25192548128222 47.6397698264894)",220000.0,1641471.0,,,3.0,,2.0,Gurtweil - Stockach,Amprion,,50.0,69709.0, +308,236_220kV,214,Al/St 240/40 2-bundle 220.0,495.366530965,86.251,1.0,"SRID=4326;LINESTRING(9.19675652645089 48.9129634380259,8.5471609252134 49.4405396661002)",220000.0,1641472.0,,,3.0,,2.0,Nord-Süd-Leitung RWE Hoheneck–Rheinau,Amprion,,50.0,86251.0, +309,227,230,Al/St 240/40 4-bundle 380.0,1711.26619788,46.128,1.0,"SRID=4326;LINESTRING(10.0821573264928 48.2660901578788,10.391776056934 48.5184609117668)",380000.0,1641474.0,0.025,,3.0,0.25,4.0,Günzburg Nord,Amprion,13.7,50.0,46128.0,2.6 +310,233,227,Al/St 240/40 4-bundle 380.0,1711.26619788,16.751,1.0,"SRID=4326;LINESTRING(9.88551509756969 48.3040743584705,10.0821573264928 48.2660901578788)",380000.0,1641476.0,0.025,,3.0,0.25,4.0,Donau West,Amprion,13.7,50.0,16751.0,2.6 +311,237,238,Al/St 240/40 2-bundle 220.0,495.366530965,39.816,1.0,"SRID=4326;LINESTRING(6.13981113770544 49.6686086456422,6.62053556843084 49.7489386710632)",220000.0,1641479.0,0.08,,3.0,0.32,2.0,Heisdorf - Trier Süd,Amprion,11.5,,39816.0,1.3 +312,110,198,Al/St 240/40 4-bundle 380.0,1711.26619788,2.163,1.0,"SRID=4326;LINESTRING(7.99288723332679 51.6837077155955,7.9706568937083 51.6768446667846)",380000.0,1703681.0,0.025,,3.0,0.25,4.0,Westfalen E,Amprion,13.7,50.0,2163.0,2.6 +313,212,100,Al/St 240/40 4-bundle 380.0,1711.26619788,45.951,1.0,"SRID=4326;LINESTRING(6.81368431456473 49.7248340737293,6.47473977006816 49.9224692993999)",380000.0,1641482.0,0.025,,3.0,0.25,4.0,Gilzem Ost,Amprion,13.7,,45951.0,2.6 +314,100_220kV,239,Al/St 240/40 2-bundle 220.0,495.366530965,26.324,1.0,"SRID=4326;LINESTRING(6.47473977006816 49.9224692993999,6.70228540768092 49.8289944201161)",220000.0,1641485.0,0.08,,3.0,0.32,2.0,Niederstedem Ost,Amprion,11.5,,26324.0,1.3 +315,238,100_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,22.939,1.0,"SRID=4326;LINESTRING(6.62053556843084 49.7489386710632,6.47473977006816 49.9224692993999)",220000.0,1641488.0,0.08,,3.0,0.32,2.0,Niederstedem West,Amprion,11.5,,22939.0,1.3 +316,213,240,Al/St 240/40 2-bundle 220.0,495.366530965,25.189,1.0,"SRID=4326;LINESTRING(8.58869049603326 49.8155140579794,8.53151493939883 50.047874332134)",220000.0,1641698.0,,,3.0,,2.0,Marxheim Ost,Amprion,,50.0,25189.0, +317,241,242,Al/St 240/40 4-bundle 380.0,1711.26619788,56.963,1.0,"SRID=4326;LINESTRING(8.4713716330449 50.0972077427754,8.06573796639762 50.4060681118621)",380000.0,1641699.0,0.025,,3.0,0.25,4.0,Hessen Ost,Amprion,13.7,50.0,56963.0,2.6 +318,70,241,Al/St 240/40 4-bundle 380.0,1711.26619788,18.92,1.0,"SRID=4326;LINESTRING(8.38855636787547 49.9823050841271,8.4713716330449 50.0972077427754)",380000.0,1641702.0,0.025,,3.0,0.25,4.0,Trebur Süd,Amprion,13.7,50.0,18920.0,2.6 +319,70,241,Al/St 240/40 4-bundle 380.0,1711.26619788,44.127,1.0,"SRID=4326;LINESTRING(8.38855636787547 49.9823050841271,8.4713716330449 50.0972077427754)",380000.0,1641706.0,0.025,,3.0,0.25,4.0,Trebur Nord,Amprion,13.7,50.0,44127.0,2.6 +322,68_220kV,213,Al/St 240/40 2-bundle 220.0,495.366530965,34.375,1.0,"SRID=4326;LINESTRING(8.77244296019056 49.974668665137,8.58869049603326 49.8155140579794)",220000.0,1641734.0,,,3.0,,2.0,Pfungstadt Ost,Amprion,,50.0,34375.0, +323,68_220kV,213,Al/St 240/40 2-bundle 220.0,495.366530965,34.375,1.0,"SRID=4326;LINESTRING(8.77244296019056 49.974668665137,8.58869049603326 49.8155140579794)",220000.0,1641735.0,,,3.0,,2.0,Kranichstein Ost,Amprion,,50.0,34375.0, +324,68,245,Al/St 240/40 4-bundle 380.0,1711.26619788,20.861,1.0,"SRID=4326;LINESTRING(8.77244296019056 49.974668665137,8.98953614977701 50.0602550608281)",380000.0,1641756.0,0.025,,3.0,0.25,4.0,Untermain Nord,Amprion,13.7,50.0,20861.0,2.6 +325,245,246,Al/St 240/40 4-bundle 380.0,1711.26619788,4.447,1.0,"SRID=4326;LINESTRING(8.98953614977701 50.0602550608281,8.95870856177306 50.0826078105489)",380000.0,1641757.0,0.025,,3.0,0.25,4.0,Untermain Süd,Amprion,13.7,50.0,4447.0,2.6 +326,68,246,Al/St 240/40 4-bundle 380.0,1711.26619788,23.705,1.0,"SRID=4326;LINESTRING(8.77244296019056 49.974668665137,8.95870856177306 50.0826078105489)",380000.0,1641761.0,0.025,,3.0,0.25,4.0,Karlstein Süd,Amprion,13.7,50.0,23705.0,2.6 +327,232_220kV,247_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,73.168,2.0,"SRID=4326;LINESTRING(10.0837512706734 48.268867515302,10.363384232266 47.7383368092793)",220000.0,1641774.0,0.04,,3.0,0.16,4.0,Füssen West,Amprion,23.0,50.0,73168.0,2.6 +328,247,227,Al/St 240/40 4-bundle 380.0,1711.26619788,73.219,1.0,"SRID=4326;LINESTRING(10.363384232266 47.7383368092793,10.0821573264928 48.2660901578788)",380000.0,1641775.0,0.025,,3.0,0.25,4.0,Vöhringen Ost,Amprion,13.7,50.0,73219.0,2.6 +329,248,208,Al/St 240/40 4-bundle 380.0,1711.26619788,5.866,1.0,"SRID=4326;LINESTRING(7.46150025901999 51.6146143079042,7.4035444340631 51.644449487967)",380000.0,1659072.0,0.025,,3.0,0.25,4.0,Stummhafen 1A,Amprion,13.7,50.0,5866.0,2.6 +330,112,208,Al/St 240/40 4-bundle 380.0,1711.26619788,10.355,1.0,"SRID=4326;LINESTRING(7.35311186939787 51.5758105020818,7.4035444340631 51.644449487967)",380000.0,1659073.0,0.025,,3.0,0.25,4.0,Mengede Süd,Amprion,13.7,50.0,10355.0,2.6 +331,167,165_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,25.186,1.0,"SRID=4326;LINESTRING(7.52228010104186 51.5321980552231,7.70831848872651 51.6762033088437)",220000.0,1685857.0,0.08,,3.0,0.32,2.0,Wambel West,Amprion,11.5,50.0,25186.0,1.3 +332,165,249,Al/St 240/40 4-bundle 380.0,1711.26619788,1.221,1.0,"SRID=4326;LINESTRING(7.70831848872651 51.6762033088437,7.71823231962748 51.6743422007117)",380000.0,1686082.0,0.025,,3.0,0.25,4.0,Gersteinwerk K2,Amprion,13.7,50.0,1221.0,2.6 +333,165_220kV,250_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,0.485,1.0,"SRID=4326;LINESTRING(7.70831848872651 51.6762033088437,7.71554341949648 51.6736430744508)",220000.0,1686885.0,0.08,,3.0,0.32,2.0,Gersteinwerk F2,Amprion,11.5,50.0,485.0,1.3 +334,165_220kV,251_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,0.487,1.0,"SRID=4326;LINESTRING(7.70831848872651 51.6762033088437,7.71459453349968 51.6730996381138)",220000.0,1686886.0,0.08,,3.0,0.32,2.0,Gersteinwerk G2,Amprion,11.5,50.0,487.0,1.3 +335,165_220kV,252_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,0.401,1.0,"SRID=4326;LINESTRING(7.70831848872651 51.6762033088437,7.71270093431027 51.672663662233)",220000.0,1686887.0,0.08,,3.0,0.32,2.0,Gersteinwerk I2,Amprion,11.5,50.0,401.0,1.3 +336,110,253,Al/St 240/40 4-bundle 380.0,1711.26619788,1.378,1.0,"SRID=4326;LINESTRING(7.99288723332679 51.6837077155955,7.98122159643085 51.6735262619046)",380000.0,1703456.0,0.025,,3.0,0.25,4.0,Lippborg Trafo 412,Amprion,13.7,50.0,1378.0,2.6 +337,69_220kV,254,Al/St 240/40 2-bundle 220.0,247.683265482,64.037,0.5,"SRID=4326;LINESTRING(8.41937105938048 49.6337356823792,7.79256714933307 49.8105487811028)",220000.0,1734958.0,0.16,,3.0,0.64,1.0,Windesheim,Amprion,5.75,50.0,64037.0,0.65 +338,128_220kV,255,Al/St 240/40 2-bundle 220.0,495.366530965,73.5,1.0,"SRID=4326;LINESTRING(7.44948529010545 50.4025276621126,6.92013045452502 49.9705818904706)",220000.0,1735055.0,,,3.0,,2.0,Kondelwald,Amprion,,50.0,73500.0, +339,237,239,Al/St 240/40 2-bundle 220.0,495.366530965,50.055,1.0,"SRID=4326;LINESTRING(6.13981113770544 49.6686086456422,6.70228540768092 49.8289944201161)",220000.0,1735096.0,0.08,,3.0,0.32,2.0,Heisdorf - Quint Nord,Amprion,11.5,,50055.0,1.3 +340,256,238,Al/St 240/40 2-bundle 220.0,247.683265482,59.101,0.5,"SRID=4326;LINESTRING(6.75552340556687 49.3648695914696,6.62053556843084 49.7489386710632)",220000.0,1735138.0,0.16,,3.0,0.64,1.0,Saar Nord,Amprion,5.75,,59101.0,0.65 +341,69_220kV,215,Al/St 240/40 2-bundle 220.0,495.366530965,13.164,1.0,"SRID=4326;LINESTRING(8.41937105938048 49.6337356823792,8.40826973550855 49.5285689245626)",220000.0,1737369.0,0.08,,3.0,0.32,2.0,Anilin 3c,Amprion,11.5,50.0,13164.0,1.3 +342,69_220kV,215,Al/St 240/40 2-bundle 220.0,495.366530965,13.157,1.0,"SRID=4326;LINESTRING(8.41937105938048 49.6337356823792,8.40826973550855 49.5285689245626)",220000.0,1737370.0,0.08,,3.0,0.32,2.0,Anilin 4d,Amprion,11.5,50.0,13157.0,1.3 +343,69,257,Al/St 240/40 4-bundle 380.0,1711.26619788,10.838,1.0,"SRID=4326;LINESTRING(8.41937105938048 49.6337356823792,8.41467459584413 49.7091468139245)",380000.0,1737371.0,0.025,,3.0,0.25,4.0,Biblis 1a,Amprion,13.7,50.0,10838.0,2.6 +344,69,257,Al/St 240/40 4-bundle 380.0,1711.26619788,10.824,1.0,"SRID=4326;LINESTRING(8.41937105938048 49.6337356823792,8.41467459584413 49.7091468139245)",380000.0,1737372.0,0.025,,3.0,0.25,4.0,Biblis 2b,Amprion,13.7,50.0,10824.0,2.6 +345,69_220kV,225,Al/St 240/40 2-bundle 220.0,990.733061929,66.41,2.0,"SRID=4326;LINESTRING(8.41937105938048 49.6337356823792,7.72920197913641 49.4826474297682)",220000.0,1737381.0,0.04,,3.0,0.16,4.0,Roxheim Nord,Amprion,23.0,50.0,66410.0,2.6 +346,258,259,Al/St 240/40 2-bundle 220.0,495.366530965,15.315,1.0,"SRID=4326;LINESTRING(9.88280429847019 51.5155074976724,9.85139357253472 51.6491352399809)",220000.0,1740850.0,0.08,2,3.0,0.32,2.0,Leitung 2014,TenneT,11.5,50.0,15315.0,1.3 +347,260,190,Al/St 240/40 4-bundle 380.0,855.633098939,33.84,0.5,"SRID=4326;LINESTRING(8.78579302528601 48.5837674015328,8.87294280487974 48.3100912582216)",380000.0,1747957.0,,,3.0,,2.0,OBJTT-ENGST 380 kV,,,50.0,33840.0, +348,260,261,Al/St 240/40 4-bundle 380.0,855.633098939,45.58,0.5,"SRID=4326;LINESTRING(8.78579302528601 48.5837674015328,9.0454349679866 48.9029620137561)",380000.0,1747958.0,,,3.0,,2.0,PULVE-OBJTT 380 kV,EnBW Transportnetze AG,,50.0,45580.0, +349,262,263,Al/St 240/40 4-bundle 380.0,3422.53239576,78.45,2.0,"SRID=4326;LINESTRING(9.8013171668981 47.6525043240978,9.81091689214732 47.142645296653)",380000.0,1750798.0,0.0125,,6.0,0.125,4.0,Bürs - Obermooweiler,,27.4,,78450.0,5.2 +350,264_220kV,263_220kV,Al/St 240/40 2-bundle 220.0,247.683265482,132.974,0.5,"SRID=4326;LINESTRING(9.42551238607291 48.0643507981263,9.81091689214732 47.142645296653)",220000.0,1750800.0,0.16,,3.0,0.64,1.0,Nord-Süd-Leitung (Abschnitt Bürs-Herbertingen),,5.75,,132974.0,0.65 +351,94_220kV,265,Al/St 240/40 2-bundle 220.0,495.366530965,21.123,1.0,"SRID=4326;LINESTRING(8.30775689679575 52.3456356447783,8.10495957410199 52.2642613740358)",220000.0,1778832.0,0.08,,3.0,0.32,2.0,Bohmte West,Amprion,11.5,50.0,21123.0,1.3 +352,14,266,Al/St 240/40 2-bundle 220.0,990.733061929,51.446,2.0,"SRID=4326;LINESTRING(11.8132328973194 48.2212664654998,12.1301906295374 47.9131654820025)",220000.0,1820695.0,0.04,250;251,6.0,0.16,2.0,,,23.0,,51446.0,2.6 +353,116,267_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,3.267,1.0,"SRID=4326;LINESTRING(6.77252309438106 51.1471054906194,6.8018236570969 51.1247880606241)",220000.0,1897790.0,0.08,,3.0,0.32,2.0,Norf Ost,Amprion,11.5,50.0,3267.0,1.3 +354,116,267_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,2.955,1.0,"SRID=4326;LINESTRING(6.77252309438106 51.1471054906194,6.8018236570969 51.1247880606241)",220000.0,1897791.0,0.08,,3.0,0.32,2.0,Norf West,Amprion,11.5,50.0,2955.0,1.3 +355,151,116,Al/St 240/40 2-bundle 220.0,495.366530965,10.958,1.0,"SRID=4326;LINESTRING(6.70967369516713 51.0909407427733,6.77252309438106 51.1471054906194)",220000.0,1897792.0,0.08,,3.0,0.32,2.0,Zons Nord,Amprion,11.5,50.0,10958.0,1.3 +356,111,114,Al/St 240/40 4-bundle 380.0,1711.26619788,50.867,1.0,"SRID=4326;LINESTRING(8.62480052815036 51.4278883849265,8.21170597594605 51.1779854801194)",380000.0,1906209.0,0.025,,3.0,0.25,4.0,Sauerland Süd,Amprion,13.7,50.0,50867.0,2.6 +357,77,111,Al/St 240/40 4-bundle 380.0,1711.26619788,33.318,1.0,"SRID=4326;LINESTRING(8.99279240395088 51.343009397907,8.62480052815036 51.4278883849265)",380000.0,2050530.0,0.025,,3.0,0.25,4.0,Twistetal Ost 3,Amprion,13.7,50.0,33318.0,2.6 +358,77,111,Al/St 240/40 4-bundle 380.0,1711.26619788,33.322,1.0,"SRID=4326;LINESTRING(8.99279240395088 51.343009397907,8.62480052815036 51.4278883849265)",380000.0,2050531.0,0.025,,3.0,0.25,4.0,Twistetal West 4,Amprion,13.7,50.0,33322.0,2.6 +359,268,120_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,15.774,1.0,"SRID=4326;LINESTRING(7.98506745761923 50.8422254447013,7.86394571365263 50.7682419129404)",220000.0,2137096.0,0.08,,3.0,0.32,2.0,Siegerland West,Amprion,11.5,50.0,15774.0,1.3 +360,269,120,Al/St 240/40 4-bundle 380.0,1711.26619788,75.879,1.0,"SRID=4326;LINESTRING(8.284861778672 50.7590735194915,7.86394571365263 50.7682419129404)",380000.0,2137098.0,0.025,,3.0,0.25,4.0,Westerwald Ost 2,Amprion;TenneT,13.7,50.0,75879.0,2.6 +361,270,271,Al/St 240/40 2-bundle 220.0,495.366530965,1.562,1.0,"SRID=4326;LINESTRING(7.22423842685352 53.3499271468647,7.20605284001418 53.3413192541639)",220000.0,2140374.0,0.08,2021,3.0,0.32,2.0,,TenneT,11.5,50.0,1562.0,1.3 +362,35,36,Al/St 240/40 2-bundle 220.0,495.366530965,2.641,1.0,"SRID=4326;LINESTRING(8.08565059715704 53.605834192587,8.06005695814032 53.6202025750861)",220000.0,2141066.0,0.08,,3.0,0.32,2.0,,TenneT,11.5,50.0,2641.0,1.3 +363,272_220kV,273,Al/St 240/40 2-bundle 220.0,247.683265482,39.544,0.5,"SRID=4326;LINESTRING(9.31819523638713 54.7160494089748,9.27063390877085 55.0370264816237)",220000.0,2143451.0,0.16,,3.0,0.64,1.0,Kassø (DK) - Flensburg (DE) 220 kV,Energinet.dk;TenneT,5.75,50.0,39544.0,0.65 +364,272_220kV,274,Al/St 240/40 2-bundle 220.0,247.683265482,35.433,0.5,"SRID=4326;LINESTRING(9.31819523638713 54.7160494089748,9.43342930362242 55.0187229179606)",220000.0,2143452.0,0.16,,3.0,0.64,1.0,Flensburg (DE) - Ensted (DK),Energinet.dk;TenneT,5.75,50.0,35433.0,0.65 +365,128_220kV,275,Al/St 240/40 2-bundle 220.0,495.366530965,5.234,1.0,"SRID=4326;LINESTRING(7.44948529010545 50.4025276621126,7.43348911422107 50.4365195361077)",220000.0,2147236.0,0.08,,3.0,0.32,2.0,Bandstahlwerk Tr201,Amprion,11.5,50.0,5234.0,1.3 +366,128,276,Al/St 240/40 4-bundle 380.0,1711.26619788,16.953,1.0,"SRID=4326;LINESTRING(7.44948529010545 50.4025276621126,7.59330184856855 50.3859437917469)",380000.0,2147303.0,0.025,,3.0,0.25,4.0,Metternich,Amprion,13.7,50.0,16953.0,2.6 +367,34,277,Al/St 240/40 2-bundle 220.0,247.683265482,0.627,0.5,"SRID=4326;LINESTRING(8.12264230304551 53.5605160490331,8.12215702005755 53.5658323292485)",220000.0,2155389.0,0.16,,3.0,0.64,1.0,,TenneT,5.75,50.0,627.0,0.65 +368,34,35,Al/St 240/40 2-bundle 220.0,495.366530965,5.844,1.0,"SRID=4326;LINESTRING(8.12264230304551 53.5605160490331,8.08565059715704 53.605834192587)",220000.0,2155415.0,0.08,,3.0,0.32,2.0,,TenneT,11.5,50.0,5844.0,1.3 +369,37,39,Al/St 240/40 4-bundle 380.0,1283.44964841,37.592,0.75,"SRID=4326;LINESTRING(11.6389127631156 52.2687346818467,11.7273534150745 52.5689469938591)",380000.0,2188188.0,0.033333,490,3.0,0.333333,3.0,490,50Hertz,10.275,50.0,37592.0,1.95 +370,20,22,Al/St 240/40 4-bundle 380.0,1711.26619788,57.421,1.0,"SRID=4326;LINESTRING(9.98460982000897 53.7411287759659,9.20210420095253 53.8951574100045)",380000.0,2189200.0,0.025,H,3.0,0.25,4.0,Brunsbüttel - Hamburg/Nord,,13.7,50.0,57421.0,2.6 +371,20,21,Al/St 240/40 4-bundle 380.0,855.633098939,31.281,0.5,"SRID=4326;LINESTRING(9.98460982000897 53.7411287759659,10.1575015165126 53.5551316685834)",380000.0,2189657.0,0.05,B,3.0,0.5,2.0,Hamburg/Ost - Hamburg/Nord,50Hertz,6.85,50.0,31281.0,1.3 +372,37_220kV,278,Al/St 240/40 2-bundle 220.0,495.366530965,20.287,1.0,"SRID=4326;LINESTRING(11.6389127631156 52.2687346818467,11.5615074728142 52.1238924527264)",220000.0,2237835.0,0.08,329,3.0,0.32,2.0,329,50Hertz Transmission,11.5,50.0,20287.0,1.3 +373,37_220kV,278,Al/St 240/40 2-bundle 220.0,495.366530965,20.29,1.0,"SRID=4326;LINESTRING(11.6389127631156 52.2687346818467,11.5615074728142 52.1238924527264)",220000.0,2237836.0,0.08,330,3.0,0.32,2.0,330,50Hertz Transmission,11.5,50.0,20290.0,1.3 +374,37_220kV,42_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,194.536,1.0,"SRID=4326;LINESTRING(11.6389127631156 52.2687346818467,12.1869848227216 53.8157751724384)",220000.0,2237880.0,0.08,332,3.0,0.32,2.0,332,50Hertz Transmission,11.5,50.0,194536.0,1.3 +375,37_220kV,42_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,194.59,1.0,"SRID=4326;LINESTRING(11.6389127631156 52.2687346818467,12.1869848227216 53.8157751724384)",220000.0,2237881.0,0.08,331,3.0,0.32,2.0,331,50Hertz Transmission,11.5,50.0,194590.0,1.3 +376,177,218,Al/St 240/40 2-bundle 220.0,495.366530965,65.497,1.0,"SRID=4326;LINESTRING(13.0786421047586 54.2848461697305,12.2135605440795 54.1119998545154)",220000.0,2238161.0,0.08,318,3.0,0.32,2.0,318,50Hertz Transmission,11.5,50.0,65497.0,1.3 +377,182,180,Al/St 240/40 2-bundle 220.0,495.366530965,26.206,1.0,"SRID=4326;LINESTRING(14.4762521006827 53.1967495480613,14.270834670665 53.0962865535819)",220000.0,2239286.0,0.08,508,3.0,0.32,2.0,220kV Krajnik - Vierraden,PSE-O;50Hertz,11.5,50.0,26206.0,1.3 +378,279,280,Al/St 240/40 4-bundle 380.0,1711.26619788,83.08,1.0,"SRID=4326;LINESTRING(12.8139099116529 50.8579185114397,13.3823233828996 51.3559961579888)",380000.0,2247028.0,0.025,572,3.0,0.25,4.0,572,50Hertz,13.7,50.0,83080.0,2.6 +379,279,280,Al/St 240/40 4-bundle 380.0,1711.26619788,83.072,1.0,"SRID=4326;LINESTRING(12.8139099116529 50.8579185114397,13.3823233828996 51.3559961579888)",380000.0,2247029.0,0.025,571,3.0,0.25,4.0,571,50Hertz,13.7,50.0,83072.0,2.6 +380,279_220kV,281_220kV,Al/St 240/40 2-bundle 220.0,743.049796447,98.493,1.5,"SRID=4326;LINESTRING(12.8139099116529 50.8579185114397,12.0449825161601 50.7812567340735)",220000.0,2247304.0,0.053333,207,3.0,0.213333,3.0,207,50Hertz Transmission,17.25,50.0,98493.0,1.95 +381,279_220kV,282_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,51.665,1.0,"SRID=4326;LINESTRING(12.8139099116529 50.8579185114397,12.5182220968156 51.1570725850832)",220000.0,2247305.0,,203,3.0,,2.0,203,50Hertz Transmission,,50.0,51665.0, +382,282_220kV,281_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,67.674,1.0,"SRID=4326;LINESTRING(12.5182220968156 51.1570725850832,12.0449825161601 50.7812567340735)",220000.0,2247306.0,0.08,208,3.0,0.32,2.0,208,50Hertz Transmission,11.5,50.0,67674.0,1.3 +383,279,78,Al/St 240/40 4-bundle 380.0,855.633098939,103.557,0.5,"SRID=4326;LINESTRING(12.8139099116529 50.8579185114397,11.6463786274424 50.534876971042)",380000.0,2247333.0,,474,3.0,,2.0,Remptendorf - Röhrsdorf,50Hertz Transmission,,50.0,103557.0, +384,279,281,Al/St 240/40 4-bundle 380.0,855.633098939,61.777,0.5,"SRID=4326;LINESTRING(12.8139099116529 50.8579185114397,12.0449825161601 50.7812567340735)",380000.0,2247334.0,,575,3.0,,2.0,Weida - Röhrsdorf,50Hertz Transmission,,50.0,61777.0, +385,78,281,Al/St 240/40 4-bundle 380.0,855.633098939,45.665,0.5,"SRID=4326;LINESTRING(11.6463786274424 50.534876971042,12.0449825161601 50.7812567340735)",380000.0,2247335.0,,573,3.0,,2.0,Remptendorf - Weida,50Hertz Transmission,,50.0,45665.0, +386,283,284,Al/St 240/40 4-bundle 380.0,1711.26619788,13.388,1.0,"SRID=4326;LINESTRING(8.48009523461158 50.5909204313802,8.65659923594081 50.6055491256016)",380000.0,2252482.0,0.025,,3.0,0.25,4.0,,TenneT,13.7,50.0,13388.0,2.6 +387,285,161,Al/St 240/40 2-bundle 220.0,247.683265482,55.571,0.5,"SRID=4326;LINESTRING(8.08162670615254 52.858637484526,8.05283751634017 53.3338809598955)",220000.0,2277544.0,0.16,206,3.0,0.64,1.0,,TenneT,5.75,50.0,55571.0,0.65 +388,285,161,Al/St 240/40 2-bundle 220.0,247.683265482,55.571,0.5,"SRID=4326;LINESTRING(8.08162670615254 52.858637484526,8.05283751634017 53.3338809598955)",220000.0,2277545.0,0.16,206,3.0,0.64,1.0,,TenneT,5.75,50.0,55571.0,0.65 +389,270,161,Al/St 240/40 2-bundle 220.0,495.366530965,57.109,1.0,"SRID=4326;LINESTRING(7.22423842685352 53.3499271468647,8.05283751634017 53.3338809598955)",220000.0,2312879.0,0.08,203,3.0,0.32,2.0,203 weiß,TenneT,11.5,50.0,57109.0,1.3 +390,270,161,Al/St 240/40 2-bundle 220.0,495.366530965,57.123,1.0,"SRID=4326;LINESTRING(7.22423842685352 53.3499271468647,8.05283751634017 53.3338809598955)",220000.0,2312880.0,0.08,203,3.0,0.32,2.0,203,TenneT,11.5,50.0,57123.0,1.3 +391,258,286,Al/St 240/40 2-bundle 220.0,495.366530965,32.534,1.0,"SRID=4326;LINESTRING(9.88280429847019 51.5155074976724,9.56710201161841 51.3252573276817)",220000.0,2317630.0,0.08,1,3.0,0.32,2.0,Leitung 2013,TenneT,11.5,50.0,32534.0,1.3 +392,258,286,Al/St 240/40 2-bundle 220.0,495.366530965,32.535,1.0,"SRID=4326;LINESTRING(9.88280429847019 51.5155074976724,9.56710201161841 51.3252573276817)",220000.0,2317631.0,0.08,2,3.0,0.32,2.0,Leitung 2013,TenneT,11.5,50.0,32535.0,1.3 +393,94_220kV,95_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,61.576,1.0,"SRID=4326;LINESTRING(8.30775689679575 52.3456356447783,7.88957671235007 52.2773967624078)",220000.0,2450379.0,0.08,,3.0,0.32,2.0,Wiehengebirge Ost,Amprion,11.5,50.0,61576.0,1.3 +394,265,95_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,18.365,2.0,"SRID=4326;LINESTRING(8.10495957410199 52.2642613740358,7.88957671235007 52.2773967624078)",220000.0,2450498.0,0.04,,3.0,0.16,4.0,Westerkappeln Nord,Amprion,23.0,50.0,18365.0,2.6 +395,265,95_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,18.365,2.0,"SRID=4326;LINESTRING(8.10495957410199 52.2642613740358,7.88957671235007 52.2773967624078)",220000.0,2450499.0,0.04,,3.0,0.16,4.0,Westerkappeln Süd,Amprion,23.0,50.0,18365.0,2.6 +396,95_220kV,287,Al/St 240/40 2-bundle 220.0,495.366530965,10.449,1.0,"SRID=4326;LINESTRING(7.88957671235007 52.2773967624078,7.74679248425685 52.2885115081219)",220000.0,2553276.0,0.08,,3.0,0.32,2.0,Ibbenbüren B,Amprion,11.5,50.0,10449.0,1.3 +397,63,288,Al/St 240/40 2-bundle 220.0,495.366530965,28.872,1.0,"SRID=4326;LINESTRING(8.12076909802284 48.7048354259965,7.91072928612534 48.5012882982432)",220000.0,2553837.0,0.08,BUEHL-WEIER,3.0,0.32,2.0,,EnBW,11.5,50.0,28872.0,1.3 +398,289,95_220kV,Al/St 240/40 2-bundle 220.0,247.683265482,13.105,0.5,"SRID=4326;LINESTRING(7.7257063478071 52.2771949017405,7.88957671235007 52.2773967624078)",220000.0,2573771.0,0.16,,3.0,0.64,1.0,Nike Süd,Amprion,5.75,50.0,13105.0,0.65 +399,8,102,Al/St 240/40 4-bundle 380.0,1711.26619788,87.218,1.0,"SRID=4326;LINESTRING(10.4149923381504 53.4126068830249,11.3706373688866 53.6009910848999)",380000.0,2910798.0,0.025,419,3.0,0.25,4.0,,50Hertz,13.7,50.0,87218.0,2.6 +400,8,101,Al/St 240/40 4-bundle 380.0,1711.26619788,111.17,1.0,"SRID=4326;LINESTRING(10.4149923381504 53.4126068830249,11.6993926965362 53.5722744892702)",380000.0,2910799.0,0.025,420,3.0,0.25,4.0,,50Hertz,13.7,,111170.0,2.6 +401,80,290,Al/St 240/40 4-bundle 380.0,855.633098939,46.125,0.5,"SRID=4326;LINESTRING(14.5081091811329 51.4137103812427,14.2192282929357 51.120707992198)",380000.0,2927579.0,,551,3.0,,2.0,,50Hertz,,50.0,46125.0, +402,80,290,Al/St 240/40 4-bundle 380.0,855.633098939,46.116,0.5,"SRID=4326;LINESTRING(14.5081091811329 51.4137103812427,14.2192282929357 51.120707992198)",380000.0,2927580.0,,552,3.0,,2.0,,50Hertz,,50.0,46116.0, +403,80,291,Al/St 240/40 4-bundle 380.0,1283.44964841,5.989,0.75,"SRID=4326;LINESTRING(14.5081091811329 51.4137103812427,14.5700865498873 51.4190136844256)",380000.0,2927597.0,0.033333,MQ,3.0,0.333333,3.0,,,10.275,50.0,5989.0,1.95 +404,80,291,Al/St 240/40 4-bundle 380.0,1283.44964841,6.169,0.75,"SRID=4326;LINESTRING(14.5081091811329 51.4137103812427,14.5700865498873 51.4190136844256)",380000.0,2927598.0,0.033333,MR,3.0,0.333333,3.0,,,10.275,50.0,6169.0,1.95 +405,80,291,Al/St 240/40 4-bundle 380.0,1283.44964841,6.702,0.75,"SRID=4326;LINESTRING(14.5081091811329 51.4137103812427,14.5700865498873 51.4190136844256)",380000.0,2927599.0,0.033333,MN,3.0,0.333333,3.0,,,10.275,50.0,6702.0,1.95 +406,80,291,Al/St 240/40 4-bundle 380.0,1283.44964841,6.554,0.75,"SRID=4326;LINESTRING(14.5081091811329 51.4137103812427,14.5700865498873 51.4190136844256)",380000.0,2927600.0,0.033333,MP,3.0,0.333333,3.0,,,10.275,50.0,6554.0,1.95 +407,278,292_220kV,Al/St 240/40 2-bundle 220.0,247.683265482,26.902,0.5,"SRID=4326;LINESTRING(11.5615074728142 52.1238924527264,11.6441811001256 51.908512873438)",220000.0,2928064.0,0.16,335,3.0,0.64,1.0,,50Hertz,5.75,50.0,26902.0,0.65 +408,278,292_220kV,Al/St 240/40 2-bundle 220.0,247.683265482,26.897,0.5,"SRID=4326;LINESTRING(11.5615074728142 52.1238924527264,11.6441811001256 51.908512873438)",220000.0,2928066.0,0.16,336,3.0,0.64,1.0,,50Hertz,5.75,50.0,26897.0,0.65 +409,37,292,Al/St 240/40 4-bundle 380.0,1283.44964841,48.851,0.75,"SRID=4326;LINESTRING(11.6389127631156 52.2687346818467,11.6441811001256 51.908512873438)",380000.0,2928164.0,0.033333,438,3.0,0.333333,3.0,,50Hertz,10.275,50.0,48851.0,1.95 +410,292,74,Al/St 240/40 4-bundle 380.0,1283.44964841,179.494,0.75,"SRID=4326;LINESTRING(11.6441811001256 51.908512873438,13.9024346886892 51.8747235190459)",380000.0,2928165.0,0.033333,532,3.0,0.333333,3.0,532,50Hertz,10.275,50.0,179494.0,1.95 +411,292,74,Al/St 240/40 4-bundle 380.0,1283.44964841,179.483,0.75,"SRID=4326;LINESTRING(11.6441811001256 51.908512873438,13.9024346886892 51.8747235190459)",380000.0,2928166.0,0.033333,531,3.0,0.333333,3.0,531,50Hertz,10.275,50.0,179483.0,1.95 +412,37,292,Al/St 240/40 4-bundle 380.0,1283.44964841,48.774,0.75,"SRID=4326;LINESTRING(11.6389127631156 52.2687346818467,11.6441811001256 51.908512873438)",380000.0,2928167.0,0.033333,437,3.0,0.333333,3.0,,50Hertz,10.275,50.0,48774.0,1.95 +413,78_220kV,293,Al/St 240/40 2-bundle 220.0,495.366530965,16.091,1.0,"SRID=4326;LINESTRING(11.6463786274424 50.534876971042,11.4746908222742 50.6040154814218)",220000.0,2952529.0,0.08,383,3.0,0.32,2.0,,50Hertz,11.5,50.0,16091.0,1.3 +414,78_220kV,293,Al/St 240/40 2-bundle 220.0,495.366530965,16.123,1.0,"SRID=4326;LINESTRING(11.6463786274424 50.534876971042,11.4746908222742 50.6040154814218)",220000.0,2952531.0,0.08,384,3.0,0.32,2.0,,50Hertz,11.5,50.0,16123.0,1.3 +415,78,294,Al/St 240/40 4-bundle 380.0,1283.44964841,50.416,0.75,"SRID=4326;LINESTRING(11.6463786274424 50.534876971042,10.9910146333086 50.573526990181)",380000.0,2952534.0,0.033333,463,3.0,0.333333,3.0,,50Hertz,10.275,50.0,50416.0,1.95 +416,78,294,Al/St 240/40 4-bundle 380.0,1283.44964841,50.381,0.75,"SRID=4326;LINESTRING(11.6463786274424 50.534876971042,10.9910146333086 50.573526990181)",380000.0,2952535.0,0.033333,464,3.0,0.333333,3.0,,50Hertz,10.275,50.0,50381.0,1.95 +417,199,248_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,2.349,1.0,"SRID=4326;LINESTRING(7.44506930371379 51.6185000869374,7.46150025901999 51.6146143079042)",220000.0,3028644.0,0.08,,3.0,0.32,2.0,Lünen 7,RWE,11.5,50.0,2349.0,1.3 +418,5_220kV,295,Al/St 240/40 2-bundle 220.0,247.683265482,10.721,0.5,"SRID=4326;LINESTRING(10.3662749375017 52.2846467462009,10.5054991208047 52.3018379056382)",220000.0,3098194.0,0.16,2,3.0,0.64,1.0,Leitung 2025,TenneT,5.75,50.0,10721.0,0.65 +419,1,2_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,43.375,1.0,"SRID=4326;LINESTRING(9.52257596986262 52.3604090557601,9.11321007472722 52.5438533223737)",220000.0,3109536.0,0.08,2016,3.0,0.32,2.0,Leitung 2016,TenneT,11.5,50.0,43375.0,1.3 +420,4,296,Al/St 240/40 4-bundle 380.0,1711.26619788,1.387,1.0,"SRID=4326;LINESTRING(9.12526570294975 52.5382640870749,9.11362795987411 52.5457924749284)",380000.0,3109537.0,0.025,,3.0,0.25,4.0,,TenneT,13.7,50.0,1387.0,2.6 +421,4,296,Al/St 240/40 4-bundle 380.0,1711.26619788,1.117,1.0,"SRID=4326;LINESTRING(9.12526570294975 52.5382640870749,9.11362795987411 52.5457924749284)",380000.0,3109538.0,0.025,,3.0,0.25,4.0,Leitung 3004,TenneT,13.7,50.0,1117.0,2.6 +422,2_220kV,201_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,76.503,1.0,"SRID=4326;LINESTRING(9.11321007472722 52.5438533223737,9.25103425143703 53.1144323051271)",220000.0,3109602.0,0.08,2,3.0,0.32,2.0,Leitung 2010/2023,TenneT,11.5,50.0,76503.0,1.3 +423,3,4,Al/St 240/40 4-bundle 380.0,1711.26619788,72.681,1.0,"SRID=4326;LINESTRING(9.38974509624863 52.0263130660355,9.12526570294975 52.5382640870749)",380000.0,3109630.0,0.025,,3.0,0.25,4.0,Leitung 3011,TenneT,13.7,50.0,72681.0,2.6 +424,5_220kV,297,Al/St 240/40 2-bundle 220.0,495.366530965,16.053,1.0,"SRID=4326;LINESTRING(10.3662749375017 52.2846467462009,10.3692300485918 52.1670924338554)",220000.0,3113413.0,0.08,3,3.0,0.32,2.0,Leitung 2028/2029,TenneT,11.5,50.0,16053.0,1.3 +425,5_220kV,298,Al/St 240/40 2-bundle 220.0,495.366530965,6.656,1.0,"SRID=4326;LINESTRING(10.3662749375017 52.2846467462009,10.4195406346214 52.2369606619834)",220000.0,3113414.0,0.08,2,3.0,0.32,2.0,Leitung 2028,TenneT,11.5,50.0,6656.0,1.3 +426,297,299,Al/St 240/40 2-bundle 220.0,495.366530965,30.7,1.0,"SRID=4326;LINESTRING(10.3692300485918 52.1670924338554,10.0876467999739 52.3151305547947)",220000.0,3113438.0,0.08,1,3.0,0.32,2.0,Leitung 2027,TenneT,11.5,50.0,30700.0,1.3 +427,61_220kV,161,Al/St 240/40 2-bundle 220.0,495.366530965,35.15,1.0,"SRID=4326;LINESTRING(8.51888180951659 53.2026352836834,8.05283751634017 53.3338809598955)",220000.0,3114809.0,0.08,201,3.0,0.32,2.0,201 grün,TenneT,11.5,50.0,35150.0,1.3 +428,5_220kV,6,Al/St 240/40 2-bundle 220.0,495.366530965,33.882,1.0,"SRID=4326;LINESTRING(10.3662749375017 52.2846467462009,9.91814864613865 52.3799963125719)",220000.0,3115297.0,0.08,1,3.0,0.32,2.0,Leitung 2024,TenneT,11.5,50.0,33882.0,1.3 +429,117,123,Al/St 240/40 4-bundle 380.0,1711.26619788,33.941,1.0,"SRID=4326;LINESTRING(6.97257742700703 50.795693017514,6.70220337779745 51.0088609096753)",380000.0,3228400.0,0.025,,3.0,0.25,4.0,Ville Ost,Amprion,13.7,50.0,33941.0,2.6 +430,133,117,Al/St 240/40 4-bundle 380.0,855.633098939,12.907,0.5,"SRID=4326;LINESTRING(6.8426506615144 50.8635385008881,6.97257742700703 50.795693017514)",380000.0,3228401.0,0.05,,3.0,0.5,2.0,Walberberg West,Amprion,6.85,50.0,12907.0,1.3 +431,71,74,Al/St 240/40 4-bundle 380.0,1283.44964841,169.144,0.75,"SRID=4326;LINESTRING(12.2621734868101 51.7410202755519,13.9024346886892 51.8747235190459)",380000.0,3302864.0,0.033333,502,3.0,0.333333,3.0,502,50Hertz,10.275,50.0,169144.0,1.95 +432,74,280,Al/St 240/40 4-bundle 380.0,1711.26619788,89.08,1.0,"SRID=4326;LINESTRING(13.9024346886892 51.8747235190459,13.3823233828996 51.3559961579888)",380000.0,3306342.0,,561,3.0,,4.0,561 Ragow-Streumen,50Hertz,,50.0,89080.0, +433,74,280,Al/St 240/40 4-bundle 380.0,1711.26619788,89.092,1.0,"SRID=4326;LINESTRING(13.9024346886892 51.8747235190459,13.3823233828996 51.3559961579888)",380000.0,3306343.0,,562,3.0,,4.0,562 Ragow-Streumen,50Hertz,,50.0,89092.0, +434,94_220kV,265,Al/St 240/40 2-bundle 220.0,495.366530965,21.136,1.0,"SRID=4326;LINESTRING(8.30775689679575 52.3456356447783,8.10495957410199 52.2642613740358)",220000.0,3345100.0,0.08,,3.0,0.32,2.0,Bohmte Ost,Amprion,11.5,50.0,21136.0,1.3 +435,300,94_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,32.823,1.0,"SRID=4326;LINESTRING(8.40314143640892 52.609644011383,8.30775689679575 52.3456356447783)",220000.0,3345101.0,0.08,,3.0,0.32,2.0,Diepholz Ost,Amprion,11.5,50.0,32823.0,1.3 +436,16,5,Al/St 240/40 4-bundle 380.0,855.633098939,36.248,0.5,"SRID=4326;LINESTRING(10.7610403785613 52.3677293804096,10.3662749375017 52.2846467462009)",380000.0,3528700.0,,1,3.0,,2.0,Leitung 3023,TenneT,,50.0,36248.0, +437,5,301,Al/St 240/40 4-bundle 380.0,1711.26619788,9.376,1.0,"SRID=4326;LINESTRING(10.3662749375017 52.2846467462009,10.2358631058452 52.2934595399896)",380000.0,3533527.0,0.025,3026,3.0,0.25,4.0,Leitung 3026,TenneT,13.7,50.0,9376.0,2.6 +438,32,294,Al/St 240/40 4-bundle 380.0,1711.26619788,55.908,1.0,"SRID=4326;LINESTRING(11.1228545605428 50.9989090670144,10.9910146333086 50.573526990181)",380000.0,3533700.0,,,6.0,,2.0,380 kV Leitung Thüringer Wald,,,50.0,55908.0, +439,5,29,Al/St 240/40 4-bundle 380.0,1711.26619788,85.872,1.0,"SRID=4326;LINESTRING(10.3662749375017 52.2846467462009,10.3821666628198 52.9932540372914)",380000.0,3534540.0,0.025,1,3.0,0.25,4.0,Leitung 3007,TenneT,13.7,50.0,85872.0,2.6 +440,5,29,Al/St 240/40 4-bundle 380.0,1711.26619788,85.877,1.0,"SRID=4326;LINESTRING(10.3662749375017 52.2846467462009,10.3821666628198 52.9932540372914)",380000.0,3534541.0,0.025,2,3.0,0.25,4.0,Leitung 3007,TenneT,13.7,50.0,85877.0,2.6 +441,9,29,Al/St 240/40 4-bundle 380.0,1711.26619788,25.563,1.0,"SRID=4326;LINESTRING(10.3787705903765 53.2197927685849,10.3821666628198 52.9932540372914)",380000.0,3534542.0,0.025,,3.0,0.25,4.0,Leitung 3021,TenneT,13.7,50.0,25563.0,2.6 +442,37_220kV,292_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,45.8,2.0,"SRID=4326;LINESTRING(11.6389127631156 52.2687346818467,11.6441811001256 51.908512873438)",220000.0,3584453.0,0.04,344,6.0,0.16,2.0,,50Hertz,23.0,50.0,45800.0,2.6 +443,302,303,Al/St 240/40 4-bundle 380.0,3422.53239576,56.513,2.0,"SRID=4326;LINESTRING(9.5142400914841 51.2590355805955,9.38765125748855 51.640733525594)",380000.0,3687783.0,0.0125,,6.0,0.125,4.0,,,27.4,50.0,56513.0,5.2 +444,51,302,Al/St 240/40 4-bundle 380.0,3422.53239576,29.729,2.0,"SRID=4326;LINESTRING(9.25938382024592 51.0603668474477,9.5142400914841 51.2590355805955)",380000.0,3687803.0,0.0125,,6.0,0.125,4.0,,,27.4,,29729.0,5.2 +445,246,304,Al/St 240/40 4-bundle 380.0,3422.53239576,89.118,2.0,"SRID=4326;LINESTRING(8.95870856177306 50.0826078105489,9.77415043834156 50.5287496465011)",380000.0,3688562.0,0.0125,,6.0,0.125,4.0,Dipperz - Großkrotzenburg,,27.4,,89118.0,5.2 +446,31,304,Al/St 240/40 4-bundle 380.0,3422.53239576,50.472,2.0,"SRID=4326;LINESTRING(9.74615081640288 50.9168399490798,9.77415043834156 50.5287496465011)",380000.0,3688563.0,0.0125,3010,6.0,0.125,4.0,,TenneT,27.4,50.0,50472.0,5.2 +447,51,284,Al/St 240/40 4-bundle 380.0,3422.53239576,72.644,2.0,"SRID=4326;LINESTRING(9.25938382024592 51.0603668474477,8.65659923594081 50.6055491256016)",380000.0,3688564.0,0.0125,3002,6.0,0.125,4.0,,TenneT,27.4,50.0,72644.0,5.2 +448,305,306,Al/St 240/40 4-bundle 380.0,855.633098939,0.254,0.5,"SRID=4326;LINESTRING(9.17745967557079 49.037809850476,9.17731110343941 49.039378200434)",380000.0,3693985.0,,,3.0,,2.0,Einspeisung Umrichter Neckarwestheim,,,,254.0, +449,307,306,Al/St 240/40 4-bundle 380.0,1283.44964841,16.813,0.75,"SRID=4326;LINESTRING(9.15135394990856 49.1433834119236,9.17731110343941 49.039378200434)",380000.0,3693986.0,0.033333,,3.0,0.333333,3.0,,,10.275,50.0,16813.0,1.95 +450,307,261,Al/St 240/40 4-bundle 380.0,1711.26619788,29.602,1.0,"SRID=4326;LINESTRING(9.15135394990856 49.1433834119236,9.0454349679866 48.9029620137561)",380000.0,3693987.0,,,6.0,,2.0,,,,50.0,29602.0, +451,308,309,Al/St 240/40 4-bundle 380.0,855.633098939,93.088,0.5,"SRID=4326;LINESTRING(10.1878339995516 49.981219040845,9.44691867105201 49.5902629324336)",380000.0,3694014.0,,,3.0,,2.0,Grafenrheinfeld - Höpfingen,,,,93088.0, +452,307,310,Al/St 240/40 4-bundle 380.0,855.633098939,7.01,0.5,"SRID=4326;LINESTRING(9.15135394990856 49.1433834119236,9.20736443093694 49.1766379540065)",380000.0,3694015.0,,,3.0,,2.0,,,,50.0,7010.0, +453,307,311,Al/St 240/40 4-bundle 380.0,1283.44964841,47.716,0.75,"SRID=4326;LINESTRING(9.15135394990856 49.1433834119236,9.69465635518013 49.2325925411591)",380000.0,3694016.0,0.033333,,3.0,0.333333,3.0,Kupferzell - Großgartach,,10.275,50.0,47716.0,1.95 +454,312,311,Al/St 240/40 4-bundle 380.0,1711.26619788,55.253,1.0,"SRID=4326;LINESTRING(10.1174866946797 48.8971053361382,9.69465635518013 49.2325925411591)",380000.0,3694017.0,0.025,,3.0,0.25,4.0,,,13.7,50.0,55253.0,2.6 +455,308,311,Al/St 240/40 4-bundle 380.0,855.633098939,102.044,0.5,"SRID=4326;LINESTRING(10.1878339995516 49.981219040845,9.69465635518013 49.2325925411591)",380000.0,3694018.0,,348,3.0,,2.0,,,,,102044.0, +456,307,261,Al/St 240/40 4-bundle 380.0,1711.26619788,29.602,1.0,"SRID=4326;LINESTRING(9.15135394990856 49.1433834119236,9.0454349679866 48.9029620137561)",380000.0,3697399.0,,,6.0,,2.0,Großgartach-Pulverdingen,,,,29602.0, +457,313,282,Al/St 240/40 4-bundle 380.0,1711.26619788,19.833,1.0,"SRID=4326;LINESTRING(12.3374229103144 51.1969683571228,12.5182220968156 51.1570725850832)",380000.0,3707291.0,,587;588,6.0,,2.0,587;588 Pulgar - Eula,50Hertz,,,19833.0, +458,282,280,Al/St 240/40 4-bundle 380.0,2566.89929682,84.472,1.5,"SRID=4326;LINESTRING(12.5182220968156 51.1570725850832,13.3823233828996 51.3559961579888)",380000.0,3707292.0,0.016667,585;586,6.0,0.166667,3.0,585;586 Eula - Streumen,50Hertz,20.55,,84472.0,3.9 +459,313,32,Al/St 240/40 4-bundle 380.0,1283.44964841,104.459,0.75,"SRID=4326;LINESTRING(12.3374229103144 51.1969683571228,11.1228545605428 50.9989090670144)",380000.0,3707293.0,0.033333,589,3.0,0.333333,3.0,Pulgar - Vieselbach,50Hertz Transmission,10.275,,104459.0,1.95 +460,13,314,Al/St 240/40 4-bundle 380.0,3422.53239576,50.049,2.0,"SRID=4326;LINESTRING(11.3704262486892 48.2914922329548,10.8675658805484 48.5394690937344)",380000.0,3710278.0,0.0125,419;420,6.0,0.125,4.0,,,27.4,,50049.0,5.2 +461,315,316,Al/St 240/40 4-bundle 380.0,1711.26619788,35.191,1.0,"SRID=4326;LINESTRING(10.8408695619217 48.2380310092335,10.9367289314287 48.4013064414835)",380000.0,3710279.0,0.025,A12,3.0,0.25,4.0,,,13.7,,35191.0,2.6 +462,314,315,Al/St 240/40 4-bundle 380.0,1711.26619788,51.635,1.0,"SRID=4326;LINESTRING(10.8675658805484 48.5394690937344,10.8408695619217 48.2380310092335)",380000.0,3710281.0,0.025,L11,3.0,0.25,4.0,,,13.7,,51635.0,2.6 +463,247_220kV,317_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,81.783,2.0,"SRID=4326;LINESTRING(10.363384232266 47.7383368092793,10.8739357569814 47.2444239286028)",220000.0,3710282.0,0.04,,3.0,0.16,4.0,Leupolz - Westtirol (220 kV),,23.0,,81783.0,2.6 +464,247,317,Al/St 240/40 4-bundle 380.0,1711.26619788,81.803,1.0,"SRID=4326;LINESTRING(10.363384232266 47.7383368092793,10.8739357569814 47.2444239286028)",380000.0,3710283.0,0.025,,3.0,0.25,4.0,Leupolz - Westtirol (380 kV),,13.7,,81803.0,2.6 +465,314,318,Al/St 240/40 4-bundle 380.0,3422.53239576,1.017,2.0,"SRID=4326;LINESTRING(10.8675658805484 48.5394690937344,10.8637616199498 48.5468231051405)",380000.0,3710284.0,0.0125,M12/1;M12/2,6.0,0.125,4.0,,,27.4,,1017.0,5.2 +466,314,316,Al/St 240/40 4-bundle 380.0,1711.26619788,30.843,1.0,"SRID=4326;LINESTRING(10.8675658805484 48.5394690937344,10.9367289314287 48.4013064414835)",380000.0,3710286.0,0.025,,3.0,0.25,4.0,,,13.7,,30843.0,2.6 +467,264_220kV,236_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,123.643,1.0,"SRID=4326;LINESTRING(9.42551238607291 48.0643507981263,9.19675652645089 48.9129634380259)",220000.0,3712136.0,0.08,,3.0,0.32,2.0,,,11.5,,123643.0,1.3 +468,264,216,Al/St 240/40 4-bundle 380.0,855.633098939,123.15,0.5,"SRID=4326;LINESTRING(9.42551238607291 48.0643507981263,9.1907874369222 48.914416873799)",380000.0,3712137.0,0.05,,3.0,0.5,2.0,,,6.85,,123150.0,1.3 +469,319,233,Al/St 240/40 4-bundle 380.0,1283.44964841,24.464,0.75,"SRID=4326;LINESTRING(9.70423149899321 48.465063732143,9.88551509756969 48.3040743584705)",380000.0,3712217.0,,,3.0,,3.0,Dellmensingen - Laichingen,,,,24464.0, +470,320,319,Al/St 240/40 4-bundle 380.0,1283.44964841,36.006,0.75,"SRID=4326;LINESTRING(9.40063139462492 48.6669424396984,9.70423149899321 48.465063732143)",380000.0,3717715.0,0.033333,,3.0,0.333333,3.0,Laichingen - Wendlingen,,10.275,,36006.0,1.95 +471,231,52,Al/St 240/40 4-bundle 380.0,1711.26619788,203.683,1.0,"SRID=4326;LINESTRING(7.95805402328778 47.5954722640613,8.3133401659254 49.0090434643525)",380000.0,3717717.0,0.025,,3.0,0.25,4.0,Daxlanden - Kühmoos,,13.7,,203683.0,2.6 +472,321,322_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,18.421,2.0,"SRID=4326;LINESTRING(7.5724373132812 48.0188749889275,7.75186249820802 48.0964057188536)",220000.0,3717719.0,0.04,,3.0,0.16,4.0,Eichstetten (DE) - Vogelgrün (FR),,23.0,,18421.0,2.6 +473,279_220kV,323,Al/St 240/40 2-bundle 220.0,495.366530965,30.268,1.0,"SRID=4326;LINESTRING(12.8139099116529 50.8579185114397,12.4772465232803 50.7531200813148)",220000.0,3730414.0,,212,3.0,,2.0,Röhrsdorf - Crossen,50Hertz,,50.0,30268.0, +474,279,65,Al/St 240/40 4-bundle 380.0,1283.44964841,81.695,0.75,"SRID=4326;LINESTRING(12.8139099116529 50.8579185114397,13.3264049984725 50.3463721850552)",380000.0,3730415.0,0.033333,445,3.0,0.333333,3.0,,,10.275,50.0,81695.0,1.95 +475,324,290,Al/St 240/40 4-bundle 380.0,1283.44964841,56.706,0.75,"SRID=4326;LINESTRING(14.9464972856333 51.0625862159619,14.2192282929357 51.120707992198)",380000.0,3730416.0,0.033333,553,3.0,0.333333,3.0,553 Schmölln - Hagenwerder,50Hertz,10.275,,56706.0,1.95 +476,324,325,Al/St 240/40 4-bundle 380.0,1283.44964841,15.195,0.75,"SRID=4326;LINESTRING(14.9464972856333 51.0625862159619,15.1149907448572 51.0905795559394)",380000.0,3730417.0,0.033333,567,3.0,0.333333,3.0,,,10.275,,15195.0,1.95 +477,324,325,Al/St 240/40 4-bundle 380.0,1283.44964841,16.031,0.75,"SRID=4326;LINESTRING(14.9464972856333 51.0625862159619,15.1149907448572 51.0905795559394)",380000.0,3730418.0,0.033333,568,3.0,0.333333,3.0,,,10.275,,16031.0,1.95 +478,279,326,Al/St 240/40 4-bundle 380.0,1283.44964841,85.425,0.75,"SRID=4326;LINESTRING(12.8139099116529 50.8579185114397,13.8412680841242 50.9901759359427)",380000.0,3730420.0,0.033333,591,3.0,0.333333,3.0,,50Hertz,10.275,,85425.0,1.95 +479,326,327,Al/St 240/40 4-bundle 380.0,1283.44964841,47.804,0.75,"SRID=4326;LINESTRING(13.8412680841242 50.9901759359427,13.255555023675 50.9301782416399)",380000.0,3730421.0,0.033333,592,3.0,0.333333,3.0,,50Hertz,10.275,,47804.0,1.95 +480,279,327,Al/St 240/40 4-bundle 380.0,1283.44964841,37.54,0.75,"SRID=4326;LINESTRING(12.8139099116529 50.8579185114397,13.255555023675 50.9301782416399)",380000.0,3730422.0,0.033333,594,3.0,0.333333,3.0,,50Hertz,10.275,,37540.0,1.95 +481,328,329,Al/St 240/40 2-bundle 220.0,495.366530965,11.577,1.0,"SRID=4326;LINESTRING(13.0818771201792 48.2565212405017,13.0027821071279 48.261968265807)",220000.0,3732376.0,,230,3.0,,2.0,Simbach (DE) - St. Peter (AT),,,,11577.0, +482,330,328,Al/St 240/40 2-bundle 220.0,247.683265482,77.901,0.5,"SRID=4326;LINESTRING(12.2208353620671 48.5857329876532,13.0818771201792 48.2565212405017)",220000.0,3732377.0,0.16,233,3.0,0.64,1.0,Altheim (DE) - St. Peter (AT),,5.75,,77901.0,0.65 +483,330,329,Al/St 240/40 2-bundle 220.0,247.683265482,74.318,0.5,"SRID=4326;LINESTRING(12.2208353620671 48.5857329876532,13.0027821071279 48.261968265807)",220000.0,3732378.0,0.16,234,3.0,0.64,1.0,,,5.75,,74318.0,0.65 +484,328,331,Al/St 240/40 2-bundle 220.0,495.366530965,42.444,1.0,"SRID=4326;LINESTRING(13.0818771201792 48.2565212405017,12.7474442277007 48.1487558434787)",220000.0,3732379.0,,256,3.0,,2.0,St. Peter (AT) - Pirach (DE),,,,42444.0, +485,332_220kV,331,Al/St 240/40 2-bundle 220.0,495.366530965,86.211,1.0,"SRID=4326;LINESTRING(13.1034346393158 48.6667929606034,12.7474442277007 48.1487558434787)",220000.0,3732380.0,,257,3.0,,2.0,,,,,86211.0, +486,332_220kV,328,Al/St 240/40 2-bundle 220.0,495.366530965,48.682,1.0,"SRID=4326;LINESTRING(13.1034346393158 48.6667929606034,13.0818771201792 48.2565212405017)",220000.0,3732381.0,,258,3.0,,2.0,Pleinting (DE) - St. Peter (AT),,,,48682.0, +488,334,78,Al/St 240/40 4-bundle 380.0,1283.44964841,56.075,0.75,"SRID=4326;LINESTRING(11.193727795361 50.1622144406172,11.6463786274424 50.534876971042)",380000.0,3732383.0,0.033333,413,3.0,0.333333,3.0,,,10.275,,56075.0,1.95 +489,335,308,Al/St 240/40 4-bundle 380.0,1711.26619788,49.559,1.0,"SRID=4326;LINESTRING(10.8360157547717 49.9280673897058,10.1878339995516 49.981219040845)",380000.0,3732385.0,0.025,423,3.0,0.25,4.0,,,13.7,,49559.0,2.6 +490,334,335,Al/St 240/40 4-bundle 380.0,1711.26619788,43.347,1.0,"SRID=4326;LINESTRING(11.193727795361 50.1622144406172,10.8360157547717 49.9280673897058)",380000.0,3732387.0,0.025,435,3.0,0.25,4.0,,,13.7,,43347.0,2.6 +491,334,336,Al/St 240/40 4-bundle 380.0,855.633098939,49.456,0.5,"SRID=4326;LINESTRING(11.193727795361 50.1622144406172,11.8107025453557 50.1837655260691)",380000.0,3732388.0,0.05,437,3.0,0.5,2.0,,,6.85,,49456.0,1.3 +492,66,336,Al/St 240/40 4-bundle 380.0,855.633098939,88.445,0.5,"SRID=4326;LINESTRING(12.1147644054101 49.6311550521523,11.8107025453557 50.1837655260691)",380000.0,3732389.0,0.05,437,3.0,0.5,2.0,,,6.85,,88445.0,1.3 +493,332,337,Al/St 240/40 4-bundle 380.0,3422.53239576,63.598,2.0,"SRID=4326;LINESTRING(13.1034346393158 48.6667929606034,12.2968837896062 48.6069099225677)",380000.0,3732390.0,0.0125,451;452,6.0,0.125,4.0,,,27.4,,63598.0,5.2 +494,338,66,Al/St 240/40 4-bundle 380.0,855.633098939,43.477,0.5,"SRID=4326;LINESTRING(12.0822544456506 49.2974477669261,12.1147644054101 49.6311550521523)",380000.0,3732391.0,0.05,457,3.0,0.5,2.0,,,6.85,,43477.0,1.3 +495,332,338,Al/St 240/40 4-bundle 380.0,855.633098939,112.737,0.5,"SRID=4326;LINESTRING(13.1034346393158 48.6667929606034,12.0822544456506 49.2974477669261)",380000.0,3732392.0,0.05,465,3.0,0.5,2.0,,,6.85,,112737.0,1.3 +496,68,70,Al/St 240/40 4-bundle 380.0,855.633098939,47.115,0.5,"SRID=4326;LINESTRING(8.77244296019056 49.974668665137,8.38855636787547 49.9823050841271)",380000.0,3736194.0,,,3.0,,2.0,Trebur Nord,,,,47115.0, +497,68_220kV,339,Al/St 240/40 2-bundle 220.0,495.366530965,52.848,1.0,"SRID=4326;LINESTRING(8.77244296019056 49.974668665137,8.95368121281126 49.5674431691125)",220000.0,3736195.0,,,3.0,,2.0,Urberach - Beerfelden,,,,52848.0, +498,324,290,Al/St 240/40 4-bundle 380.0,1283.44964841,56.706,0.75,"SRID=4326;LINESTRING(14.9464972856333 51.0625862159619,14.2192282929357 51.120707992198)",380000.0,3738981.0,0.033333,554,3.0,0.333333,3.0,554 Schmöln - Hagenwerder,50Hertz,10.275,,56706.0,1.95 +499,326,290,Al/St 240/40 4-bundle 380.0,855.633098939,36.468,0.5,"SRID=4326;LINESTRING(13.8412680841242 50.9901759359427,14.2192282929357 51.120707992198)",380000.0,3738982.0,,555;556,3.0,,2.0,,50Hertz,,50.0,36468.0, +500,340,282_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,27.915,2.0,"SRID=4326;LINESTRING(12.4696440085589 51.3716629493371,12.5182220968156 51.1570725850832)",220000.0,3739951.0,0.04,205;206,6.0,0.16,2.0,,50Hertz,23.0,,27915.0,2.6 +501,341,282_220kV,Al/St 240/40 2-bundle 220.0,247.683265482,26.927,0.5,"SRID=4326;LINESTRING(12.2488239057592 51.1978824277899,12.5182220968156 51.1570725850832)",220000.0,3739952.0,0.16,233,3.0,0.64,1.0,,50Hertz,5.75,,26927.0,0.65 +502,341,342,Al/St 240/40 2-bundle 220.0,495.366530965,131.762,1.0,"SRID=4326;LINESTRING(12.2488239057592 51.1978824277899,10.7346694298176 51.4401867580454)",220000.0,3739953.0,,357,3.0,,2.0,,50Hertz,,,131762.0, +503,282_220kV,342,Al/St 240/40 2-bundle 220.0,247.683265482,158.686,0.5,"SRID=4326;LINESTRING(12.5182220968156 51.1570725850832,10.7346694298176 51.4401867580454)",220000.0,3739954.0,0.16,358,3.0,0.64,1.0,,50Hertz,5.75,,158686.0,0.65 +504,343,294,Al/St 240/40 4-bundle 380.0,2566.89929682,8.083,1.5,"SRID=4326;LINESTRING(11.0049169149012 50.5074149690608,10.9910146333086 50.573526990181)",380000.0,3739955.0,0.016667,"461,462",6.0,0.166667,3.0,Altenfeld - Goldisthal,,20.55,,8083.0,3.9 +505,340,282_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,27.915,1.0,"SRID=4326;LINESTRING(12.4696440085589 51.3716629493371,12.5182220968156 51.1570725850832)",220000.0,3742236.0,,205;206,3.0,,2.0,205;206 Taucha - Eula,50Hertz,,,27915.0, +506,279_220kV,344,Al/St 240/40 2-bundle 220.0,495.366530965,18.307,1.0,"SRID=4326;LINESTRING(12.8139099116529 50.8579185114397,13.0025898987886 50.8659127196838)",220000.0,3742237.0,0.08,226,3.0,0.32,2.0,,50Hertz,11.5,,18307.0,1.3 +507,281_220kV,345,Al/St 240/40 2-bundle 220.0,990.733061929,32.217,2.0,"SRID=4326;LINESTRING(12.0449825161601 50.7812567340735,12.2428369226029 50.5674531670625)",220000.0,3742238.0,,231;232,6.0,,2.0,,50Hertz,,,32217.0, +508,32_220kV,342,Al/St 240/40 2-bundle 220.0,495.366530965,65.759,1.0,"SRID=4326;LINESTRING(11.1228545605428 50.9989090670144,10.7346694298176 51.4401867580454)",220000.0,3742239.0,0.08,367,3.0,0.32,2.0,367 Wolkramshausen - Vieselbach,50Hertz,11.5,,65759.0,1.3 +509,32_220kV,342,Al/St 240/40 2-bundle 220.0,495.366530965,65.756,1.0,"SRID=4326;LINESTRING(11.1228545605428 50.9989090670144,10.7346694298176 51.4401867580454)",220000.0,3742240.0,0.08,368,3.0,0.32,2.0,368 Wolkramshausen - Vieselbach,,11.5,,65756.0,1.3 +510,72,32,Al/St 240/40 4-bundle 380.0,1711.26619788,78.038,1.0,"SRID=4326;LINESTRING(11.8942661378135 51.4057031412478,11.1228545605428 50.9989090670144)",380000.0,3742241.0,0.025,471,3.0,0.25,4.0,471 Lauchstädt - Vieselbach,50Hertz,13.7,,78038.0,2.6 +511,72,32,Al/St 240/40 4-bundle 380.0,1711.26619788,78.034,1.0,"SRID=4326;LINESTRING(11.8942661378135 51.4057031412478,11.1228545605428 50.9989090670144)",380000.0,3742242.0,0.025,472,3.0,0.25,4.0,472 Lauchstädt - Vieselbach,50Hertz,13.7,,78034.0,2.6 +512,346,347,Al/St 240/40 4-bundle 380.0,2566.89929682,4.993,1.5,"SRID=4326;LINESTRING(13.249028367237 52.5316238928337,13.3155158513838 52.5219993683183)",380000.0,3749523.0,0.016667,903;904,6.0,0.166667,3.0,,,20.55,,4993.0,3.9 +513,348,47,Al/St 240/40 4-bundle 380.0,1711.26619788,6.55,1.0,"SRID=4326;LINESTRING(13.3686584616208 52.5031422115792,13.4564306927748 52.5214517451981)",380000.0,3749524.0,,919;920,6.0,,2.0,,,,,6550.0, +514,46,47,Al/St 240/40 4-bundle 380.0,1711.26619788,5.184,1.0,"SRID=4326;LINESTRING(13.5241987309997 52.5356342713481,13.4564306927748 52.5214517451981)",380000.0,3749525.0,,921;922,6.0,,2.0,,,,,5184.0, +515,348,347,Al/St 240/40 4-bundle 380.0,1711.26619788,4.169,1.0,"SRID=4326;LINESTRING(13.3686584616208 52.5031422115792,13.3155158513838 52.5219993683183)",380000.0,3749526.0,,,6.0,,2.0,Charlottenburg - Mitte,,,,4169.0, +516,349,330,Al/St 240/40 2-bundle 220.0,495.366530965,45.232,1.0,"SRID=4326;LINESTRING(11.7924138446413 48.8382240226632,12.2208353620671 48.5857329876532)",220000.0,3749845.0,0.08,219;220,6.0,0.32,1.0,,,11.5,,45232.0,1.3 +517,349,350,Al/St 240/40 2-bundle 220.0,495.366530965,73.862,1.0,"SRID=4326;LINESTRING(11.7924138446413 48.8382240226632,11.3269526751765 49.393473117636)",220000.0,3749846.0,0.08,221,6.0,0.32,1.0,,,11.5,,73862.0,1.3 +518,338_220kV,350,Al/St 240/40 2-bundle 220.0,495.366530965,62.842,1.0,"SRID=4326;LINESTRING(12.0822544456506 49.2974477669261,11.3269526751765 49.393473117636)",220000.0,3749847.0,0.08,223,3.0,0.32,2.0,,,11.5,,62842.0,1.3 +519,349,351_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,24.279,1.0,"SRID=4326;LINESTRING(11.7924138446413 48.8382240226632,11.5077305510922 48.7611239877457)",220000.0,3749848.0,,227;228,3.0,,2.0,,,,,24279.0, +520,338_220kV,336_220kV,Al/St 240/40 2-bundle 220.0,247.683265482,131.429,0.5,"SRID=4326;LINESTRING(12.0822544456506 49.2974477669261,11.8107025453557 50.1837655260691)",220000.0,3749849.0,0.16,248,3.0,0.64,1.0,,,5.75,,131429.0,0.65 +521,14,352,Al/St 240/40 2-bundle 220.0,495.366530965,32.772,1.0,"SRID=4326;LINESTRING(11.8132328973194 48.2212664654998,11.8076056361242 48.4554267991033)",220000.0,3749850.0,0.08,258,3.0,0.32,2.0,,,11.5,,32772.0,1.3 +522,352,353_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,39.745,1.0,"SRID=4326;LINESTRING(11.8076056361242 48.4554267991033,11.5813705216127 48.7648940508515)",220000.0,3749851.0,0.08,262,3.0,0.32,2.0,,,11.5,,39745.0,1.3 +523,15,353,Al/St 240/40 4-bundle 380.0,855.633098939,76.222,0.5,"SRID=4326;LINESTRING(11.8681562938884 48.2068029185353,11.5813705216127 48.7648940508515)",380000.0,3749852.0,0.05,421,3.0,0.5,2.0,,,6.85,,76222.0,1.3 +524,354,15,Al/St 240/40 4-bundle 380.0,1711.26619788,17.705,1.0,"SRID=4326;LINESTRING(11.6393121107521 48.1787834095237,11.8681562938884 48.2068029185353)",380000.0,3749853.0,0.025,429,3.0,0.25,4.0,,,13.7,,17705.0,2.6 +525,354,15,Al/St 240/40 4-bundle 380.0,1711.26619788,17.629,1.0,"SRID=4326;LINESTRING(11.6393121107521 48.1787834095237,11.8681562938884 48.2068029185353)",380000.0,3749854.0,0.025,430,3.0,0.25,4.0,,,13.7,,17629.0,2.6 +526,15,337,Al/St 240/40 4-bundle 380.0,5133.79859363,61.235,3.0,"SRID=4326;LINESTRING(11.8681562938884 48.2068029185353,12.2968837896062 48.6069099225677)",380000.0,3749855.0,0.008333,443;444;446,9.0,0.083333,4.0,,,41.1,,61235.0,7.8 +527,232,227,Al/St 240/40 4-bundle 380.0,855.633098939,0.161,0.5,"SRID=4326;LINESTRING(10.0837512706734 48.268867515302,10.0821573264928 48.2660901578788)",380000.0,3749856.0,,,3.0,,2.0,Vöhringen Amprion - LEW,,,,161.0, +528,315_220kV,232_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,59.714,1.0,"SRID=4326;LINESTRING(10.8408695619217 48.2380310092335,10.0837512706734 48.268867515302)",220000.0,3749857.0,,,3.0,,2.0,Vöhringen LEW - Oberottmarshausen,,,,59714.0, +529,235,322_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,72.34,2.0,"SRID=4326;LINESTRING(8.25192548128222 47.6397698264894,7.75186249820802 48.0964057188536)",220000.0,3756855.0,,5120,6.0,,2.0,,EnBW,,,72340.0, +530,322_220kV,288,Al/St 240/40 2-bundle 220.0,1981.46612386,48.816,4.0,"SRID=4326;LINESTRING(7.75186249820802 48.0964057188536,7.91072928612534 48.5012882982432)",220000.0,3756856.0,0.02,,6.0,0.08,4.0,Weier - Eichstetten,,46.0,,48816.0,5.2 +531,355,100_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,23.056,2.0,"SRID=4326;LINESTRING(6.17791451185192 49.9530650802033,6.47473977006816 49.9224692993999)",220000.0,3756857.0,,,6.0,,2.0,Niederstedem (DE) - Vianden (LU),,,,23056.0, +532,356,355,Al/St 240/40 2-bundle 220.0,495.366530965,2.552,1.0,"SRID=4326;LINESTRING(6.21102031526762 49.9586257531557,6.17791451185192 49.9530650802033)",220000.0,3756858.0,,,3.0,,2.0,Bauler (DE) - Vianden (LU),,,,2552.0, +533,357,356,Al/St 240/40 2-bundle 220.0,495.366530965,10.361,1.0,"SRID=4326;LINESTRING(6.11451700702834 49.9053646588261,6.21102031526762 49.9586257531557)",220000.0,3756859.0,,,3.0,,2.0,Bauler (DE) - Flebour (LU),,,,10361.0, +534,356,358,Al/St 240/40 2-bundle 220.0,495.366530965,28.063,1.0,"SRID=4326;LINESTRING(6.21102031526762 49.9586257531557,6.08346044961418 49.7764406189112)",220000.0,3756860.0,,,3.0,,2.0,Bauler (DE) - Roost (LU),,,,28063.0, +535,359,360,Al/St 240/40 4-bundle 380.0,3422.53239576,40.228,2.0,"SRID=4326;LINESTRING(6.27694178519793 49.1977400673896,6.77320783777607 49.2910742103854)",380000.0,3756990.0,0.0125,,6.0,0.125,4.0,Ensdorf (DE) - Vigy (FR),,27.4,,40228.0,5.2 +536,360_220kV,361,Al/St 240/40 2-bundle 220.0,495.366530965,20.195,1.0,"SRID=4326;LINESTRING(6.77320783777607 49.2910742103854,6.70365235663769 49.1385111923078)",220000.0,3756991.0,0.08,,3.0,0.32,2.0,Ensdorf (DE) - Saint Avold (FR),,11.5,,20195.0,1.3 +537,235,362_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,22.565,2.0,"SRID=4326;LINESTRING(8.25192548128222 47.6397698264894,8.04936352422006 47.550881853478)",220000.0,3756997.0,,,6.0,,2.0,Gurtweil (DE) - Laufenburg (CH),,,,22565.0, +538,167,363,Al/St 240/40 2-bundle 220.0,990.733061929,1.864,2.0,"SRID=4326;LINESTRING(7.52228010104186 51.5321980552231,7.50006173413631 51.535174092458)",220000.0,3763835.0,0.04,,6.0,0.16,2.0,Wambel - Ratsbusch,,23.0,,1864.0,2.6 +539,125_220kV,364,Al/St 240/40 2-bundle 220.0,990.733061929,3.618,2.0,"SRID=4326;LINESTRING(6.96568442352005 51.0693295322353,6.97593254033334 51.0376007284972)",220000.0,3763836.0,,,6.0,,2.0,Wiesdorf Ltg,,,,3618.0, +540,365,346,Al/St 240/40 4-bundle 380.0,1711.26619788,1.538,1.0,"SRID=4326;LINESTRING(13.2428489964032 52.5349539946502,13.249028367237 52.5316238928337)",380000.0,3764521.0,,901;902,6.0,,2.0,Reuter,,,,1538.0, +541,261,366,Al/St 240/40 4-bundle 380.0,1283.44964841,56.618,0.75,"SRID=4326;LINESTRING(9.0454349679866 48.9029620137561,8.56218193445384 49.132949935954)",380000.0,3767046.0,0.033333,,3.0,0.333333,3.0,Bruchsal-Kändelweg-Pulverdingen,,10.275,50.0,56618.0,1.95 +542,54,56,Al/St 240/40 4-bundle 380.0,855.633098939,22.305,0.5,"SRID=4326;LINESTRING(8.43846751211921 49.2524597288652,8.62320967451897 49.3565822492832)",380000.0,3767048.0,,,3.0,,2.0,HD-Neurott - Phillipsburg,,,,22305.0, +543,367,366,Al/St 240/40 4-bundle 380.0,1283.44964841,21.649,0.75,"SRID=4326;LINESTRING(8.4413154852023 49.2514026469585,8.56218193445384 49.132949935954)",380000.0,3767049.0,0.033333,,3.0,0.333333,3.0,Philippsburg-Bruchsal-Kändelweg,,10.275,50.0,21649.0,1.95 +544,231,368,Al/St 240/40 4-bundle 380.0,855.633098939,7.644,0.5,"SRID=4326;LINESTRING(7.95805402328778 47.5954722640613,7.94208128553147 47.6533415488354)",380000.0,3782492.0,,,3.0,,2.0,Wehr - Kühmoos II,Schluchseewerk AG,,,7644.0, +545,231,368,Al/St 240/40 4-bundle 380.0,855.633098939,7.667,0.5,"SRID=4326;LINESTRING(7.95805402328778 47.5954722640613,7.94208128553147 47.6533415488354)",380000.0,3782493.0,,,3.0,,2.0,Wehr - Kühmoos I,Schluchseewerk AG,,,7667.0, +546,30,369,Al/St 240/40 4-bundle 380.0,855.633098939,32.522,0.5,"SRID=4326;LINESTRING(9.5017896678173 53.5428143723363,9.06879105808819 53.5666414121342)",380000.0,3803146.0,0.05,,3.0,0.5,2.0,Alfstedt - Dollern,TenneT,6.85,50.0,32522.0,1.3 +547,61,369,Al/St 240/40 4-bundle 380.0,855.633098939,58.735,0.5,"SRID=4326;LINESTRING(8.51888180951659 53.2026352836834,9.06879105808819 53.5666414121342)",380000.0,3803147.0,0.05,,3.0,0.5,2.0,Farge - Alfstedt,TenneT,6.85,50.0,58735.0,1.3 +548,256,370,Al/St 240/40 2-bundle 220.0,990.733061929,1.3,2.0,"SRID=4326;LINESTRING(6.75552340556687 49.3648695914696,6.7413761724965 49.3574919373628)",220000.0,3803492.0,0.04,,6.0,0.16,2.0,Diefflen - Dillinger Hütte,,23.0,,1300.0,2.6 +549,256,371,Al/St 240/40 2-bundle 220.0,495.366530965,0.481,1.0,"SRID=4326;LINESTRING(6.75552340556687 49.3648695914696,6.74970009407096 49.3651763747607)",220000.0,3803493.0,0.08,2460,3.0,0.32,2.0,Diefflen - Obergraben,amprion,11.5,50.0,481.0,1.3 +550,256,372,Al/St 240/40 2-bundle 220.0,495.366530965,1.123,1.0,"SRID=4326;LINESTRING(6.75552340556687 49.3648695914696,6.76601564520935 49.3600771523749)",220000.0,3803494.0,0.08,2451,3.0,0.32,2.0,Diefflen - Saarwellingen,amprion,11.5,50.0,1123.0,1.3 +551,99,360,Al/St 240/40 4-bundle 380.0,3422.53239576,23.317,2.0,"SRID=4326;LINESTRING(6.99647659179489 49.3754093063084,6.77320783777607 49.2910742103854)",380000.0,3803495.0,0.0125,,6.0,0.125,4.0,Uchtelfangen - Ensdorf,,27.4,,23317.0,5.2 +552,356,100_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,20.457,2.0,"SRID=4326;LINESTRING(6.21102031526762 49.9586257531557,6.47473977006816 49.9224692993999)",220000.0,3803496.0,,,6.0,,2.0,,,,,20457.0, +553,356,355,Al/St 240/40 2-bundle 220.0,495.366530965,2.552,1.0,"SRID=4326;LINESTRING(6.21102031526762 49.9586257531557,6.17791451185192 49.9530650802033)",220000.0,3803497.0,,,3.0,,2.0,Bauler (DE) - Vianden (LU),,,,2552.0, +554,334,78,Al/St 240/40 4-bundle 380.0,1283.44964841,56.065,0.75,"SRID=4326;LINESTRING(11.193727795361 50.1622144406172,11.6463786274424 50.534876971042)",380000.0,3805411.0,0.033333,414,3.0,0.333333,3.0,,,10.275,,56065.0,1.95 +555,338_220kV,373,Al/St 240/40 2-bundle 220.0,495.366530965,35.887,1.0,"SRID=4326;LINESTRING(12.0822544456506 49.2974477669261,12.1504016214021 49.0127518128214)",220000.0,3861963.0,,275,3.0,,2.0,,,,,35887.0, +556,207,374,Al/St 240/40 2-bundle 220.0,495.366530965,3.523,1.0,"SRID=4326;LINESTRING(7.60711612154456 51.8986046029193,7.5913598406626 51.928441667004)",220000.0,3861964.0,,,3.0,,2.0,Amelsbüren - Münster,,,,3523.0, +557,57,375,Al/St 240/40 2-bundle 220.0,495.366530965,2.681,1.0,"SRID=4326;LINESTRING(8.44554248929306 48.9860881753906,8.45154437251894 49.0074029130057)",220000.0,3861966.0,0.08,,3.0,0.32,2.0,Oberwald - Elfmorgenbruch,,11.5,,2681.0,1.3 +558,209,376,Al/St 240/40 4-bundle 380.0,1711.26619788,5.847,1.0,"SRID=4326;LINESTRING(8.93969252741257 52.4030332405173,8.99620817966153 52.379587985752)",380000.0,3862869.0,0.025,,3.0,0.25,4.0,3016,,13.7,,5847.0,2.6 +559,377,378,Al/St 240/40 4-bundle 380.0,1283.44964841,19.652,0.75,"SRID=4326;LINESTRING(9.56282337410874 48.7014898200493,9.37084541589578 48.7191233300187)",380000.0,3862870.0,0.033333,,3.0,0.333333,3.0,AL1-BZW,,10.275,,19652.0,1.95 +560,378,379,Al/St 240/40 4-bundle 380.0,1283.44964841,13.38,0.75,"SRID=4326;LINESTRING(9.37084541589578 48.7191233300187,9.37760577643558 48.812606857093)",380000.0,3862871.0,0.033333,,3.0,0.333333,3.0,AL1-EDS,,10.275,,13380.0,1.95 +561,16,49,Al/St 240/40 4-bundle 380.0,855.633098939,26.178,0.5,"SRID=4326;LINESTRING(10.7610403785613 52.3677293804096,10.99933301239 52.2001419745622)",380000.0,3883856.0,,3024,3.0,,2.0,,,,,26178.0, +562,320,379,Al/St 240/40 4-bundle 380.0,1283.44964841,20.599,0.75,"SRID=4326;LINESTRING(9.40063139462492 48.6669424396984,9.37760577643558 48.812606857093)",380000.0,3862873.0,0.033333,,3.0,0.333333,3.0,EDS-WDL,,10.275,,20599.0,1.95 +563,380,299,Al/St 240/40 2-bundle 220.0,495.366530965,26.528,1.0,"SRID=4326;LINESTRING(9.82158380229154 52.4248552546066,10.0876467999739 52.3151305547947)",220000.0,3862874.0,0.08,,3.0,0.32,2.0,Mehrum - Lahe,,11.5,,26528.0,1.3 +564,307_220kV,381,Al/St 240/40 2-bundle 220.0,495.366530965,27.26,1.0,"SRID=4326;LINESTRING(9.15135394990856 49.1433834119236,9.0762254363019 49.3633869626072)",220000.0,3866628.0,0.08,,3.0,0.32,2.0,Großgartach - Obrigheim,,11.5,,27260.0,1.3 +565,382_220kV,308_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,2.735,1.0,"SRID=4326;LINESTRING(10.1602912689993 49.9891947837947,10.1878339995516 49.981219040845)",220000.0,3866867.0,0.08,234,3.0,0.32,2.0,,,11.5,50.0,2735.0,1.3 +566,382_220kV,383,Al/St 240/40 2-bundle 220.0,495.366530965,5.191,1.0,"SRID=4326;LINESTRING(10.1602912689993 49.9891947837947,10.1964915719944 50.0279724033143)",220000.0,3866868.0,,271,3.0,,2.0,,,,,5191.0, +567,384_220kV,382_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,90.818,1.0,"SRID=4326;LINESTRING(10.8431177989998 49.3786926858391,10.1602912689993 49.9891947837947)",220000.0,3866869.0,0.08,278,3.0,0.32,2.0,,,11.5,,90818.0,1.3 +568,246,385,Al/St 240/40 4-bundle 380.0,1283.44964841,29.655,0.75,"SRID=4326;LINESTRING(8.95870856177306 50.0826078105489,9.09051754868887 49.9700413735997)",380000.0,3866871.0,0.033333,412,3.0,0.333333,3.0,,,10.275,,29655.0,1.95 +569,246,308,Al/St 240/40 4-bundle 380.0,1283.44964841,116.059,0.75,"SRID=4326;LINESTRING(8.95870856177306 50.0826078105489,10.1878339995516 49.981219040845)",380000.0,3866872.0,,417,3.0,,3.0,,,,,116059.0, +570,385,308,Al/St 240/40 4-bundle 380.0,855.633098939,86.838,0.5,"SRID=4326;LINESTRING(9.09051754868887 49.9700413735997,10.1878339995516 49.981219040845)",380000.0,3866873.0,,418,3.0,,2.0,,,,,86838.0, +571,382,308,Al/St 240/40 4-bundle 380.0,855.633098939,2.265,0.5,"SRID=4326;LINESTRING(10.1602912689993 49.9891947837947,10.1878339995516 49.981219040845)",380000.0,3866874.0,,485,3.0,,2.0,,,,,2265.0, +572,382,308,Al/St 240/40 4-bundle 380.0,1711.26619788,2.958,1.0,"SRID=4326;LINESTRING(10.1602912689993 49.9891947837947,10.1878339995516 49.981219040845)",380000.0,3866875.0,0.025,487,3.0,0.25,4.0,,,13.7,,2958.0,2.6 +573,254,225,Al/St 240/40 2-bundle 220.0,247.683265482,46.959,0.5,"SRID=4326;LINESTRING(7.79256714933307 49.8105487811028,7.72920197913641 49.4826474297682)",220000.0,3868253.0,0.16,,3.0,0.64,1.0,Pfalz,,5.75,,46959.0,0.65 +574,220,76,Al/St 240/40 4-bundle 380.0,3422.53239576,32.023,2.0,"SRID=4326;LINESTRING(8.65937290290079 51.9996101318907,8.70221844882815 51.7363525828065)",380000.0,3873611.0,0.0125,,6.0,0.125,4.0,Bechterdissen - Elsen,TenneT,27.4,,32023.0,5.2 +575,220_220kV,386,Al/St 240/40 2-bundle 220.0,495.366530965,12.009,1.0,"SRID=4326;LINESTRING(8.65937290290079 51.9996101318907,8.8139941935628 51.9834366294197)",220000.0,3873612.0,0.08,A3,3.0,0.32,2.0,Bechterdissen - Lage,,11.5,,12009.0,1.3 +576,387,386,Al/St 240/40 2-bundle 220.0,247.683265482,40.637,0.5,"SRID=4326;LINESTRING(8.93221843866486 52.1933124298166,8.8139941935628 51.9834366294197)",220000.0,3873613.0,0.16,A6,3.0,0.64,1.0,Lage - Veltheim,,5.75,,40637.0,0.65 +577,220_220kV,387,Al/St 240/40 2-bundle 220.0,495.366530965,31.846,1.0,"SRID=4326;LINESTRING(8.65937290290079 51.9996101318907,8.93221843866486 52.1933124298166)",220000.0,3873614.0,,,3.0,,2.0,Veltheim - Bechterdissen,,,,31846.0, +578,388,387,Al/St 240/40 2-bundle 220.0,990.733061929,11.098,2.0,"SRID=4326;LINESTRING(8.95904413219088 52.2784949764899,8.93221843866486 52.1933124298166)",220000.0,3873615.0,0.04,A8;A9,6.0,0.16,2.0,Veltheim - Meißen,,23.0,,11098.0,2.6 +579,389,385_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,46.72,2.0,"SRID=4326;LINESTRING(9.59430158866246 49.7984427303145,9.09051754868887 49.9700413735997)",220000.0,3874447.0,,216;217,6.0,,2.0,,,,,46720.0, +580,334,390,Al/St 240/40 4-bundle 380.0,1711.26619788,81.073,1.0,"SRID=4326;LINESTRING(11.193727795361 50.1622144406172,10.9729038959004 49.5585073756737)",380000.0,3874448.0,0.025,432,3.0,0.25,4.0,,,13.7,,81073.0,2.6 +581,384,334,Al/St 240/40 4-bundle 380.0,1711.26619788,102.607,1.0,"SRID=4326;LINESTRING(10.8431177989998 49.3786926858391,11.193727795361 50.1622144406172)",380000.0,3874451.0,0.025,436,3.0,0.25,4.0,,,13.7,,102607.0,2.6 +582,384,390,Al/St 240/40 4-bundle 380.0,1711.26619788,27.503,1.0,"SRID=4326;LINESTRING(10.8431177989998 49.3786926858391,10.9729038959004 49.5585073756737)",380000.0,3874452.0,0.025,438,3.0,0.25,4.0,,,13.7,,27503.0,2.6 +583,391_220kV,385_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,28.905,1.0,"SRID=4326;LINESTRING(8.95464412133747 50.0860157333265,9.09051754868887 49.9700413735997)",220000.0,3874453.0,0.08,,6.0,0.32,1.0,Staudinger - Aschaffenburg,,11.5,,28905.0,1.3 +584,384_220kV,389,Al/St 240/40 2-bundle 220.0,495.366530965,114.917,1.0,"SRID=4326;LINESTRING(10.8431177989998 49.3786926858391,9.59430158866246 49.7984427303145)",220000.0,3874454.0,0.08,211,6.0,0.32,1.0,Trennfeld - Raitersaich,,11.5,,114917.0,1.3 +585,144,392,Al/St 240/40 2-bundle 220.0,495.366530965,0.647,1.0,"SRID=4326;LINESTRING(7.17937912538938 51.4722019484347,7.17590438914786 51.4755782506206)",220000.0,3883649.0,,,3.0,,2.0,,,,50.0,647.0, +586,37,49,Al/St 240/40 4-bundle 380.0,855.633098939,48.738,0.5,"SRID=4326;LINESTRING(11.6389127631156 52.2687346818467,10.99933301239 52.2001419745622)",380000.0,3883858.0,,491,3.0,,2.0,,50Hertz,,,48738.0, +587,37,49,Al/St 240/40 4-bundle 380.0,855.633098939,48.758,0.5,"SRID=4326;LINESTRING(11.6389127631156 52.2687346818467,10.99933301239 52.2001419745622)",380000.0,3883859.0,,492,3.0,,2.0,,50Hertz,,,48758.0, +588,393,233,Al/St 240/40 4-bundle 380.0,855.633098939,41.042,0.5,"SRID=4326;LINESTRING(10.231641549556 48.5297622792522,9.88551509756969 48.3040743584705)",380000.0,3883861.0,,,3.0,,2.0,Delmensingen - Niederstotzingen,,,50.0,41042.0, +589,394,312,Al/St 240/40 4-bundle 380.0,855.633098939,20.318,0.5,"SRID=4326;LINESTRING(10.2224539935941 48.7367522347984,10.1174866946797 48.8971053361382)",380000.0,3883862.0,,,3.0,,2.0,Goldshöfe - Rotensohl,,,,20318.0, +590,262,395,Al/St 240/40 4-bundle 380.0,855.633098939,18.314,0.5,"SRID=4326;LINESTRING(9.8013171668981 47.6525043240978,9.64533503255392 47.7445961090448)",380000.0,3883863.0,,,3.0,,2.0,Grünkraut - Obermooweiler,,,50.0,18314.0, +591,396,230,Al/St 240/40 4-bundle 380.0,855.633098939,2.192,0.5,"SRID=4326;LINESTRING(10.4015868558303 48.5099297352537,10.391776056934 48.5184609117668)",380000.0,3883864.0,,,3.0,,2.0,Gundelfingen - Gundremmingen,LEW,,50.0,2192.0, +592,397,284,Al/St 240/40 4-bundle 380.0,1711.26619788,50.615,1.0,"SRID=4326;LINESTRING(8.80962330905886 50.2034748086043,8.65659923594081 50.6055491256016)",380000.0,3885010.0,0.025,3023,3.0,0.25,4.0,,TenneT,13.7,,50615.0,2.6 +593,397,246,Al/St 240/40 4-bundle 380.0,1711.26619788,19.665,1.0,"SRID=4326;LINESTRING(8.80962330905886 50.2034748086043,8.95870856177306 50.0826078105489)",380000.0,3885011.0,0.025,3024,3.0,0.25,4.0,,,13.7,50.0,19665.0,2.6 +594,25,398,Al/St 240/40 4-bundle 380.0,1711.26619788,50.881,1.0,"SRID=4326;LINESTRING(9.34476098807139 53.9218370583068,9.72460611042234 54.2882702503445)",380000.0,3885012.0,0.025,303,6.0,0.25,2.0,,TenneT,13.7,50.0,50881.0,2.6 +595,81,74,Al/St 240/40 4-bundle 380.0,855.633098939,43.423,0.5,"SRID=4326;LINESTRING(14.4281804852225 51.8888641394403,13.9024346886892 51.8747235190459)",380000.0,3885041.0,,539,3.0,,2.0,,50Hertz,,,43423.0, +596,81,74,Al/St 240/40 4-bundle 380.0,855.633098939,43.44,0.5,"SRID=4326;LINESTRING(14.4281804852225 51.8888641394403,13.9024346886892 51.8747235190459)",380000.0,3885042.0,,540,3.0,,2.0,,50Hertz,,,43440.0, +597,280,80,Al/St 240/40 4-bundle 380.0,855.633098939,94.056,0.5,"SRID=4326;LINESTRING(13.3823233828996 51.3559961579888,14.5081091811329 51.4137103812427)",380000.0,3885043.0,,557,3.0,,2.0,,50Hertz,,50.0,94056.0, +598,280,80,Al/St 240/40 4-bundle 380.0,855.633098939,94.068,0.5,"SRID=4326;LINESTRING(13.3823233828996 51.3559961579888,14.5081091811329 51.4137103812427)",380000.0,3885044.0,,558,3.0,,2.0,,50Hertz,,50.0,94068.0, +599,81,280,Al/St 240/40 4-bundle 380.0,855.633098939,121.638,0.5,"SRID=4326;LINESTRING(14.4281804852225 51.8888641394403,13.3823233828996 51.3559961579888)",380000.0,3885045.0,,559,3.0,,2.0,,50Hertz,,,121638.0, +600,81,280,Al/St 240/40 4-bundle 380.0,855.633098939,121.608,0.5,"SRID=4326;LINESTRING(14.4281804852225 51.8888641394403,13.3823233828996 51.3559961579888)",380000.0,3885046.0,,560,3.0,,2.0,,50Hertz,,,121608.0, +601,334_220kV,336_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,49.216,1.0,"SRID=4326;LINESTRING(11.193727795361 50.1622144406172,11.8107025453557 50.1837655260691)",220000.0,3888198.0,,246,3.0,,2.0,,,,50.0,49216.0, +602,399,261_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,9.513,1.0,"SRID=4326;LINESTRING(9.09714220939812 48.8277618380686,9.0454349679866 48.9029620137561)",220000.0,3888199.0,,9333,3.0,,2.0,,,,,9513.0, +603,220_220kV,387,Al/St 240/40 2-bundle 220.0,495.366530965,34.201,1.0,"SRID=4326;LINESTRING(8.65937290290079 51.9996101318907,8.93221843866486 52.1933124298166)",220000.0,3888200.0,,A1,3.0,,2.0,,,,50.0,34201.0, +604,220_220kV,400,Al/St 240/40 2-bundle 220.0,990.733061929,1.688,2.0,"SRID=4326;LINESTRING(8.65937290290079 51.9996101318907,8.62886493303814 51.9936340707213)",220000.0,3888201.0,,,6.0,,2.0,,,,50.0,1688.0, +605,400,387,Al/St 240/40 2-bundle 220.0,495.366530965,33.114,1.0,"SRID=4326;LINESTRING(8.62886493303814 51.9936340707213,8.93221843866486 52.1933124298166)",220000.0,3888202.0,,A2,3.0,,2.0,,Netz Veltheim,,50.0,33114.0, +606,401,24_220kV,Al/St 240/40 2-bundle 220.0,247.683265482,24.702,0.5,"SRID=4326;LINESTRING(10.0604081018258 54.3487708201584,9.72699196993402 54.2914209867842)",220000.0,3889743.0,0.16,207A,3.0,0.64,1.0,207A,TenneT,5.75,50.0,24702.0,0.65 +607,83,401,Al/St 240/40 2-bundle 220.0,247.683265482,19.436,0.5,"SRID=4326;LINESTRING(10.1562824575947 54.2755482549913,10.0604081018258 54.3487708201584)",220000.0,3889744.0,0.16,207A,3.0,0.64,1.0,207A,TenneT,5.75,50.0,19436.0,0.65 +608,22_220kV,402,Al/St 240/40 2-bundle 220.0,495.366530965,18.353,1.0,"SRID=4326;LINESTRING(9.20210420095253 53.8951574100045,9.44963652666114 53.967286497351)",220000.0,3889745.0,,212,3.0,,2.0,,,,,18353.0, +609,99_220kV,239,Al/St 240/40 2-bundle 220.0,495.366530965,70.865,1.0,"SRID=4326;LINESTRING(6.99647659179489 49.3754093063084,6.70228540768092 49.8289944201161)",220000.0,3889746.0,0.08,,3.0,0.32,2.0,Osburg,,11.5,,70865.0,1.3 +610,362_220kV,231_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,9.168,2.0,"SRID=4326;LINESTRING(8.04936352422006 47.550881853478,7.95805402328778 47.5954722640613)",220000.0,3915992.0,,,6.0,,2.0,Kühmoss - Laufenberg 220 kV,,,50.0,9168.0, +611,403,362,Al/St 240/40 4-bundle 380.0,855.633098939,22.98,0.5,"SRID=4326;LINESTRING(8.25885994832776 47.6342116213731,8.04936352422006 47.550881853478)",380000.0,3916226.0,,,3.0,,2.0,Tiengen (DE) - Laufenberg (CH),,,,22980.0, +612,403_220kV,404_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,10.791,1.0,"SRID=4326;LINESTRING(8.25885994832776 47.6342116213731,8.23209442033504 47.5561434831307)",220000.0,3916290.0,,,3.0,,2.0,Tiengen (DE) - Beznau (CH) 220 kV,,,,10791.0, +613,403,404,Al/St 240/40 4-bundle 380.0,855.633098939,9.656,0.5,"SRID=4326;LINESTRING(8.25885994832776 47.6342116213731,8.23209442033504 47.5561434831307)",380000.0,3916291.0,,,3.0,,2.0,Tiengen (DE) - Beznau (CH) 380 kV,,,,9656.0, +614,405,231,Al/St 240/40 4-bundle 380.0,855.633098939,27.702,0.5,"SRID=4326;LINESTRING(7.75259055668336 47.5391330214816,7.95805402328778 47.5954722640613)",380000.0,3916522.0,,,3.0,,2.0,Asphard (CH) - Kühmoos (DE),,,,27702.0, +615,189,362,Al/St 240/40 4-bundle 380.0,855.633098939,82.181,0.5,"SRID=4326;LINESTRING(8.61664767275337 48.0659537906797,8.04936352422006 47.550881853478)",380000.0,3916550.0,,,3.0,,2.0,Trossingen (DE) - Laufenburg (CH),,,,82181.0, +616,234,406,Al/St 240/40 2-bundle 220.0,495.366530965,12.24,1.0,"SRID=4326;LINESTRING(9.01340238045655 47.8453486310857,8.86152575255362 47.8064353220893)",220000.0,3918228.0,,,3.0,,2.0,Beuren - Stockach,,,,12240.0, +617,235,406,Al/St 240/40 2-bundle 220.0,495.366530965,77.251,1.0,"SRID=4326;LINESTRING(8.25192548128222 47.6397698264894,8.86152575255362 47.8064353220893)",220000.0,3918229.0,,,3.0,,2.0,Gurtweil - Beuren,,,,77251.0, +619,235,403_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,0.878,1.0,"SRID=4326;LINESTRING(8.25192548128222 47.6397698264894,8.25885994832776 47.6342116213731)",220000.0,3918231.0,,,3.0,,2.0,Tiengen - Gurtweil,,,,878.0, +620,216,261,Al/St 240/40 4-bundle 380.0,855.633098939,12.774,0.5,"SRID=4326;LINESTRING(9.1907874369222 48.914416873799,9.0454349679866 48.9029620137561)",380000.0,3926258.0,,,3.0,,2.0,Hoheneck Amprion - Pulverdingen,,,,12774.0, +621,409,261,Al/St 240/40 4-bundle 380.0,855.633098939,30.516,0.5,"SRID=4326;LINESTRING(9.21565014323537 48.8523053231829,9.0454349679866 48.9029620137561)",380000.0,3926259.0,,,3.0,,2.0,Mühlhausen - Pulverdingen,,,,30516.0, +622,409,320,Al/St 240/40 4-bundle 380.0,855.633098939,38.236,0.5,"SRID=4326;LINESTRING(9.21565014323537 48.8523053231829,9.40063139462492 48.6669424396984)",380000.0,3926260.0,,,3.0,,2.0,Mühlhausen - Wendlingen,,,,38236.0, +623,410,230,Al/St 240/40 4-bundle 380.0,6845.06479151,1.871,4.0,"SRID=4326;LINESTRING(10.4012813869114 48.5146084080517,10.391776056934 48.5184609117668)",380000.0,3926261.0,0.00625,,12.0,0.0625,4.0,,,54.8,,1871.0,10.4 +624,220_220kV,411,Al/St 240/40 2-bundle 220.0,495.366530965,38.674,1.0,"SRID=4326;LINESTRING(8.65937290290079 51.9996101318907,8.73757268935919 51.7001692737445)",220000.0,3937928.0,,,3.0,,2.0,Bechterdissen - Paderbord/Süd,,,,38674.0, +625,23,412,Al/St 240/40 2-bundle 220.0,495.366530965,48.771,1.0,"SRID=4326;LINESTRING(9.98853660865951 53.766277668594,10.6414677086397 53.9191626884641)",220000.0,3943779.0,,208,3.0,,2.0,,,,,48771.0, +626,384_220kV,350,Al/St 240/40 2-bundle 220.0,495.366530965,39.322,1.0,"SRID=4326;LINESTRING(10.8431177989998 49.3786926858391,11.3269526751765 49.393473117636)",220000.0,3943780.0,,237,3.0,,2.0,,,,,39322.0, +627,307_220kV,312_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,104.688,1.0,"SRID=4326;LINESTRING(9.15135394990856 49.1433834119236,10.1174866946797 48.8971053361382)",220000.0,3945260.0,,,3.0,,2.0,Großgartach - Goldshöfe,,,,104688.0, +628,409,320,Al/St 240/40 4-bundle 380.0,855.633098939,38.323,0.5,"SRID=4326;LINESTRING(9.21565014323537 48.8523053231829,9.40063139462492 48.6669424396984)",380000.0,3945261.0,,,3.0,,2.0,Mühlhausen - Wendlingen,,,,38323.0, +629,413_220kV,56_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,9.616,1.0,"SRID=4326;LINESTRING(8.67371728898445 49.2904393851271,8.62320967451897 49.3565822492832)",220000.0,3945263.0,0.08,,3.0,0.32,2.0,Wiesloch - HD-Neurott,,11.5,,9616.0,1.3 +630,409,261,Al/St 240/40 4-bundle 380.0,855.633098939,30.516,0.5,"SRID=4326;LINESTRING(9.21565014323537 48.8523053231829,9.0454349679866 48.9029620137561)",380000.0,3945265.0,,,3.0,,2.0,,,,,30516.0, +631,216,261,Al/St 240/40 4-bundle 380.0,855.633098939,12.774,0.5,"SRID=4326;LINESTRING(9.1907874369222 48.914416873799,9.0454349679866 48.9029620137561)",380000.0,3945266.0,,,3.0,,2.0,,,,,12774.0, +632,384_220kV,350,Al/St 240/40 2-bundle 220.0,495.366530965,39.322,1.0,"SRID=4326;LINESTRING(10.8431177989998 49.3786926858391,11.3269526751765 49.393473117636)",220000.0,3946835.0,,237,3.0,,2.0,,,,,39322.0, +633,27,414,Al/St 240/40 2-bundle 220.0,495.366530965,4.544,1.0,"SRID=4326;LINESTRING(9.52960512906833 53.6189906941937,9.48789502699389 53.6365577623199)",220000.0,3947498.0,,,3.0,,2.0,Götzdorf - Stade,,,,4544.0, +634,30_220kV,27,Al/St 240/40 2-bundle 220.0,990.733061929,11.53,2.0,"SRID=4326;LINESTRING(9.5017896678173 53.5428143723363,9.52960512906833 53.6189906941937)",220000.0,3947499.0,,,6.0,,2.0,,,,,11530.0, +635,27,415,Al/St 240/40 2-bundle 220.0,495.366530965,7.031,1.0,"SRID=4326;LINESTRING(9.52960512906833 53.6189906941937,9.49151079250535 53.6580982806881)",220000.0,3947500.0,,,3.0,,2.0,,,,,7031.0, +636,415,414,Al/St 240/40 2-bundle 220.0,495.366530965,3.107,1.0,"SRID=4326;LINESTRING(9.49151079250535 53.6580982806881,9.48789502699389 53.6365577623199)",220000.0,3947501.0,,,3.0,,2.0,,,,,3107.0, +637,405,362,Al/St 240/40 4-bundle 380.0,855.633098939,23.657,0.5,"SRID=4326;LINESTRING(7.75259055668336 47.5391330214816,8.04936352422006 47.550881853478)",380000.0,3948321.0,,,3.0,,2.0,Asphard - Laufenburg,,,,23657.0, +638,416,362,Al/St 240/40 4-bundle 380.0,855.633098939,48.408,0.5,"SRID=4326;LINESTRING(7.47226685748746 47.6466103447741,8.04936352422006 47.550881853478)",380000.0,3948325.0,,,3.0,,2.0,Sierentz (FR) - Laufenburg (CH),,,,48408.0, +639,328,417,Al/St 240/40 2-bundle 220.0,495.366530965,36.61,1.0,"SRID=4326;LINESTRING(13.0818771201792 48.2565212405017,13.4428541936116 48.4345463643775)",220000.0,3948357.0,,235,3.0,,2.0,,,,,36610.0, +640,328,417,Al/St 240/40 2-bundle 220.0,495.366530965,36.533,1.0,"SRID=4326;LINESTRING(13.0818771201792 48.2565212405017,13.4428541936116 48.4345463643775)",220000.0,3948358.0,,236,3.0,,2.0,,,,,36533.0, +641,418,417,Al/St 240/40 2-bundle 220.0,495.366530965,24.409,1.0,"SRID=4326;LINESTRING(13.7106901040113 48.5193051606747,13.4428541936116 48.4345463643775)",220000.0,3948359.0,,237,3.0,,2.0,,,,,24409.0, +642,418,417,Al/St 240/40 2-bundle 220.0,495.366530965,24.482,1.0,"SRID=4326;LINESTRING(13.7106901040113 48.5193051606747,13.4428541936116 48.4345463643775)",220000.0,3948360.0,,238,3.0,,2.0,,,,,24482.0, +643,6,299,Al/St 240/40 2-bundle 220.0,247.683265482,14.726,0.5,"SRID=4326;LINESTRING(9.91814864613865 52.3799963125719,10.0876467999739 52.3151305547947)",220000.0,3952096.0,0.16,,3.0,0.64,1.0,,,5.75,,14726.0,0.65 +646,264_220kV,403_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,112.46,1.0,"SRID=4326;LINESTRING(9.42551238607291 48.0643507981263,8.25885994832776 47.6342116213731)",220000.0,3983637.0,,,3.0,,2.0,Herbertingen - Tiengen,,,,112460.0, +647,264,403,Al/St 240/40 4-bundle 380.0,855.633098939,113.534,0.5,"SRID=4326;LINESTRING(9.42551238607291 48.0643507981263,8.25885994832776 47.6342116213731)",380000.0,3983638.0,,,3.0,,2.0,Herbertingen - Tiengen,,,,113534.0, +648,24_220kV,272_220kV,Al/St 240/40 2-bundle 220.0,247.683265482,63.31,0.5,"SRID=4326;LINESTRING(9.72699196993402 54.2914209867842,9.31819523638713 54.7160494089748)",220000.0,3983706.0,0.16,206,3.0,0.64,1.0,,,5.75,,63310.0,0.65 +649,224,229,Al/St 240/40 2-bundle 220.0,990.733061929,12.509,2.0,"SRID=4326;LINESTRING(8.3523338799966 49.4212743386606,8.50180851652818 49.4449115114399)",220000.0,3989160.0,,,6.0,,2.0,,,,,12509.0, +650,421_220kV,56_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,37.257,1.0,"SRID=4326;LINESTRING(9.09183529743294 49.2946188662059,8.62320967451897 49.3565822492832)",220000.0,3989162.0,,,3.0,,2.0,,,,,37257.0, +651,421,309,Al/St 240/40 4-bundle 380.0,855.633098939,45.271,0.5,"SRID=4326;LINESTRING(9.09183529743294 49.2946188662059,9.44691867105201 49.5902629324336)",380000.0,3989163.0,,,3.0,,2.0,,,,,45271.0, +652,422,56_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,3.193,2.0,"SRID=4326;LINESTRING(8.66378315807233 49.3600873718872,8.62320967451897 49.3565822492832)",220000.0,4013370.0,0.04,,6.0,0.16,2.0,HD-Neurott - HD-Süd,,23.0,,3193.0,2.6 +653,286,303_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,46.045,2.0,"SRID=4326;LINESTRING(9.56710201161841 51.3252573276817,9.38765125748855 51.640733525594)",220000.0,4013372.0,0.04,,6.0,0.16,2.0,Würgassen - Sandershausen,,23.0,,46045.0,2.6 +654,231_220kV,423,Al/St 240/40 2-bundle 220.0,495.366530965,4.032,1.0,"SRID=4326;LINESTRING(7.95805402328778 47.5954722640613,7.95849127307054 47.5791483196323)",220000.0,4013373.0,,,3.0,,2.0,Leitung 1 von Säckingen nach Kühlmoos,,,,4032.0, +655,424,52_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,2.842,2.0,"SRID=4326;LINESTRING(8.34912612288763 49.0130521177978,8.3133401659254 49.0090434643525)",220000.0,4013374.0,,,6.0,,2.0,,,,,2842.0, +656,220,157,Al/St 240/40 4-bundle 380.0,1711.26619788,26.567,1.0,"SRID=4326;LINESTRING(8.65937290290079 51.9996101318907,8.364229530696 51.9271204904611)",380000.0,4430965.0,0.025,4375,3.0,0.25,4.0,,,13.7,,26567.0,2.6 +657,297,425,Al/St 240/40 2-bundle 220.0,990.733061929,3.637,2.0,"SRID=4326;LINESTRING(10.3692300485918 52.1670924338554,10.4203895577612 52.1652004276784)",220000.0,4441425.0,,,6.0,,2.0,,,,,3637.0, +658,297,426,Al/St 240/40 2-bundle 220.0,990.733061929,2.421,2.0,"SRID=4326;LINESTRING(10.3692300485918 52.1670924338554,10.3884721771883 52.1513649329361)",220000.0,4441426.0,,,6.0,,2.0,,,,,2421.0, +659,279_220kV,344,Al/St 240/40 2-bundle 220.0,495.366530965,18.21,1.0,"SRID=4326;LINESTRING(12.8139099116529 50.8579185114397,13.0025898987886 50.8659127196838)",220000.0,4713421.0,0.08,225,3.0,0.32,2.0,,50hertz,11.5,50.0,18210.0,1.3 +660,201_220kV,62,Al/St 240/40 2-bundle 220.0,247.683265482,38.821,0.5,"SRID=4326;LINESTRING(9.25103425143703 53.1144323051271,8.82002487241632 53.1073882918815)",220000.0,4741567.0,0.16,2144,3.0,0.64,1.0,,,5.75,50.0,38821.0,0.65 +661,279_220kV,323,Al/St 240/40 2-bundle 220.0,495.366530965,30.264,1.0,"SRID=4326;LINESTRING(12.8139099116529 50.8579185114397,12.4772465232803 50.7531200813148)",220000.0,4815174.0,,211,3.0,,2.0,Röhrsdorf - Crossen,50Hertz,,50.0,30264.0, +662,52,53,Al/St 240/40 4-bundle 380.0,1711.26619788,120.885,1.0,"SRID=4326;LINESTRING(8.3133401659254 49.0090434643525,7.75349546540486 48.0993557006259)",380000.0,4819879.0,0.025,7510,3.0,0.25,4.0,Eichstetten - Daxlanden,,13.7,50.0,120885.0,2.6 +663,256,371,Al/St 240/40 2-bundle 220.0,495.366530965,0.484,1.0,"SRID=4326;LINESTRING(6.75552340556687 49.3648695914696,6.74970009407096 49.3651763747607)",220000.0,4819954.0,0.08,2460,3.0,0.32,2.0,Diefflen - Obergraben,amprion,11.5,50.0,484.0,1.3 +664,256,372,Al/St 240/40 2-bundle 220.0,495.366530965,1.11,1.0,"SRID=4326;LINESTRING(6.75552340556687 49.3648695914696,6.76601564520935 49.3600771523749)",220000.0,4819990.0,0.08,2451,3.0,0.32,2.0,Diefflen - Saarwellingen,amprion,11.5,50.0,1110.0,1.3 +665,52,427,Al/St 240/40 4-bundle 380.0,1711.26619788,0.802,1.0,"SRID=4326;LINESTRING(8.3133401659254 49.0090434643525,8.30626265141083 49.0128258362562)",380000.0,4858100.0,0.025,7564,3.0,0.25,4.0,,,13.7,50.0,802.0,2.6 +666,279,65,Al/St 240/40 4-bundle 380.0,1283.44964841,81.703,0.75,"SRID=4326;LINESTRING(12.8139099116529 50.8579185114397,13.3264049984725 50.3463721850552)",380000.0,5134203.0,0.033333,446,3.0,0.333333,3.0,,,10.275,50.0,81703.0,1.95 +667,60,160,Al/St 240/40 4-bundle 380.0,427.81654947,0.207,0.25,"SRID=4326;LINESTRING(8.47470704898602 53.4286511943138,8.47791063608626 53.428485468955)",380000.0,5152505.0,0.1,,3.0,1.0,1.0,,,3.425,50.0,207.0,0.65 +668,60,160,Al/St 240/40 4-bundle 380.0,427.81654947,0.177,0.25,"SRID=4326;LINESTRING(8.47470704898602 53.4286511943138,8.47791063608626 53.428485468955)",380000.0,5152506.0,0.1,,3.0,1.0,1.0,,,3.425,50.0,177.0,0.65 +669,159_220kV,161,Al/St 240/40 2-bundle 220.0,495.366530965,0.143,1.0,"SRID=4326;LINESTRING(8.05408735442182 53.3323026933898,8.05283751634017 53.3338809598955)",220000.0,5152510.0,,,3.0,,2.0,,,,50.0,143.0, +670,159_220kV,161,Al/St 240/40 2-bundle 220.0,495.366530965,0.113,1.0,"SRID=4326;LINESTRING(8.05408735442182 53.3323026933898,8.05283751634017 53.3338809598955)",220000.0,5152511.0,,,3.0,,2.0,,,,50.0,113.0, +671,269,284,Al/St 240/40 4-bundle 380.0,1711.26619788,37.921,1.0,"SRID=4326;LINESTRING(8.284861778672 50.7590735194915,8.65659923594081 50.6055491256016)",380000.0,5152665.0,0.025,3015/3005,3.0,0.25,4.0,Gießen-Nord - Dillenburg,,13.7,,37921.0,2.6 +672,16,49,Al/St 240/40 4-bundle 380.0,1711.26619788,26.178,1.0,"SRID=4326;LINESTRING(10.7610403785613 52.3677293804096,10.99933301239 52.2001419745622)",380000.0,5207092.0,0.025,3024,3.0,0.25,4.0,,,13.7,50.0,26178.0,2.6 +673,24_220kV,272_220kV,Al/St 240/40 2-bundle 220.0,247.683265482,63.307,0.5,"SRID=4326;LINESTRING(9.72699196993402 54.2914209867842,9.31819523638713 54.7160494089748)",220000.0,5207200.0,0.16,206,3.0,0.64,1.0,,TenneT,5.75,50.0,63307.0,0.65 +674,13_220kV,14,Al/St 240/40 2-bundle 220.0,495.366530965,39.297,1.0,"SRID=4326;LINESTRING(11.3704262486892 48.2914922329548,11.8132328973194 48.2212664654998)",220000.0,5247406.0,0.08,264,3.0,0.32,2.0,Oba-Nfi II,TenneT TSO GmbH,11.5,50.0,39297.0,1.3 +675,8,21,Al/St 240/40 4-bundle 380.0,855.633098939,28.584,0.5,"SRID=4326;LINESTRING(10.4149923381504 53.4126068830249,10.1575015165126 53.5551316685834)",380000.0,5247533.0,,991;992,3.0,,2.0,991;992 II,50Hertz,,50.0,28584.0, +676,52,367,Al/St 240/40 4-bundle 380.0,1283.44964841,33.164,0.75,"SRID=4326;LINESTRING(8.3133401659254 49.0090434643525,8.4413154852023 49.2514026469585)",380000.0,5247596.0,0.033333,7520,3.0,0.333333,3.0,Daxladen-Phillippsburg II,,10.275,,33164.0,1.95 +677,13,64,Al/St 240/40 4-bundle 380.0,1711.26619788,17.116,1.0,"SRID=4326;LINESTRING(11.3704262486892 48.2914922329548,11.4407746483177 48.1718035473026)",380000.0,5247617.0,0.025,439;440,3.0,0.25,4.0,Oberbachern - Menzing II,Stadtwerke München,13.7,,17116.0,2.6 +678,231_220kV,423,Al/St 240/40 2-bundle 220.0,495.366530965,4.03,1.0,"SRID=4326;LINESTRING(7.95805402328778 47.5954722640613,7.95849127307054 47.5791483196323)",220000.0,5268323.0,,,3.0,,2.0,Leitung 2 von Säckingen nach Kühlmoos,,,,4030.0, +679,231_220kV,423,Al/St 240/40 2-bundle 220.0,495.366530965,3.999,1.0,"SRID=4326;LINESTRING(7.95805402328778 47.5954722640613,7.95849127307054 47.5791483196323)",220000.0,5268327.0,,,3.0,,2.0,Leitung 3 von Säckingen nach Kühlmoos,,,,3999.0, +680,231_220kV,423,Al/St 240/40 2-bundle 220.0,495.366530965,4.002,1.0,"SRID=4326;LINESTRING(7.95805402328778 47.5954722640613,7.95849127307054 47.5791483196323)",220000.0,5268331.0,,,3.0,,2.0,Leitung 4 von Säckingen nach Kühlmoos,,,,4002.0, +681,28,27,Al/St 240/40 2-bundle 220.0,495.366530965,23.787,1.0,"SRID=4326;LINESTRING(9.77654483874895 53.6906352606291,9.52960512906833 53.6189906941937)",220000.0,5379925.0,,,3.0,,2.0,AKW Stade - UW Kummerfeld,,,50.0,23787.0, +682,30,201,Al/St 240/40 4-bundle 380.0,855.633098939,52.722,0.5,"SRID=4326;LINESTRING(9.5017896678173 53.5428143723363,9.25103425143703 53.1144323051271)",380000.0,5497488.0,0.05,3100,3.0,0.5,2.0,3100 schwarz,TenneT,6.85,50.0,52722.0,1.3 +683,30,201,Al/St 240/40 4-bundle 380.0,855.633098939,52.725,0.5,"SRID=4326;LINESTRING(9.5017896678173 53.5428143723363,9.25103425143703 53.1144323051271)",380000.0,5497490.0,0.05,3100,3.0,0.5,2.0,3100 grün,TenneT,6.85,50.0,52725.0,1.3 +684,313,32,Al/St 240/40 4-bundle 380.0,1283.44964841,104.48,0.75,"SRID=4326;LINESTRING(12.3374229103144 51.1969683571228,11.1228545605428 50.9989090670144)",380000.0,5507286.0,0.033333,590,3.0,0.333333,3.0,Pulgar - Vieselbach,50Hertz Transmission,10.275,50.0,104480.0,1.95 +685,428,81,Al/St 240/40 4-bundle 380.0,855.633098939,7.324,0.5,"SRID=4326;LINESTRING(14.4598868223649 51.8332759246019,14.4281804852225 51.8888641394403)",380000.0,5581798.0,,,3.0,,2.0,,50Hertz,,50.0,7324.0, +686,428,81,Al/St 240/40 4-bundle 380.0,855.633098939,6.772,0.5,"SRID=4326;LINESTRING(14.4598868223649 51.8332759246019,14.4281804852225 51.8888641394403)",380000.0,5581799.0,,,3.0,,2.0,,50Hertz,,50.0,6772.0, +687,428,81,Al/St 240/40 4-bundle 380.0,855.633098939,6.721,0.5,"SRID=4326;LINESTRING(14.4598868223649 51.8332759246019,14.4281804852225 51.8888641394403)",380000.0,5581800.0,,,3.0,,2.0,,50Hertz,,50.0,6721.0, +688,428,81,Al/St 240/40 4-bundle 380.0,855.633098939,6.792,0.5,"SRID=4326;LINESTRING(14.4598868223649 51.8332759246019,14.4281804852225 51.8888641394403)",380000.0,5581801.0,,,3.0,,2.0,,50Hertz,,50.0,6792.0, +689,428,81,Al/St 240/40 4-bundle 380.0,855.633098939,6.72,0.5,"SRID=4326;LINESTRING(14.4598868223649 51.8332759246019,14.4281804852225 51.8888641394403)",380000.0,5581802.0,,,3.0,,2.0,,50Hertz,,50.0,6720.0, +690,428,81,Al/St 240/40 4-bundle 380.0,855.633098939,7.324,0.5,"SRID=4326;LINESTRING(14.4598868223649 51.8332759246019,14.4281804852225 51.8888641394403)",380000.0,5581803.0,,,3.0,,2.0,,50Hertz,,50.0,7324.0, +691,119,431,Al/St 240/40 4-bundle 380.0,1711.26619788,20.673,1.0,"SRID=4326;LINESTRING(6.45655246642153 50.8713380368705,6.2140153990158 50.9438998573728)",380000.0,69607.0,0.025,,3.0,0.25,4.0,Kirchberg Süd,Amprion,13.7,50.0,20673.0,2.6 +692,429,431,Al/St 240/40 4-bundle 380.0,1711.26619788,3.765,1.0,"SRID=4326;LINESTRING(6.21619297137216 50.9123617956925,6.2140153990158 50.9438998573728)",380000.0,69607.0,0.025,,3.0,0.25,4.0,Kirchberg Süd,Amprion,13.7,50.0,3765.0,2.6 +693,430,431,Al/St 240/40 4-bundle 380.0,1711.26619788,34.211,1.0,"SRID=4326;LINESTRING(5.91852734477995 51.1484263858426,6.2140153990158 50.9438998573728)",380000.0,69607.0,0.025,,3.0,0.25,4.0,Kirchberg Süd,Amprion,13.7,50.0,34211.0,2.6 +694,432,434,Al/St 240/40 2-bundle 220.0,495.366530965,42.194,1.0,"SRID=4326;LINESTRING(10.9662583424068 47.2695304688802,11.2869438320852 47.4928624569052)",220000.0,110486.0,0.08,250;251,3.0,0.32,2.0,Silz (AT) - Krün (DE) - Oberbrunn,,11.5,50.0,42194.0,1.3 +695,433,434,Al/St 240/40 2-bundle 220.0,495.366530965,0.806,1.0,"SRID=4326;LINESTRING(11.2777210418419 47.4958726669837,11.2869438320852 47.4928624569052)",220000.0,110486.0,0.08,250;251,3.0,0.32,2.0,Silz (AT) - Krün (DE) - Oberbrunn,,11.5,50.0,806.0,1.3 +696,19,434,Al/St 240/40 2-bundle 220.0,495.366530965,71.413,1.0,"SRID=4326;LINESTRING(11.2939727001158 48.0520427719624,11.2869438320852 47.4928624569052)",220000.0,110486.0,0.08,250;251,3.0,0.32,2.0,Silz (AT) - Krün (DE) - Oberbrunn,,11.5,50.0,71413.0,1.3 +697,45,435,Al/St 240/40 2-bundle 220.0,495.366530965,38.755,1.0,"SRID=4326;LINESTRING(13.3057532949594 52.2307818451873,13.5053368398164 52.4747788520482)",220000.0,241441.0,0.08,302,3.0,0.32,2.0,302 Marzahn - Thyrow,50Hertz,11.5,50.0,38755.0,1.3 +698,44,435,Al/St 240/40 2-bundle 220.0,495.366530965,0.08,1.0,"SRID=4326;LINESTRING(13.5061534469385 52.473822309174,13.5053368398164 52.4747788520482)",220000.0,241441.0,0.08,302,3.0,0.32,2.0,302 Marzahn - Thyrow,50Hertz,11.5,50.0,80.0,1.3 +699,46_220kV,435,Al/St 240/40 2-bundle 220.0,495.366530965,9.094,1.0,"SRID=4326;LINESTRING(13.5241987309997 52.5356342713481,13.5053368398164 52.4747788520482)",220000.0,241441.0,0.08,302,3.0,0.32,2.0,302 Marzahn - Thyrow,50Hertz,11.5,50.0,9094.0,1.3 +700,231,437,Al/St 240/40 4-bundle 380.0,1711.26619788,67.448,1.0,"SRID=4326;LINESTRING(7.95805402328778 47.5954722640613,8.53055554991256 47.9148481845625)",380000.0,282239.0,0.025,7630,3.0,0.25,4.0,ENGST-KUHMO-VILLI,EnBW,13.7,50.0,67448.0,2.6 +701,190,437,Al/St 240/40 4-bundle 380.0,1711.26619788,55.454,1.0,"SRID=4326;LINESTRING(8.87294280487974 48.3100912582216,8.53055554991256 47.9148481845625)",380000.0,282239.0,0.025,7630,3.0,0.25,4.0,ENGST-KUHMO-VILLI,EnBW,13.7,50.0,55454.0,2.6 +702,436,437,Al/St 240/40 4-bundle 380.0,1711.26619788,16.231,1.0,"SRID=4326;LINESTRING(8.46542980021065 48.0435236210637,8.53055554991256 47.9148481845625)",380000.0,282239.0,0.025,7630,3.0,0.25,4.0,ENGST-KUHMO-VILLI,EnBW,13.7,50.0,16231.0,2.6 +703,288,439,Al/St 240/40 2-bundle 220.0,990.733061929,47.453,2.0,"SRID=4326;LINESTRING(7.91072928612534 48.5012882982432,8.24975045659741 48.8491773731136)",220000.0,339244.0,0.04,DAXLA-KUMRA-WEIER,3.0,0.16,4.0,Daxlanden - Weier,EnBW,23.0,50.0,47453.0,2.6 +704,438,439,Al/St 240/40 2-bundle 220.0,990.733061929,0.304,2.0,"SRID=4326;LINESTRING(8.24872901623214 48.8463165189067,8.24975045659741 48.8491773731136)",220000.0,339244.0,0.04,DAXLA-KUMRA-WEIER,3.0,0.16,4.0,Daxlanden - Weier,EnBW,23.0,50.0,304.0,2.6 +705,52_220kV,439,Al/St 240/40 2-bundle 220.0,990.733061929,19.936,2.0,"SRID=4326;LINESTRING(8.3133401659254 49.0090434643525,8.24975045659741 48.8491773731136)",220000.0,339244.0,0.04,DAXLA-KUMRA-WEIER,3.0,0.16,4.0,Daxlanden - Weier,EnBW,23.0,50.0,19936.0,2.6 +706,23,440,Al/St 240/40 2-bundle 220.0,247.683265482,39.532,0.5,"SRID=4326;LINESTRING(9.98853660865951 53.766277668594,9.45967747872262 53.8646574512707)",220000.0,908964.0,0.16,202,3.0,0.64,1.0,202,TenneT,5.75,50.0,39532.0,0.65 +707,402,440,Al/St 240/40 2-bundle 220.0,247.683265482,12.335,0.5,"SRID=4326;LINESTRING(9.44963652666114 53.967286497351,9.45967747872262 53.8646574512707)",220000.0,908964.0,0.16,202,3.0,0.64,1.0,202,TenneT,5.75,50.0,12335.0,0.65 +708,26_220kV,440,Al/St 240/40 2-bundle 220.0,247.683265482,9.587,0.5,"SRID=4326;LINESTRING(9.34400177215961 53.8517774635399,9.45967747872262 53.8646574512707)",220000.0,908964.0,0.16,202,3.0,0.64,1.0,202,TenneT,5.75,50.0,9587.0,0.65 +709,32,442,Al/St 240/40 4-bundle 380.0,1711.26619788,25.884,1.0,"SRID=4326;LINESTRING(11.1228545605428 50.9989090670144,11.4299065773025 50.9114464610955)",380000.0,918569.0,0.025,415,3.0,0.25,4.0,415,50Hertz,13.7,50.0,25884.0,2.6 +710,441,442,Al/St 240/40 4-bundle 380.0,1711.26619788,3.832,1.0,"SRID=4326;LINESTRING(11.4703941249156 50.9303902294723,11.4299065773025 50.9114464610955)",380000.0,918569.0,0.025,415,3.0,0.25,4.0,415,50Hertz,13.7,50.0,3832.0,2.6 +711,78,442,Al/St 240/40 4-bundle 380.0,1711.26619788,51.85,1.0,"SRID=4326;LINESTRING(11.6463786274424 50.534876971042,11.4299065773025 50.9114464610955)",380000.0,918569.0,0.025,415,3.0,0.25,4.0,415,50Hertz,13.7,50.0,51850.0,2.6 +712,86,444,Al/St 240/40 4-bundle 380.0,1711.26619788,84.868,1.0,"SRID=4326;LINESTRING(7.30820430094578 52.4768353862806,7.25501773539194 53.0267136928067)",380000.0,962119.0,0.025,305,3.0,0.25,4.0,Emsland West,Amprion;TenneT,13.7,50.0,84868.0,2.6 +713,443,444,Al/St 240/40 4-bundle 380.0,1711.26619788,0.06,1.0,"SRID=4326;LINESTRING(7.25465688214231 53.0266716315927,7.25501773539194 53.0267136928067)",380000.0,962119.0,0.025,305,3.0,0.25,4.0,Emsland West,Amprion;TenneT,13.7,50.0,60.0,2.6 +714,221,444,Al/St 240/40 4-bundle 380.0,1711.26619788,13.17,1.0,"SRID=4326;LINESTRING(7.31136229105814 53.1257665563186,7.25501773539194 53.0267136928067)",380000.0,962119.0,0.025,305,3.0,0.25,4.0,Emsland West,Amprion;TenneT,13.7,50.0,13170.0,2.6 +715,207,445,Al/St 240/40 2-bundle 220.0,247.683265482,42.126,0.5,"SRID=4326;LINESTRING(7.60711612154456 51.8986046029193,7.37185639416872 52.1977066547834)",220000.0,962174.0,0.16,,3.0,0.64,1.0,Amelsbüren,Amprion,5.75,50.0,42126.0,0.65 +716,95_220kV,445,Al/St 240/40 2-bundle 220.0,247.683265482,41.204,0.5,"SRID=4326;LINESTRING(7.88957671235007 52.2773967624078,7.37185639416872 52.1977066547834)",220000.0,962174.0,0.16,,3.0,0.64,1.0,Amelsbüren,Amprion,5.75,50.0,41204.0,0.65 +717,86_220kV,445,Al/St 240/40 2-bundle 220.0,247.683265482,35.337,0.5,"SRID=4326;LINESTRING(7.30820430094578 52.4768353862806,7.37185639416872 52.1977066547834)",220000.0,962174.0,0.16,,3.0,0.64,1.0,Amelsbüren,Amprion,5.75,50.0,35337.0,0.65 +718,181,447,Al/St 240/40 2-bundle 220.0,495.366530965,61.718,1.0,"SRID=4326;LINESTRING(14.0336963684666 53.5078685980791,13.3984708281663 53.7933318754816)",220000.0,1021993.0,0.08,316,3.0,0.32,2.0,316,50Hertz Transmission,11.5,50.0,61718.0,1.3 +719,446,447,Al/St 240/40 2-bundle 220.0,495.366530965,0.2,1.0,"SRID=4326;LINESTRING(13.399828840425 53.7944477778114,13.3984708281663 53.7933318754816)",220000.0,1021993.0,0.08,316,3.0,0.32,2.0,316,50Hertz Transmission,11.5,50.0,200.0,1.3 +720,42_220kV,447,Al/St 240/40 2-bundle 220.0,495.366530965,89.481,1.0,"SRID=4326;LINESTRING(12.1869848227216 53.8157751724384,13.3984708281663 53.7933318754816)",220000.0,1021993.0,0.08,316,3.0,0.32,2.0,316,50Hertz Transmission,11.5,50.0,89481.0,1.3 +721,135,448,Al/St 240/40 4-bundle 380.0,1711.26619788,28.631,1.0,"SRID=4326;LINESTRING(7.7186272145384 51.5342075115271,7.42474524551043 51.4592583943398)",380000.0,1082812.0,0.025,,3.0,0.25,4.0,Schwerterheide Nord,Amprion,13.7,50.0,28631.0,2.6 +722,109,448,Al/St 240/40 4-bundle 380.0,1711.26619788,8.884,1.0,"SRID=4326;LINESTRING(7.31318284290137 51.4466113896128,7.42474524551043 51.4592583943398)",380000.0,1082812.0,0.025,,3.0,0.25,4.0,Schwerterheide Nord,Amprion,13.7,50.0,8884.0,2.6 +723,112,448,Al/St 240/40 4-bundle 380.0,1711.26619788,15.771,1.0,"SRID=4326;LINESTRING(7.35311186939787 51.5758105020818,7.42474524551043 51.4592583943398)",380000.0,1082812.0,0.025,,3.0,0.25,4.0,Schwerterheide Nord,Amprion,13.7,50.0,15771.0,2.6 +724,113,449,Al/St 240/40 4-bundle 380.0,1711.26619788,11.486,1.0,"SRID=4326;LINESTRING(7.12323882950453 51.5256121803004,7.12336046768833 51.4473443869048)",380000.0,1082813.0,0.025,,3.0,0.25,4.0,Oerkhaus West,Amprion,13.7,50.0,11486.0,2.6 +725,132,449,Al/St 240/40 4-bundle 380.0,1711.26619788,39.21,1.0,"SRID=4326;LINESTRING(7.00877914033175 51.1742549180397,7.12336046768833 51.4473443869048)",380000.0,1082813.0,0.025,,3.0,0.25,4.0,Oerkhaus West,Amprion,13.7,50.0,39210.0,2.6 +726,107,449,Al/St 240/40 4-bundle 380.0,1711.26619788,0.129,1.0,"SRID=4326;LINESTRING(7.1255729106725 51.4489772588145,7.12336046768833 51.4473443869048)",380000.0,1082813.0,0.025,,3.0,0.25,4.0,Oerkhaus West,Amprion,13.7,50.0,129.0,2.6 +727,450,451,Al/St 240/40 4-bundle 380.0,1711.26619788,0.455,1.0,"SRID=4326;LINESTRING(7.41670791195847 51.4570521583476,7.41658261384975 51.4607823586596)",380000.0,1082817.0,0.025,,3.0,0.25,4.0,Witten West,Amprion,13.7,50.0,455.0,2.6 +728,112,451,Al/St 240/40 4-bundle 380.0,1711.26619788,16.266,1.0,"SRID=4326;LINESTRING(7.35311186939787 51.5758105020818,7.41658261384975 51.4607823586596)",380000.0,1082817.0,0.025,,3.0,0.25,4.0,Witten West,Amprion,13.7,50.0,16266.0,2.6 +729,109,451,Al/St 240/40 4-bundle 380.0,1711.26619788,8.291,1.0,"SRID=4326;LINESTRING(7.31318284290137 51.4466113896128,7.41658261384975 51.4607823586596)",380000.0,1082817.0,0.025,,3.0,0.25,4.0,Witten West,Amprion,13.7,50.0,8291.0,2.6 +730,119,453,Al/St 240/40 4-bundle 380.0,1711.26619788,60.217,1.0,"SRID=4326;LINESTRING(6.45655246642153 50.8713380368705,6.53303653131644 50.3922427776709)",380000.0,1082945.0,0.025,,3.0,0.25,4.0,Selhausen West,Amprion,13.7,50.0,60217.0,2.6 +731,100,453,Al/St 240/40 4-bundle 380.0,1711.26619788,58.563,1.0,"SRID=4326;LINESTRING(6.47473977006816 49.9224692993999,6.53303653131644 50.3922427776709)",380000.0,1082945.0,0.025,,3.0,0.25,4.0,Selhausen West,Amprion,13.7,50.0,58563.0,2.6 +732,452,453,Al/St 240/40 4-bundle 380.0,1711.26619788,0.344,1.0,"SRID=4326;LINESTRING(6.53175001008574 50.39005126464,6.53303653131644 50.3922427776709)",380000.0,1082945.0,0.025,,3.0,0.25,4.0,Selhausen West,Amprion,13.7,50.0,344.0,2.6 +733,124,454,Al/St 240/40 4-bundle 380.0,1711.26619788,0.376,1.0,"SRID=4326;LINESTRING(6.61240231530956 51.0359242594819,6.60879226770999 51.0346787720857)",380000.0,1082950.0,0.025,,3.0,0.25,4.0,Neurath 2b,Amprion,13.7,50.0,376.0,2.6 +734,125,454,Al/St 240/40 4-bundle 380.0,1711.26619788,35.531,1.0,"SRID=4326;LINESTRING(6.96568442352005 51.0693295322353,6.60879226770999 51.0346787720857)",380000.0,1082950.0,0.025,,3.0,0.25,4.0,Neurath 2b,Amprion,13.7,50.0,35531.0,2.6 +735,131,454,Al/St 240/40 4-bundle 380.0,1711.26619788,0.226,1.0,"SRID=4326;LINESTRING(6.80109333956604 51.1225390518584,6.60879226770999 51.0346787720857)",380000.0,1082950.0,0.025,,3.0,0.25,4.0,Neurath 2b,Amprion,13.7,50.0,226.0,2.6 +736,455,456,Al/St 240/40 4-bundle 380.0,1711.26619788,13.64,1.0,"SRID=4326;LINESTRING(6.80890624663756 51.2686075940546,6.72033167479422 51.3441235773954)",380000.0,1082951.0,0.025,,3.0,0.25,4.0,Meerbusch West,Amprion,13.7,50.0,13640.0,2.6 +737,173,456,Al/St 240/40 4-bundle 380.0,1711.26619788,16.533,1.0,"SRID=4326;LINESTRING(6.62284323546559 51.258713368098,6.72033167479422 51.3441235773954)",380000.0,1082951.0,0.025,,3.0,0.25,4.0,Meerbusch West,Amprion,13.7,50.0,16533.0,2.6 +738,184,456,Al/St 240/40 4-bundle 380.0,1711.26619788,23.236,1.0,"SRID=4326;LINESTRING(6.61738109142906 51.4757909635544,6.72033167479422 51.3441235773954)",380000.0,1082951.0,0.025,,3.0,0.25,4.0,Meerbusch West,Amprion,13.7,50.0,23236.0,2.6 +739,452,453,Al/St 240/40 4-bundle 380.0,1711.26619788,0.341,1.0,"SRID=4326;LINESTRING(6.53175001008574 50.39005126464,6.53303653131644 50.3922427776709)",380000.0,1082968.0,0.025,,3.0,0.25,4.0,Selhausen Ost,Amprion,13.7,50.0,341.0,2.6 +740,119,453,Al/St 240/40 4-bundle 380.0,1711.26619788,60.217,1.0,"SRID=4326;LINESTRING(6.45655246642153 50.8713380368705,6.53303653131644 50.3922427776709)",380000.0,1082968.0,0.025,,3.0,0.25,4.0,Selhausen Ost,Amprion,13.7,50.0,60217.0,2.6 +741,100,453,Al/St 240/40 4-bundle 380.0,1711.26619788,58.563,1.0,"SRID=4326;LINESTRING(6.47473977006816 49.9224692993999,6.53303653131644 50.3922427776709)",380000.0,1082968.0,0.025,,3.0,0.25,4.0,Selhausen Ost,Amprion,13.7,50.0,58563.0,2.6 +742,144,457,Al/St 240/40 2-bundle 220.0,495.366530965,11.605,1.0,"SRID=4326;LINESTRING(7.17937912538938 51.4722019484347,7.13937473425833 51.5608313916517)",220000.0,1082978.0,0.08,,3.0,0.32,2.0,Hochlarmark West,Amprion,11.5,50.0,11605.0,1.3 +743,140,457,Al/St 240/40 2-bundle 220.0,495.366530965,10.574,1.0,"SRID=4326;LINESTRING(7.2640834625924 51.5735734103802,7.13937473425833 51.5608313916517)",220000.0,1082978.0,0.08,,3.0,0.32,2.0,Hochlarmark West,Amprion,11.5,50.0,10574.0,1.3 +744,87_220kV,457,Al/St 240/40 2-bundle 220.0,495.366530965,17.383,1.0,"SRID=4326;LINESTRING(7.06030699176008 51.6964760130648,7.13937473425833 51.5608313916517)",220000.0,1082978.0,0.08,,3.0,0.32,2.0,Hochlarmark West,Amprion,11.5,50.0,17383.0,1.3 +745,165_220kV,458_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,76.723,1.0,"SRID=4326;LINESTRING(7.70831848872651 51.6762033088437,7.41946378046051 51.4575100936229)",220000.0,1082988.0,0.08,,3.0,0.32,2.0,Haarstrang Ost,Amprion,11.5,50.0,76723.0,1.3 +746,141,458_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,12.092,1.0,"SRID=4326;LINESTRING(7.2789471130269 51.4722816773138,7.41946378046051 51.4575100936229)",220000.0,1082988.0,0.08,,3.0,0.32,2.0,Haarstrang Ost,Amprion,11.5,50.0,12092.0,1.3 +747,450_220kV,458_220kV,Al/St 240/40 2-bundle 220.0,495.366530965,0.14,1.0,"SRID=4326;LINESTRING(7.41670791195847 51.4570521583476,7.41946378046051 51.4575100936229)",220000.0,1082988.0,0.08,,3.0,0.32,2.0,Haarstrang Ost,Amprion,11.5,50.0,140.0,1.3 +748,106_220kV,460,Al/St 240/40 2-bundle 220.0,495.366530965,5.295,1.0,"SRID=4326;LINESTRING(7.16721460965932 51.3899067582106,7.17920262035811 51.4218244823956)",220000.0,1082991.0,0.08,,3.0,0.32,2.0,Welper Ost,Amprion,11.5,50.0,5295.0,1.3 +749,144,460,Al/St 240/40 2-bundle 220.0,495.366530965,6.85,1.0,"SRID=4326;LINESTRING(7.17937912538938 51.4722019484347,7.17920262035811 51.4218244823956)",220000.0,1082991.0,0.08,,3.0,0.32,2.0,Welper Ost,Amprion,11.5,50.0,6850.0,1.3 +750,459,460,Al/St 240/40 2-bundle 220.0,495.366530965,1.686,1.0,"SRID=4326;LINESTRING(7.19597933005019 51.4115753148661,7.17920262035811 51.4218244823956)",220000.0,1082991.0,0.08,,3.0,0.32,2.0,Welper Ost,Amprion,11.5,50.0,1686.0,1.3 +751,199,462,Al/St 240/40 2-bundle 220.0,247.683265482,9.879,0.5,"SRID=4326;LINESTRING(7.44506930371379 51.6185000869374,7.34371334412713 51.6131049439167)",220000.0,1082993.0,0.16,,3.0,0.64,1.0,Elmenhorst Süd,Amprion,5.75,50.0,9879.0,0.65 +752,140,462,Al/St 240/40 2-bundle 220.0,247.683265482,7.666,0.5,"SRID=4326;LINESTRING(7.2640834625924 51.5735734103802,7.34371334412713 51.6131049439167)",220000.0,1082993.0,0.16,,3.0,0.64,1.0,Elmenhorst Süd,Amprion,5.75,50.0,7666.0,0.65 +753,461,462,Al/St 240/40 2-bundle 220.0,247.683265482,1.719,0.5,"SRID=4326;LINESTRING(7.32427958320741 51.6213874676194,7.34371334412713 51.6131049439167)",220000.0,1082993.0,0.16,,3.0,0.64,1.0,Elmenhorst Süd,Amprion,5.75,50.0,1719.0,0.65 +754,461,462,Al/St 240/40 2-bundle 220.0,247.683265482,1.719,0.5,"SRID=4326;LINESTRING(7.32427958320741 51.6213874676194,7.34371334412713 51.6131049439167)",220000.0,1082995.0,0.16,,3.0,0.64,1.0,Cappenberg Nord,Amprion,5.75,50.0,1719.0,0.65 +755,165_220kV,462,Al/St 240/40 2-bundle 220.0,247.683265482,29.538,0.5,"SRID=4326;LINESTRING(7.70831848872651 51.6762033088437,7.34371334412713 51.6131049439167)",220000.0,1082995.0,0.16,,3.0,0.64,1.0,Cappenberg Nord,Amprion,5.75,50.0,29538.0,0.65 +756,140,462,Al/St 240/40 2-bundle 220.0,247.683265482,7.656,0.5,"SRID=4326;LINESTRING(7.2640834625924 51.5735734103802,7.34371334412713 51.6131049439167)",220000.0,1082995.0,0.16,,3.0,0.64,1.0,Cappenberg Nord,Amprion,5.75,50.0,7656.0,0.65 +757,184_220kV,463,Al/St 240/40 2-bundle 220.0,247.683265482,21.12,0.5,"SRID=4326;LINESTRING(6.61738109142906 51.4757909635544,6.52272548882226 51.3224954692993)",220000.0,1083000.0,0.16,,3.0,0.64,1.0,Utfort West,Amprion,5.75,50.0,21120.0,0.65 +758,153,463,Al/St 240/40 2-bundle 220.0,247.683265482,22.758,0.5,"SRID=4326;LINESTRING(6.33006858948983 51.2429161503794,6.52272548882226 51.3224954692993)",220000.0,1083000.0,0.16,,3.0,0.64,1.0,Utfort West,Amprion,5.75,50.0,22758.0,0.65 +759,176,463,Al/St 240/40 2-bundle 220.0,247.683265482,3.463,0.5,"SRID=4326;LINESTRING(6.54240844257145 51.2994458149911,6.52272548882226 51.3224954692993)",220000.0,1083000.0,0.16,,3.0,0.64,1.0,Utfort West,Amprion,5.75,50.0,3463.0,0.65 +760,117_220kV,465,Al/St 240/40 2-bundle 220.0,495.366530965,6.532,1.0,"SRID=4326;LINESTRING(6.97257742700703 50.795693017514,6.92909269080674 50.8441529722133)",220000.0,1083936.0,0.08,,3.0,0.32,2.0,Godorf Ost,Amprion,11.5,50.0,6532.0,1.3 +761,464,465,Al/St 240/40 2-bundle 220.0,495.366530965,3.015,1.0,"SRID=4326;LINESTRING(6.96330545403678 50.8337629177582,6.92909269080674 50.8441529722133)",220000.0,1083936.0,0.08,,3.0,0.32,2.0,Godorf Ost,Amprion,11.5,50.0,3015.0,1.3 +762,134,465,Al/St 240/40 2-bundle 220.0,495.366530965,17.868,1.0,"SRID=4326;LINESTRING(6.80461158012622 50.9662452961379,6.92909269080674 50.8441529722133)",220000.0,1083936.0,0.08,,3.0,0.32,2.0,Godorf Ost,Amprion,11.5,50.0,17868.0,1.3 +763,466,467,Al/St 240/40 2-bundle 220.0,495.366530965,0.1,1.0,"SRID=4326;LINESTRING(7.09582671880563 50.8268716757217,7.09435790958289 50.8269670893312)",220000.0,1083940.0,0.08,,3.0,0.32,2.0,Gremberghoven Ost,Amprion,11.5,50.0,100.0,1.3 +764,150,467,Al/St 240/40 2-bundle 220.0,495.366530965,12.128,1.0,"SRID=4326;LINESTRING(7.0473845241055 50.9055286094924,7.09435790958289 50.8269670893312)",220000.0,1083940.0,0.08,,3.0,0.32,2.0,Gremberghoven Ost,Amprion,11.5,50.0,12128.0,1.3 +765,149,467,Al/St 240/40 2-bundle 220.0,495.366530965,8.449,1.0,"SRID=4326;LINESTRING(7.19316578267403 50.798335064694,7.09435790958289 50.8269670893312)",220000.0,1083940.0,0.08,,3.0,0.32,2.0,Gremberghoven Ost,Amprion,11.5,50.0,8449.0,1.3 +766,105_220kV,469,Al/St 240/40 2-bundle 220.0,247.683265482,10.851,0.5,"SRID=4326;LINESTRING(6.68078798893986 51.6462959356851,6.65066741434228 51.5537022721424)",220000.0,1167322.0,0.16,,3.0,0.64,1.0,Wesel Ost,Amprion,5.75,50.0,10851.0,0.65 +767,468,469,Al/St 240/40 2-bundle 220.0,247.683265482,5.443,0.5,"SRID=4326;LINESTRING(6.57729057687864 51.5563911802085,6.65066741434228 51.5537022721424)",220000.0,1167322.0,0.16,,3.0,0.64,1.0,Wesel Ost,Amprion,5.75,50.0,5443.0,0.65 +768,184_220kV,469,Al/St 240/40 2-bundle 220.0,247.683265482,9.235,0.5,"SRID=4326;LINESTRING(6.61738109142906 51.4757909635544,6.65066741434228 51.5537022721424)",220000.0,1167322.0,0.16,,3.0,0.64,1.0,Wesel Ost,Amprion,5.75,50.0,9235.0,0.65 +769,110,470,Al/St 240/40 4-bundle 380.0,1711.26619788,14.875,1.0,"SRID=4326;LINESTRING(7.99288723332679 51.6837077155955,7.82599043340792 51.7500675542728)",380000.0,1547589.0,0.025,,3.0,0.25,4.0,Uentrop Nord,Amprion,13.7,50.0,14875.0,2.6 +770,90,470,Al/St 240/40 4-bundle 380.0,1711.26619788,42.332,1.0,"SRID=4326;LINESTRING(7.54571772261949 51.9703244223875,7.82599043340792 51.7500675542728)",380000.0,1547589.0,0.025,,3.0,0.25,4.0,Uentrop Nord,Amprion,13.7,50.0,42332.0,2.6 +771,165,470,Al/St 240/40 4-bundle 380.0,1711.26619788,12.433,1.0,"SRID=4326;LINESTRING(7.70831848872651 51.6762033088437,7.82599043340792 51.7500675542728)",380000.0,1547589.0,0.025,,3.0,0.25,4.0,Uentrop Nord,Amprion,13.7,50.0,12433.0,2.6 +772,464,471,Al/St 240/40 2-bundle 220.0,495.366530965,3.03,1.0,"SRID=4326;LINESTRING(6.96330545403678 50.8337629177582,6.9289790539233 50.84381694694)",220000.0,1548410.0,0.08,,3.0,0.32,2.0,Olefin West,Amprion,11.5,50.0,3030.0,1.3 +773,134,471,Al/St 240/40 2-bundle 220.0,495.366530965,17.906,1.0,"SRID=4326;LINESTRING(6.80461158012622 50.9662452961379,6.9289790539233 50.84381694694)",220000.0,1548410.0,0.08,,3.0,0.32,2.0,Olefin West,Amprion,11.5,50.0,17906.0,1.3 +774,117_220kV,471,Al/St 240/40 2-bundle 220.0,495.366530965,6.494,1.0,"SRID=4326;LINESTRING(6.97257742700703 50.795693017514,6.9289790539233 50.84381694694)",220000.0,1548410.0,0.08,,3.0,0.32,2.0,Olefin West,Amprion,11.5,50.0,6494.0,1.3 +775,472,473,Al/St 240/40 4-bundle 380.0,1711.26619788,13.579,1.0,"SRID=4326;LINESTRING(7.9939591196577 51.823095434189,7.83402074322725 51.7550403464858)",380000.0,1610621.0,0.025,,3.0,0.25,4.0,Gersteinwerk Ost,Amprion,13.7,50.0,13579.0,2.6 +776,110,473,Al/St 240/40 4-bundle 380.0,1711.26619788,14.635,1.0,"SRID=4326;LINESTRING(7.99288723332679 51.6837077155955,7.83402074322725 51.7550403464858)",380000.0,1610621.0,0.025,,3.0,0.25,4.0,Gersteinwerk Ost,Amprion,13.7,50.0,14635.0,2.6 +777,165,473,Al/St 240/40 4-bundle 380.0,1711.26619788,13.222,1.0,"SRID=4326;LINESTRING(7.70831848872651 51.6762033088437,7.83402074322725 51.7550403464858)",380000.0,1610621.0,0.025,,3.0,0.25,4.0,Gersteinwerk Ost,Amprion,13.7,50.0,13222.0,2.6 +778,474,475,Al/St 240/40 2-bundle 220.0,495.366530965,0.797,1.0,"SRID=4326;LINESTRING(9.14517687189382 52.8532464360106,9.13794792584036 52.8470269944345)",220000.0,1631562.0,0.08,1,3.0,0.32,2.0,Leitung 2010/2023,TenneT,11.5,50.0,797.0,1.3 +779,201_220kV,475,Al/St 240/40 2-bundle 220.0,495.366530965,33.866,1.0,"SRID=4326;LINESTRING(9.25103425143703 53.1144323051271,9.13794792584036 52.8470269944345)",220000.0,1631562.0,0.08,1,3.0,0.32,2.0,Leitung 2010/2023,TenneT,11.5,50.0,33866.0,1.3 +780,2_220kV,475,Al/St 240/40 2-bundle 220.0,495.366530965,42.718,1.0,"SRID=4326;LINESTRING(9.11321007472722 52.5438533223737,9.13794792584036 52.8470269944345)",220000.0,1631562.0,0.08,1,3.0,0.32,2.0,Leitung 2010/2023,TenneT,11.5,50.0,42718.0,1.3 +781,265,477,Al/St 240/40 2-bundle 220.0,247.683265482,28.125,0.5,"SRID=4326;LINESTRING(8.10495957410199 52.2642613740358,8.33466775124759 52.07052004268)",220000.0,1633668.0,0.16,,3.0,0.64,1.0,Halle West,Amprion,5.75,50.0,28125.0,0.65 +782,476,477,Al/St 240/40 2-bundle 220.0,247.683265482,1.188,0.5,"SRID=4326;LINESTRING(8.31888925383353 52.0660374597241,8.33466775124759 52.07052004268)",220000.0,1633668.0,0.16,,3.0,0.64,1.0,Halle West,Amprion,5.75,50.0,1188.0,0.65 +783,157_220kV,477,Al/St 240/40 2-bundle 220.0,247.683265482,18.526,0.5,"SRID=4326;LINESTRING(8.364229530696 51.9271204904611,8.33466775124759 52.07052004268)",220000.0,1633668.0,0.16,,3.0,0.64,1.0,Halle West,Amprion,5.75,50.0,18526.0,0.65 +784,136,478,Al/St 240/40 4-bundle 380.0,1711.26619788,4.089,1.0,"SRID=4326;LINESTRING(7.93077008561427 51.6729710197069,7.9818783185962 51.6766515664854)",380000.0,1703254.0,0.025,,3.0,0.25,4.0,Geithe West,Amprion,13.7,50.0,4089.0,2.6 +785,110,478,Al/St 240/40 4-bundle 380.0,1711.26619788,1.127,1.0,"SRID=4326;LINESTRING(7.99288723332679 51.6837077155955,7.9818783185962 51.6766515664854)",380000.0,1703254.0,0.025,,3.0,0.25,4.0,Geithe West,Amprion,13.7,50.0,1127.0,2.6 +786,253,478,Al/St 240/40 4-bundle 380.0,1711.26619788,0.243,1.0,"SRID=4326;LINESTRING(7.98122159643085 51.6735262619046,7.9818783185962 51.6766515664854)",380000.0,1703254.0,0.025,,3.0,0.25,4.0,Geithe West,Amprion,13.7,50.0,243.0,2.6 +787,226,480,Al/St 240/40 2-bundle 220.0,247.683265482,13.34,0.5,"SRID=4326;LINESTRING(9.79733602695253 52.0008392513306,9.8524162497002 51.8987652474175)",220000.0,1740849.0,0.16,2030/2001,3.0,0.64,1.0,Leitung 2030/2001,TenneT,5.75,50.0,13340.0,0.65 +788,479,480,Al/St 240/40 2-bundle 220.0,247.683265482,5.244,0.5,"SRID=4326;LINESTRING(9.92382983732643 51.8975617615529,9.8524162497002 51.8987652474175)",220000.0,1740849.0,0.16,2030/2001,3.0,0.64,1.0,Leitung 2030/2001,TenneT,5.75,50.0,5244.0,0.65 +789,259,480,Al/St 240/40 2-bundle 220.0,247.683265482,30.649,0.5,"SRID=4326;LINESTRING(9.85139357253472 51.6491352399809,9.8524162497002 51.8987652474175)",220000.0,1740849.0,0.16,2030/2001,3.0,0.64,1.0,Leitung 2030/2001,TenneT,5.75,50.0,30649.0,0.65 +790,259,480,Al/St 240/40 2-bundle 220.0,247.683265482,30.65,0.5,"SRID=4326;LINESTRING(9.85139357253472 51.6491352399809,9.8524162497002 51.8987652474175)",220000.0,1740852.0,0.16,2001,3.0,0.64,1.0,Leitung 2001,TenneT,5.75,50.0,30650.0,0.65 +791,479,480,Al/St 240/40 2-bundle 220.0,247.683265482,5.244,0.5,"SRID=4326;LINESTRING(9.92382983732643 51.8975617615529,9.8524162497002 51.8987652474175)",220000.0,1740852.0,0.16,2001,3.0,0.64,1.0,Leitung 2001,TenneT,5.75,50.0,5244.0,0.65 +792,6,480,Al/St 240/40 2-bundle 220.0,247.683265482,61.001,0.5,"SRID=4326;LINESTRING(9.91814864613865 52.3799963125719,9.8524162497002 51.8987652474175)",220000.0,1740852.0,0.16,2001,3.0,0.64,1.0,Leitung 2001,TenneT,5.75,50.0,61001.0,0.65 +793,301,481,Al/St 240/40 4-bundle 380.0,1711.26619788,76.464,1.0,"SRID=4326;LINESTRING(10.2358631058452 52.2934595399896,9.4136812208929 52.0274828920508)",380000.0,2318069.0,0.025,3019;3026;3027,3.0,0.25,4.0,Leitung 3019/3026/3027,,13.7,50.0,76464.0,2.6 +794,3,481,Al/St 240/40 4-bundle 380.0,1711.26619788,1.614,1.0,"SRID=4326;LINESTRING(9.38974509624863 52.0263130660355,9.4136812208929 52.0274828920508)",380000.0,2318069.0,0.025,3019;3026;3027,3.0,0.25,4.0,Leitung 3019/3026/3027,,13.7,50.0,1614.0,2.6 +795,210,481,Al/St 240/40 4-bundle 380.0,1711.26619788,0.856,1.0,"SRID=4326;LINESTRING(9.40952032742215 52.034316665873,9.4136812208929 52.0274828920508)",380000.0,2318069.0,0.025,3019;3026;3027,3.0,0.25,4.0,Leitung 3019/3026/3027,,13.7,50.0,856.0,2.6 +796,299,482,Al/St 240/40 2-bundle 220.0,495.366530965,29.267,1.0,"SRID=4326;LINESTRING(10.0876467999739 52.3151305547947,10.3552149923206 52.1758109606876)",220000.0,3113412.0,0.08,2,3.0,0.32,2.0,Leitung 2019/2027/2029,TenneT,11.5,50.0,29267.0,1.3 +797,298,482,Al/St 240/40 2-bundle 220.0,495.366530965,8.465,1.0,"SRID=4326;LINESTRING(10.4195406346214 52.2369606619834,10.3552149923206 52.1758109606876)",220000.0,3113412.0,0.08,2,3.0,0.32,2.0,Leitung 2019/2027/2029,TenneT,11.5,50.0,8465.0,1.3 +798,297,482,Al/St 240/40 2-bundle 220.0,495.366530965,1.433,1.0,"SRID=4326;LINESTRING(10.3692300485918 52.1670924338554,10.3552149923206 52.1758109606876)",220000.0,3113412.0,0.08,2,3.0,0.32,2.0,Leitung 2019/2027/2029,TenneT,11.5,50.0,1433.0,1.3 +799,315,484,Al/St 240/40 4-bundle 380.0,1711.26619788,50.233,1.0,"SRID=4326;LINESTRING(10.8408695619217 48.2380310092335,10.7037422782749 47.826415980499)",380000.0,3710280.0,0.025,,3.0,0.25,4.0,K12,,13.7,,50233.0,2.6 +800,483,484,Al/St 240/40 4-bundle 380.0,1711.26619788,0.088,1.0,"SRID=4326;LINESTRING(10.7027318899547 47.8264597977002,10.7037422782749 47.826415980499)",380000.0,3710280.0,0.025,,3.0,0.25,4.0,K12,,13.7,,88.0,2.6 +801,247,484,Al/St 240/40 4-bundle 380.0,1711.26619788,30.408,1.0,"SRID=4326;LINESTRING(10.363384232266 47.7383368092793,10.7037422782749 47.826415980499)",380000.0,3710280.0,0.025,,3.0,0.25,4.0,K12,,13.7,,30408.0,2.6 +802,485,487,Al/St 240/40 2-bundle 220.0,495.366530965,42.986,1.0,"SRID=4326;LINESTRING(10.1949334440241 47.9709368782958,10.6418025411196 48.0258492685192)",220000.0,3710285.0,0.08,O11,3.0,0.32,2.0,,,11.5,,42986.0,1.3 +803,315_220kV,487,Al/St 240/40 2-bundle 220.0,495.366530965,37.574,1.0,"SRID=4326;LINESTRING(10.8408695619217 48.2380310092335,10.6418025411196 48.0258492685192)",220000.0,3710285.0,0.08,O11,3.0,0.32,2.0,,,11.5,,37574.0,1.3 +804,486,487,Al/St 240/40 2-bundle 220.0,495.366530965,0.117,1.0,"SRID=4326;LINESTRING(10.6412867739286 48.0274512409907,10.6418025411196 48.0258492685192)",220000.0,3710285.0,0.08,O11,3.0,0.32,2.0,,,11.5,,117.0,1.3 +805,421,488,Al/St 240/40 4-bundle 380.0,855.633098939,31.831,0.5,"SRID=4326;LINESTRING(9.09183529743294 49.2946188662059,8.67009921732699 49.3092022842852)",380000.0,3767047.0,,,3.0,,2.0,HD-Neurott - Hüffenhardt,,,,31831.0, +806,56,488,Al/St 240/40 4-bundle 380.0,855.633098939,7.598,0.5,"SRID=4326;LINESTRING(8.62320967451897 49.3565822492832,8.67009921732699 49.3092022842852)",380000.0,3767047.0,,,3.0,,2.0,HD-Neurott - Hüffenhardt,,,,7598.0, +807,413,488,Al/St 240/40 4-bundle 380.0,855.633098939,2.205,0.5,"SRID=4326;LINESTRING(8.67371728898445 49.2904393851271,8.67009921732699 49.3092022842852)",380000.0,3767047.0,,,3.0,,2.0,HD-Neurott - Hüffenhardt,,,,2205.0, +808,489,490,Al/St 240/40 4-bundle 380.0,855.633098939,0.261,0.5,"SRID=4326;LINESTRING(9.22808874457675 51.8124533663924,9.23187002273009 51.810119094224)",380000.0,3862868.0,,3012,3.0,,2.0,,TenneT,,,261.0, +809,3,490,Al/St 240/40 4-bundle 380.0,855.633098939,28.944,0.5,"SRID=4326;LINESTRING(9.38974509624863 52.0263130660355,9.23187002273009 51.810119094224)",380000.0,3862868.0,,3012,3.0,,2.0,,TenneT,,,28944.0, +810,303,490,Al/St 240/40 4-bundle 380.0,855.633098939,27.913,0.5,"SRID=4326;LINESTRING(9.38765125748855 51.640733525594,9.23187002273009 51.810119094224)",380000.0,3862868.0,,3012,3.0,,2.0,,TenneT,,,27913.0, +811,233,491,Al/St 240/40 4-bundle 380.0,855.633098939,40.726,0.5,"SRID=4326;LINESTRING(9.88551509756969 48.3040743584705,10.2341629653667 48.5289320393823)",380000.0,3883860.0,,,3.0,,2.0,Delmensingen - Niederstotzingen - Rotensohl,,,50.0,40726.0, +812,394,491,Al/St 240/40 4-bundle 380.0,855.633098939,26.42,0.5,"SRID=4326;LINESTRING(10.2224539935941 48.7367522347984,10.2341629653667 48.5289320393823)",380000.0,3883860.0,,,3.0,,2.0,Delmensingen - Niederstotzingen - Rotensohl,,,50.0,26420.0, +813,393,491,Al/St 240/40 4-bundle 380.0,855.633098939,0.246,0.5,"SRID=4326;LINESTRING(10.231641549556 48.5297622792522,10.2341629653667 48.5289320393823)",380000.0,3883860.0,,,3.0,,2.0,Delmensingen - Niederstotzingen - Rotensohl,,,50.0,246.0, +814,391_220kV,494,Al/St 240/40 2-bundle 220.0,1486.09959289,21.038,3.0,"SRID=4326;LINESTRING(8.95464412133747 50.0860157333265,8.74326052934848 50.1623379810729)",220000.0,3945262.0,,,9.0,,2.0,Staudinger - Dörnigheim - Frankfurt Nord,,,,21038.0, +815,492,494,Al/St 240/40 2-bundle 220.0,1486.09959289,0.77,3.0,"SRID=4326;LINESTRING(8.73513573367143 50.1591915403542,8.74326052934848 50.1623379810729)",220000.0,3945262.0,,,9.0,,2.0,Staudinger - Dörnigheim - Frankfurt Nord,,,,770.0, +816,493,494,Al/St 240/40 2-bundle 220.0,1486.09959289,0.661,3.0,"SRID=4326;LINESTRING(8.87225963914833 50.1297253197387,8.74326052934848 50.1623379810729)",220000.0,3945262.0,,,9.0,,2.0,Staudinger - Dörnigheim - Frankfurt Nord,,,,661.0, +817,23,440,Al/St 240/40 2-bundle 220.0,247.683265482,39.532,0.5,"SRID=4326;LINESTRING(9.98853660865951 53.766277668594,9.45967747872262 53.8646574512707)",220000.0,5299662.0,0.16,,3.0,0.64,1.0,202 II,TenneT,5.75,50.0,39532.0,0.65 +818,402,440,Al/St 240/40 2-bundle 220.0,247.683265482,12.335,0.5,"SRID=4326;LINESTRING(9.44963652666114 53.967286497351,9.45967747872262 53.8646574512707)",220000.0,5299662.0,0.16,,3.0,0.64,1.0,202 II,TenneT,5.75,50.0,12335.0,0.65 +819,26_220kV,440,Al/St 240/40 2-bundle 220.0,247.683265482,9.587,0.5,"SRID=4326;LINESTRING(9.34400177215961 53.8517774635399,9.45967747872262 53.8646574512707)",220000.0,5299662.0,0.16,,3.0,0.64,1.0,202 II,TenneT,5.75,50.0,9587.0,0.65 +820,432,434,Al/St 240/40 2-bundle 220.0,495.366530965,42.194,1.0,"SRID=4326;LINESTRING(10.9662583424068 47.2695304688802,11.2869438320852 47.4928624569052)",220000.0,5308445.0,0.08,250;251,3.0,0.32,2.0,Silz (AT) - Krün (DE) - Oberbrunn II,,11.5,50.0,42194.0,1.3 +821,433,434,Al/St 240/40 2-bundle 220.0,495.366530965,0.806,1.0,"SRID=4326;LINESTRING(11.2777210418419 47.4958726669837,11.2869438320852 47.4928624569052)",220000.0,5308445.0,0.08,250;251,3.0,0.32,2.0,Silz (AT) - Krün (DE) - Oberbrunn II,,11.5,50.0,806.0,1.3 +822,19,434,Al/St 240/40 2-bundle 220.0,495.366530965,71.413,1.0,"SRID=4326;LINESTRING(11.2939727001158 48.0520427719624,11.2869438320852 47.4928624569052)",220000.0,5308445.0,0.08,250;251,3.0,0.32,2.0,Silz (AT) - Krün (DE) - Oberbrunn II,,11.5,50.0,71413.0,1.3 +823,272,495,Al/St 240/40 4-bundle 380.0,1711.26619788,1.008,1.0,"SRID=4326;LINESTRING(9.31819523638713 54.7160494089748,9.33223016559803 54.7210227584184)",380000.0,5502026.0,0.025,,6.0,0.25,2.0,Audorf - Jardelund,TenneT TSO GmbH,13.7,50.0,1008.0,2.6 +824,333,495,Al/St 240/40 4-bundle 380.0,1711.26619788,17.345,1.0,"SRID=4326;LINESTRING(9.19698235871826 54.8510986052119,9.33223016559803 54.7210227584184)",380000.0,5502026.0,0.025,,6.0,0.25,2.0,Audorf - Jardelund,TenneT TSO GmbH,13.7,50.0,17345.0,2.6 +825,398,495,Al/St 240/40 4-bundle 380.0,1711.26619788,64.968,1.0,"SRID=4326;LINESTRING(9.72460611042234 54.2882702503445,9.33223016559803 54.7210227584184)",380000.0,5502026.0,0.025,,6.0,0.25,2.0,Audorf - Jardelund,TenneT TSO GmbH,13.7,50.0,64968.0,2.6 +826,100_220kV,255,Al/St 240/40 2-bundle 220.0,990.733061929,40.3942931461,2.0,,220000.0,,,,6.0,,2.0,,,,,, +827,384,351,Al/St 240/40 4-bundle 380.0,3422.53239576,105.026710536,2.0,,380000.0,,,,6.0,,4.0,,,,,, +828,351,353,Al/St 240/40 4-bundle 380.0,3422.53239576,6.76729770521,2.0,,380000.0,,,,6.0,,4.0,,,,,, +829,465,471,Al/St 240/40 2-bundle 220.0,990.733061929,0.0477583608948,2.0,,220000.0,,,,6.0,,2.0,,,,,, +830,250_220kV,251_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,0.111333083682,2.0,,220000.0,,,,6.0,,2.0,,,,,, +832,251_220kV,252_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,0.17410828166,2.0,,220000.0,,,,6.0,,2.0,,,,,, +833,194,195,Al/St 240/40 4-bundle 380.0,3422.53239576,0.202860889681,2.0,,380000.0,,,,6.0,,4.0,,,,,, +834,249,250,Al/St 240/40 4-bundle 380.0,3422.53239576,0.25131623141,2.0,,380000.0,,,,6.0,,4.0,,,,,, +835,2,296,Al/St 240/40 4-bundle 380.0,3422.53239576,0.271834647991,2.0,,380000.0,,,,6.0,,4.0,,,,,, +836,250_220kV,252_220kV,Al/St 240/40 2-bundle 220.0,990.733061929,0.280290738538,2.0,,220000.0,,,,6.0,,2.0,,,,,, +837,54,367,Al/St 240/40 4-bundle 380.0,3422.53239576,0.297238180249,2.0,,380000.0,,,,6.0,,4.0,,,,,, +838,131,267,Al/St 240/40 4-bundle 380.0,3422.53239576,0.319024526206,2.0,,380000.0,,,,6.0,,4.0,,,,,, +839,91,92,Al/St 240/40 4-bundle 380.0,3422.53239576,0.334379896942,2.0,,380000.0,,,,6.0,,4.0,,,,,, +840,249,251,Al/St 240/40 4-bundle 380.0,3422.53239576,0.357978005136,2.0,,380000.0,,,,6.0,,4.0,,,,,, +841,186,191,Al/St 240/40 4-bundle 380.0,3422.53239576,0.364946877358,2.0,,380000.0,,,,6.0,,4.0,,,,,, +842,129,173,Al/St 240/40 4-bundle 380.0,3422.53239576,0.369972585975,2.0,,380000.0,,,,6.0,,4.0,,,,,, +843,96,202,Al/St 240/40 4-bundle 380.0,3422.53239576,0.415539855369,2.0,,380000.0,,,,6.0,,4.0,,,,,, +844,53,322,Al/St 240/40 4-bundle 380.0,3422.53239576,0.437151890814,2.0,,380000.0,,,,6.0,,4.0,,,,,, +845,24,398,Al/St 240/40 4-bundle 380.0,3422.53239576,0.478801358598,2.0,,380000.0,,,,6.0,,4.0,,,,,, +846,133,156,Al/St 240/40 4-bundle 380.0,3422.53239576,0.511956287238,2.0,,380000.0,,,,6.0,,4.0,,,,,, +847,448,458,Al/St 240/40 4-bundle 380.0,3422.53239576,0.517942259441,2.0,,380000.0,,,,6.0,,4.0,,,,,, +848,451,458,Al/St 240/40 4-bundle 380.0,3422.53239576,0.518771820797,2.0,,380000.0,,,,6.0,,4.0,,,,,, +849,249,252,Al/St 240/40 4-bundle 380.0,3422.53239576,0.530804092004,2.0,,380000.0,,,,6.0,,4.0,,,,,, +850,187,195,Al/St 240/40 4-bundle 380.0,3422.53239576,0.549385438663,2.0,,380000.0,,,,6.0,,4.0,,,,,, +851,216,236,Al/St 240/40 4-bundle 380.0,3422.53239576,0.581473472567,2.0,,380000.0,,,,6.0,,4.0,,,,,, +852,246,391,Al/St 240/40 4-bundle 380.0,3422.53239576,0.596468032004,2.0,,380000.0,,,,6.0,,4.0,,,,,, +853,396,410,Al/St 240/40 4-bundle 380.0,3422.53239576,0.650913832377,2.0,,380000.0,,,,6.0,,4.0,,,,,, +854,448,451,Al/St 240/40 4-bundle 380.0,3422.53239576,0.737949921293,2.0,,380000.0,,,,6.0,,4.0,,,,,, +855,448,450,Al/St 240/40 4-bundle 380.0,3422.53239576,0.760627388463,2.0,,380000.0,,,,6.0,,4.0,,,,,, +856,198,478,Al/St 240/40 4-bundle 380.0,3422.53239576,0.967542623967,2.0,,380000.0,,,,6.0,,4.0,,,,,, +857,470,473,Al/St 240/40 4-bundle 380.0,3422.53239576,0.977332411594,2.0,,380000.0,,,,6.0,,4.0,,,,,, +858,198,253,Al/St 240/40 4-bundle 380.0,3422.53239576,1.0207499483,2.0,,380000.0,,,,6.0,,4.0,,,,,, +859,112,143,Al/St 240/40 4-bundle 380.0,3422.53239576,1.04541601079,2.0,,380000.0,,,,6.0,,4.0,,,,,, diff --git a/PyPSA/source/examples/networks/scigrid-de/scigrid-de/loads-p_set.csv b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/loads-p_set.csv new file mode 100644 index 0000000000000000000000000000000000000000..423b3d5981b8afaec33c52c2b1e90792f7c75f42 --- /dev/null +++ b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/loads-p_set.csv @@ -0,0 +1,25 @@ +,1,3,4,6,7,8,9,11,14,16,17,19,21,23,25,27,28,29,31,33,34,35,36,38,39,40,41,43,44,45,47,48,49,50,51,53,54,55,57,58,59,60,62,63,64,65,66,67,70,71,72,73,75,76,77,79,80,81,82,83,84,85,88,89,90,91,92,93,97,98,101,102,103,108,109,110,111,112,113,114,115,116,118,119,121,122,126,127,130,131,132,133,134,135,136,137,139,140,141,142,144,145,146,147,148,149,150,151,152,153,154,155,158,161,162,163,164,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,185,186,187,188,189,190,192,193,194,196,197,198,199,200,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,221,223,224,225,226,227,228,229,230,234,235,237,238,239,240,241,242,245,246,249,253,254,255,256,257,258,259,260,262,265,266,268,269,270,271,274,275,276,277,278,280,283,284,285,286,287,288,289,290,291,293,294,295,296,297,298,299,300,301,302,304,305,306,309,310,311,313,314,316,318,319,320,321,323,324,325,326,327,328,329,330,331,333,335,337,339,340,341,342,343,344,345,346,347,348,349,350,352,354,355,356,359,361,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,383,386,387,388,389,390,392,393,394,395,396,397,398,399,400,401,402,405,406,409,410,411,412,414,415,416,417,418,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,451,452,453,454,455,456,457,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,2_220kV,5_220kV,10_220kV,12_220kV,13_220kV,15_220kV,18_220kV,20_220kV,22_220kV,24_220kV,26_220kV,30_220kV,32_220kV,37_220kV,42_220kV,46_220kV,52_220kV,56_220kV,61_220kV,68_220kV,69_220kV,74_220kV,78_220kV,86_220kV,87_220kV,94_220kV,95_220kV,96_220kV,99_220kV,100_220kV,104_220kV,105_220kV,106_220kV,107_220kV,117_220kV,120_220kV,123_220kV,124_220kV,125_220kV,128_220kV,129_220kV,138_220kV,143_220kV,156_220kV,157_220kV,159_220kV,160_220kV,165_220kV,184_220kV,191_220kV,195_220kV,201_220kV,220_220kV,231_220kV,232_220kV,233_220kV,236_220kV,247_220kV,248_220kV,250_220kV,251_220kV,252_220kV,261_220kV,263_220kV,264_220kV,267_220kV,272_220kV,279_220kV,281_220kV,282_220kV,292_220kV,303_220kV,307_220kV,308_220kV,312_220kV,315_220kV,317_220kV,322_220kV,332_220kV,334_220kV,336_220kV,338_220kV,351_220kV,353_220kV,360_220kV,362_220kV,382_220kV,384_220kV,385_220kV,391_220kV,403_220kV,404_220kV,413_220kV,421_220kV,450_220kV,458_220kV +0,231.716205807,40.6136182527,66.7904423051,196.124424327,147.804142492,123.671946198,83.6374044159,73.2806236338,175.260369441,298.900165491,70.3988408785,173.928998016,747.921370062,97.9282662331,46.8397722592,57.4971172961,169.886756036,62.4029423779,122.80737946,153.977375725,30.5483888554,2.02246482091,6.69609006042,161.323762449,52.8252208715,55.1775950522,59.303409169,90.9444503482,116.349940133,61.7611501482,268.542438731,184.587165465,46.6912401424,67.988748069,88.1089793313,133.691264609,67.9229461881,66.612560754,79.5145889177,182.14026015,52.0203715372,27.4265832318,209.094217722,123.426526448,1309.80557609,4.36429413625,138.474076677,297.050220867,561.951264678,130.59023567,213.888885996,68.4738921681,85.3425697308,82.3743042419,79.7851430716,82.384309234,87.504848067,98.7810563344,3.28749160356,219.957427768,21.6534516805,75.0899663825,60.6075547519,75.1929487096,86.8445355194,30.1654675681,32.9211076609,32.2890112672,4.54120826603,83.4945543239,59.1226590573,114.434662266,133.796263648,94.3769519801,38.2116172718,49.6002110586,87.4089740744,44.2399591568,69.7382020372,102.707318308,66.209369949,110.792275187,171.545985137,33.2526564093,45.1571222422,39.5346019805,207.280900338,160.249942297,220.955519304,48.7867874671,127.033418431,42.5512186385,42.3769720063,42.4331567652,41.1062509726,32.8812170059,64.4109720527,44.5429449017,52.7755277629,58.0719203584,52.8975152175,153.880271421,198.380616991,121.595636552,171.593765884,110.625259087,144.432762013,34.0353596672,70.6538772694,126.058008097,129.486589712,134.871727119,35.5342083846,110.31336852,164.885793388,32.9756015238,33.1082161789,38.7088611262,54.4536238731,37.1639032819,127.222756473,95.597173307,81.5921514988,40.0432050683,38.2531128601,57.4042158939,38.2280415557,133.210530907,21.7219179391,115.044605911,42.3899665252,37.2618654616,28.1241828346,1.27403859876,381.359834198,81.5515950734,43.9151103263,25.685867536,38.2280415557,140.272951571,185.537222886,44.6829835279,38.1663766721,26.3468609898,36.9631102669,58.5631415941,34.6545312798,44.6165556377,38.2289285856,39.1738002691,5.16149946249,151.282513484,75.6480670507,36.3057852664,107.957769205,31.7893056778,44.1760262168,50.7499862575,178.346826314,44.700116324,209.544153391,140.996654086,298.144461751,100.530356941,32.2134963045,98.1395388397,157.722199764,89.9731194263,89.1689305852,103.691182524,186.183466619,65.0396378223,81.0261981305,79.4437123837,175.79538611,42.16577289,150.199172289,48.842768973,0.404229416013,55.2664815946,76.3575744018,481.648935221,356.403480516,129.772956931,104.700897957,124.743342544,24.7546114225,61.4353304745,157.553518325,69.1720065365,22.0854738113,108.614519828,124.561954659,60.1163546169,195.911939553,121.927895519,174.90540692,226.424483142,109.121517759,138.015044424,47.9936794729,38.5407240711,0.786372632701,107.536840147,209.037450679,39.1329520167,152.550194778,119.090366456,157.304111433,222.452375044,89.1685712597,135.272852202,60.3269465374,126.435535629,77.7100579792,49.9825683607,78.6080831551,51.73105093,101.985396439,89.8835868988,23.9526175327,50.1172191206,142.282902411,149.540926694,78.9605817077,76.4727984681,208.33260991,175.494738682,90.1391873304,77.4025832661,91.8066568438,113.185376207,185.187192183,157.299663707,70.7420354442,219.1137962,85.8516339054,115.596080355,189.967033957,6.80537864199,160.983854408,20.4942809596,2.13225557544,376.931182373,122.786957087,9.95518553499,85.6711674957,145.996836311,219.592257035,17.4348783467,183.192884138,186.96215924,74.368064815,201.845495901,124.838647485,105.548553435,95.4180009336,165.518834353,90.8060458882,227.672506185,248.950920631,247.144796075,86.430824974,225.935868859,118.529254428,784.580016652,2.3587098193,16.0248693062,0.0401691546252,2.34534263844,60.0269282166,171.107812279,251.926152437,59.4385071655,53.349216497,105.73797674,51.1093458975,25.1670822465,20.6806356874,60.8843822742,306.343405393,177.652942274,119.880333958,61.4726058483,201.651490536,221.173706269,158.268487905,157.963853923,54.4433743941,182.061963657,64.245144473,81.089981044,65.1533686081,132.687496679,712.529915491,58.8319285629,67.0145837832,128.600312885,160.597730372,57.7623864245,80.0550986564,56.7022070377,454.689241982,196.93573219,83.9089653238,44.7052909529,22.1972165238,78.2021673913,589.567028772,44.669897171,88.4250403816,255.67562883,33.1022986801,31.2995789899,2.19845286915,109.750732012,9.31902455358,132.712714166,5.1927953177,108.214725258,57.8400013851,58.2331272175,83.8833352658,61.4557426544,129.070640003,9.30566057602,76.4229534429,6.3318767796,53.2131971581,23.8591460328,269.244803096,143.41744591,95.9054484994,112.107586059,57.3064091906,29.8250464663,119.281410078,63.6823434674,14.5029854556,38.922041592,72.4678106853,61.2427053161,46.9507387749,62.1317210925,134.70741993,61.2094948167,28.3822937213,53.9602861787,39.6553307035,199.955603677,122.363287957,52.2864362506,35.04205671,24.4476501347,39.8598364808,39.8598364808,122.240122938,116.265770241,141.499767923,185.959848285,193.438566526,59.1738602002,30.3210565618,30.3577075938,49.0359951449,39.8110245,36.7389836683,60.2374114115,37.3279169051,95.0598778085,93.5950217134,32.7367175369,60.1124191346,31.0474358871,30.4683520681,34.4342505451,55.5694326301,69.8053353383,126.502770867,55.448456585,48.1540063653,54.6768024634,46.4986134854,36.4526509938,54.4506132643,500.919041291,126.93056974,365.74443757,96.2628953081,29.953423148,41.1863412875,64.2264097674,82.7801542737,113.137761384,89.0596389986,6.3082427164,788.843794969,36.9960627714,51.6404422712,28.2725346933,47.832253957,188.784000166,57.8171748944,37.8614019827,268.542438731,84.2921668418,80.1796890123,116.563719538,153.607862231,97.0432632042,84.6513739942,55.9174589132,30.8045528146,76.6695506757,68.3937941092,76.5165619341,35.9416608788,202.425709785,19.9223958352,80.434423756,72.6595989984,62.2117368146,33.4878705523,97.1982503844,115.796038377,35.1071808756,38.5452175664,84.1285416945,107.840332723,45.6983282751,46.4631259279,44.5965013226,39.9638465665,110.242654211,138.016727635,108.786924369,36.1889266923,27.0523726567,28.7042305854,25.8468989457,66.7647701962,91.7141574962,50.0938218194,93.3004463696,200.891162216,100.689684537,130.148033063,42.1294566999,36.0242876019,29.726163695,28.6553044943,142.299015443,0.984488900083,144.163758043,54.927987582,84.9754252157,180.542207533,106.458282087,56.975447408,89.0091279226,48.8100073972,133.345283582,93.7526018348,185.114772824,139.678268735,0.224353273862,227.053289983,209.969357612,147.735914789,230.642366258,169.816461335,299.527884466,87.1886400439,166.12765466,3.12183659128,202.010113522,222.695090803,212.621815818,77.5702413948,16.1489700445,0.0927935020717,58.4270560337,67.0136859474,38.4492433154,66.7526175534 +1,221.822547124,38.879526002,63.9386700817,187.750438974,141.493303194,118.391486767,80.0663122279,70.151738126,167.777223108,286.137932451,67.3929997425,166.502697661,715.987139433,93.7469926873,44.8398399804,55.042145052,162.633048541,59.7385045993,117.563834703,147.402955973,29.244054825,1.93611101339,6.410185027,154.435671745,50.5697260376,52.8216601686,56.7713131208,87.0613669545,111.382110665,59.1241151714,257.076398964,176.70579003,44.6976497855,65.085811413,84.3469629228,127.983007234,65.0228190954,63.7683835944,76.1195298613,174.36336099,49.7992416049,26.255541904,200.166457078,118.15654578,1253.8804013,4.17795051638,132.561606092,284.366975487,537.957457299,125.014383889,204.756405915,65.550241164,81.6986715707,78.8571430337,76.3785320807,78.8667208395,83.7686264384,94.5633709453,3.14712455529,210.565837277,20.7289075405,71.8838268064,58.0197751954,71.9824120652,83.1365074604,28.8774832466,31.51546492,30.910357342,4.3473108887,79.9295614506,56.5982805527,109.548610011,128.083523096,90.3473100001,36.5800840018,47.4824154686,83.6768460073,42.3510319041,66.7605684026,98.321992094,63.3824079522,106.061743062,164.221433022,31.8328574287,43.229034593,37.8465808221,198.430563469,153.407700825,211.52131299,46.7037228764,121.609433177,40.7343960633,40.5675892937,40.6213751145,39.3511246299,31.4772774886,61.6607968085,42.6410809728,50.522154691,55.5924055698,50.6389336095,147.309997762,189.91029828,116.403829957,164.26717366,105.901858101,138.265871593,32.5821413629,67.6371467656,120.675670241,123.957860644,129.11306717,34.0169932719,105.603284422,157.845613545,31.5676320416,31.6945844076,37.0560968811,52.1286006113,35.5771045809,121.790686995,91.5154311586,78.1083861088,38.3334679262,36.61980784,54.9532102899,36.5958070128,127.522799568,20.794450474,110.132510703,40.5800289815,35.670884039,26.9233558803,1.2196405777,365.076795118,78.0695613337,42.0400532449,24.589150086,36.5958070128,134.283673866,177.615282555,42.7751403263,36.5367750539,25.2219208974,35.3848848875,56.06265298,33.1748760132,42.7115487284,36.596656169,37.5011843732,4.94111731963,144.823157101,72.4180980773,34.7556258963,103.348262861,30.4319878371,42.2898287287,48.5830983593,170.731896575,42.7915415982,200.597181705,134.97647623,285.414495228,96.2379811216,30.838066665,93.9492445219,150.987886099,86.1315092501,85.3616571094,99.2638479546,178.233933419,62.2626202408,77.5665974269,76.0516795647,168.289396005,40.3654078115,143.786071661,46.7573141226,0.386969907307,52.9067514947,73.0973113731,461.0838213,341.186010606,124.232000753,100.230451255,119.417137371,23.6976561035,58.8122071213,150.826406904,66.2185479267,21.1424834884,103.976971999,119.243494261,57.5495479849,187.547026728,116.721902361,167.437416532,216.756766783,104.462322483,132.122173281,45.9444784601,36.895138826,0.752796636589,102.945306341,200.112113836,37.4620802314,146.036711814,114.005528156,150.587649015,212.954257016,85.3613131261,129.49706529,57.7511481969,121.037078364,74.3920475398,47.8484471424,75.2517294564,49.5222742093,97.6308943468,86.0457995207,22.9299051955,47.977348677,136.207805212,143.155931382,75.5891772689,73.2076156817,199.437367872,168.001585413,86.2904865015,74.0977011699,87.8867595521,108.352665106,177.280197221,150.583391195,67.7215408235,209.758226508,82.1860000744,110.660438672,181.855952609,6.51480728012,154.110276864,19.6192302913,2.04121399804,360.837234812,117.54428431,9.53012589161,82.0132390949,139.763164125,210.216258353,16.6904559403,175.371040765,178.97937796,71.1927484939,193.22723618,119.508373047,101.041914124,91.3439089636,158.451625364,86.9288719927,217.951502599,238.321385986,236.59237809,82.7404612424,216.289015016,113.468374102,751.080560424,2.25799925483,15.3406504929,0.0384540397758,2.24520281664,57.4639398513,163.801968966,241.169583397,56.9006427963,51.0713484594,101.223249563,48.9271143094,24.0925155328,19.7976281735,58.2847829272,293.263366031,170.067639517,114.76176617,58.847890937,193.041514323,211.730183958,151.510849206,151.219222276,52.1187887576,174.288407547,61.5020496204,77.6276569816,62.371495028,127.022097494,682.106804771,56.3199634683,64.1532413217,123.109425454,153.740639285,55.2960879022,76.6369612881,54.2811753234,435.275234441,188.527106173,80.3262782155,42.7964952843,21.2494550877,74.8631452038,564.39410255,42.7626127217,84.6495290165,244.758967234,31.6889195701,29.9631711614,2.10458484537,105.06467098,8.92112727288,127.046238262,4.97107692601,103.594247582,55.3703889128,55.7467293297,80.3017424921,58.8317477561,123.559670869,8.9083339012,73.1598989049,6.06152267356,50.9411367804,22.8404246772,257.748774259,137.293906756,91.8105438295,107.320893698,54.8595796697,28.5515972101,114.188414729,60.9632787038,13.883746989,37.2601751144,69.373630104,58.6278065391,44.9460685244,59.4788637332,128.955775456,58.5960140387,27.1704461264,51.6563271094,37.9621547534,191.418037271,117.138704713,50.0539460707,33.54585515,23.403801237,38.1579286841,38.1579286841,117.020798508,111.301534601,135.45810846,178.019862986,185.179260076,56.6472955445,29.0264290088,29.0615151403,46.9422900567,38.1112008436,35.1703278918,57.66543598,35.7341152635,91.0010767326,89.5987660524,31.3389477583,57.5457805371,29.7217938908,29.1674353932,32.9640006733,53.1967673359,66.8248352924,121.101442837,53.0809566425,46.0979598255,52.3422500762,44.5132477687,34.8962208523,52.1257185475,479.531145676,121.510975849,350.128133921,92.152728621,28.6744925528,39.4277953038,61.4841148363,79.2456643609,108.307083308,85.2570319795,6.03889772128,755.162286876,35.4164304087,49.4355342966,27.0653735137,45.7899453795,180.723431179,55.3485370518,36.2448219634,257.076398964,80.6931180595,76.756231971,111.586762265,147.049219712,92.8997769071,81.0369880328,53.5299338329,29.4892812722,73.3959670993,65.4735630767,73.2495105654,34.4070486406,193.782676492,19.0717631233,77.0000902264,69.557229572,59.5554629992,32.0580285605,93.0481465516,110.851859024,33.6081986889,36.8994404611,80.5364792659,103.235840601,43.7471325841,44.4792754343,42.6923506786,38.257497819,105.535589423,132.123784624,104.14201533,34.643756869,25.8973091138,27.4786371525,24.7433058877,63.9140941029,87.7982096778,47.9549503777,89.3167682834,192.313649977,96.3905058578,124.591062144,40.3306422251,34.4861474249,28.4569364672,27.4318000738,136.223230261,0.942453872278,138.008353368,52.5827102659,81.3472031388,172.833540922,101.912799809,54.5427490649,85.2086775906,46.7259513779,127.651798666,89.7496179265,177.210869975,133.714382388,0.214773992568,217.358725263,201.00423085,141.42798863,220.794557635,162.565755234,286.738849515,83.4659196473,159.034450681,2.98854256643,193.384825072,213.186609465,203.543436225,74.2582007475,15.4594524636,0.0888314691433,55.9323779071,64.1523818211,36.8075640524,63.9024603453 +2,213.127360021,37.355493583,61.432348219,180.390839074,135.946929477,113.750677516,76.9278054596,67.4018711871,161.200550152,274.921656683,64.7512721552,159.975984628,687.921272271,90.0722190794,43.0821701521,52.8845566733,156.258021274,57.396824362,112.955468455,141.624930707,28.0977217237,1.86021770258,6.15891319327,148.381972161,48.5874513078,50.7511122273,54.5459433552,83.6486621372,107.016060844,56.8065182886,246.999301672,169.779127586,42.9455536483,62.5345229377,81.0406596018,122.966221481,62.4739998461,61.2687367648,73.1357323937,167.528519052,47.847168973,25.2263550503,192.320163639,113.524945943,1204.72974085,4.01417969181,127.365344563,273.220119183,516.87014762,120.113964736,196.730191794,62.9807475801,78.4961782055,75.7660342983,73.3845820258,75.7752366652,80.4849932382,90.8565962545,3.02376091529,202.311900171,19.9163583572,69.066063996,55.7454671615,69.1607848267,79.8776525925,27.745519327,30.280095172,29.6987071107,4.17690133356,76.7964151545,54.3796934635,105.254431293,123.062797238,86.8058000186,35.1461870477,45.6211597397,80.3968104938,40.6909204718,64.1436313918,94.4678838046,60.8978909211,101.904245493,157.784142922,30.5850462617,41.5345064714,36.3630386586,190.65231505,147.394296511,203.229922341,44.8729909958,116.842484151,39.1376548848,38.9773867474,39.0290642263,37.8086060905,30.2434046438,59.2437649415,40.9695999526,48.5417447029,53.4132476157,48.6539460229,141.53561635,182.466034395,111.840934552,157.82809058,101.750627837,132.846009459,31.3049591329,64.9858488975,115.945323641,119.098855979,124.051984376,32.6835665079,101.463757901,151.658248189,30.3302174028,30.452193383,35.6035407731,50.0852198025,34.1825232592,117.016633029,87.9281321797,75.0466277782,36.8308403517,35.1843537575,52.7991080509,35.161293735,122.524053423,19.9793320835,105.815443754,38.9893388139,34.2726266712,25.8679914916,1.1718320787,350.766207299,75.0093248893,40.392131816,23.6252838628,35.161293735,129.019909274,170.652969056,41.0984043347,35.1045757605,24.2332507907,33.9978383554,53.8650618717,31.874459271,41.0373054538,35.1621096052,36.0311813507,4.74743123969,139.146256965,69.5793924514,33.3932455883,99.2971305748,29.2390891367,40.63211639,46.6786971351,164.039403839,41.1141626957,192.734004359,129.685554588,274.226577362,92.4655636502,29.6292501446,90.2665428762,145.069335721,82.7552537786,82.0155789507,95.3728200055,171.247369522,59.8219975919,74.5260765856,73.0705417501,161.692646464,38.7831305454,138.149824074,44.9244815272,0.37180113483,50.8328680719,70.2319813722,443.009869177,327.811913889,119.362250113,96.3015335755,114.736123803,22.7687354126,56.5068366731,144.914186324,63.6228574912,20.3137226066,99.9011950429,114.569287302,55.2936723133,180.195400352,112.146538886,160.874063601,208.260152405,100.367520349,126.94313701,44.1435080819,35.4488920875,0.723287879743,98.9099694845,192.267950589,35.993610046,140.312241738,109.536643406,144.684787468,204.60669662,82.0152484512,124.420930214,55.4873700304,116.292564984,71.4759653797,45.9728433956,72.3019487602,47.5810583788,93.8038762891,82.6729037714,22.0310794515,46.0966921392,130.868616899,137.544384565,72.6261690091,70.3379618795,191.619653886,161.416117714,82.9079993057,71.1931570468,84.4417003003,104.105366044,170.331018679,144.68069655,65.066934806,201.535946809,78.9644039959,106.322677557,174.727409752,6.25943437517,148.069332382,18.8501791717,1.96120077187,346.692832851,112.936684414,9.1565559871,78.7984150469,134.284609857,201.976024335,16.0362093854,168.496698947,171.963593499,68.4020751535,185.652952169,114.823783146,97.0811880389,87.7633334562,152.240505046,83.5213608245,209.40805596,228.979463496,227.318230708,79.4971309279,207.810735966,109.020545169,721.639071777,2.16948829751,14.7393147493,0.0369466859244,2.15719346488,55.2114199196,157.381121372,231.716014864,54.6702034571,49.0694107112,97.2554153389,47.0092280581,23.1481168093,19.0215840634,56.0000868969,281.767781514,163.401184889,110.263237765,56.5411217245,185.474510393,203.430605809,145.57179928,145.291603788,50.075792562,167.456503697,59.0912404595,74.5847426763,59.9266046134,122.042978291,655.369007513,54.1122860867,61.6385085147,118.283678466,147.714184169,53.1285452577,73.6328811074,52.1534160773,418.212949051,181.137061818,77.1775810814,41.1189222031,20.4165010429,71.9285965605,542.270506976,41.0863677977,81.3313654548,235.16469901,30.4467506006,28.7886495321,2.02208755534,100.946257482,8.57142940934,122.066172769,4.77621646417,99.53347298,53.1999337547,53.5615220628,77.1540071299,56.5256113376,118.716274782,8.55913752303,70.2921155452,5.82391800053,48.9443031794,21.945106466,247.645320634,131.912144519,88.2116767755,103.114038881,52.7091476426,27.4324076459,109.712361037,58.5735887393,13.3395201765,35.7996193759,66.6542640989,56.3296643802,43.1842346601,57.1473611174,123.900858315,56.2991181091,26.1053960861,49.6314588752,36.4740822256,183.914671763,112.547003067,48.0918893186,32.230896455,22.4863993167,36.6621820448,36.6621820448,112.43371865,106.938643268,130.148307392,171.041690402,177.920447413,54.4267871244,27.8886265877,27.9223373834,45.1022066188,36.6172858815,33.7916917456,55.4050176332,34.3333793049,87.4339398519,86.0865981273,30.1104972787,55.2900525452,28.5567339711,28.024105685,31.6718500004,51.1115156277,64.2053790939,116.354406444,51.0002445795,44.2909731554,50.2904944556,42.768379977,33.5283293848,50.0824507122,460.734079789,116.747886236,336.40351632,88.5404483192,27.5504856334,37.8822713627,59.0740086982,76.1393260391,104.061571,81.9150550048,5.8021799202,725.560799277,34.0281473287,47.4977185702,26.0044421982,43.9950325192,173.639281851,53.1789384614,34.8240668932,246.999301672,77.5300412281,73.7474765175,107.212690338,141.285060498,89.2582131772,77.8604318965,51.4316223837,28.3333355781,70.5189301396,62.9070751851,70.3782145323,33.0583321576,186.186619863,18.3241720831,73.9817758116,66.8306666803,57.2209577808,30.8013909458,89.4007669033,106.506594453,32.2907961994,35.4530251035,77.3795424939,99.1891151378,42.032295621,42.7357393189,41.0188599454,36.7578481848,101.398716468,126.94468519,100.059768866,33.2857616974,24.8821645709,26.4015063806,23.7733969355,61.40873559,84.3566214786,46.0751718273,85.8156543444,184.775177487,92.6121095932,119.707236713,38.7497277297,33.1343303611,27.3414574946,26.3565052612,130.883437304,0.905510771307,132.598585651,50.5215288842,78.1584869293,166.05866616,97.9179360134,52.4047364308,81.868596052,44.8943481658,122.647995906,86.2315367825,170.26440898,128.472933364,0.206355100645,208.83851481,193.125097649,135.884175169,212.13966653,156.193365787,275.499018494,80.1941522029,152.800484286,2.87139515685,185.804363758,204.829941122,195.564769112,71.3473652267,14.8534598202,0.0853493783689,53.7398934308,61.6376827055,35.3647501437,61.3975578622 +3,207.997500501,36.4563671905,59.9537050439,176.04892979,132.6747609,111.012760639,75.0761950652,65.7795448442,157.320540675,268.304442087,63.1927442879,156.125449776,671.363381783,87.9042298055,42.0452057724,51.6116541869,152.496975776,56.0153140491,110.236691827,138.216095732,27.4214248595,1.81544327526,6.0106715059,144.810498877,47.417977808,49.5295605871,53.2330521959,81.6352843816,104.440242524,55.4392163214,241.054162958,165.69263642,41.9118775534,61.0293510172,79.0900550474,120.006491478,60.9702846835,59.7940316284,71.3753951333,163.496198809,46.695513666,24.6191704186,187.691122008,110.792462302,1175.7325519,3.91756057213,124.299730063,266.643859667,504.429364574,117.222887,191.995003187,61.4648352754,76.6068179329,73.9423871031,71.6182550903,73.9513679741,78.5477632706,88.6697274491,2.95098063633,197.442362881,19.4369824549,67.4036814383,54.4037041173,67.4961223902,77.9550409836,27.0776997827,29.5512697671,28.9838753999,4.07636559256,74.9479672532,53.0708038486,102.721014437,120.10074271,84.7164316731,34.3002374606,44.523083263,78.4617030369,39.71151217,62.5997291069,92.194093274,59.4321118422,99.4514657852,153.986364507,29.8488808501,40.5347935139,35.4878001157,186.063417627,143.846596042,198.338288758,43.7929225334,114.030149172,38.19563284,38.0392222695,38.0896558996,36.8985735266,29.5154623599,57.8177997744,39.9834839874,47.3733713362,52.1276198261,47.4828720315,138.128931123,178.07417629,109.148984149,154.029254368,99.3015456225,129.648478339,30.5514658104,63.4216748958,113.154582827,116.232211358,121.066120652,31.8968908564,99.0215804898,148.007916725,29.600185583,29.7192256678,34.7465829309,48.8796958305,33.3597685349,114.200106381,85.8117499102,73.2402963063,35.9443420768,34.3374855185,51.5282622663,34.3149805382,119.574966165,19.4984404379,103.268523633,38.0508866562,33.4477032067,25.2453630201,1.14362671856,342.323455662,73.2038912777,39.4199151945,23.0566361429,34.3149805382,125.914470302,166.545445002,40.1091881182,34.2596277344,23.6499696379,33.1795288961,52.568559159,31.1072583903,40.0495598541,34.3157767708,35.1639304326,4.63316315447,135.797082317,67.9046543573,32.5894883478,96.9071027037,28.5353201802,39.6541234704,45.555166309,160.091064699,40.124567184,188.095001808,126.564093896,267.626092946,90.2399678756,28.9160902251,88.0938763343,141.577595793,80.7633798758,80.0415086183,93.0772481533,167.125538572,58.3821146796,72.7322744975,71.3117735931,157.800792496,37.8496417084,134.824633024,43.8431737158,0.36285208394,49.6093486131,68.541535818,432.34686282,319.921659597,116.489265739,93.9836080932,111.974487771,22.2207043475,55.1467478792,141.426180757,62.0914899506,19.8247823632,97.4966276689,111.811666933,53.9627837251,175.858195172,109.447232752,157.001912477,203.247443923,97.9517287789,123.887684816,43.0809978761,34.5956565549,0.705878734268,96.52926037,187.640165697,35.1272634487,136.935002472,106.900156035,141.202303407,199.68192483,80.0411860737,121.426186165,54.1518192436,113.493466259,69.7555778078,44.8663020847,70.561680218,46.4358082089,91.5460680579,80.6830119884,21.5008033638,44.9871698566,127.718680541,134.233766114,70.878096662,68.6449654322,187.007473143,157.530919643,80.9124489012,69.4795765174,82.4092345479,101.599606563,166.231243795,141.198310954,63.5008091106,196.685086294,77.0637737835,103.763548595,170.521816129,6.10877319767,144.505384168,18.3964656219,1.91399573706,338.348125119,110.218359908,8.93616266973,76.9017800981,131.052452408,197.114571395,15.6502265564,164.44107514,167.824523429,66.7556744457,181.184386688,112.060037201,94.7444966982,85.6509178023,148.576158976,81.5110471422,204.367718065,223.468052476,221.846804657,77.5836782683,202.808844693,106.396480002,704.269612212,2.11726989536,14.384547468,0.0360573993096,2.10527099266,53.8825111017,153.593043466,226.138736543,53.3543214248,47.888336711,94.9145304419,45.8777415327,22.5909542405,18.5637448912,54.6521952943,274.98578442,159.468207332,107.609261664,55.1802077081,181.010239909,198.534141884,142.067965328,141.794513988,48.8704954986,163.425916824,57.6689464736,72.7895285274,58.4842038671,119.10547025,639.594632315,52.8098328218,60.1549031735,115.436654725,144.158784178,51.8497700989,71.8605777482,50.898111747,408.146791062,176.777191359,75.3199587237,40.1292121326,19.9250869785,70.1973144025,529.21835111,40.0974412941,79.3737637687,229.504412739,29.7139138902,28.0957224117,1.9734170088,98.5165360242,8.36511977013,119.128106449,4.66125553427,97.1377564759,51.9194403137,52.2723253881,75.2969521844,55.1650706477,115.858838683,8.35312374283,68.6002225942,5.68373946506,47.7662404493,21.4168997013,241.684632595,128.737091019,86.088469742,100.632140105,51.4404671561,26.7721245293,107.071644239,57.1637543484,13.0184451885,34.9379420285,65.0499322517,54.973840027,42.1448136433,55.7718552489,120.918632113,54.9440289879,25.4770534152,48.4368566817,35.5961709245,179.487945745,109.838057979,46.9343437261,31.4551163253,21.9451639277,35.7797432835,35.7797432835,109.727500256,104.364688347,127.015708491,166.924810037,173.637999111,53.1167639907,27.2173625295,27.2502619248,44.0166210612,35.7359277464,32.978344123,54.0714490237,33.5069935576,85.3294525226,84.0145405798,29.3857540028,53.9592510828,27.869388932,27.3495807187,30.9095258145,49.881289274,62.6599905754,113.55381923,49.7726964593,43.2249135456,49.0800296345,41.738968356,32.7213207508,48.8769933907,449.644461333,113.937828177,328.306466835,86.4093279336,26.887360444,36.9704657159,57.6521294714,74.306693915,101.556865641,79.9434042291,5.66252460848,708.096945866,33.2091083489,46.3544743441,25.3785294324,42.9360960394,169.459878874,51.8989503656,33.9858705629,241.054162958,75.6639353466,71.9724149092,104.632139253,137.884406014,87.1098165822,75.9863736914,50.1936912346,27.6513676167,68.8215778848,61.3929361348,68.6842492256,32.262636101,181.705209292,17.8831192375,72.2010747468,65.2220889189,55.8436804807,30.0600182354,87.2489391178,103.943038712,31.5135743154,34.5996900914,75.5170590349,96.8016871394,41.0206011496,41.7071133407,40.031558319,35.8731067916,98.9581045683,123.889195732,97.6513847066,32.4845915357,24.2832644166,25.7660364962,23.2011842146,59.9306607581,82.3262035271,44.9661675268,83.7501182662,180.327739565,90.3829865375,116.825948699,37.8170428803,32.3368050691,26.6833634986,25.7221185293,127.733144227,0.883715620043,129.407009887,49.3055031901,76.2772547019,162.061724475,95.5611046045,51.1433829565,79.898063518,43.813765648,119.69592542,84.1559906405,166.166237356,125.380659804,0.201388245721,203.81188546,188.476681694,132.613517054,207.033580255,152.433876511,268.867907113,78.2639226207,149.12265982,2.80228223874,181.332153883,199.899795958,190.857631594,69.6300729904,14.4959451292,0.0832950652996,52.4464034544,60.1540972411,34.5135398619,59.9197520721 +4,200.194898925,35.0887810059,57.7046642028,169.444813617,127.697738118,106.848343571,72.2598648855,63.3119595173,151.418981782,258.239548722,60.8221975025,150.268722279,646.178555195,84.6066820928,40.4679657189,49.6755483523,146.776363073,53.9140139054,106.101387396,133.031201087,26.3927660905,1.74734062727,5.78519343596,139.378228662,45.6391891807,47.671560243,51.2361229253,78.5729033576,100.522380054,53.3595273078,232.011508179,159.477015449,40.3396390358,58.7399595135,76.1231531053,115.50469296,58.6831089327,57.5509806094,68.6978929074,157.36297271,44.9438268061,23.6956325037,180.650272763,106.636309277,1131.62734563,3.7706012855,119.636879465,256.641259661,485.506726823,112.82550972,184.792702626,59.1591074688,73.7330695614,71.1685894144,68.931642469,71.1772333863,75.6012042974,85.3434636551,2.84028062259,190.035715743,18.7078437413,64.8751699429,52.3628602298,64.9641431612,75.0307167769,26.0619351581,28.4427142147,27.8966044888,3.92344905981,72.1364472781,51.0799609934,98.8676452993,115.595408549,81.538467699,33.0135340809,42.8528906943,75.5183724376,38.2218158674,60.2514280753,88.7356200916,57.2026375051,95.7207470899,148.209880426,28.7291610255,39.0142134925,34.1565477475,179.083628388,138.450484674,190.89803279,42.1501204538,109.752540934,36.7628016556,36.6122585083,36.6608002237,35.5143988742,28.4082500493,55.6488830587,38.4835852174,45.5962560297,50.172158594,45.70164903,132.947306281,171.394087129,105.054482851,148.251161362,95.576450876,124.784980369,29.4053899455,61.042540246,108.909819673,111.871997253,116.524572308,30.7003441178,95.3069880637,142.455701907,28.4897950537,28.6043695927,33.4431358121,47.0460738359,32.1083449268,109.916122545,82.5926973086,70.4928361204,34.595963469,33.0493848539,49.5952847093,33.0277241019,115.089355438,18.7669962536,99.3946158058,36.6234853294,32.192980909,24.2983347682,1.10072589708,329.481890123,70.4577967521,37.9411575571,22.1917135112,33.0277241019,121.19104602,160.297832658,38.6045738143,32.9744477457,22.7627893115,31.9348666101,50.596556988,29.9403331015,38.5471823828,33.0284904656,33.8448273744,4.45935949796,130.702932022,65.3573498856,31.3669602283,93.2718305947,27.4648759024,38.1665799877,43.846257252,154.085575255,38.6193759658,181.039001836,121.816300311,257.586646465,86.8547997179,27.8313621365,84.7892144192,136.266601337,77.7337065692,77.0389148381,89.5856452186,160.856165202,56.1920288424,70.0038717125,68.6366579994,151.881217938,36.4297896749,129.766962179,42.1984865656,0.349240428823,47.7483551841,65.9703400386,416.128252956,307.920451701,112.119408764,90.4580049158,107.773993473,21.3871399905,53.0780302221,136.120866327,59.7622544733,19.0810961278,93.8392407345,107.617280527,51.938480066,169.261234019,105.341543267,151.112306271,195.623031021,94.2772696555,119.240291254,41.4649021965,33.297871131,0.679399134699,92.9081622448,180.601227974,33.8095358775,131.798165425,102.890014933,135.905387278,192.191265097,77.0386043931,116.871130699,52.1204243029,109.235990585,67.1388397218,43.1832343626,67.9147028458,44.6938636689,88.1119042184,77.656353519,20.6942446224,43.2995680268,122.927574995,129.198260434,68.2192495729,66.069889702,179.992269589,151.621468814,77.8771835744,66.8731920563,79.3178203635,97.7883047526,159.995418068,135.901544594,61.1187059023,189.306846841,74.1728836461,99.8710709171,164.12503834,5.87961504305,139.084559708,17.706359773,1.84219609467,325.655685989,106.083743162,8.60094077156,74.0169668231,126.136287217,189.720220685,15.0631402592,158.272403935,161.528929069,64.2514716096,174.387624334,107.856333691,91.1903503383,82.4378984889,143.00262771,78.4533266245,196.701280366,215.085104728,213.524674711,74.6732849756,195.200884946,102.405233278,677.850375572,2.03784483793,13.8449405378,0.0347047795906,2.02629604956,51.8612187061,147.831314021,217.655603535,51.3528429866,46.0919035298,91.3540056133,44.1567317247,21.7435007147,17.867363902,52.6020297677,264.670254139,153.48608311,103.572519912,53.1102348738,174.220010315,191.086538886,136.738575659,136.475382289,47.0372186357,157.295327211,55.5056136856,70.0589779744,56.2902883624,114.637471703,615.601545503,50.8287797619,57.8983147953,111.106284302,138.750961709,49.904731832,69.1648748871,48.9887730002,392.835997482,170.14575594,72.4944842481,38.6238466687,19.1776380203,67.56400547,509.365805139,38.5932676483,76.3962190733,220.895023259,28.5992570756,27.0417687466,1.89938829871,94.8208893101,8.05131938048,114.659258751,4.48639804948,93.4938318616,49.9717885109,50.3114358222,72.4723407522,53.0956656494,111.512630891,8.03977335965,66.0268253003,5.47052558315,45.9743874601,20.6134884345,232.618327036,123.907781884,82.8590365609,96.8571308235,49.5107830534,25.7678229365,103.055070107,55.0193728116,12.5300847961,33.6273164638,62.6097168515,52.9116086549,40.5638370038,53.679687965,116.382616501,52.8829159161,24.5213337712,46.6198468937,34.2608532467,172.754821909,105.717707483,45.173697644,30.2751418562,21.121935907,34.4375392634,34.4375392634,105.611297107,100.449660138,122.250973507,160.662966588,167.12432408,51.1241970346,26.1963587422,26.2280239838,42.365427388,34.3953673768,31.741228873,52.0430690078,32.2500471034,82.1284923229,80.862906617,28.2834074363,51.9350799436,26.8239257054,26.3236170359,29.7500180595,48.0100945463,60.3094289578,109.294079537,47.9055753694,41.6034191634,47.2388925263,40.1732161739,31.4938472078,47.0434727726,432.776967376,109.663683172,315.990720014,83.1678584125,25.8787360111,35.5835941747,55.4894275389,71.51923,97.747167169,76.9444906347,5.45010655861,681.534134618,31.9633364501,44.615580878,24.4265057145,41.3254360563,163.102937493,49.9520672014,32.7109600155,232.011508179,72.8255572902,69.2725167053,100.707078169,132.711954032,83.8420696596,73.1359000189,48.3107773852,26.6140830145,66.239876899,59.0899054777,66.1076998441,31.0523691762,174.888909341,17.2122705301,69.492598836,62.7754154129,53.7488188199,28.9323780229,83.9759732973,100.043827828,30.3314069142,33.3017533576,72.6841907436,93.1703694801,39.4817970466,40.1425561345,38.5298561385,34.5274004301,95.2458933118,119.241745491,93.9881923779,31.2659984059,23.3723273296,24.7994762419,22.3308391571,57.6824843763,79.2379040819,43.2793535566,80.6084035667,173.563112579,86.9924533267,112.44346175,36.3984137253,31.1237558469,25.6823915946,24.7572057678,122.941496106,0.850564832782,124.552570113,47.4559079018,73.4158692221,155.982309753,91.9763248662,49.224843361,76.9008507878,42.1701816814,115.205777153,80.9990504675,159.932850212,120.677260333,0.193833576844,196.166298686,181.406363781,127.63879171,199.267138179,146.715630848,258.781876508,75.32801135,143.528627691,2.69716034179,174.529848344,192.400963235,183.697997206,67.0180429614,13.9521593431,0.0801704209831,50.4789837052,57.8975390958,33.218834878,57.6719849074 +5,185.808225453,32.5671841142,53.5578144771,157.267943886,118.520952539,99.1698650572,67.0670298692,58.7621508392,140.537508478,239.681593025,56.4513114309,139.46991046,599.74200797,78.5265635915,37.5598026638,46.1056976842,136.228523841,50.0395729592,98.4765876457,123.471135062,24.4960938486,1.62177089905,5.3694501309,129.362044056,42.3594047517,44.2457228476,47.5541241608,72.9263923305,93.2985063882,49.5249336188,215.338386997,148.016464956,37.4406979644,54.5187100121,70.652689306,107.204140294,54.4659449037,53.4151750996,63.7610330835,146.054344383,41.7140134377,21.9927852815,167.668141346,98.973068255,1050.30482842,3.49963329491,111.039374087,238.19816235,450.616593334,104.717492102,171.512882376,54.9077365967,68.4343651411,66.0541770949,63.9779846215,66.0621998822,70.1682494811,79.2103975722,2.63616857925,176.379115075,17.3634356634,60.2130237473,48.5998903629,60.2956030532,69.6387590975,24.189037531,26.39872586,25.8918614014,3.64149691812,66.9524814629,47.4091845465,91.7626863943,107.288336757,75.6788413225,30.6410713582,39.7733389722,70.0913701952,35.4750686359,55.9215593994,82.3587823278,53.0918650932,88.8419347912,137.559023854,26.6645876472,36.2105219235,31.7019442486,166.214081264,128.500970847,177.179463141,39.1210721462,101.865357106,34.120904054,33.9811794396,34.0262327853,32.9622156623,26.3667384046,51.649768626,35.7180263669,42.3195569202,46.5666198567,42.4173760278,123.393269217,159.077136099,97.5049171545,137.597338204,88.7080081848,115.817515282,27.2922205005,56.6558196096,101.083196607,103.832502222,108.150727709,28.4941149448,88.4579098694,132.218359804,26.4424233129,26.5487641432,31.0398004755,43.6651859809,29.8009321173,102.017183197,76.6573104745,65.4269856876,32.1097820901,30.674345775,46.0312020545,30.654241637,106.818650312,17.4183372788,92.2517870418,33.9915994638,29.8794858753,22.5521753501,1.02162406108,305.804222043,65.3944643652,35.2145793683,20.5969429263,30.654241637,112.481853048,148.778295502,35.8303203195,30.6047938974,21.1269793139,29.6399205313,46.960519566,27.7887208562,35.7770532277,30.6549529272,31.4126251416,4.13889504389,121.310183182,60.6605526304,29.1128257971,86.5690056072,25.4911582719,35.4238021908,40.6953188942,143.012471645,35.844058739,168.028935051,113.062174479,239.075610503,80.6131239823,25.8313075822,78.6959785359,126.474028653,72.1475030237,71.5026413449,83.1477218128,149.296504406,52.1538821413,64.9731598937,63.7041987203,140.966526801,33.8118234254,120.441475254,39.1659625079,0.324142846219,44.3169990479,61.2294912655,386.22388811,285.792260535,104.062133916,83.9573908311,100.028994669,19.8501887436,49.263665853,126.338766648,55.4675394447,17.7098648879,87.0956397609,99.8835436374,48.2060075737,157.097556931,97.771348451,140.252871692,181.564907233,87.502190462,110.671286028,38.4850959594,30.9049749991,0.630575245777,86.2314716785,167.622621079,31.3798698095,122.326709457,95.4959951238,126.138772642,178.37975946,71.5023532095,108.472381257,48.3748766906,101.385927789,62.3140186622,40.0799430422,63.0341256732,41.4820134857,81.7798887586,72.0757088218,19.2070871488,40.187916581,114.093589256,119.913642315,63.3167866562,61.3218869579,167.057424476,140.725444115,72.2806692925,62.0674613244,73.6177771146,90.7609108609,148.497613433,126.135206107,56.7265117477,175.702625125,68.8425727181,92.6940024989,152.330465412,5.45708628623,129.089479133,16.4339216749,1.70980973603,302.252981706,98.4602113849,7.98284846704,68.6978605982,117.071712711,176.086292544,13.9806527356,146.898420845,149.920921216,59.6341464575,161.855547731,100.105417644,84.6371074677,76.5136360145,132.725981694,72.8154087805,182.565669982,199.628371379,198.18007907,69.3070135382,181.173097983,95.046051496,629.137785648,1.89139850774,12.8499969112,0.032210778322,1.88067965385,48.134298473,137.207662494,202.014145564,47.6624563291,42.779585537,84.7890019286,40.9834816353,20.1809401969,16.58335551,48.8218723796,245.650166492,142.456061006,96.1294530173,49.293556171,161.699979015,177.35442256,126.912085326,126.66780591,43.6569671449,145.991560115,51.5167950681,65.0243060357,52.2450804043,106.39924045,571.362364228,47.1760540364,53.7375486919,103.121815959,128.7798546,46.3184112742,64.1944561798,45.4682763006,364.605491839,157.918514154,67.2847887015,35.8482081622,17.7994689578,62.708630581,472.761078667,35.8198266523,70.9061318423,205.020769778,26.5440190286,25.0984570082,1.76289191745,88.0067437943,7.47272470316,106.419461808,4.16399051488,86.7750531222,46.3806490364,46.6958881586,67.264236511,49.2800339396,103.498961126,7.46200841792,61.2819173077,5.07739536002,42.6705145642,19.1321343699,215.901597825,115.003355207,76.9045096993,89.8966541977,45.9527729695,23.9160611951,95.6491888819,51.0654970859,11.6296310907,31.2107452909,58.1103736747,49.109203895,37.6487843183,49.8220864629,108.018973319,49.0825731122,22.7591489006,43.2695891291,31.7987540065,160.340083937,98.1204802518,41.9273649901,28.0994691366,19.6040431105,31.9627427765,31.9627427765,98.0217168804,93.2310123676,113.465610609,149.117189689,155.114212471,47.4502416382,24.3138009877,24.3431906594,39.3209064057,31.9236015024,29.4601982481,48.3030804051,29.9324511026,76.226464808,75.0518283141,26.2508673984,48.2028517954,24.8962688948,24.4319140758,27.6120824882,44.5599289462,55.975392145,101.439842275,44.4629208661,38.6136586349,43.8441480768,37.2862348047,29.2305942549,43.6627718387,401.676170356,101.78288496,293.282572437,77.1911385822,24.019003686,33.0264383576,51.501772112,66.379619478,90.7227295614,71.4150027798,5.05844371472,632.556817477,29.6663444336,41.4093563574,22.6711355045,38.3556523215,151.381816143,46.3623449661,30.3602412748,215.338386997,67.59206973,64.2943624978,93.4699314802,123.174830165,77.8168987593,67.8801101946,44.8390037129,24.7015062,61.4796583077,54.8435076916,61.3569799369,28.8208423078,162.320808725,15.9753393338,64.4986287921,58.2641645845,49.8862493438,26.8532007931,77.9411796347,92.8543445219,28.1516907998,30.9085782359,67.4608622551,86.4748358269,36.6445033631,37.257778098,35.7609721052,32.0461462199,88.4012055902,110.672635759,87.2338872422,29.0191194282,21.692713896,23.0173031252,20.726070539,53.537228567,73.5436038837,40.1691547901,74.8156147023,161.090288142,80.7408853435,104.362899373,33.7827022563,28.8870988941,23.8367692345,22.9780703484,114.106509949,0.789440405628,115.601806802,44.0455680032,68.1399598767,144.772900473,85.3665992434,45.6873818575,71.374499038,39.1396917071,106.926705574,75.1781884132,148.439541912,112.004989715,0.179904049205,182.069133871,168.369897141,118.466242217,184.94713669,136.172156037,240.184947326,69.9146890906,133.214181561,2.50333339941,161.98755105,178.57438799,170.496846141,62.2019027601,12.9495106154,0.0744091070087,46.8513954916,53.7368287367,30.831618555,53.527483625 +6,185.913530487,32.5856412543,53.5881678561,157.357073989,118.588123155,99.2260686295,67.1050394667,58.795453726,140.617156766,239.817430275,56.4833046716,139.548953698,600.081906015,78.5710677702,37.5810893226,46.1318276472,136.305730053,50.0679324084,98.5323983096,123.541111147,24.5099767754,1.62269002219,5.37249321539,129.435358757,42.3834115391,44.2707986877,47.5810750057,72.9677226657,93.3513824241,49.5530014011,215.460427968,148.100351871,37.4619171218,54.5496079694,70.6927310418,107.264897215,54.4968129569,53.4454476391,63.797169039,146.137119285,41.7376544557,22.0052494822,167.763165661,99.0291602946,1050.90007863,3.5016166786,111.10230459,238.333158879,450.871975978,104.776839739,171.610085661,54.9388550311,68.4731496628,66.0916126678,64.0142435322,66.0996400019,70.2080166652,79.2552893073,2.63766260257,176.479076251,17.3732762245,60.2471489024,48.6274338858,60.3297750094,69.678226198,24.2027464366,26.4136870854,25.9065353661,3.64356070166,66.9904261412,47.4360532483,91.8146920189,107.349141396,75.7217315818,30.6584368922,39.7958801315,70.1310938087,35.4951737916,55.9532524082,82.4054583797,53.1219543998,88.8922851078,137.636984114,26.6796995472,36.2310438904,31.7199110222,166.308281504,128.573797543,177.279877907,39.1432436396,101.923088323,34.1402417499,34.0004379479,34.0455168272,32.9808966827,26.3816815013,51.6790406383,35.7382692166,42.3435411242,46.593011043,42.4414156698,123.463201171,159.167291544,97.5601771334,137.675320179,88.7582825997,115.88315375,27.3076881053,56.6879287534,101.140484542,103.891348299,108.212021099,28.5102637118,88.5080425435,132.293293295,26.4574093033,26.5638104013,31.0573919854,43.6899328073,29.8178215104,102.07500046,76.7007552718,65.4640658058,32.1279800019,30.691730167,46.0572897978,30.6716146352,106.87918876,17.4282089548,92.3040698595,34.0108638776,29.8964197881,22.5649565797,1.02220305674,305.97753366,65.4315260522,35.2345368942,20.6086160466,30.6716146352,112.545601062,148.862614177,35.8506268107,30.6221388716,21.1389528273,29.6567186727,46.9871339912,27.8044698479,35.7973295304,30.6723263284,31.4304279464,4.14124072306,121.378934567,60.6949314182,29.1293252126,86.6180677629,25.5056051419,35.4438782918,40.7183825769,143.093522595,35.8643730164,168.124163842,113.126251384,239.211104318,80.6588106991,25.8459472286,78.7405787289,126.545706599,72.1883919308,71.5431647828,83.1948449886,149.381116771,52.1834398551,65.0099828105,63.7403024655,141.046418231,33.8309859529,120.509734308,39.1881594424,0.324326551076,44.3421152832,61.2641924939,386.442776804,285.954230564,104.121110135,84.0049728792,100.085685144,19.8614386481,49.2915855643,126.410367935,55.4989751421,17.7199017844,87.1450003815,99.9401516802,48.2333278673,157.186590468,97.8267594273,140.332358665,181.667807407,87.5517814913,110.734007921,38.5069070194,30.922490098,0.630932618264,86.2803425401,167.717619596,31.3976540505,122.39603695,95.5501165654,126.210260585,178.480854482,71.5428764841,108.533856941,48.4022926892,101.443387294,62.3493345361,40.1026579665,63.0698496608,41.50552302,81.8262367281,72.1165570402,19.2179725816,40.2106926983,114.158250706,119.981602224,63.3526708393,61.3566405506,167.152102673,140.805198914,72.3216336703,62.1026374643,73.6594992855,90.8123487389,148.581773035,126.206692028,56.7586609556,175.802202906,68.8815885875,92.746535938,152.416797246,5.46017903768,129.16263936,16.4432354428,1.71077875434,302.424280691,98.5160127677,7.98737267003,68.7367944535,117.138061984,176.186087766,13.9885761368,146.98167412,150.005887464,59.6679435395,161.947277814,100.162151432,84.6850747387,76.5569993857,132.801202875,72.8566762168,182.669137328,199.741508849,198.292395734,69.3462926251,181.275776103,95.0999179363,629.494343469,1.89247043975,12.857279524,0.0322290334725,1.88174551105,48.1615781261,137.285423624,202.128635149,47.6894685702,42.8038304578,84.8370552842,41.0067086314,20.1923775517,16.5927539681,48.8495417087,245.789386373,142.536796616,96.1839334651,49.3214928223,161.791620931,177.454936482,126.984011536,126.739593677,43.6817093134,146.074299435,51.5459917189,65.061157939,52.2746898038,106.459541202,571.686178381,47.2027906137,53.7680039335,103.180259262,128.85283935,46.3446617907,64.2308377737,45.4940450112,364.812128513,158.008012961,67.3229217118,35.8685247913,17.8095566366,62.7441700975,473.029011485,35.8401271964,70.9463172142,205.136963338,26.5590625974,25.1126813186,1.76389101958,88.0566207743,7.4769597984,106.47977402,4.16635041667,86.8242320534,46.4069348256,46.7223526064,67.3023578736,49.3079629274,103.557618172,7.4662374398,61.316648248,5.08027292527,42.6946976701,19.1429773241,216.02395799,115.068532258,76.9480945945,89.9476022664,45.978816264,23.9296153939,95.7033971446,51.0944379678,11.6362220729,31.2284336822,58.143307173,49.1370360665,37.6701214066,49.8503226537,108.080192039,49.1103901909,22.7720474305,43.2941117547,31.8167756461,160.430955146,98.1760890952,41.9511269231,28.1153942419,19.6151535143,31.9808573552,31.9808573552,98.0772697507,93.2838501519,113.529916158,149.20170043,155.202121967,47.4771336087,24.3275806018,24.3569869298,39.3431911532,31.9416938981,29.4768945336,48.3304557138,29.9494150329,76.2696654277,75.0943632198,26.2657448263,48.2301703005,24.910378617,24.4457606294,27.6277313718,44.5851828596,56.007115663,101.497332336,44.4881198011,38.6355425565,43.8689963282,37.3073664215,29.2471604146,43.6875172969,401.903816483,101.840569437,293.448787529,77.2348859215,24.0326162265,33.0451557756,51.5309602486,66.4172394923,90.7741458005,71.4554765494,5.0613105393,632.915313001,29.6831575505,41.4328247139,22.6839841536,38.3773900205,151.467610339,46.3886203815,30.3774476511,215.460427968,67.6303768889,64.3308007126,93.5229046697,123.244638322,77.8610007422,67.9185805976,44.8644157893,24.7155055423,61.5145013158,54.8745897249,61.3917534183,28.8371762445,162.41280249,15.9843932045,64.5351827729,58.2971852424,49.9145218984,26.8684195877,77.9853520526,92.9069688333,28.1676455012,30.9260953769,67.4990950534,86.5238446128,36.6652712848,37.2788935873,35.781239294,32.0643080667,88.4513061277,110.735358417,87.2833262132,29.0355657365,21.7050080341,23.0303479616,20.7378168412,53.5675702791,73.5852840176,40.1919202743,74.8580157362,161.181584522,80.7866444678,104.422046048,33.8018482796,28.9034703811,23.8502784954,22.99109295,114.171178722,0.789887813426,115.66732302,44.0705304075,68.178577547,144.854949129,85.4149799468,45.7132747441,71.4149498525,39.161873753,106.987305261,75.2207949323,148.523668603,112.068467471,0.180006008103,182.17231981,168.465319169,118.533381826,185.05195371,136.249330303,240.321069847,69.9543125717,133.289679424,2.50475213967,162.079355944,178.675593316,170.593473597,62.2371550934,12.9568496267,0.0744512776582,46.8779480718,53.7672835703,30.8490920798,53.5578198142 +7,189.017521731,33.1296874292,54.4828698359,159.98428987,120.568057025,100.882735831,68.2254176037,59.7770959608,142.964884886,243.821394923,57.4263435278,141.87884722,610.100805538,79.8828814168,38.2085389326,46.9020394156,138.581475081,50.903860936,100.17748407,125.603739543,24.9191925711,1.64978227098,5.4621917535,131.596396843,43.0910401768,45.0099389274,48.3754832439,74.1859834972,94.9099665298,50.380332699,219.057730865,150.573018569,38.0873770462,55.4603620445,71.8730088709,109.055779787,55.4066855724,54.3377667784,64.8623193461,148.577008074,42.4345016063,22.3726466347,170.56412047,100.682539936,1068.44578729,3.56007927399,112.957256081,242.312342301,458.399683412,106.526181985,174.475268211,55.856107931,69.6163696127,67.1950707458,65.0830180905,67.2032321034,71.380201758,80.5785266392,2.68170071858,179.425550916,17.6633384762,61.2530284755,49.4393120132,61.3370340986,70.8415659692,24.6068327493,26.8546870127,26.3390679458,3.70439317838,68.1088907071,48.2280402196,93.3476197157,109.1414305,76.9859730342,31.1703066815,40.4603076379,71.3019946062,36.0877971922,56.8874415718,83.7812905776,54.0088725326,90.3764206299,139.934955609,27.1251407884,36.8359532948,32.2495030633,169.084945732,130.720451101,180.239722666,39.7967748011,103.624784659,34.7102433586,34.5681054082,34.6139369194,33.5315419975,26.822147067,52.5418680464,36.3349513109,43.05050409,47.3709226772,43.1500127379,125.524528792,161.824730612,99.1890307988,139.973931728,90.2401808311,117.817925758,27.7636141724,57.63438399,102.829114614,105.625906481,110.018716744,28.986267845,89.9857627953,134.502047133,26.8991392118,27.0073167705,31.5759226837,44.4193749761,30.3156564805,103.779233104,77.9813423929,66.5570464324,32.6643851164,31.204155817,46.8262570908,31.1837044385,108.663631588,17.7191883572,93.8451681533,34.578705408,30.3955670261,22.9416985385,1.03926964318,311.0860999,66.5239633988,35.8228087307,20.9526951655,31.1837044385,114.424649668,151.34800752,36.4491848147,31.1334026332,21.491886389,30.151863888,47.7716258574,28.2686901267,36.3949976889,31.1844280141,31.9551868108,4.21038240904,123.405463509,61.7082871174,29.6156651265,88.0642332099,25.931443836,36.0356452686,41.3982120797,145.482595838,36.4631605254,170.931145836,115.014994462,243.204945817,82.0054810652,26.2774682356,80.0552225113,128.658499381,73.3936411446,72.7376413573,84.5838566959,151.875166975,53.0546886553,66.0953821189,64.8045033347,143.401313252,34.3958242619,122.52175118,39.8424405131,0.329741470432,45.0824462167,62.2870525115,392.894781628,290.728490004,105.859504394,85.4075103899,101.756704405,20.1930429778,50.1145522929,128.520901118,56.4255797439,18.0157512572,88.5999634358,101.608741131,49.0386260453,159.810959976,99.4600639205,142.675331828,184.70091254,89.0135361169,112.582810373,39.1498139768,31.4387684896,0.641466597784,87.7208693649,170.517813973,31.9218657257,124.439547317,97.1454104873,128.317452798,181.460750609,72.7373482452,110.345925921,49.2104118847,103.137074596,63.3903119623,40.7722074009,64.122856726,42.1984945304,83.192398307,73.3206069069,19.5388336748,40.8820458684,116.064223928,121.984801463,64.4103998548,62.3810440706,169.942855244,143.156066565,73.5291074732,63.1394960642,74.8893099411,92.3285414258,151.062477486,128.313824661,57.706297129,178.737376571,70.0316277874,94.2950215953,154.961530827,5.55134156834,131.319124155,16.717769839,1.73934172262,307.473522222,100.160824957,8.12072893929,69.8844161869,119.093785797,179.127670728,14.2221278193,149.4356635,152.510368768,60.6641527654,164.651131219,101.834447365,86.0989671531,77.8351863748,135.018436714,74.0730830295,185.718960552,203.076370453,201.60306311,70.5040904709,184.302335925,96.6876953929,640.004309716,1.92406691183,13.0719432064,0.0327671257643,1.91316292093,48.9656783786,139.577525504,205.503351943,48.485686535,43.5184783622,86.2534851486,41.6913519933,20.5295071998,16.8697847086,49.6651281232,249.893058124,144.916574825,97.7898095221,50.1449588792,164.492875506,180.417703999,129.104122208,128.855623575,44.4110141836,148.51313939,52.4065977573,66.1474116616,53.1474621029,108.236977641,581.230986053,47.9908830585,54.6657084361,104.902945182,131.004151759,47.1184270139,65.3032285648,46.253608433,370.902990484,160.646096846,68.446937586,36.4673816179,18.1069029766,63.7917396535,480.926650274,36.4385098999,72.1308288893,208.561906839,27.0024896979,25.531959801,1.79334074422,89.5268041394,7.60179427217,108.257548265,4.23591133124,88.273839596,47.1817397522,47.5024237127,68.4260304163,50.1312030906,105.286604434,7.59089289429,62.340384061,5.16509258651,43.4075235058,19.4625863049,219.630669521,116.989703437,78.2328112648,91.4493572443,46.7464733716,24.3291415417,97.3012502217,51.947504916,11.8304991184,31.7498200733,59.1140612429,49.9574224542,38.2990574862,50.6826179935,109.884686681,49.9303317021,23.1522469547,44.0169453401,32.347984928,163.109492199,99.815225957,42.6515381858,28.5848056792,19.9426458953,32.5148061267,32.5148061267,99.7147567359,94.8413067464,115.425398777,151.692755118,157.793359013,48.269806453,24.7337511316,24.7636484243,40.0000606157,32.4749888009,29.9690374255,49.1373755274,30.4494470735,77.5430551216,76.3481302011,26.7042747265,49.0354157602,25.3262794764,24.8539042796,28.089000845,45.3295720218,56.9422041214,103.191920298,45.2308884095,39.2805971983,44.6014281213,37.930245985,29.7354676916,44.4169191366,408.613957074,103.540888047,298.348175242,78.5243908274,24.4338620645,33.5968739529,52.3913153233,67.5261341988,92.2897006555,72.6484890911,5.1458136067,643.482395368,30.1787441863,42.1245824617,23.0627133833,39.0181345767,153.996496402,47.1631195323,30.8846260758,219.057730865,68.759526,65.4048604737,95.0843524914,125.302316842,79.1609591902,69.0525415252,45.6134669929,25.1281528229,62.5415404612,55.7907696585,62.4167431787,29.3186384723,165.124428242,16.2512668227,65.6126548737,59.2705084431,50.7478891015,27.3170116774,79.287386656,94.4581330601,28.6379293195,31.4424339619,68.6260522979,87.9684369207,37.2774305006,37.9012977715,36.3786388118,32.5996501218,89.9280791147,112.584183416,88.7405987399,29.5203402528,22.0673923894,23.4148600422,21.0840530819,54.4619283641,74.8138555836,40.8629600221,76.1078366867,163.872653998,82.135449132,106.165464699,34.3662001092,29.3860394483,24.2484800433,23.3749496334,116.077367788,0.803075690877,117.598491542,44.8063269907,69.3168793542,147.27343095,86.8410587758,46.4764984006,72.607285765,39.8157159607,108.77355319,76.4766728031,151.003402948,113.939549908,0.183011368023,185.213848225,171.277997053,120.512401741,188.141560166,138.524133687,244.333443205,71.1222618497,135.515068812,2.54657119764,164.78541451,181.658740781,173.441682907,63.2762595844,13.1731757203,0.0756943077558,47.6606169823,54.6649760458,31.3641450265,54.4520151066 +8,200.711395045,35.1793088832,57.8535402996,169.881975549,128.027193996,107.124008711,72.4462929115,63.4753022478,151.809637673,258.90579809,60.9791167306,150.656410538,647.845674178,84.8249644928,40.5723717121,49.8037095997,147.155041163,54.0531102516,106.375125414,133.374417121,26.4608585411,1.75184870744,5.80011904084,139.737819814,45.756936757,47.7945512684,51.3683104027,78.775618811,100.781724421,53.4971930973,232.610090083,159.888460796,40.4437139505,58.8915066375,76.3195482853,115.802691191,58.834509384,57.6994602077,68.8751311653,157.768963898,45.0597803707,23.7567664403,181.116344403,106.911427376,1134.5469062,3.78032931026,119.945538598,257.303385497,486.759317882,113.116595749,185.269461596,59.3117359805,73.923298406,71.3522019863,69.1094837929,71.3608682594,75.796252867,85.5636469268,2.84760845126,190.526001511,18.7561093502,65.0425457038,52.4979546517,65.13174847,75.2242935078,26.1291740761,28.5160954627,27.968576792,3.93357142669,72.3225568908,51.2117455783,99.1227205057,115.893640823,81.7488342089,33.0987078907,42.9634497139,75.7132073038,38.3204268694,60.4068747374,88.964555013,57.3502183899,95.9677034046,148.592256943,28.8032812967,39.1148688538,34.2446704943,179.545658139,138.807682277,191.390543307,42.2588663498,110.035698805,36.8576484499,36.7067169063,36.7553838578,35.6060248313,28.4815423809,55.7924553092,38.5828715755,45.7138928398,50.3016010791,45.8195577505,133.290305869,171.836278121,105.325519891,148.633644383,95.8230349113,125.10692143,29.4812548646,61.200027951,109.190803352,112.160623251,116.825201794,30.7795499751,95.552876894,142.823232836,28.5632977687,28.6781679062,33.52941798,47.1674511274,32.191183379,110.199702452,82.8057836955,70.6747052715,34.6852198936,33.1346511575,49.7232388792,33.1129345216,115.386282112,18.815414474,99.6510505784,36.7179726923,32.2760377192,24.3610236564,1.10356573295,330.331942341,70.6395755028,38.0390444701,22.2489673873,33.1129345216,121.503714851,160.711395686,38.704172319,33.0595207141,22.8215165439,32.017257494,50.7270944068,30.0175781559,38.6466328195,33.1137028624,33.932145893,4.47086449579,131.0401412,65.5259696546,31.4478859329,93.512468787,27.5357343604,38.265048483,43.959379124,154.483110867,38.7190126596,181.506076384,122.130582277,258.251211367,87.0788821859,27.9031661166,85.0079677465,136.618164594,77.934256923,77.2376726526,89.8167731764,161.271168709,56.3370023908,70.1844793519,68.8137382733,152.273066382,36.5237773095,130.101756585,42.3073572446,0.350141457407,47.8715443378,66.1405413017,417.201849881,308.714876131,112.408673075,90.6913835323,108.052046759,21.4423180938,53.2149697585,136.472053593,59.9164390839,19.1303247153,94.0813428259,107.894929498,52.0724796013,169.697922322,105.613320913,151.502170949,196.127731875,94.5205018468,119.547927203,41.5718802524,33.3837785209,0.681151961663,93.147862185,181.067173081,33.8967633453,132.138200268,103.155467718,136.256018616,192.68711211,77.2373614067,117.172654293,52.2548932482,109.517815774,67.3120556748,43.2946456579,68.0899204996,44.8091723371,88.3392299739,77.8567043046,20.747635079,43.411279459,123.244724013,129.53158761,68.3952529471,66.2403477999,180.456643603,152.012647114,78.0781040941,67.0457226473,79.5224576777,98.0405952966,160.408200876,136.252166018,61.2763901121,189.795252152,74.3642471964,100.128734928,164.54847543,5.89478425252,139.443393204,17.7520415869,1.84694889876,326.495866728,106.357435659,8.62313091007,74.2079281132,126.461714605,190.209692486,15.1020026554,158.680741425,161.945668284,64.4172382498,174.837538551,108.134599412,91.4256183814,82.6505855,143.37156969,78.655733574,197.208763067,215.640017079,214.075561205,74.8659395445,195.704496677,102.669435342,679.599206789,2.04310240921,13.8806600197,0.0347943167571,2.03152382536,51.9950189097,148.212713849,218.217147686,51.4853315979,46.2108190939,91.5896958815,44.2706546104,21.7995982167,17.9134611012,52.7377412389,265.353094511,153.882072052,103.839733537,53.2472574971,174.669492094,191.579535742,137.091356595,136.827484194,47.158573081,157.701143875,55.6488163059,70.2397277861,56.4355154171,114.933232532,617.189776828,50.9599163076,58.0476905042,111.392934787,139.108934533,50.0334843651,69.3433179428,49.1151623902,393.84950117,170.584726279,72.6815175842,38.7234948967,19.2271156827,67.7383183364,510.679951819,38.6928369834,76.5933187543,221.464925005,28.6730421989,27.1115356023,1.90428865677,95.0655240219,8.07209151424,114.95507579,4.49797280587,93.7350428097,50.1007140479,50.4412376379,72.6593169588,53.2326506846,111.800329736,8.06051570507,66.1971722934,5.48463935554,46.0929998366,20.6666705428,233.218474512,124.227459802,83.0728100941,97.1070189701,49.6385192119,25.8343030548,103.320948729,55.1613209467,12.5624119943,33.7140738116,62.7712478195,53.0481188293,40.6684903418,53.8181797583,116.682879745,53.0193520642,24.5845979893,46.7401245339,34.3492450981,173.200523551,105.99045562,45.2902442677,30.3532507061,21.1764297927,34.5263869588,34.5263869588,105.883770709,100.70881689,122.566376916,161.07747165,167.555499227,51.256095747,26.2639444685,26.2956914053,42.4747287686,34.4841062701,31.8231201781,52.177338379,32.3332511425,82.3403810765,81.0715302019,28.3563776782,52.0690707066,26.8931305333,26.3915310844,29.8267721076,48.1339589788,60.4650252603,109.576054594,48.0291701459,41.7107545885,47.3607672835,40.2768617232,31.5751002766,47.1648433534,433.893517426,109.946611794,316.805965465,83.3824286957,25.9455022814,35.6753986534,55.6325883995,71.7037472129,97.9993515795,77.1430048379,5.4641676506,683.29246981,32.0458007853,44.7306875792,24.4895252794,41.4320542943,163.523737597,50.0809418581,32.7953531944,232.610090083,73.0134447837,69.4512374733,100.966899051,133.054346419,84.0583793852,73.3245881862,48.4354175693,26.6827464554,66.4107735581,59.2423554505,66.2782554908,31.1324832464,175.340116857,17.2566776104,69.6718874085,62.9373738776,53.8874889684,29.0070225871,84.1926284898,100.301937546,30.409660926,33.3876707635,72.8717135161,93.4107459062,39.5836587578,40.2461225819,38.6292618741,34.6164799643,95.4916245197,119.549385192,94.2306787595,31.3466636323,23.4326271496,24.8634580582,22.3884519726,57.8313032499,79.4423352148,43.3910128361,80.8163705423,174.010899584,87.2168909364,112.733562107,36.492320411,31.2040540923,25.7486513029,24.8210785277,123.258681039,0.852759261502,124.873911563,47.5783425514,73.6052797005,156.384738876,92.2136206972,49.3518418046,77.0992524017,42.2789793348,115.503004191,81.208025299,160.345471598,120.988603614,0.194333660967,196.672401153,181.874386111,127.968095509,199.781240707,147.094152251,259.449525063,75.5223550909,143.898926733,2.70411892494,174.980129495,192.897351262,184.171931874,67.1909472628,13.9881554459,0.0803772579783,50.6092177889,58.0469128034,33.304538357,57.8207766927 +9,214.406064006,37.5796159975,61.8009249635,181.473133177,136.76257267,114.433149464,77.3893505726,67.806263384,162.167707939,276.571109002,65.1397615062,160.935795368,692.048605677,90.6126269629,43.3406510092,53.2018490819,157.195525284,57.7411891028,113.633169374,142.474640305,28.266300121,1.87137848358,6.19586493353,149.2722221,48.8789622974,51.0556045716,54.8732036148,84.1505304927,107.658126994,57.1473413596,248.481227744,170.797754416,43.2032148449,62.9097124194,81.5268806784,123.703984092,62.8488262063,61.6363318868,73.5745261389,168.533642867,48.1342384777,25.3777060582,193.474030321,114.206063566,1211.95777915,4.0382636366,128.12950067,274.859362757,519.971222572,120.834614611,197.9105174,63.3586142836,78.9671331121,76.2206091123,73.8248687986,76.2298666908,80.9678804735,91.4017101796,3.04190262706,203.525714451,20.0358508841,69.4804408799,56.0799242253,69.5757300088,80.3568959554,27.9119846093,30.4617671938,29.8768909683,4.20196156226,77.2571719628,54.7059562707,105.885928163,123.801139276,87.32661031,35.3570542468,45.8948738175,80.8791686576,40.935054506,64.5284750699,95.0346644354,61.2632610727,102.515642194,158.730803231,30.7685479039,41.7837017834,36.5812066238,191.796175112,148.278620675,204.449244495,45.1422162724,117.543506067,39.372469764,39.2112400631,39.2632275922,38.0354470522,30.4248565327,59.5992108043,41.215405985,48.8329814659,53.7337120207,48.9458559621,142.384790087,183.560779084,112.511948582,158.775014562,102.361102876,133.643048008,31.4927800491,65.3757456428,116.640962847,119.813415489,124.796261257,32.879658679,102.072511801,152.568154859,30.5121901438,30.6348979454,35.8171519655,50.3857169803,34.3876087477,117.718699789,88.4556761472,75.4968863562,37.051814995,35.3954499461,53.1158877919,35.3722515699,123.25916315,20.0992024351,106.45030654,39.2232638386,34.4782527547,26.023192137,1.17886274031,352.870705508,75.4593596605,40.6344732026,23.7670288956,35.3722515699,129.793992302,171.676838688,41.344983157,35.3151933036,24.3786434535,34.2018157866,54.1882370347,32.0656970273,41.2835177001,35.3730723351,36.2473582658,4.77591448674,139.98109522,69.9968491611,33.5935956336,99.8928853227,29.4145154162,40.8758976163,46.9587561386,165.023593947,41.3608360637,193.890353969,130.4636313,275.871859401,93.0203309254,29.80701728,90.8081166476,145.939710777,83.2517619362,82.5076492682,95.9450299981,172.274805389,60.1809126873,74.9732120034,73.5089443699,162.662756691,39.0158183786,138.978684013,45.1940157324,0.374031836665,51.1378509282,70.6533534311,445.667803312,329.778692818,120.078389913,96.8793155872,115.424508151,22.9053413966,56.845861739,145.783630527,64.0045766729,20.4355992069,100.500574007,115.256670678,55.6254187356,181.276521878,112.819386455,161.839262561,209.50965452,100.969697133,127.704759991,44.408356667,35.6615754314,0.727627402809,99.5034013752,193.421504007,36.2095615438,141.154075573,110.193832339,145.552855343,205.834279031,82.5073167857,125.167420667,55.8202785843,116.990287539,71.9048009915,46.248667476,72.7357400391,47.8665312951,94.3666730623,83.1689178522,22.163259708,46.3732592772,131.653791651,138.369612038,73.0619055179,70.7599697916,192.769317707,162.384568847,83.4054238933,71.6202958884,84.9483266608,104.729968848,171.3529567,145.54873988,65.4573180439,202.745105588,79.4381681251,106.960583603,175.775724878,6.29698921413,148.957706571,18.9632749246,1.97296742278,348.772891963,113.614272634,9.21149273777,79.2711832892,135.090279602,203.187823456,16.1324221141,169.507631567,172.995326508,68.8124682918,186.766817465,115.512693426,97.6636477573,88.2898886779,153.153905093,84.022465408,210.664445172,230.353275627,228.682075911,79.9740912683,209.057541709,109.674637659,725.96870246,2.18250461477,14.8277464763,0.0371683556084,2.17013601661,55.5426728507,158.325363665,233.106245531,54.9982092423,49.3638133213,97.8389203719,47.2912701539,23.2869989745,19.1357081972,56.3360716072,283.458308649,164.381545864,110.92478606,56.880352491,186.587305089,204.651132007,146.445188975,146.163312389,50.376233179,168.46119544,59.4457712184,75.0322300742,60.2861473214,122.775201703,659.301036522,54.4369445252,62.0083221628,118.993347141,148.600427568,53.4473015289,74.0746576046,52.4663218488,420.722108667,182.223833046,77.6406247776,41.3656241268,20.5389942848,72.3601478316,545.523976913,41.3328744042,81.8193306844,236.575620807,30.6294225079,28.9613733011,2.03421950975,101.551906526,8.62285556575,122.798535341,4.80487241444,100.130645716,53.5191183368,53.8828760726,77.6169093891,56.8647490462,119.428538912,8.61048993159,70.7138483922,5.85885986421,49.2379551795,22.0767709088,249.131122638,132.703580142,88.7409219305,103.732694002,53.0253876466,27.5969943465,110.370604227,58.9250137341,13.4195535312,36.0144069845,67.0541708644,56.6676264624,43.4433278754,57.4902291492,124.644228483,56.6368969222,26.262021092,49.9292336156,36.6929164208,185.018107867,113.222253309,48.3804270762,32.4242727337,22.6213113638,36.8821447858,36.8821447858,113.108289217,107.580244934,130.92916049,172.067892253,178.987919863,54.7533324803,28.0559504731,28.0898635241,45.3728071244,36.8369792583,33.9944323553,55.7374320959,34.539369887,87.9585188049,86.6030934104,30.291151761,55.6217772499,28.7280663122,28.1922424068,31.8618721582,51.4181702907,64.5905932407,117.052500032,51.3062316476,44.5567064893,50.5922232234,43.0249781817,33.7294898947,50.3829312762,463.498354184,117.448340591,338.421842438,89.0716660107,27.7157807686,38.1095542953,59.4284360715,76.5961404982,104.685911045,82.406522207,5.83699136153,729.913959218,34.2323066051,47.7826914711,26.1604615093,44.2589902928,174.681068517,53.4979970776,35.0330014624,248.481227744,77.9951995857,74.1899405548,107.855936209,142.132730971,89.7937372551,78.3275725048,51.7401975968,28.5033275919,70.9420238101,63.284499875,70.8004639489,33.2566728169,187.303687015,18.4341119419,74.4256455785,67.2316318114,57.5642673718,30.986190595,89.9371462634,107.145603949,32.4845318596,35.6657332443,77.8437979015,99.7842218236,42.2844775273,42.9921416888,41.2649615238,36.9783848957,102.007080138,127.70631746,100.660099229,33.4854668697,25.0314505331,26.5599079647,23.9160306049,61.7771706655,84.8627373902,46.3516098494,86.3305240413,185.883776383,93.1677561027,120.425446335,38.9822151555,33.3331269881,27.50549852,26.5146368513,131.668700974,0.910943580273,133.394139727,50.8246437932,78.6274157836,167.054971272,98.5054159834,52.7191500535,82.3597845136,45.1637015794,123.383849253,86.7489016566,171.285947362,129.243734691,0.207593172986,210.091486936,194.283793708,136.699441854,213.412444634,157.130481882,277.15193482,80.6752944742,153.71724405,2.88862271708,186.919137481,206.058862937,196.738102511,71.7754292739,14.9425763854,0.0858614505416,54.0623176185,62.007491399,35.5769286597,61.7659258745 +10,229.464683886,40.2189870208,66.1414581549,194.218737848,146.367970751,122.470260296,82.8247230208,72.5685761969,173.557413179,295.995835721,69.7147949231,172.23897832,740.654026143,96.9767245088,46.3846432206,56.9384338001,168.236013682,61.7965903441,121.614094313,152.481220393,30.2515586579,2.0028130928,6.63102601581,159.756224321,52.3119328936,54.6414497087,58.7271744373,90.0607684203,115.219400127,61.161034231,265.933086549,182.793583321,46.2375543491,67.3281203151,87.2528488866,132.392223777,67.2629578127,65.9653050294,78.7419677741,180.370453904,51.5149040564,27.1600867513,207.062507367,122.227225221,1297.07855963,4.32188750406,137.12856259,294.163866496,556.490940546,129.321326674,211.810587184,67.8085504039,84.5133197187,81.5738960399,79.0098930286,81.5838038163,86.6545877977,97.8212282859,3.25554796207,217.820162623,21.4430511135,74.3603380654,60.0186480003,74.4623197419,86.0006913235,29.8723581103,32.6012224147,31.9752679259,4.49708260844,82.6832609635,58.5481806242,113.322732482,132.49620257,93.4599173886,37.8403256153,49.118259604,86.559645386,43.810091779,69.0605753256,101.709339864,65.5660319173,109.715737466,169.879120514,32.9295495962,44.7183430458,39.1504552495,205.266809484,158.692838181,218.808555982,48.3127398233,125.799070038,42.1377602825,41.9652067565,42.0208455837,40.7068329656,32.5617193661,63.7851085531,44.1101334956,52.2627226391,57.5076516609,52.3835247755,152.385059625,196.453007715,120.414125573,169.92643699,109.550344215,143.029349039,33.7046475442,69.9673531963,124.833137495,128.228404536,133.561216062,35.1889323655,109.241484202,163.283643996,32.655186776,32.7865128513,38.3327378895,53.9245131561,36.8027919708,125.986568336,94.6682821642,80.7993432575,39.6541163826,37.8814180025,56.846435095,37.856590309,131.916161231,21.5108521044,113.926749475,41.9780750112,36.8998022792,27.8509079727,1.26165912007,377.654266764,80.7591809076,43.4883994142,25.4362851057,37.856590309,138.909958317,183.734409299,44.2488114055,37.7955246059,26.0908558699,36.6039500057,57.9940998365,34.3178028385,44.1830289766,37.8574687199,38.793159348,5.11134660788,149.81254338,74.9130158246,35.9530120494,106.90877359,31.4804178367,43.7467800575,50.2568627571,176.61387981,44.265777727,207.508071138,139.626628816,295.247474945,99.5535314252,31.900486721,97.1859442383,156.189657019,89.0988756507,88.302500889,102.683644145,184.37437366,64.4076657523,80.2388891006,78.6717799277,174.087231241,41.7560598032,148.73972871,48.3681773725,0.400301631214,54.7294725653,75.6156290902,476.968886488,352.940407029,128.511989205,103.683548454,123.531246182,24.5140777488,60.8383804564,156.022614611,68.4998813891,21.8708754053,107.55914275,123.351620793,59.5322207204,194.008317729,120.743156073,173.205899731,224.224379419,108.061214323,136.673990626,47.5273382389,38.1662345754,0.778731668558,106.491934582,207.006291914,38.7527080068,151.067906967,117.93319849,155.775631136,220.290867191,88.302145055,133.958443523,59.7407663778,125.206996698,76.9549709608,49.4969016465,77.8442702759,51.2283946971,100.994432709,89.0102130859,23.7198766105,49.6302440424,140.900379024,148.087879104,78.1933436981,75.7297335562,206.308299887,173.78950512,89.2633299192,76.6504838939,90.9145970952,112.085585389,183.387779746,155.771226627,70.0546547627,216.984728388,85.0174374583,114.472865395,188.121177127,6.73925267064,159.419619072,20.295143732,2.1115370411,373.268646917,121.593870378,9.85845376617,84.8387245905,144.578225661,217.458540153,17.2654684833,181.412849838,185.145499942,73.6454510146,199.88421936,123.625625071,104.522967499,94.490850759,163.910533878,89.9237087979,225.460275769,246.531933781,244.743358835,85.5910006884,223.740512865,117.377538632,776.956470861,2.33579089206,15.8691601084,0.0397788421227,2.32255359637,59.4436632516,169.44520526,249.478256086,58.8609597242,52.8308369994,104.710550231,50.6127305994,24.9225407085,20.4796877027,60.2927856666,303.366751732,175.926738048,118.715490097,60.8752936359,199.692099092,219.024622884,156.73063703,156.428963092,53.9143632685,180.292918196,63.6208922739,80.3020522533,64.5202914477,131.398209181,705.606460492,58.2602750771,66.3634217127,127.350739415,159.037246896,57.2011253821,79.27722553,56.1512474636,450.271153089,195.022162458,83.0936452469,44.2709020755,21.981532345,77.4422986827,583.838369941,44.2358522051,87.5658388596,253.191299861,32.7806528512,30.9954496868,2.17709111412,108.684314677,9.22847418408,131.423181636,5.14233836997,107.163232873,57.2779861798,57.6672921179,83.0682642291,60.8585942969,127.816496495,9.21524006056,75.6803728612,6.27035169414,52.6961393226,23.6273133472,266.628626234,142.02389848,94.9735619324,111.018267828,56.7495787522,29.5352447856,118.122385795,63.0635598492,14.3620639799,38.54384694,71.7636611258,60.6476269832,46.4945315055,61.5280044406,133.3985054,60.614739181,28.1065108677,53.4359690869,39.2700108852,198.012690694,121.174317923,51.778383493,34.7015627919,24.2100991184,39.4725295367,39.4725295367,121.052349664,115.13604809,140.124854036,184.152928164,191.55897778,58.5988842598,30.026435289,30.062730193,48.5595260194,39.4241918483,36.382001183,59.6521012398,36.9652119192,94.1362074166,92.6855849208,32.4186239583,59.5283234781,30.7457565884,30.1722995658,34.0996625104,55.0294799098,69.1270563112,125.273578629,54.9096793553,47.6861072795,54.1455231822,46.0467993918,36.0984507234,53.9215318006,496.051750297,125.697220699,362.190600602,95.3275355298,29.6623740668,40.7861450652,63.6023396083,81.9758025399,112.038433225,88.1942712583,6.24694727656,781.17881923,36.6365823199,51.1386664574,27.9978183377,47.3674812498,186.949638546,57.2553814877,37.4935132708,265.933086549,83.4731233024,79.4006052761,115.431102298,152.11529736,96.1003208082,83.8288401389,55.3741245184,30.5052335432,75.9245739774,67.7292306355,75.7730717848,35.5924257572,200.458795467,19.7288154562,79.6528648331,71.9535858851,61.6072426721,33.1624782288,96.253802021,114.67088048,34.7660541641,38.1706844086,83.3110880551,106.792478206,45.2542903302,46.0116566569,44.1631695238,39.5755289853,109.171457003,136.675657482,107.729872084,35.8372889576,26.7895122762,28.4253195609,25.595751818,66.1160354948,90.822996538,49.6070740863,92.3938718837,198.939158671,99.7113108792,128.883420827,41.7200964875,35.6742496194,29.4373228307,28.376868871,140.916335491,0.974922895276,142.762958905,54.3942676048,84.1497426447,178.78792912,105.423856571,56.4218328332,88.1442509844,48.3357341318,132.049604552,92.84163388,183.316064066,138.32105385,0.222173295504,224.847076319,207.929143767,146.300405998,228.401278538,168.166402014,296.617455326,86.34145228,164.513438448,3.09150257346,200.047237438,220.531224548,210.555828775,76.8165129352,15.9920549944,0.0918918534225,57.8593365817,66.3625326009,38.0756427127,66.1040009359 +11,235.166199306,41.2183093163,67.7848768158,199.04449626,150.004779814,125.51328228,84.8826712305,74.3716896396,177.869799079,303.350452531,71.4470003826,176.518605032,759.057077448,99.3863078953,47.5371637481,58.3531846574,172.416178619,63.332051953,124.635843116,156.269925542,31.0032199821,2.05257704574,6.79578730471,163.725691695,53.6117289535,55.9991273381,60.1863701832,92.2985108524,118.082259785,62.6807041594,272.540733389,187.335460599,47.3864201553,69.0010240038,89.4208228635,135.681777065,68.934242407,67.6043468086,80.6984716499,184.852123618,52.7948963185,27.8349341865,212.20739528,125.264208505,1329.30710688,4.42927356376,140.535799821,301.472964315,570.318086512,132.534577295,217.073450769,69.4933913525,86.6132245377,83.6007649166,80.9730540507,83.6109188718,88.8076967646,100.251795081,3.33643865334,223.232346297,21.9758472043,76.2079714653,61.5099330261,76.3124870834,88.1375529065,30.6145974278,33.4112659,32.7697583025,4.6088217458,84.7376942615,60.0029289059,116.138465586,135.788339421,95.7821185703,38.7805452444,50.3387023743,88.7103953162,44.8986423548,70.7765253725,104.236514677,67.1951529464,112.441847463,174.100111774,33.7477510361,45.8294609663,40.1232277062,210.367079641,162.63588357,224.245298293,49.5131678211,128.92480305,43.184758391,43.0079174226,43.0649387093,41.7182767829,33.370781318,65.3699789295,45.2061392165,53.5612959704,58.9365458904,53.6850996809,156.171375464,201.334281093,123.40605872,174.148603923,112.272344682,146.583203275,34.5421078645,71.7058339783,127.934869954,131.414499291,136.879815333,36.0632727523,111.955810416,167.340757266,33.4665711152,33.6011602532,39.2851925001,55.2643770429,37.7172319723,129.116960123,97.0205076192,82.8069667995,40.6394033216,38.8226586552,58.258900058,38.7972140673,135.193885779,22.0453328484,116.757490606,43.0211054152,37.8166526986,28.5429202622,1.29300759952,387.037852894,82.7658065346,44.5689568909,26.0683011932,38.7972140673,142.361457937,188.299663306,45.3482628602,38.734631063,26.7391360956,37.5134493754,59.4350808573,35.1704982456,45.2808459335,38.7981143041,39.7570540634,5.23834838002,153.534939836,76.7743816243,36.8463375454,109.565138877,32.2626126592,44.8337575253,51.5055964318,181.002209822,45.3656507444,212.664029985,143.095935568,302.583497213,102.027140805,32.6931190069,99.6007261132,160.070505796,91.3127179063,90.4965555986,105.235027523,188.95552888,66.0080048249,82.2325870226,80.6265398442,178.412781545,42.7935737891,152.43546891,49.5699828286,0.410247937042,56.089337301,77.4944527413,488.820145753,361.709927195,131.705130195,106.259776483,126.600630511,25.1231797244,62.3500333974,159.89931288,70.201899858,22.4143016583,110.231667779,126.416541965,61.0114194739,198.828847827,123.743264649,177.509551567,229.795688846,110.746214338,140.069933094,48.7082513415,39.1145520735,0.798080836109,109.137942659,212.14978304,39.7155976266,154.821495478,120.863487972,159.6461926,225.76444053,90.4961909231,137.286912716,61.2251468711,128.318018447,78.8670732769,50.7267525463,79.7784690369,52.5012680531,103.503844203,91.2218523403,24.3092450482,50.8634081044,144.401334683,151.767422779,80.1362158961,77.6113923642,211.434447985,178.107657815,91.4812583746,78.5550206113,93.1735546323,114.870579064,187.944421039,159.641678652,71.7953047291,222.376154004,87.1298681049,117.317175884,192.795429277,6.90670307026,163.380722807,20.799417736,2.16400246103,382.543263428,124.615116676,10.1034070427,86.9467147535,148.170564878,222.861738583,17.6944640617,185.920419992,189.745815358,75.475321596,204.85074815,126.697354435,107.120052597,96.8386675709,167.983223499,92.1580457057,231.062293548,252.657519636,250.824503918,87.7176826767,229.299799647,120.294021617,796.261530025,2.39382835369,16.2634615735,0.0407672281256,2.38026215055,60.9206616152,173.655415015,255.677049294,60.3234796369,54.1435262841,107.312296201,51.8703065324,25.5417917746,20.9885470759,61.7908821986,310.904513898,180.297994629,121.665217201,62.3878637596,204.653854264,224.466733816,160.624927553,160.315757914,55.2539749607,184.772661378,65.2016823639,82.2973195918,66.123428934,134.66306416,723.138683953,59.7078697616,68.0123555083,130.5150268,162.988849818,58.6224033445,81.2470289703,57.54643908,461.459053025,199.867883583,85.1582753747,45.3709024177,22.527708094,79.3665096442,598.34501825,45.3349816622,89.7415897071,259.482351171,33.5951546496,31.7655944921,2.23118535793,111.384796883,9.45777434027,134.688657106,5.27011019529,109.825920718,58.7011739046,59.1001529382,85.132263714,62.3707494917,130.992356559,9.44421138811,77.5608052027,6.42615129808,54.0054817703,24.2143818629,273.253555168,145.552770258,97.3333726823,113.776741834,58.1596371229,30.269107839,121.057376019,64.6305018848,14.7189185854,39.501546983,73.5467748211,62.1545402673,47.6497824304,63.056792488,136.713061795,62.1208353009,28.8048741294,54.7636941055,40.2457539441,202.932717558,124.18513959,53.064922436,35.5637934933,24.8116481284,40.4533045822,40.4533045822,124.060140782,117.996836698,143.606540204,188.728581121,196.318649059,60.0548923762,30.7725029665,30.8096996911,49.7660859207,40.403765846,37.285985784,61.1342786679,37.8836875738,96.4752123955,94.9885462419,33.2241304146,61.0074253967,31.5096972623,30.9219915371,34.9469377786,56.3967989364,70.844658213,128.386254741,56.2740216942,48.8709653209,55.4908785113,47.1909255043,36.9953899383,55.2613216095,508.377162012,128.820423058,371.189960586,97.6961414716,30.3993959049,41.7995595525,65.1826687196,84.0126575986,114.82225531,90.3856367832,6.40216535028,800.588791202,37.5468925056,52.4093103333,28.6934809131,48.5444223814,191.594781445,58.6780075531,38.4251156438,272.540733389,85.547182332,81.3734743363,118.299222125,155.894910408,98.4881281674,85.9117376747,56.7500069392,31.2631979343,77.8110739908,69.4121007236,77.655807419,36.4767917555,205.439600766,20.2190178857,81.6320017937,73.7414186463,63.1379995541,33.986467253,98.6454229322,117.520111055,35.6298872844,39.1191124718,85.3811209904,109.445953899,46.3787249479,47.1549095769,45.2604930323,40.558863261,111.884043249,140.071641367,110.406639229,36.7277390788,27.4551520371,29.1316042712,26.2317301794,67.7588224782,93.0796780735,50.8396624438,94.6895850028,203.882205511,102.18884061,132.085790789,42.7567168919,36.5606486975,30.1687528145,29.0819497256,144.41768762,0.999146831725,146.310194139,55.7458034935,86.240613654,183.230277796,108.043326084,57.8237476918,90.3343736542,49.5367334698,135.33064477,95.148472554,187.870923438,141.757920944,0.227693641592,230.433857914,213.095565019,149.935536278,234.076371494,172.344837309,303.987517536,88.48678076,168.601108455,3.16831722438,205.017816742,226.010770053,215.787515342,78.7251749809,16.3894100322,0.0941750928749,59.2969691362,68.0114443047,39.0217092722,67.7464888962 +12,232.057193536,40.6733842299,66.888729437,196.413036088,148.021647343,123.853938718,83.7604831127,73.388461553,175.518278184,299.340019442,70.5024380388,174.184947546,749.021992304,98.072375002,46.9087004877,57.581728605,170.136757105,62.4947729754,122.988099705,154.203964951,30.5933430947,2.02544102918,6.70594385782,161.561162432,52.9029571354,55.2587930107,59.3906785715,91.0782819096,116.521157773,61.8520363005,268.937619017,184.858799287,46.7599497948,68.0887985975,88.2386382851,133.888001305,68.0228998842,66.7105861199,79.6316005831,182.408293167,52.0969234052,27.4669435006,209.401915503,123.608157814,1311.73305297,4.37071652153,138.677851639,297.487352487,562.778217999,130.782408971,214.203639491,68.5746566224,85.4681577059,82.4955241923,79.9025528777,82.5055439075,87.6336179964,98.9264200478,3.29232939337,220.281111576,21.6853163544,75.2004668849,60.6967433023,75.3036007582,86.9723337496,30.2098583101,32.9695535334,32.3365269626,4.54789099368,83.6174228061,59.2096624726,114.603061431,133.993154858,94.5158347245,38.2678485249,49.6732014793,87.537602918,44.3050615659,69.840827018,102.85845981,66.3068019895,110.955314307,171.798427886,33.3015901806,45.2235743254,39.5927801045,207.585929688,160.485762074,221.280671879,48.8585808742,127.220357608,42.6138359396,42.4393328905,42.4956003294,41.1667418957,32.9296041763,64.5057576161,44.608493177,52.8531908997,58.1573775331,52.9753578679,154.106717751,198.672548908,121.774573627,171.846278946,110.788052431,144.645305627,34.0854452448,70.7578497348,126.243511885,129.677138914,135.070200955,35.5864996302,110.475702894,165.128435166,33.0241275881,33.1569373955,38.7658241108,54.5337564538,37.2185927455,127.409974276,95.7378516983,81.7122204529,40.1021316398,38.309405177,57.4886904916,38.2842969783,133.406560168,21.7538833662,115.213902654,42.4523465317,37.3166990838,28.1655696734,1.27591344186,381.92103372,81.6716043457,43.979734696,25.7236662115,38.2842969783,140.479373711,185.810254788,44.7487378805,38.2225413502,26.3856323666,37.0175042485,58.6493216375,34.7055280148,44.6822122367,38.2851853136,39.2314474464,5.16909499504,151.505137019,75.7593888399,36.3592119451,108.116637137,31.836086019,44.2410345438,50.8246686584,178.60927701,44.7658958888,209.852513287,141.204141209,298.583203628,100.678294881,32.2609008739,98.2839585121,157.954299778,90.1055216016,89.3001493366,103.843771855,186.457449517,65.1353485144,81.1454342419,79.560619749,176.054082171,42.2278229789,150.420201607,48.9146447611,0.404824269835,55.3478103563,76.4699402842,482.357717658,356.927954897,129.963927544,104.854973161,124.926911704,24.7910396849,61.5257371587,157.785370112,69.2737983182,22.117974238,108.774354219,124.745256893,60.2048203295,196.200238627,122.107321538,175.16279331,226.757683706,109.282098235,138.218143886,48.0643057623,38.5974396295,0.787529838851,107.695088651,209.345064924,39.1905390828,152.774683801,119.265616839,157.535596199,222.779730354,89.2997894823,135.471916322,60.4157221518,126.621594978,77.8244141423,50.0561214488,78.7237608295,51.807177041,102.135475578,90.0158573203,23.9878656011,50.1909703573,142.492282345,149.760987354,79.07677811,76.5853339113,208.639186929,175.752992317,90.2718338871,77.5164869572,91.9417572066,113.351936954,185.459708988,157.531141928,70.846137641,219.436238541,85.9779710065,115.766188634,190.246584657,6.81539326572,161.220754192,20.524439827,2.13539334902,377.485864803,122.967647279,9.96983533516,85.797239027,146.211681576,219.915403467,17.4605350743,183.46246617,187.237288042,74.4775029852,202.142526646,125.022356893,105.703876026,95.5584156594,165.762407699,90.9396737771,228.007543308,249.317270533,247.508488129,86.5580143981,226.268350391,118.703679092,785.734584834,2.36218083723,16.0484511,0.0402282665409,2.34879398556,60.1152623316,171.359610223,252.296880614,59.5259753749,53.4277238597,105.893578082,51.184557123,25.2041174905,20.7110687897,60.973978197,306.794212629,177.914371867,120.056746836,61.5630673861,201.948235788,221.499179922,158.501391824,158.19630955,54.523491892,182.329881455,64.3396860087,81.2093110167,65.2492466635,132.882756256,713.57845656,58.9185041465,67.113200756,128.789557865,160.834061945,57.8473880968,80.1729057223,56.7856485767,455.358351213,197.225537851,84.0324436427,44.7710781326,22.2298813883,78.3172477303,590.434620755,44.7356322661,88.5551644429,256.051874643,33.1510111887,31.3456386616,2.20168805699,109.912238424,9.33273819529,132.908010852,5.20043690444,108.373971321,57.9251172737,58.3188216202,84.0067758682,61.5461793767,129.260577104,9.31935455163,76.5354155354,6.34119460992,53.2915043582,23.8942565509,269.641016963,143.62849541,96.0465805407,112.272560757,57.3907398583,29.8689362531,119.456941596,63.7760567994,14.5243276836,38.9793182876,72.5744524895,61.3328285381,47.0198302989,62.2231525677,134.905651975,61.2995691669,28.4240603895,54.0396927761,39.7136864889,200.249853305,122.543354688,52.3633796526,35.0936237177,24.4836266806,39.9184932117,39.9184932117,122.420008422,116.436864018,141.707995416,186.233502112,193.723225846,59.2609389618,30.3656762646,30.4023812313,49.1081552797,39.8696094005,36.7930478309,60.3260552682,37.382847727,95.1997655293,93.7327537889,32.7848920655,60.2008790559,31.0931245114,30.5131885272,34.4849231205,55.6512072068,69.9080591108,126.688929157,55.5300531365,48.2248685877,54.7572634682,46.5670396733,36.5062937965,54.5307414147,501.65618113,127.117357568,366.282657869,96.4045533591,29.9975018507,41.2469500695,64.3209237336,82.9019714628,113.304252062,89.1906969191,6.31752576742,790.00463762,37.0505052451,51.7164350448,28.3141398429,47.9026426966,189.061809943,57.9022571921,37.9171178678,268.937619017,84.4162090705,80.2976794222,116.735251771,153.83390769,97.186069625,84.7759448231,55.9997456367,30.8498840183,76.7823756545,68.4944406932,76.6291617785,35.9945517212,202.723594358,19.9517131309,80.5527890272,72.7665230334,62.3032860388,33.5371504588,97.3412848804,115.966440909,35.1588437184,38.6019397373,84.2523431365,107.999027839,45.7655767835,46.5314998932,44.6621284102,40.0226563561,110.404884522,138.219829574,108.947012466,36.2421814049,27.0921822466,28.7464710079,25.8849345909,66.8630195497,91.8491217393,50.1675386253,93.4377449554,201.186788588,100.83785694,130.339555629,42.1914533468,36.0773000357,29.7699079685,28.6974729183,142.508419089,0.985937649141,144.375905797,55.0088182244,85.1004729101,180.807888897,106.614943412,57.0592910407,89.1401115124,48.8818349741,133.541511142,93.8905658012,185.387183059,139.883815756,0.224683426487,227.387415883,210.278343228,147.953319239,230.981773752,170.06635896,299.968662153,87.3169446495,166.37212393,3.12643060735,202.307386515,223.022803287,212.934704725,77.6843918074,16.1727344619,0.0929300546511,58.5130358171,67.112301599,38.505824253,66.8508490234 +13,229.539901768,40.2321706922,66.1631391399,194.282402207,146.415949762,122.510405705,82.8518727334,72.5923639732,173.614304814,296.092862328,69.7376472378,172.295437775,740.896810461,97.0085132078,46.3998479769,56.9570980594,168.291160976,61.8168470936,121.653959073,152.531203311,30.2614750342,2.00346960933,6.63319964759,159.808591964,52.3290805989,54.6593610231,58.7464250408,90.0902900882,115.257168725,61.181082647,266.020258671,182.853502546,46.2527108901,67.3501902846,87.2814501264,132.435621578,67.2850064222,65.9869282719,78.7677791709,180.429578834,51.5317904979,27.1689897517,207.130381878,122.267290964,1297.50373835,4.32330420669,137.17351295,294.260292588,556.67335672,129.363717843,211.880018102,67.830777857,84.5410229485,81.6006357349,79.0357922505,81.610546759,86.6829929292,97.8532938109,3.25661512158,217.891563463,21.4500800856,74.3847131762,60.0383219453,74.486728282,86.0288821096,29.8821501857,32.6119090042,31.9857493293,4.49855673954,82.7103643052,58.567372554,113.359879357,132.539634454,93.4905532881,37.8527295682,49.1343604321,86.5880193957,43.8244526045,69.083213189,101.742679901,65.5875242792,109.751701977,169.934806415,32.9403438104,44.7330015936,39.1632886593,205.33409537,158.744857249,218.880280814,48.3285766043,125.840306621,42.1515729225,41.9789628338,42.0346198993,40.7201765516,32.5723930067,63.8060171333,44.124592674,52.2798542134,57.5265025083,52.4006959484,152.435011021,196.517404462,120.453596987,169.9821384,109.586254511,143.076233659,33.7156958334,69.990288299,124.874057448,128.270437449,133.604997055,35.2004671991,109.277293255,163.337167918,32.6658910548,32.7972601785,38.3453032537,53.9421894607,36.8148558231,126.02786638,94.6993141623,80.8258290562,39.667114891,37.8938354253,56.8650691975,37.8689995934,131.95940298,21.5179033016,113.964094344,41.9918353067,36.9118979312,27.8600374224,1.2620726884,377.778060777,80.7856535412,43.5026547898,25.4446230488,37.8689995934,138.955492613,183.794636924,44.2633160421,37.8079138731,26.0994083795,36.6159486781,58.0131101402,34.3290521183,44.19751205,37.8698782922,38.8058756371,5.113022093,149.861651512,74.9375721017,35.9647973461,106.943817986,31.4907370296,43.7611201297,50.2733368161,176.671773346,44.2802879251,207.576091703,139.672398034,295.344256241,99.5861647943,31.9109436114,97.2178015189,156.240855551,89.128082013,88.3314462018,102.717303556,184.434811064,64.428778405,80.265191184,78.6975683171,174.144296548,41.7697473228,148.788485177,48.3840323257,0.400432848969,54.7474127333,75.6404156819,477.125235555,353.056099907,128.554115075,103.717535632,123.571739379,24.5221133949,60.8583231074,156.073758388,68.5223354586,21.8780446171,107.594400336,123.392055109,59.5517352158,194.071913113,120.782735341,173.262676141,224.297879543,108.096636487,136.718791978,47.5429175674,38.1787453603,0.77898693462,106.52684234,207.074147998,38.7654110361,151.117426605,117.971856663,155.826693952,220.363077921,88.3310902511,134.002354726,59.7603492339,125.248039201,76.980196585,49.5131265924,77.86978741,51.2451872215,101.027538401,89.0393903848,23.7276519197,49.6465126976,140.946565774,148.136421897,78.2189752574,75.7545575511,206.375927171,173.846472834,89.2925901891,76.6756097082,90.9443986458,112.12232673,183.447893747,155.822288,70.0776184825,217.055855375,85.0453059365,114.51038928,188.182842723,6.74146177882,159.471876378,20.3017964233,2.11222919704,373.391003335,121.633728508,9.86168533974,84.8665344871,144.625617999,217.529822454,17.2711280556,181.472316462,185.206190119,73.6695917874,199.949740848,123.666149205,104.557229836,94.5218245955,163.964263293,89.9531855381,225.534181017,246.612746259,244.823585023,85.6190571791,223.81385438,117.41601466,777.211155018,2.33655655778,15.8743619747,0.0397918815159,2.32331492295,59.4631487181,169.500748924,249.560034361,58.8802541821,52.8481547999,104.744874056,50.629321311,24.9307102476,20.4864008871,60.3125494731,303.466194504,175.984406341,118.754404703,60.8952483868,199.757557603,219.096418543,156.782012895,156.480240069,53.932036246,180.352017711,63.6417470244,80.3283750414,64.5414410187,131.441281146,705.837756316,58.2793726323,66.3851754567,127.392484631,159.08937886,57.219875751,79.3032123828,56.1696536854,450.418750713,195.086090177,83.1208831114,44.2854139533,21.9887378298,77.4676840515,584.029750525,44.2503525937,87.5945426966,253.27429526,32.7913982575,31.0056099085,2.1778047585,108.719941091,9.2314992521,131.466261787,5.1440240141,107.198360681,57.2967617435,57.6861952949,83.0954937738,60.8785435739,127.858394385,9.21826079048,75.7051806758,6.27240709789,52.7134129696,23.6350583145,266.716026352,142.070453516,95.0046940003,111.054659305,56.7681811054,29.5449263562,118.161105982,63.0842319077,14.3667718243,38.5564815052,71.7871850532,60.6675071057,46.5097722829,61.5481731483,133.442233057,60.6346085229,28.1157241033,53.4534852481,39.2828834849,198.0775987,121.214038526,51.7953563022,34.7129378671,24.2180351212,39.4854685215,39.4854685215,121.092030286,115.173789365,140.170786571,184.213292979,191.621770277,58.6180928102,30.0362778705,30.0725846719,48.5754436962,39.4371149881,36.3939271012,59.6716550317,36.9773290123,94.1670650021,92.7159669963,32.4292506925,59.5478366961,30.7558349614,30.1821899612,34.1108402844,55.0475184194,69.1497159669,125.314642958,54.9276785946,47.7017386521,54.1632719333,46.0618934038,36.1102836946,53.9392071279,496.214354673,125.738423896,362.309325668,95.3587836293,29.67209731,40.7995146494,63.6231882773,82.0026739787,112.07515911,88.2231810937,6.2489950084,781.434887461,36.6485916891,51.1554295692,28.0069959442,47.3830081777,187.010920115,57.2741496416,37.5058035395,266.020258671,83.5004855587,79.4266325724,115.46894029,152.165160329,96.1318222245,83.8563189982,55.3922760015,30.5152330734,75.9494618404,67.7514320878,75.7979099858,35.6040928548,200.5245053,19.7352825068,79.6789748194,71.9771720693,61.6274373539,33.1733487964,96.2853537481,114.708469274,34.7774503794,38.1831966522,83.3383971968,106.827484481,45.26912456,46.0267391493,44.1776460872,39.588501733,109.207243102,136.72045938,107.765185635,35.8490363207,26.7982938034,28.4346373011,25.6041420338,66.1377081463,90.8527680622,49.6233351465,92.4241583364,199.00437037,99.743995968,128.925668452,41.7337722184,35.6859435386,29.4469723028,28.3861707292,140.962527471,0.975242472274,142.809756204,54.4120978936,84.1773266949,178.846535304,105.458414216,56.4403277522,88.1731444234,48.3515784502,132.092890043,92.8720671079,183.376154559,138.366395104,0.222246123289,224.920780562,207.997302359,146.348362862,228.476147838,168.22152649,296.714685698,86.3697547666,164.567365493,3.09251595936,200.112812363,220.603514067,210.624848386,76.8416931733,15.9972971453,0.091921975315,57.8783027104,66.3842860534,38.0881238019,66.1256696425 +14,229.073550903,40.1504319293,66.0287170331,193.887683181,146.118479892,122.261504171,82.6835445157,72.4448797602,173.26157668,295.491297365,69.5959628863,171.945389152,739.391547689,96.8114232738,46.3055784878,56.8413796516,167.949247749,61.691255247,121.406797561,152.221309222,30.1999935011,1.99939920684,6.61972313056,159.483912575,52.2227648262,54.548310874,58.627071299,89.9072557468,115.023003423,61.0567824681,265.479791515,182.48200335,46.158740336,67.2133564736,87.1041224396,132.166555214,67.1483050437,65.8528641685,78.6077485108,180.063004267,51.4270945609,27.1137911488,206.709559912,122.01888336,1294.86763026,4.31452065036,136.894820722,293.662450814,555.542376443,129.100892594,211.449546411,67.6929676475,84.3692629237,81.4348496262,78.8752170746,81.4447405143,86.506881114,97.6544875559,3.24999873259,217.448878255,21.4065004582,74.2335874892,59.9163434867,74.3353953332,85.8540992361,29.821439318,32.5456521492,31.9207646283,4.48941712672,82.5423235869,58.448382589,113.129568734,132.27035677,93.3006107113,37.7758250603,49.0345352978,86.4121005359,43.7354154861,68.9428584359,101.535971671,65.4542716356,109.528722004,169.589553832,32.8734196821,44.6421185974,39.083721519,204.916922877,158.422339025,218.435586852,48.2303885622,125.584639805,42.0659345548,41.893675154,41.9492191423,40.6374463185,32.5062164354,63.6763839363,44.0349457681,52.1736384528,57.4096272547,52.2942346765,152.125312364,196.118144634,120.208874223,169.636789654,109.363610674,142.785549012,33.6471964404,69.8480906625,124.620353738,128.009833392,133.333554898,35.1289512308,109.055277127,163.005319603,32.5995245258,32.7306267499,38.2673979954,53.8325963724,36.740059939,125.771818503,94.5069157742,80.6616171042,39.5865241388,37.8168474036,56.7495377625,37.79206203,131.691304138,21.4741858793,113.732556152,41.9065214742,36.836904889,27.8034348341,1.25950856475,377.0105379,80.6215232129,43.4142714606,25.3929278015,37.79206203,138.673179979,183.421225646,44.1733872951,37.7311004162,26.0463828202,36.5415569091,57.8952462572,34.2593065837,44.1077169954,37.7929389437,38.7270346446,5.10263408526,149.55718109,74.785323184,35.8917285061,106.726542725,31.4267580336,43.6722116824,50.1711976501,176.312833424,44.1903246968,207.154364198,139.388628881,294.744212203,99.3838379057,31.8461108914,97.0202863788,155.923424649,88.9470025672,88.1519852625,102.508615206,184.06009916,64.2978799584,80.1021182669,78.5376803028,173.790491642,41.6848847013,148.486195082,48.2857316156,0.399619298888,54.6361836916,75.4867388133,476.155871341,352.338804063,128.292934678,103.506815133,123.320681558,24.472292389,60.7346786716,155.756666972,68.3831202276,21.8335955041,107.375803302,123.141362348,59.4307453442,193.677621733,120.537343875,172.910662402,223.842178772,107.877019072,136.441023597,47.4463257305,38.1011784937,0.777404285032,106.310414239,206.65344028,38.6866522545,150.810404853,117.732175993,155.510104492,219.915371394,88.151630035,133.730105267,59.6389355261,124.993575681,76.8237977148,49.4125319278,77.7115811788,51.1410735697,100.822283108,88.8584911318,23.6794450027,49.5456470356,140.660207923,147.835456583,78.0600595895,75.6006487831,205.956638012,173.493273009,89.1111765159,76.51982966,90.759629032,111.894530414,183.075186939,155.505707491,69.9352434193,216.614868056,84.8725213717,114.277741193,187.80051603,6.72776530813,159.147881085,20.260549737,2.10793783024,372.632393541,121.386608099,9.84164958361,84.6941131281,144.331785504,217.087872187,17.2360387075,181.103623389,184.829911022,73.5199189958,199.543507623,123.414899574,104.34480335,94.3297868088,163.631140923,89.7704297488,225.075968481,246.111708894,244.326182655,85.4451069373,223.359136991,117.177463282,775.63211324,2.33180943031,15.8421104037,0.0397110372782,2.3185946982,59.3423388256,169.156378206,249.053009059,58.7606285428,52.7407844363,104.532066338,50.5264588996,24.880059105,20.4447791441,60.1900138729,302.849649314,175.626862926,118.513134148,60.7715289308,199.351714832,218.651285459,156.463482536,156.162322814,53.8224637857,179.985600722,63.5124475712,80.1651737551,64.4103136782,131.174234961,704.403722207,58.1609677901,66.2503022438,127.133664291,158.76616068,57.1036234639,79.1420938956,56.0555351099,449.503645442,194.689738317,82.9520083516,44.1954403106,21.9440638239,77.3102947644,582.843190901,44.1604501843,87.416578907,252.759723781,32.7247767384,30.9426165339,2.17338016336,108.499057322,9.21274383035,131.199164849,5.13357302048,106.980568271,57.1803532488,57.5689955972,82.9266705969,60.7548580566,127.598627467,9.199532265,75.5513722257,6.25966359466,52.6063163578,23.5870395177,266.174145621,141.781812289,94.8116751791,110.829032144,56.6528465157,29.4849006184,117.921040819,62.9560651449,14.3375831891,38.4781472009,71.6413367034,60.5442503463,46.4152794632,61.4231271603,133.171121584,60.5114186028,28.0586020424,53.3448850487,39.2030733666,197.675169062,120.96777079,51.6901248847,34.6424124008,24.168831904,39.4052468159,39.4052468159,120.846010432,114.939793463,139.886004853,183.839031127,191.232456795,58.498999798,29.9752538652,30.0114869029,48.4767541001,39.3569915213,36.3199864082,59.5504215218,36.9022030353,93.9757479722,92.5275981283,32.3633649402,59.4268547449,30.6933490487,30.1208695097,34.0415380857,54.9356796599,69.0092261015,125.06004412,54.816083311,47.6048241421,54.0532296768,45.9683105292,36.0369192733,53.8296200987,495.206207541,125.482964073,361.57323026,95.1650454124,29.6118132019,40.716623227,63.4939265294,81.8360710583,111.847458623,88.0439401141,6.23629907097,779.847264424,36.5741336,51.051498276,27.9500947837,47.286741225,186.63097439,57.1577870875,37.4296038732,265.479791515,83.3308395695,79.2652633352,115.234344737,151.856009921,95.9365134432,83.6859500704,55.2797368061,30.453235986,75.7951570899,67.6137830833,75.6439131397,35.5317568496,200.117104339,19.6951867935,79.5170929044,71.8309377274,61.5022303267,33.1059512773,96.0897330403,114.475418752,34.7067938445,38.105620742,83.1690805187,106.610445573,45.1771523353,45.9332276967,44.0878913939,39.5080706973,108.985369293,136.442687611,107.54624162,35.7762026696,26.7438483348,28.3768673117,25.5521226956,66.003337707,90.6681846121,49.5225165737,92.2363823293,198.600057832,99.5413484174,128.663733178,41.6489826866,35.6134412393,29.3871455759,28.3284992081,140.676137191,0.973261094886,142.519612953,54.3015501032,84.0063055834,178.483176969,105.244156815,56.3256592545,87.9940051021,48.2533436759,131.824519999,92.683381095,183.003593503,138.085279326,0.221794591023,224.463814257,207.57471909,146.051030306,228.011958176,167.879754738,296.111857391,86.19427935,164.233017814,3.0862329668,199.706247829,220.155319052,210.196926794,76.6855756973,15.9647958097,0.0917352195814,57.7607127125,66.2494146476,38.010741049,65.9913236615 +15,237.036617293,41.5461432796,68.3240106421,200.627616654,151.197857898,126.511564777,85.5577940821,74.9632123433,179.284504392,305.763180825,72.0152612765,177.922563488,765.094314156,100.176786878,47.9152553548,58.8173025728,173.787508014,63.8357697895,125.627146813,157.512834091,31.2498072063,2.06890242344,6.84983828163,165.02790043,54.0381352246,56.4445220221,60.6650685238,93.0326163292,119.021438899,63.1792414358,274.708413487,188.825452002,47.7633128077,69.5498305789,90.1320403599,136.760935707,69.482517828,68.1420447714,81.3403150498,186.32236355,53.2148058293,28.0563221314,213.895208113,126.26050997,1339.87988447,4.46450223594,141.653565421,303.870759817,574.854168698,133.588704368,218.799966263,70.0461140208,87.3021115188,84.2656919975,81.6170813694,84.2759267132,89.514037701,101.049157803,3.3629753533,225.007847185,22.1506343124,76.814099221,61.9991584567,76.9194461145,88.8385637864,30.8580937035,33.6770057593,33.0303958667,4.64547847249,85.4116640241,60.4801682278,117.062184538,136.868345618,96.543931271,39.0889902062,50.7390762998,89.4159623557,45.2557482166,71.3394535761,105.065570266,67.7295963447,113.336164991,175.4848345,34.0161671637,46.1939701874,40.4423518279,212.040255339,163.929424406,226.028855797,49.9069757746,129.950219421,43.5282327045,43.3499852137,43.4074600249,42.0500872877,33.6361991791,65.889905623,45.5656907854,53.9873011178,59.4053036279,54.1120895135,157.413500185,202.935613521,124.387581203,175.533712336,113.165314052,147.749067506,34.8168419889,72.2761535313,128.952412793,132.45971771,137.968502693,36.3501056145,112.846262199,168.671718787,33.7327508501,33.8684104563,39.5976512246,55.7039278167,38.0172197653,130.143904834,97.7921699716,83.4655803274,40.9626328979,39.1314385703,58.7222680713,39.1057916063,136.269163929,22.2206726175,117.686133035,43.3632780983,38.1174312443,28.7699392454,1.30329166533,390.116197333,83.4240926902,44.9234405662,26.2756380452,39.1057916063,143.493744092,189.797323591,45.7089448238,39.0427108418,26.9518085003,37.811816363,59.9078037428,35.450230336,45.6409916898,39.1066990033,40.0732657863,5.28001211002,154.756095401,77.38501438,37.1393985921,110.436576215,32.5192165896,45.1903473191,51.9152513664,182.441829078,45.7264710043,214.355474709,144.234063464,304.990125451,102.838623918,32.9531470128,100.392910493,161.343642641,92.03898278,91.2163290434,106.072024885,190.458405658,66.5330061213,82.8866321632,81.2678111272,179.831805523,43.1339367766,153.647879723,49.964242666,0.413510885216,56.535449479,78.110812655,492.708025881,364.586823433,132.752660177,107.104924289,127.607561342,25.3229994578,62.8459406507,161.171088126,70.7602577199,22.5925760579,111.10840642,127.422008629,61.4966799268,200.410253039,124.727469134,178.921391615,231.623391939,111.627045478,141.183993376,49.095657311,39.4256535922,0.804428452201,110.005982249,213.837137648,40.0314796215,156.052883793,121.824787864,160.915954629,227.560080684,91.2159614674,138.378837966,61.7121072264,129.338608695,79.4943504658,51.1302128678,80.4129951036,52.9188421619,104.327072425,91.9473945055,24.5025910704,51.2679553297,145.549845203,152.97452022,80.7735873384,78.2286823689,213.116113105,179.524254961,92.2088637518,79.1798158581,93.9146198577,115.784213753,189.439255869,160.911404779,72.3663358967,224.144845081,87.8228642625,118.250269826,194.328847089,6.96163622686,164.680187796,20.9648479941,2.18121407196,385.585859697,125.606255523,10.1837655054,87.6382541825,149.349054349,224.634291806,17.8351987589,187.399156727,191.254977759,76.0756221473,206.480049151,127.70505457,107.972042694,97.6088836402,169.319294939,92.8910339789,232.900070708,254.667056596,252.819461803,88.4153540765,231.123558635,121.250792201,802.594676083,2.41286790795,16.3928146487,0.0410914743693,2.39919380465,61.4052002164,175.036600801,257.710602387,60.8032684913,54.5741622587,108.165815328,52.2828622254,25.744940981,21.1554815933,62.2823421866,313.377324177,181.732012839,122.632893726,62.8840719005,206.28158925,226.252052529,161.902474048,161.590845397,55.6934430005,186.2422693,65.7202704936,82.9518795894,66.6493482677,135.734120367,728.89024011,60.182762301,68.5532986092,131.553091175,164.285198006,59.0886625178,81.8932353759,58.0041404634,465.129313954,201.457552871,85.8355902715,45.7317644473,22.7068844831,79.9977591506,603.104015452,45.6955579925,90.4553584548,261.546170114,33.8623570865,32.0182453386,2.24893131478,112.270707053,9.53299769845,135.759916869,5.31202654608,110.699432209,59.1680595874,59.5702119408,85.8093717244,62.8668215125,132.034217424,9.519326872,78.1776928573,6.47726233789,54.4350197941,24.4069733812,275.426904766,146.710438836,98.1075234384,114.681676578,58.6222156385,30.5098562285,122.020218018,65.1445470732,14.8359869828,39.8157265046,74.131736482,62.6488926463,48.028769761,63.5583210207,137.800422864,62.6149196041,29.0339765891,55.1992626472,40.5658525905,204.546763311,125.172858571,53.4869796266,35.846653697,25.008990064,40.7750540035,40.7750540035,125.046865573,118.935336391,144.748729246,190.229652847,197.880089153,60.5325449948,31.0172551598,31.0547477322,50.1619054838,40.7251212559,37.5825436171,61.6205162938,38.1849992881,97.2425376876,95.7440471854,33.488381873,61.4926540824,31.7603128045,31.1679327027,35.2248917585,56.8453565414,71.4081283179,129.407387714,56.7216027781,49.2596654524,55.9322307872,47.5662632703,37.2896364881,55.7008480816,512.420590834,129.845009232,374.142257222,98.4731775458,30.6411805537,42.1320165479,65.7011056224,84.6808607094,115.73550565,91.104528024,6.45308561545,806.956354567,37.8455254865,52.8261530472,28.9216973956,48.9305253208,193.118649783,59.14470898,38.7307336606,274.708413487,86.2275904393,82.0206864379,119.240126871,157.134836237,99.2714633873,86.5950453095,57.2013738196,31.5118529193,78.4299521834,69.9641768387,78.2734506833,36.7669135826,207.073585265,20.3798318754,82.2812701194,74.3279284264,63.6401739753,34.256782034,99.4300092118,118.454819063,35.9132731717,39.4302502621,86.0602083122,110.316443286,46.7476027952,47.5299608866,45.6204769097,40.8814522539,112.773924225,141.185715235,111.284769524,37.0198568406,27.6735193465,29.3633054118,26.4403668802,68.2977490787,93.8199966423,51.2440208055,95.4427081281,205.503803111,103.001609818,133.136348393,43.0967867343,36.8514374892,30.4087030203,29.3132559338,145.566328205,1.00709364641,147.473886964,56.1891833409,86.926537037,184.687618215,108.90265953,58.2836546771,91.0528571686,49.9307288551,136.407010646,95.9052454875,189.365173698,142.885406807,0.229504625841,232.266636748,214.790441999,151.128063626,235.938121426,173.715599283,306.40531279,89.1905692587,169.942094306,3.19351675366,206.64844654,227.808369414,217.50380302,79.3513235675,16.5197648511,0.0949241239353,59.7685935362,68.5523801583,39.3320723564,68.2853174003 +16,257.731563761,45.1734107497,74.2891723074,218.143803959,164.398483232,137.556904905,93.0275849891,81.5080225261,194.937289449,332.458434628,78.3026948134,193.456441624,831.892372851,108.922917604,52.0985906383,63.9524624568,188.960367028,69.4090767902,136.595271097,171.264800799,33.9781328746,2.2495320046,7.44787683861,179.435984734,58.7560405361,61.3725216494,65.9615512359,101.155011243,129.412838906,68.6952289417,298.69236996,205.31122819,51.9333824493,75.6220148565,98.0011948039,148.701117252,75.5488252339,74.0913195497,88.4418906837,202.589602695,57.8608287541,30.5058343259,232.569748496,137.283931265,1456.86072398,4.85428435415,154.020907593,330.400792095,625.04293862,145.251928039,237.902726169,76.1616272969,94.9241934786,91.6226733995,88.7428206292,91.6338016771,97.3292362115,109.871452583,3.65658650801,244.652598303,24.0845388566,83.5205047104,67.4121165051,83.6350491209,96.5947887228,33.5522200567,36.6172427546,35.9141793179,5.05106107574,92.8686967601,65.7605078571,127.282528003,148.817904796,104.972888418,42.5017311113,55.1689508056,97.222598206,49.2068900167,77.5678843971,114.238525808,73.6428615047,123.231201016,190.805881929,36.9860153102,50.2270252956,43.9732506219,230.552845434,178.241604108,245.762748072,54.2642021131,141.295778032,47.3285503768,47.1347406395,47.1972334017,45.7213525772,36.572873476,71.6425529787,49.5438927289,58.7007682579,64.5918000965,58.8364515491,171.156794352,220.653305016,135.247482782,190.859027141,123.045433602,160.648589435,37.8565946204,78.5863647778,140.21085594,144.024372988,150.014113241,39.523722832,122.698526301,183.397933795,36.6778547798,36.8253584139,43.0548017768,60.5672684153,41.3363876513,141.50637353,106.330107045,90.7527064118,44.5389558505,42.547885513,63.8491307819,42.5199993905,148.166410376,24.1606919933,127.960951546,47.1491940854,41.4453482899,31.2817551808,1.41707809865,424.176056575,90.7075966151,48.8455695955,28.5696841254,42.5199993905,156.02174667,206.367950934,49.699653842,42.4514112362,29.3048889674,41.1130511023,65.1381719689,38.5452821786,49.6257679264,42.5209860095,43.5719408,5.7409939162,168.267379635,84.1412647364,40.38192658,120.078457959,35.3583705322,49.1357791656,56.4478141417,198.370270555,49.71871018,233.070199569,156.826701076,331.617886139,111.817151544,35.8301861014,109.157910315,175.430065599,100.07462657,99.180149436,115.332850921,207.086750066,72.3418006266,90.1232120434,88.363056664,195.532373778,46.8998296701,167.062409015,54.3264688027,0.449613263538,61.4713877062,84.9304302524,535.724865796,396.417790649,144.342891372,116.455929631,138.748589598,27.5338735589,68.3328286783,175.242445873,76.9381207114,24.5650651925,120.808943617,138.546836842,66.8657681024,217.907462988,135.617045294,194.542474399,251.845726158,121.372863474,153.510338641,53.3820499009,42.867787554,0.874660654914,119.610270142,232.506608075,43.5265064122,169.677386678,132.460939747,174.965037445,247.42765955,99.1797497681,150.46027364,67.1000037046,140.630769433,86.4347605435,55.5942363183,87.433609252,57.5390253987,113.435551965,99.9750420012,26.6418378093,55.7440046558,158.257359718,166.33026054,87.8256836986,85.0585908127,231.72263307,195.197971911,100.259339333,86.0927648754,102.114019819,125.892981474,205.978621407,174.960090362,78.6844206914,243.714250109,95.4904115613,128.574341405,211.295107972,7.56943552257,179.057914368,22.7952251397,2.37164924173,419.250188933,136.572555852,11.0728791132,95.2896837364,162.388266265,244.246428939,19.3923357438,203.760407411,207.952868455,82.7175534484,224.507194547,138.854594663,117.398753509,106.130818541,184.102047853,101.001067768,253.23386787,276.901263124,274.892360446,96.1346298598,251.302253928,131.836830268,872.666777378,2.62352806991,17.8240214618,0.0446790460791,2.60866012222,66.7663015765,190.318514308,280.210531645,66.1118170224,59.338866461,117.609443842,56.8475199873,27.9926535138,23.0025023789,67.7200241457,340.737345689,197.598482418,133.339598875,68.3742890516,224.291407714,246.005431433,176.037686876,175.698850875,60.5558682017,202.502515659,71.4581075219,90.1941560241,72.4683002521,147.584653787,792.52743116,65.437136323,74.5384787099,143.038591646,178.62843922,64.2475140137,89.0430848025,63.0683056356,505.73834037,219.046199384,93.3296347193,49.7244657774,24.6893535388,86.9821203102,655.759193616,49.6850982471,98.3527408188,284.380971131,36.8187765427,34.8136610042,2.44527867116,122.072721185,10.3652947474,147.612702503,5.77580343534,120.364263111,64.3338430001,64.7711060529,93.3011271107,68.3555325847,143.561723565,10.3504303626,85.0031495618,7.04277242185,59.1875758908,26.5378720303,299.473590528,159.519281254,106.672993065,124.69418513,63.7403430753,33.1735790263,132.673432314,70.8321194399,16.1312719051,43.2919165466,80.603953035,68.1185770117,52.2220156422,69.1074048128,149.831358873,68.0816378865,31.5688448223,60.0185424641,44.1075338607,222.405119397,136.10132033,58.1567652183,38.976316058,27.1924489596,44.3350000131,44.3350000131,135.964327266,129.319219049,157.386300763,206.838025543,215.156398185,65.8174574804,33.7252774158,33.7660433508,54.5413889602,44.2807077891,40.86376125,67.0004162461,41.5188154942,105.732488037,104.103168885,36.4121506902,66.8613907737,34.5332091646,33.889110157,38.3002699748,61.8083518121,77.6425549219,140.705553339,61.6737934839,53.5603770945,60.8155038242,51.7191291158,40.54528129,60.563919798,557.158474869,141.181382261,406.807480314,107.070571321,33.3163688791,45.810434832,71.4372694241,92.0740892295,125.840020815,99.0585874096,7.01648490326,877.409260751,41.1497032669,57.4382438792,31.4467628737,53.2025007386,209.979252018,64.3084537222,42.1121962757,298.69236996,93.7558592302,89.1816632293,129.65061986,170.853801101,107.938553072,94.155395468,62.1954518765,34.2630570022,85.2774328828,76.0725364286,85.1072677125,39.9769210367,225.152550495,22.159133042,89.4649976798,80.8172739016,69.1964041001,37.2476375334,108.110941051,128.796749214,39.0487518766,42.8727855452,93.5738634784,119.94783659,50.8289938834,51.6796572814,49.6034620632,44.4506875712,122.619872719,153.51221083,121.000704447,40.2519480016,30.0896101936,31.9269263434,28.7487949342,74.2606179267,102.011135338,55.7179804811,103.775520831,223.445715433,111.994367248,144.760078239,46.8594361706,40.0688244687,33.063594439,31.8725071976,158.275281801,1.09501993113,160.349383716,61.0948901242,94.5158287305,200.812132714,118.410619672,63.3722233856,99.0024053288,54.2900290029,148.316292071,104.278440921,205.898071346,155.360297306,0.249541977033,252.545130716,233.543142531,164.322595434,256.53716156,188.882180164,333.156629183,96.9775267228,184.779221939,3.47233299319,224.6902942,247.697625614,236.493398853,86.2792464015,17.9620553004,0.103211660628,64.9868077423,74.5374800718,42.766036025,74.2471008763 +17,261.582719295,45.8484147277,75.399238738,221.40341914,166.855008615,139.612349833,94.4176502706,82.725956672,197.850141139,337.426196906,79.4727333296,196.347165736,844.322929933,110.550498995,52.8770741609,64.9080725345,191.783908516,70.4462223624,138.636346806,173.823926176,34.4858513416,2.28314565091,7.55916678573,182.117208079,59.6340030462,62.2895809453,66.9471821355,102.666520643,131.346591076,69.7217078379,303.155582601,208.379092525,52.7093973475,76.7519972953,99.4655782817,150.923084645,76.6777140363,75.1984295642,89.7634341988,205.616799124,58.7254145565,30.9616679497,236.044923444,139.335297283,1478.62987463,4.92681952904,156.322365986,335.337808036,634.382646713,147.422355899,241.457589171,77.2996728981,96.3425988445,92.9917457806,90.0688607919,93.0030403426,98.7835789438,111.513207464,3.7112250752,248.308321312,24.4444222319,84.7685103842,68.4194224855,84.8847663754,98.0381569688,34.0535743187,37.1643961378,36.4508271712,5.12653658805,94.2563878532,66.7431346648,129.184447988,151.041617287,106.541446472,43.1368134992,55.9933132046,98.6753474992,49.9421642843,78.726943004,115.945535708,74.743270432,125.072584023,193.657000036,37.5386790796,50.9775429411,44.6303211996,233.997882796,180.904980413,249.435059501,55.075045299,143.407091095,48.0357575424,47.8390518018,47.9024783626,46.4045441794,37.1193638709,72.7130722831,50.2842026617,59.5779048616,65.5569634811,59.7156156013,173.714305842,223.950418432,137.268419155,193.71093937,124.884040777,163.049082006,38.4222670267,79.7606420342,142.305957549,146.176458099,152.255700084,40.1143063122,124.531949812,186.13835859,37.2259138586,37.3756215666,43.6981484267,61.4722952096,41.9540568872,143.620833418,107.918945347,92.1087793057,45.2044794821,43.1836575634,64.8031968255,43.1553547523,150.380387907,24.5217132873,129.87300888,47.8537212185,42.0646456708,31.7491830069,1.43825279717,430.514310003,92.0629954555,49.5754448304,28.9965868125,43.1553547523,158.353102614,209.451605355,50.4422912362,43.0857417191,29.7427774575,41.7273831304,66.1114995186,39.1212453031,50.3673012799,43.1563561139,44.2230148031,5.82677875438,170.781716027,85.3985461209,40.9853337752,121.872731082,35.8867132092,49.8699908589,57.2912859642,201.334419587,50.4616323237,236.552852511,159.170085057,336.573088516,113.487980044,36.3655788856,110.789003085,178.051430472,101.569992315,100.662149451,117.05621278,210.181145148,73.4227684434,91.4698787136,89.6834222012,198.454117519,47.6006306738,169.558740126,55.138242409,0.456331612593,62.3899243087,86.1995037476,543.729938017,402.341266011,146.499735946,118.196073102,140.821841283,27.94529864,69.3538924062,177.861007237,78.0877690704,24.9321288357,122.614132027,140.617073834,67.8649102683,221.163546642,137.643503858,197.449426562,255.608932526,123.18647826,155.804167854,54.1797115217,43.5083397427,0.887730277323,121.397547366,235.980839548,44.1769015117,172.21279211,134.440238183,177.579453632,251.124848929,100.661743811,152.708527237,68.1026459375,142.732145602,87.7263125037,56.4249535488,88.7400865154,58.3988026524,115.130563423,101.468919703,27.0399336405,56.5769598006,160.622121324,168.815651518,89.1380195369,86.3295793479,235.185149992,198.11471885,101.757465149,87.3792065631,103.63985921,127.774138151,209.05645827,177.574432627,79.8601631489,247.355951844,96.9172776445,130.495564321,214.452386469,7.6825418611,181.733488394,23.1358429366,2.40708762563,425.514837553,138.613292138,11.2383356799,96.7135504428,164.814753969,247.89608276,19.6821058442,206.805098597,211.060205517,83.9535610178,227.861894732,140.929430328,119.152985131,107.716678974,186.852993876,102.510276867,257.017816554,281.038862012,278.999941294,97.571122179,255.057339459,133.806802944,885.70660626,2.66273015481,18.0903570161,0.0453466630099,2.64764004272,67.7639574633,193.162349921,284.3975793,67.0996932695,60.2255378512,119.366823706,57.696964417,28.4109339173,23.3462174172,68.731931038,345.828815649,200.551099001,135.332026681,69.3959723015,227.642883503,249.68136916,178.668131134,178.324232074,61.4607246482,205.528410789,72.5258707489,91.5418827752,73.5511582904,149.789938416,804.36977735,66.4149311494,75.652270403,145.175946713,181.297595811,65.2075329012,90.3736116646,64.0107041944,513.295338743,222.319298615,94.7242133808,50.4674739241,25.0582743613,88.2818511971,665.557879545,50.4275181442,99.8223772752,288.630335604,37.3689413458,35.3338643556,2.48181726329,123.896793599,10.5201782302,149.818406251,5.86210841498,122.16280688,65.2951518591,65.7389487179,94.695279797,69.3769355659,145.706895535,10.5050917342,86.2733096658,7.14800909364,60.0719866207,26.9344143522,303.948476563,161.90289913,108.266954943,126.557428786,64.6927835579,33.6692754423,134.655905922,71.8905288359,16.3723135383,43.9388062855,81.8083781168,69.1364392828,53.0023434437,70.1400426495,152.070214908,69.0989481944,32.0405624872,60.9153699174,44.7666109664,225.72840931,138.135015176,59.0257730531,39.5587199091,27.5987723014,44.9974760333,44.9974760333,137.995975092,131.251572304,159.738046565,209.928704057,218.371374036,66.8009352582,34.2292175885,34.2705926687,55.3563740123,44.9423725474,41.4743682629,68.0015703928,42.1392106592,107.312396414,105.658731149,36.9562394839,67.8604675314,35.0492218626,34.3954984014,38.872572003,62.7319235029,78.8027292935,142.808046966,62.595354536,54.3607033705,61.7242398776,52.4919425321,41.1511294141,61.4688965555,565.48381893,143.290985965,412.88620368,108.670474015,33.8141989334,46.4949575464,72.5047212776,93.4499068945,127.720386128,100.538770983,7.12132877363,890.51995422,41.7645829702,58.2965152044,31.9166563283,53.9974794449,213.11686833,65.2693832017,42.7414580369,303.155582601,95.1568067541,90.5142608004,131.587925073,173.406785113,109.551425589,95.5623130648,63.1248078125,34.7750329498,86.551691467,77.2092507889,86.3789836026,40.5742764341,228.516893915,22.4902460287,90.8018289777,82.0248865319,70.2303718092,37.8042105945,109.726389476,130.72129546,39.6322381003,43.5134124164,94.9720915296,121.740157902,51.5885064484,52.4518808896,50.3446621112,45.1148922535,124.452120948,155.806068019,122.808758244,40.8534129911,30.5392243856,32.4039946437,29.1783739857,75.3702576833,103.535437378,56.5505467595,105.326187212,226.784554459,113.667843794,146.923156631,47.5596335942,40.6675531336,33.5576474096,32.3487623395,160.640311208,1.11138227343,162.745405404,62.0078009092,95.9281321036,203.812769287,120.179971111,64.3191632374,100.481749401,55.1012581072,150.53250921,105.836622189,208.97470459,157.681769538,0.253270759614,256.318787941,237.032862426,166.777986864,260.370469732,191.704553345,338.134824237,98.4266140338,187.54028664,3.52421835106,228.047730353,251.398848963,240.027202973,87.5684745906,18.2304534262,0.104753901525,65.9578735311,75.6512568428,43.4050677908,75.3565386543 +18,257.460779388,45.1259495325,74.2111207615,217.914612267,164.225758791,137.412381433,92.929846024,81.4223865314,194.732479564,332.109138843,78.2204264802,193.253187585,831.018349306,108.808478288,52.0438535156,63.8852711232,188.761836767,69.3361524921,136.451757961,171.084862296,33.9424339199,2.24716854509,7.4400517642,179.247461218,58.6943087971,61.3080409177,65.8922490632,101.048733239,129.276871957,68.6230546443,298.378550321,205.095518979,51.8788189018,75.5425629663,97.8982303406,148.54488517,75.4694502399,74.0134758768,88.3489696553,202.376752946,57.8000375649,30.4737835243,232.325400257,137.139694592,1455.33008057,4.84918422467,153.8590863,330.053658162,624.386240394,145.09931983,237.652774864,76.0816084656,94.8244618513,91.5264104978,88.6495834302,91.5375270835,97.2269777382,109.756016693,3.65274473375,244.395555279,24.0592345568,83.4327543115,67.3412903034,83.5471783765,96.493301893,33.5169685851,36.5787710323,35.8764462657,5.04575420378,92.7711247301,65.6914169097,127.148799254,148.661550012,104.86259918,42.4570768809,55.1109878245,97.1204517713,49.1551910447,77.4863880888,114.118501675,73.565489002,123.101728774,190.605412687,36.9471561389,50.1742545236,43.927050347,230.310616245,178.054335461,245.504538675,54.2071897016,141.147326332,47.278824873,47.0852187609,47.1476458654,45.6733156677,36.5344483701,71.5672820901,49.4918396868,58.6390945904,64.5239370461,58.7746353267,170.976969325,220.421476729,135.105385693,190.658502062,122.916156535,160.479804801,37.8168207793,78.5037984082,140.063544109,143.873054504,149.856501666,39.482197431,122.56961371,183.205247677,36.6393193758,36.7866680359,43.0095664655,60.5036337189,41.2929577831,141.357700569,106.218391852,90.6573575365,44.4921612201,42.5031827907,63.7820480132,42.4753259666,148.010740081,24.1353076835,127.826510015,47.0996570214,41.4018039428,31.2488891618,1.41558925266,423.730398131,90.6122951341,48.794250243,28.5396675302,42.4753259666,155.857823205,206.151131482,49.6474371502,42.4068098741,29.2740999329,41.0698558816,65.0697348755,38.5047847714,49.5736288625,42.4763115491,43.5261621592,5.73496216977,168.090590357,84.0528621391,40.3394995116,119.95229813,35.3212214377,49.0841549059,56.3885075292,198.161853826,49.6664734668,232.825325534,156.66193189,331.269473472,111.699671415,35.7925412963,109.043224105,175.245750881,99.9694836656,99.07594631,115.21167704,206.869175412,72.265795077,90.0285245431,88.2702184621,195.326938671,46.8505545996,166.886885733,54.269390971,0.44914087962,61.4068031014,84.8411985222,535.162009156,396.001296287,144.191238237,116.333575793,138.602814089,27.5049452329,68.2610351349,175.058328277,76.8572860612,24.5392560301,120.682016307,138.401273303,66.7955159189,217.678519606,135.474559927,194.338079325,251.58112571,121.245343685,153.349053774,53.3259643182,42.8227487282,0.873741697088,119.484602213,232.262326175,43.4807755068,169.499115984,132.321770325,174.781211307,247.167700922,99.075547062,150.302193309,67.0295054226,140.483016422,86.3439482963,55.5358265131,87.3417475694,57.4785723106,113.316371472,99.8700037253,26.6138466961,55.6854374972,158.091087418,166.155506487,87.733410085,84.9692244313,231.479174849,194.992888142,100.154002361,86.0023119442,102.006734236,125.760712645,205.762211003,174.776269421,78.6017512998,243.458192955,95.3900850398,128.439255419,211.073111828,7.56148273315,178.869788069,22.7712754509,2.36915748036,418.809705827,136.429066582,11.0612454483,95.1895681086,162.217653849,243.989812655,19.3719612836,203.546327562,207.734383818,82.6306466661,224.27131719,138.70870778,117.275409098,106.01931273,183.908621961,100.894951504,252.967808978,276.610338202,274.603546168,96.0336264936,251.038224477,131.698316564,871.74991441,2.62077167332,17.8052947432,0.0446321042637,2.60591934656,66.696153897,190.118557117,279.916129857,66.0423569737,59.2765223789,117.48587807,56.7877934259,27.963243173,22.9783349152,67.6488744423,340.379351708,197.390876565,133.199506295,68.3024519481,224.055757073,245.746967062,175.852733764,175.51425376,60.4922454829,202.289757407,71.38303042,90.0993939869,72.3921617963,147.429594711,791.694766194,65.3683851242,74.4601652315,142.888308868,178.440764147,64.1800126856,88.9495321326,63.0020432369,505.206988921,218.816059595,93.2315784072,49.6722230171,24.6634137934,86.8907329822,655.070223511,49.6328968481,98.2494070054,284.082187691,36.78009308,34.777084206,2.4427095514,121.944466094,10.3544045025,147.457613959,5.76973511646,120.237803003,64.266250971,64.7030546156,93.2031007499,68.2837151876,143.410891161,10.3395557349,84.9138414295,7.03537296836,59.1253907613,26.5099901483,299.158950104,159.351683122,106.56091762,124.56317581,63.6733746039,33.1387253721,132.534039638,70.7577000292,16.1143236652,43.2464321119,80.5192668964,68.0470085708,52.1671488437,69.0347974649,149.673939308,68.0101082555,31.535677174,59.9554842838,44.0611925017,222.171450574,135.958326161,58.0956631049,38.9353657873,27.1638793496,44.2884196679,44.2884196679,135.821477028,129.183350461,157.220943636,206.62071221,214.930345196,65.7483066991,33.6898441224,33.7305672269,54.4840853237,44.2341844858,40.8208279444,66.9300225952,41.4751939591,105.62140073,103.993793413,36.3738944469,66.7911431892,34.4969270218,33.8535047335,38.2600299885,61.7434131776,77.5609801614,140.557721756,61.6089962225,53.5041041532,60.7516083205,51.6647906724,40.5026825938,60.5002886197,556.573099115,141.033050751,406.380070077,106.958078162,33.2813652035,45.7623043287,71.3622142156,91.9773520499,125.707807629,98.9545120021,7.00911306862,876.487415116,41.1064695378,57.3778966766,31.4137234902,53.1466037983,209.758638372,64.2408883681,42.0679513082,298.378550321,93.6573551074,89.0879649626,129.514403087,170.674294413,107.825147973,94.0564715745,62.1301065373,34.2270586934,85.1878365761,75.9926112001,85.017850189,39.9349194854,224.915995098,22.1358516602,89.3710017292,80.7323636386,69.1237032455,37.20850349,107.997354833,128.661429557,39.0077255015,42.8277414683,93.4755505685,119.821813997,50.7755906561,51.6253603089,49.5513464349,44.4039856795,122.491042765,153.350923997,120.873575665,40.2096574945,30.0579966957,31.8933824785,28.7185901571,74.1825963813,101.903957851,55.6594406647,103.666489601,223.210953314,111.876700928,144.60798679,46.8102035393,40.0267263594,33.0288563395,31.8390205079,158.108990671,1.09386945394,160.180913441,61.0307010846,94.4165261496,200.601150455,118.286212149,63.3056416773,98.8983889487,54.2329894565,148.160464303,104.1688813,205.681745572,155.197068789,0.249279797007,252.279795442,233.297771601,164.149950725,256.267632079,188.68373205,332.806599843,96.8756377713,184.585084577,3.46868480396,224.454224471,247.437383348,236.244928251,86.1885975445,17.9431835572,0.103103221815,64.9185296791,74.4591676426,42.721104104,74.1690935326 +19,248.399531927,43.5377565791,71.5992847719,210.245179153,158.445887218,132.576197859,89.6592106547,78.5567524146,187.878933987,320.420666921,75.4674842943,186.451705202,801.770931797,104.978999678,50.2121872066,61.6368500157,182.118425999,66.8958894075,131.649383214,165.063586831,32.7478411206,2.16808018717,7.17820158915,172.938905768,56.6285819016,59.1503245798,63.5731930272,97.4923563039,124.727014963,66.2078888039,287.877215369,197.877249637,50.0529609317,72.8838673061,94.4527343192,143.316896788,72.8133277581,71.4085959339,85.2395567233,195.254169681,55.7657842513,29.4012687349,224.148784199,132.313108142,1404.11021699,4.67851878061,148.444066357,318.437528206,602.411172003,139.992596987,229.288663609,73.4039412764,91.4871461011,88.3051685803,85.5295904953,88.3158939214,93.8051062314,105.893189781,3.52418758411,235.794134084,23.2124777088,80.4963659617,64.9712357374,80.6067629094,93.0972518663,32.3373498984,35.2913932154,34.613786538,4.86817054396,89.5060755045,63.3794290953,122.673839079,143.429455649,101.171994488,40.9628140228,53.1713747321,93.7023294109,47.4251902614,74.7592801427,110.102138538,70.9763758098,118.769203914,183.897117872,35.6468131293,48.4083881363,42.3810522561,222.204909848,171.787771681,236.864087175,52.299385487,136.1796926,45.61486218,45.4280699713,45.4882999745,44.0658583432,35.2486304747,69.048495133,47.7499906652,56.5753109408,62.2530383015,56.7060813654,164.959491119,212.663815345,130.350396075,183.948338783,118.590162828,154.831770848,36.4858702087,75.7408830405,135.134053736,138.809489666,144.582351373,38.0926344768,118.255816465,176.757399235,35.3498105799,35.4919733523,41.4958589181,58.3742282278,39.8396657138,136.382662786,102.480070483,87.4667016522,42.926274238,41.0072972502,61.5372598092,40.980420838,142.801550759,23.2858734669,123.327698033,45.442000082,39.9446810687,30.1490947845,1.36576805445,408.81734612,87.4232252063,47.0769526525,27.5352233172,40.980420838,150.372458374,198.895710206,47.9001119271,40.9143161468,28.2438076131,39.6244158103,62.7796269559,37.1496215343,47.8289012978,40.9813717331,41.9942731963,5.53312206224,162.174697309,81.0946492983,38.9197640926,115.730616445,34.0781026652,47.3566542161,54.403932551,191.187612549,47.9184782667,224.63111477,151.148266718,319.610553297,107.768438214,34.5328345764,105.205483693,169.078034311,96.4510905646,95.5890009624,111.156839956,199.588482816,69.722424185,86.8600002292,85.1635771525,188.452471301,45.201664738,161.013356674,52.3593976032,0.433333514069,59.2456108609,81.8552404417,516.32715824,382.064160896,139.116475027,112.239253851,133.724733629,26.5369177309,65.8586104521,168.897207984,74.1523191541,23.6756049842,116.434652432,133.530286005,64.4446697029,210.017395696,130.706577343,187.498414534,242.726810728,116.978153687,147.951984258,51.4491745411,41.3156161697,0.842990645404,115.279380932,224.087929988,41.9504839122,163.53365034,127.664749135,168.629844055,238.468714973,95.5886157658,145.012357044,64.670424023,135.538762859,83.3051014317,53.5812613628,84.2677834875,55.4556328609,109.328239044,96.3551117881,25.6771811141,53.7256068376,152.527123587,160.307718079,84.6456615696,81.978760521,223.332341416,188.13017757,96.6291151853,82.9754888586,98.4166407716,121.334605722,198.520477655,168.625076097,75.8353885123,234.88976192,92.0328623674,123.918878064,203.644463072,7.29535883507,172.574524702,21.9698479001,2.28577576198,404.069835963,131.627490451,10.6719485525,91.8394025637,156.508456869,235.402671439,18.690171477,196.382581503,200.423240497,79.7224882312,216.378161937,133.826900427,113.147939643,102.287997885,177.436018492,97.3439868665,244.064690133,266.875128317,264.938964669,92.6537545914,242.203016724,127.063237624,841.068962862,2.52853447616,17.1786432502,0.0430612920322,2.51420487216,64.3488046947,183.427397023,270.064573722,63.7180179392,57.1903046678,113.351001229,54.7891657118,26.9790860257,22.1696199747,65.2679945538,328.399812364,190.443769579,128.511593476,65.8985696142,216.170188387,237.097983374,169.663654632,169.33708732,58.363240797,195.170235922,68.8707281399,86.9283754462,69.8443434692,142.24085861,763.831329259,63.0677663073,71.8395642036,137.859401828,172.160600136,61.9212182458,85.8189826017,60.7847070445,487.426395092,211.114900339,89.9503236659,47.9240254635,23.7953930559,83.8326422103,632.015242425,47.8860833661,94.7915514368,274.08400852,35.4856297998,33.5531161644,2.35673919207,117.652670713,9.98998464122,142.267891729,5.56667118651,116.006073068,62.0044214031,62.425851887,89.9228482704,65.8804922878,138.36359201,9.97565847115,81.9253267057,6.78776533044,57.0444920779,25.5769797633,288.630149238,153.74335304,102.810541169,120.179215802,61.4324111248,31.9724188308,127.869547699,68.2674060465,15.5471853433,41.724388156,77.6854177781,65.6521164825,50.3311431962,66.6051404713,144.406214237,65.6165148617,30.4257893869,57.8453707318,42.5104733218,214.352199436,131.173317584,56.0510053476,37.5650483929,26.2078555492,42.7297033027,42.7297033027,131.041284811,124.636784926,151.687604229,199.348764194,207.36594238,63.4343166673,32.504141138,32.5434310062,52.5665358588,42.67737691,39.3841523295,64.574442461,40.0154881475,101.904090266,100.333766054,35.093727194,64.44045087,33.2828190201,32.6620417677,36.9134808155,59.5703740562,74.8312469718,135.610838964,59.4406878615,51.6210448032,58.6134754448,49.8464653555,39.0772039999,58.3710008583,536.98469193,136.069438911,392.077657155,103.193723776,32.1100385003,44.1517150775,68.850644555,88.7402393928,121.28356268,95.471840495,6.76242963924,845.639728788,39.659740861,55.3585004728,30.3081278229,51.2761265558,202.376252077,61.9799514287,40.5873835966,287.877215369,90.3611152952,85.9525433338,124.956186263,164.66746875,104.03027735,90.7461849895,59.9434578702,33.0224486188,82.1896786832,73.3180762404,82.0256749111,38.5294231273,217.000150618,21.3567876407,86.2256187145,77.8910146505,66.6909172423,35.8989624463,104.196423447,124.133232856,37.6348614308,41.320433192,90.1857093072,115.604724659,48.9885604414,49.8084227307,47.8074030929,42.8412020065,118.180010798,147.953788659,116.61946992,38.7944918226,29.0001153871,30.7709053708,27.7078488211,71.5717642979,98.317481567,53.700524955,100.017981592,215.355117218,107.939237231,139.518556252,45.16273382,38.6179988887,31.8664166028,30.7184566519,152.544396741,1.05537107824,154.543398871,58.8827456309,91.0935675622,193.541058936,114.123167812,61.0776204376,95.4176926741,52.324277228,142.946005487,100.502691781,198.442844178,149.734958992,0.240506476531,243.400891008,225.086933253,158.372747192,247.248377046,182.043070152,321.093581011,93.4661315591,178.088673229,3.34660558302,216.554631856,238.728906015,227.930365694,83.1552181984,17.3116791128,0.0994745378308,62.6337433765,71.8386017246,41.2175488946,71.5587366774 +20,245.074901564,42.9550383013,70.6409852361,207.431214485,156.325214915,130.801770792,88.459193361,77.5053327026,185.364323739,316.132090892,74.4574119815,183.956197278,791.039864941,103.573939181,49.5401369781,60.8118897533,179.680915574,66.0005410815,129.887360825,162.854341877,32.3095372878,2.13906215656,7.08212706449,170.624255927,55.8706533284,58.3586444846,62.7223163521,96.1874985796,123.057642973,65.3217488193,284.024207581,195.228819879,49.3830418204,71.9083746538,93.18855952,141.398714,71.8387792216,70.4528486167,84.0986929856,192.640847764,55.0194035391,29.0077561144,221.148730825,130.542202322,1385.31731741,4.61590052416,146.457260478,314.175494913,594.348377126,138.118907311,226.219817034,72.4214878472,90.2626633438,87.1232740638,84.3848448861,87.1338558547,92.5495994195,104.475893576,3.47701913352,232.638216956,22.9017971387,79.4189860636,64.1016473715,79.5279054359,91.8512191227,31.9045401644,34.8190459588,34.1505085084,4.80301394935,88.3081078031,62.5311457965,121.031947216,141.50976635,99.8178877302,40.414559305,52.4597181299,92.4481981851,46.7904417726,73.7586865798,108.628508898,70.0264134155,117.17957249,181.435801069,35.1697088596,47.7604803251,41.8138155466,219.230873687,169.488528855,233.693849574,51.599399768,134.35703562,45.0043434941,44.8200513507,44.8794752231,43.4760718428,34.7768555634,68.1243358898,47.110894981,55.8180953571,61.4198308485,55.9471155235,162.751639402,209.817479154,128.605759597,181.486336429,117.002927728,152.75947062,35.9975358266,74.7271515027,133.325391801,136.951634941,142.647231481,37.5827948317,116.673056324,174.391641893,34.8766814533,35.01694149,40.940469818,57.5929355656,39.3064434438,134.55728921,101.108456167,86.2960293493,42.3517401654,40.4584471599,60.7136324825,40.431930467,140.890265469,22.9742105529,121.677054787,44.8337950179,39.4100532516,29.7455731064,1.34748833425,403.345650777,86.2531348011,46.4468650474,27.1666862319,40.431930467,148.359842501,196.233649162,47.2590069891,40.3667105346,27.8657866899,39.0940744892,61.9393715321,36.6524033682,47.1887494575,40.4328686352,41.4322132171,5.45906561991,160.004117845,80.0092618531,38.3988539748,114.181654101,33.6219943386,46.7228230278,53.6757791417,188.628718268,47.2771275099,221.624605785,149.125267267,315.332819995,106.326043299,34.0706400244,103.797391887,166.815059167,95.1601693549,94.309618137,109.669093975,196.917149561,68.7892449369,85.6974481428,84.023730341,185.930184712,44.5966763716,158.858320831,51.6586086697,0.427533689298,58.4526554345,80.7596730884,509.416529487,376.950535681,137.254511547,110.737020621,131.934934324,26.1817421726,64.9771452808,166.636653057,73.1598492817,23.3587258235,114.876267125,131.743089226,63.582129005,207.206479729,128.957173661,184.988897237,239.47810523,115.412494048,145.971764508,50.76056822,40.7626394756,0.831707885433,113.736458016,221.0886911,41.3890100178,161.344882369,125.956057907,166.372867581,235.277000704,94.309238096,143.071481868,63.8048617829,133.724684213,82.1901288411,52.8641187537,83.1399261625,54.7134032786,107.864967434,95.0654751781,25.3335124473,53.006532279,150.485669232,158.162126649,83.5127466467,80.8815399496,220.34321548,185.612204627,95.3358112575,81.8649278704,97.0994122341,119.710638433,195.863438801,166.368163439,74.8203920939,231.745949094,90.8010756315,122.260322344,200.918843744,7.19771625376,170.264751812,21.6757989426,2.25518246962,398.661682272,129.865761079,10.5291130007,90.6102051335,154.413715532,232.251993726,18.4400183825,193.75415669,197.740734674,78.6554660716,213.482112168,132.0357337,111.633544375,100.918954308,175.061178371,96.0411149493,240.798078183,263.303216776,261.39296714,91.4136577064,238.961321794,125.362597149,829.811923085,2.4946920513,16.94872076,0.0424849508537,2.48055423767,63.4875470738,180.97236706,266.449973989,62.8652028977,56.4248578818,111.833888143,54.0558562628,26.6179923961,21.8728972267,64.3944343069,324.004441813,187.894831045,126.791567909,65.0165696212,213.276922179,233.924615258,167.392841425,167.070644957,57.5820951928,192.558037391,67.9489481666,85.7649082119,68.9095324284,140.337077738,753.608053838,62.2236543672,70.8780487186,136.014263274,169.856367298,61.0924519406,84.6703637091,59.9711520386,480.90258009,208.289295144,88.7464100558,47.2826004619,23.4769106271,82.7106089055,623.556220588,47.2451661893,93.5228418397,270.415611846,35.0106828409,33.104034365,2.32519611058,116.077983199,9.85627663458,140.363749041,5.49216571581,114.453423955,61.1745414897,61.5903314615,88.7193023966,64.9987342455,136.511705271,9.84214220891,80.8288213035,6.69691648487,56.2809968774,25.234652212,284.767064028,151.68562042,101.434503766,118.57071249,60.6101871145,31.5444934092,126.158115405,67.3537010601,15.3390986209,41.1659403736,76.6456601879,64.7734150688,49.6575008363,65.7136835888,142.473451801,64.7382899474,30.0185643714,57.071156407,41.9415044141,211.483265565,129.417666955,55.3008071777,37.0622700684,25.8570842267,42.1578001759,42.1578001759,129.287401336,122.968620593,149.657386174,196.680639383,204.590514009,62.5852987419,32.0690990358,32.1078630403,51.8629745443,42.1061741303,38.8570267442,63.7101648578,39.4799126339,100.540184988,98.990878318,34.6240255401,63.5779666378,32.8373549332,32.2248862911,36.4194232052,58.7730719325,73.82969019,133.795795637,58.6451214845,50.9301381353,57.8289806487,49.1793100234,38.5541866741,57.5897513918,529.797578503,134.248257588,386.830009249,101.812557779,31.6802711487,43.560779453,67.9291333845,87.5525217992,119.660278563,94.1940257694,6.67191989176,834.321512942,39.1289267421,54.6175709304,29.902477614,50.5898363445,199.667606746,61.1503990265,40.0441537168,284.024207581,89.1517035655,84.8021368369,123.283746997,162.463525521,102.637914748,89.5316194079,59.1411623161,32.5804693828,81.0896351398,72.3367720465,80.9278264279,38.0137374132,214.095776024,21.0709440077,85.0715573206,76.8485053095,65.7983123058,35.4184833583,102.80183711,122.471808167,37.1311487142,40.7673920258,88.9786452473,114.057447276,48.3328874849,49.1417765689,47.167538989,42.2678065581,116.598265256,145.973544758,115.058610978,38.2752583746,28.6119718854,30.3590612522,27.3370012806,70.6138331018,97.0015801967,52.9817860975,98.6793203796,212.47276009,106.494556307,137.651211234,44.5582665129,38.1011276584,31.4399099368,30.3073145177,150.502711198,1.04124577493,152.474958273,58.0946468673,89.8743525409,190.950665645,112.59571989,60.2601450187,94.140602674,51.6239583528,141.032786785,99.1575431079,195.786844386,147.730875542,0.237287488443,240.143163482,222.0743235,156.25305381,243.939153977,179.606568305,316.795998561,92.2151616539,175.705097842,3.30181392642,213.656220177,235.533709295,224.879698857,82.0422516758,17.0799760433,0.0981431501818,61.7954404886,70.8770991216,40.665884753,70.6009798457 +21,248.043500622,43.4753538676,71.496661443,209.94383452,158.218786564,132.386176258,89.5307020154,78.4441569401,187.609646917,319.961407648,75.3593166711,186.184463781,800.621752692,104.828533169,50.1402180268,61.5485058549,181.857395471,66.8000074601,131.460690017,164.827001022,32.7009036061,2.16497267559,7.16791306539,172.691032256,56.5474160966,59.0655443584,63.4820735039,97.3526204088,124.548243604,66.1129929685,287.464600659,197.593631971,49.9812199711,72.7794027837,94.3173551175,143.111480532,72.7089643402,71.3062459196,85.1173827785,194.974311678,55.6858550951,29.3591278661,223.827511515,132.123463627,1402.09770437,4.67181305481,148.231301323,317.981111368,601.547735448,139.791945453,228.960023931,73.2987313315,91.35601748,88.1786006909,85.4070008448,88.1893106594,93.6706552757,105.741412962,3.5191363624,235.456170108,23.1792072405,80.3809904372,64.8781123981,80.4912291528,92.963815479,32.2910007414,35.240810025,34.564174562,4.86119299009,89.377786354,63.2885872941,122.498010538,143.223878062,101.026984564,40.9041019791,53.0951641458,93.5680257653,47.3572156872,74.6521275892,109.944329029,70.874645297,118.598971891,183.633537942,35.5957205152,48.3390043435,42.3203074501,221.886423321,171.54154809,236.524589634,52.2244247238,135.984506106,45.5494823509,45.3629578717,45.4231015471,44.0026987028,35.1981085762,68.9495278536,47.6815505542,56.4942214891,62.1638109574,56.6248044803,164.723054509,212.359004079,130.163564718,183.684685439,118.420187425,154.60985031,36.4335749732,75.6323235546,134.940365957,138.610533881,144.375121339,38.0380362644,118.086320281,176.504052672,35.2991436598,35.4411026702,41.4363828607,58.2905603861,39.7825634798,136.187185374,102.333185692,87.3413355383,42.8647479647,40.9485214487,61.4490583911,40.9216835584,142.596873149,23.2524978004,123.150932316,45.3768680163,39.8874283161,30.1058820557,1.36381049769,408.231387795,87.2979214072,47.0094772076,27.4957570532,40.9216835584,150.156929374,198.610632779,47.8314566471,40.8556736152,28.2033257344,39.5676220942,62.6896448517,37.0963749434,47.7603480841,40.9226330907,41.9340827611,5.52519143267,161.942252148,80.9784162536,38.8639803545,115.564739633,34.0292584854,47.2887778747,54.3259553383,190.913583147,47.8497966623,224.309150761,150.931625751,319.15245516,107.6139736,34.4833386289,105.054692564,168.83569459,96.3128471168,95.4519931485,110.997518742,199.302412437,69.6224909624,86.7355037011,85.0415121093,188.182362179,45.1368771452,160.782576063,52.2843508245,0.432712416695,59.1606940656,81.737917241,515.587105991,381.516547939,138.91707924,112.078381213,133.53306583,26.4988823393,65.7642152377,168.655127442,74.0460365584,23.6416707151,116.267766524,133.338896908,64.3523010912,209.716377545,130.519235471,187.229672863,242.378910139,116.810488779,147.739924526,51.3754323861,41.2563984544,0.84178238604,115.114150876,223.766744527,41.8903562403,163.29925739,127.481767118,168.388146725,238.126917517,95.451608504,144.804510683,64.5777318374,135.34449501,83.1857001437,53.5044632855,84.1470023863,55.376148245,109.171538766,96.2170059068,25.6403779838,53.6486018697,152.308506303,160.077948861,84.5243388554,81.8612602788,223.012238941,187.860530393,96.4906165746,82.8565600047,98.2755800986,121.160696706,198.235938049,168.383385601,75.7266935716,234.553094181,91.9009515707,123.741265008,203.352579253,7.28490238971,172.327173458,21.9383584944,2.28249955721,403.49068225,131.438828633,10.6566524376,91.7077690531,156.284133136,235.065268546,18.6633828349,196.101106146,200.135973659,79.6082219064,216.068026894,133.635086192,112.985764584,102.141388391,177.181699263,97.2044636295,243.714871961,266.49261592,264.559227377,92.5209538692,241.85586689,126.881117754,839.863457848,2.52491032508,17.1540210831,0.0429995722378,2.51060125972,64.2565734864,183.164490345,269.677489889,63.6266908382,57.108333745,113.188535122,54.710636344,26.9404168738,22.1378442355,65.1744458697,327.929116573,190.170806328,128.327397676,65.8041171263,215.860351433,236.758150589,169.42047554,169.094376297,58.2795887036,194.890498221,68.772015654,86.8037809158,69.7442354995,142.03698464,762.736529026,62.9773712126,71.736596482,137.661807805,171.91384217,61.8324664997,85.6959781652,60.697584261,486.727766336,210.812309134,89.821397774,47.8553359083,23.7612870945,83.7124847976,631.109374325,47.8174481933,94.6556866082,273.691163628,35.4347682099,33.5050244483,2.35336127535,117.484039018,9.97566598591,142.063979013,5.55869247095,115.839801444,61.9155504018,62.336376849,89.793961759,65.7860657101,138.16527533,9.96136034956,81.8079030503,6.77803641938,56.9627301484,25.5403202518,288.216455347,153.522992533,102.663182714,120.006962808,61.3443599864,31.9265927299,127.686272144,68.1695583029,15.5249015465,41.6645845473,77.5740711885,65.5580172361,50.2590035166,66.5096752546,144.199236661,65.5224666431,30.3821800715,57.7624609022,42.4495430165,214.044968207,130.985306732,55.9706673837,37.5112063702,26.1702918027,42.6684587748,42.6684587748,130.853463202,124.458142893,151.470190229,199.063037404,207.06872456,63.3433961956,32.4575529189,32.4967864729,52.4911921886,42.6162073815,39.3277029833,64.4818878459,39.958133907,101.758031029,100.189957564,35.0434273186,64.3480883052,33.2351147212,32.6152272295,36.8605726853,59.4849917775,74.7239912682,135.416467808,59.3554914622,51.5470563064,58.5294646899,49.7750203652,39.021194603,58.2873376424,536.215031216,135.874410443,391.515691844,103.045816105,32.0640151489,44.0884323787,68.751960855,88.613047916,121.109726825,95.3350006073,6.75273704184,844.427672491,39.6028965134,55.2791550768,30.264687152,51.2026324306,202.086185985,61.8911155002,40.5292096578,287.464600659,90.2316006152,85.8293474646,124.777086433,164.431450697,103.881170646,90.6161183887,59.8575408501,32.9751175091,82.0718761318,73.2129893659,81.9081074265,38.4741988653,216.689124078,21.3261769349,86.1020314461,77.7793733787,66.5953290816,35.8475084263,104.047078605,123.955312565,37.580919345,41.2612085724,90.0564460369,115.439028288,48.9183450871,49.7370322669,47.7388806926,42.7797976674,118.010623267,147.741726341,116.452319114,38.7388876374,28.9585494917,30.7268014004,27.6681351328,71.4691804141,98.1765630191,53.6235559371,99.8746257152,215.046448506,107.784527811,139.318584161,45.0980020269,38.5626476709,31.8207424349,30.6744278563,152.325754699,1.05385841379,154.321891658,58.7983489308,90.9630030577,193.263656336,113.959594958,60.9900778211,95.2809303965,52.2492807873,142.741120829,100.358641169,198.158415844,149.520343721,0.24016175835,243.05202426,224.764315919,158.14575137,246.893995692,181.782147632,320.633357249,93.3321664561,177.83341855,3.34180888978,216.244243878,238.386735627,227.603672866,83.0360317382,17.2868662653,0.0993319608729,62.5439703674,71.7356353825,41.1584717391,71.4561714662 +22,235.532259662,41.2824698507,67.8903909427,199.354329474,150.23827767,125.708656602,85.0147998315,74.4874568175,178.146671701,303.822648685,71.5582149811,176.793374381,760.238627796,99.5410128972,47.6111602288,58.4440173861,172.68456212,63.4306348004,124.829851614,156.51317574,31.0514796801,2.05577209285,6.80636564604,163.98054756,53.6951811191,56.0862957347,60.2800564537,92.4421829698,118.266066958,62.7782731169,272.964971049,187.627067496,47.4601819881,69.1084311888,89.5600155639,135.892979694,69.0415456395,67.709579922,80.8240871143,185.139864945,52.8770770002,27.8782621221,212.537717899,125.459195119,1331.37631,4.43616818325,140.754558236,301.942237965,571.205845224,132.740880985,217.411347903,69.6015649578,86.7480469228,83.7308980987,81.0990969308,83.7410678595,88.9459350712,100.407847303,3.34163216299,223.579830385,22.0100548689,76.3265970046,61.605679558,76.431275312,88.2747480653,30.6622521949,33.463273969,32.820767799,4.61599585049,84.8695971909,60.0963296311,116.319247043,135.999707926,95.9312132811,38.8409111484,50.4170597377,88.8484821631,44.9685317058,70.8866963078,104.398769524,67.2997491074,112.616874754,174.37111649,33.8002828788,45.9007992321,40.1856836335,210.69453762,162.889043036,224.594359145,49.5902401552,129.125487755,43.2519799054,43.0748636659,43.1319737121,41.783215568,33.4227263686,65.4717340197,45.2765072179,53.6446696319,59.0282866808,53.7686660556,156.414472259,201.647678592,123.598152932,174.419684122,112.447108124,146.811375095,34.5958762052,71.817451478,128.134013727,131.619059464,137.092882832,36.1194089425,112.13008114,167.601240352,33.5186652724,33.6534639123,39.3463439395,55.3504017252,37.77594272,129.317943941,97.1715300099,82.9358643532,40.7026627293,38.8830901131,58.349586023,38.8576059181,135.404328956,22.0796486745,116.939235639,43.088072187,37.8755182049,28.5873502508,1.29502029872,387.640317087,82.8946400181,44.6383330525,26.108879183,38.8576059181,142.583058176,188.592771083,45.4188520917,38.7949254969,26.7807583088,37.5718429145,59.5275976687,35.2252447405,45.3513302236,38.8585075562,39.8189400038,5.24650240761,153.773932748,76.8938888392,36.9036926564,109.735688276,32.3128327314,44.9035458764,51.5857701857,181.283958363,45.4362670419,212.995063403,143.318679097,303.054499523,102.185956535,32.7440092065,99.7557648792,160.319671988,91.4548555357,90.6374227875,105.398836659,189.249657579,66.1107530679,82.3605904951,80.7520433392,178.690499375,42.8601863846,152.672750383,49.6471436011,0.410886530116,56.1766461187,77.6150808209,489.581044544,362.272965869,131.910142766,106.425180745,126.797697403,25.1622865355,62.447087632,160.148212593,70.3111763297,22.4491918223,110.403254698,126.613322304,61.1063900183,199.138345362,123.935883757,177.785863426,230.153389452,110.918602202,140.28796634,48.7840707404,39.1754378935,0.799323130947,109.307827083,212.480015979,39.7774190358,155.062491046,121.051624412,159.894698306,226.115866083,90.6370575444,137.500613905,61.3204501042,128.51775863,78.9898379814,50.8057139497,79.9026524226,52.5829916722,103.664958574,91.3638485281,24.3470848863,50.9425822263,144.6261102,152.003664369,80.2609561516,77.7322024724,211.763567432,178.384900688,91.6236583546,78.6772995738,93.3185888453,115.049386926,188.236975845,159.890177331,71.9070614992,222.722305341,87.2654946988,117.499792124,193.095535176,6.91745406337,163.635041692,20.8317941672,2.16737095325,383.13873133,124.809092912,10.119134034,87.0820562503,148.40120759,223.208645782,17.7220073134,186.209824232,190.041174219,75.5928066905,205.169619391,126.894571888,107.286795967,96.9894069088,168.244706649,92.3014991748,231.421965754,253.05080703,251.214938035,87.8542242643,229.65672835,120.481271624,797.500992927,2.39755459353,16.2887773228,0.0408306865058,2.3839672732,61.0154908856,173.925727515,256.075036896,60.4173793323,54.2278062469,107.479338819,51.9510479951,25.5815501984,21.0212179064,61.8870660568,311.388468724,180.578646987,121.854601614,62.484976881,204.97241902,224.816139355,160.87495676,160.565305866,55.3399834511,185.060279014,65.3031754831,82.4254238273,66.2263568465,134.872681058,724.26432363,59.8008111969,68.1182237291,130.718186852,163.242558713,58.7136551398,81.3734983204,57.6360160263,462.177361464,200.178998484,85.2908329818,45.44152689,22.5627747868,79.4900517728,599.276403761,45.4055502202,89.8812817141,259.886262117,33.6474489603,31.8150409044,2.23465842723,111.558178767,9.47249633798,134.898313843,5.27831366341,109.99687605,58.7925483144,59.1921483998,85.2647808313,62.467835973,131.196259623,9.4589122737,77.6815365667,6.43615426298,54.0895468527,24.2520740367,273.678902409,145.779338103,97.4848820796,113.953847025,58.250168575,30.316224816,121.245814265,64.7311059029,14.7418300948,39.5630352004,73.6612579344,62.2512901967,47.7239542136,63.1549468657,136.925869725,62.2175327651,28.8497118762,54.8489394233,40.3084005961,203.248603188,124.378446522,53.1475234411,35.6191521927,24.8502700086,40.5162743081,40.5162743081,124.253253141,118.1805109,143.830078542,189.022356553,196.624239212,60.1483739878,30.8204035298,30.8576581549,49.8435519478,40.4666584597,37.3440252527,61.2294404553,37.9426574267,96.6253859781,95.1364056759,33.275847188,61.1023897239,31.5587453443,30.9701247947,35.0013362787,56.4845863497,70.954935204,128.586101141,56.3616179921,48.9470380008,55.5772557664,47.2643830295,37.0529770647,55.3473415357,509.16850331,129.020945285,371.767755906,97.8482155558,30.4467156887,41.8646248626,65.2841322421,84.143431934,115.00098795,90.5263313156,6.41213097858,801.83498993,37.6053381024,52.4908908109,28.7381452649,48.6199867636,191.893018412,58.7693459021,38.4849282851,272.964971049,85.6803453128,81.5001405117,118.483367022,156.137576857,98.6414350603,86.0454681233,56.8383441571,31.3118623148,77.9321949239,69.5201477917,77.7766866637,36.5335716306,205.759388617,20.2504908649,81.7590703936,73.8562047427,63.2362803389,34.039370682,98.7989746706,117.703043185,35.6853488656,39.1800053906,85.5140254797,109.616317774,46.4509181995,47.2283110397,45.330945641,40.6219972999,112.058202261,140.289677271,110.578498509,36.7849095791,27.4978888027,29.176950607,26.2725625632,67.8642960488,93.224566158,50.918799603,94.8369790729,204.199569116,102.347908042,132.291395897,42.8232721157,36.6175591045,30.2157135786,29.127218769,144.642488593,1.00070210645,146.537940992,55.8325775655,86.3748560319,183.515494554,108.211506624,57.9137562975,90.4749883903,49.6138424862,135.541300826,95.2965809297,188.163363837,141.978581716,0.228048070145,230.792551895,213.427270165,150.168926349,234.440735422,172.61310976,304.460705347,88.6245195278,168.863553407,3.17324903574,205.336948043,226.362579044,216.123410785,78.8477188061,16.4149218332,0.0943216860851,59.3892709624,68.1173111072,39.0824505729,67.8519432683 +23,220.383378324,38.6272784217,63.5238405693,186.532327572,140.575304776,117.62337128,79.5468477282,69.696598673,166.6886965,284.281490038,66.9557587866,165.422440083,711.341866149,93.1387689125,44.5489223099,54.6850355568,161.577896969,59.3509254597,116.801088963,146.446616151,29.0543214916,1.9235496638,6.36859620562,153.433704167,50.2416332767,52.4789570203,56.402984907,86.4965190407,110.659471508,58.7405221464,255.408505698,175.55933552,44.4076546348,64.6635393297,83.799725868,127.152662648,64.6009557018,63.3546588885,75.625671803,173.232103991,49.4761476918,26.0851978285,198.86779144,117.389954573,1245.74531506,4.15084427263,131.701555885,282.522022916,534.467227842,124.203299519,203.427961017,65.1249558938,81.1686164405,78.3455235371,75.8829936345,78.3550392028,83.2251415892,93.9498505668,3.12670623656,209.199701205,20.5944198729,71.4174496861,57.6433470491,71.5153953308,82.5971237539,28.6901282034,31.3109948406,30.7098131573,4.31910584699,79.4109841802,56.2310749618,108.837866474,127.252526371,89.7611431231,36.3427550365,47.1743529575,83.133956623,42.0762614422,66.327430616,97.6840860508,62.9711874286,105.373622069,163.155976125,31.6263281294,42.9485677124,37.6010349161,197.143160184,152.412402649,210.148977859,46.4007124671,120.820439884,40.4701142189,40.3043896796,40.3578265419,39.0958173515,31.2730551666,61.2607459747,42.3644286932,50.1943705696,55.2317260238,50.3103918348,146.354261046,188.678173865,115.648610245,163.201420001,105.21477443,137.36881252,32.3707507631,67.1983218013,119.892735135,123.153630921,128.275390837,33.7962934557,104.918137876,156.821522509,31.3628235056,31.4889522138,36.8156795784,51.7903939838,35.3462828742,121.00051774,90.9216855952,77.6016244935,38.0847631315,36.3822211493,54.596677797,36.3583760378,126.695440777,20.6595375686,109.417978861,40.3167486597,35.439453898,26.7486790755,1.21172763697,362.708203016,77.5630516107,41.7673003901,24.4296174412,36.3583760378,133.412451007,176.462927322,42.4976182793,36.299727074,25.0582828809,35.1553102885,55.6989225025,32.9596397935,42.4344392591,36.3592196847,37.2578793747,4.90905970432,143.883554829,71.9482546433,34.5301338846,102.677746733,30.2345472794,42.0154553485,48.2678946965,169.624200257,42.5139131408,199.295721556,134.100758519,283.562746424,95.6135959923,30.637991497,93.3397085517,150.008287507,85.5726941865,84.8078367913,98.6198312179,177.077564423,61.8586648197,77.0633508978,75.5582617142,167.197546455,40.1035199364,142.853197926,46.4539560171,0.384459274261,52.56349628,72.6230612519,458.092342488,338.972420204,123.425992429,99.5801632654,118.642367536,23.5439074078,58.4306377334,149.847855978,65.7889267301,21.0053125697,103.302376851,118.46985101,57.176170639,186.33023505,115.964619017,166.351094565,215.350464403,103.784578416,131.264974083,45.6463939742,36.6557658076,0.747912545923,102.277404566,198.813800772,37.2190289377,145.089236086,113.265868454,149.610647133,211.572625047,84.8074950398,128.656897605,57.3764628833,120.251798467,73.909397263,47.5380098441,74.7635016249,49.2009772408,96.9974720962,85.4875405357,22.7811376127,47.6660750748,135.324098727,142.227145952,75.0987601002,72.732649914,198.143432512,166.911603158,85.7306400048,73.6169605912,87.3165565502,107.649680775,176.130015998,149.606416937,67.2821683165,208.397330156,81.6527831917,109.942481671,180.676084213,6.47253967705,153.110420424,19.4919421302,2.0279707478,358.496148674,116.781665411,9.46829511734,81.4811430731,138.856390725,208.852390323,16.5821694574,174.233246012,177.818172573,70.7308550402,191.97359171,118.733011282,100.386361421,90.7512762239,157.423602566,86.3648836967,216.537448859,236.775173902,235.057383685,82.2036470554,214.885747376,112.732199417,746.207603537,2.24334951737,15.2411214512,0.0382045527197,2.23063610155,57.0911179249,162.739233522,239.604892411,56.5314755008,50.7400012081,100.566520369,48.6096786957,23.9362050174,19.6691825797,57.9066354297,291.360694315,168.964252851,114.01720005,58.466090035,191.789074803,210.356493687,150.527857667,150.238122792,51.7806457886,173.15763684,61.103028727,77.1240143024,61.966833235,126.197987222,677.681344671,55.9545635788,63.7370196864,122.310700318,152.743181028,54.937330844,76.1397461717,53.9290029453,432.451199893,187.30395581,79.8051270751,42.5188346878,21.1115901448,74.3774384791,560.732354033,42.4851719529,84.1003289345,243.170988583,31.4833241293,29.7687722527,2.09093044962,104.383018919,8.86324763802,126.221971366,4.93882493492,102.922135523,55.011149795,55.3850485421,79.7807505377,58.4500515899,122.758024573,8.85053726883,72.6852427203,6.02219594855,50.6106343332,22.6922376376,256.076518671,136.403153721,91.2148835963,106.624603426,54.5036546456,28.3663564922,113.447568472,60.5677533176,13.7936702328,37.0184337667,68.9235389602,58.247433529,44.6544616507,59.092969125,128.11911962,58.215847296,26.9941662178,51.3211845586,37.7158590121,190.176130754,116.378717185,49.7291996532,33.3282120442,23.2519590506,37.9103627755,37.9103627755,116.261575948,110.579418215,134.579265954,176.864882864,183.977830298,56.2797719479,28.8381076162,28.8729661113,46.6377318406,37.8639381019,34.9421453232,57.2913067611,35.5022748828,90.4106682639,89.0174556749,31.1356229096,57.1724276342,29.5289610206,28.9781991612,32.7501325977,52.8516305191,66.3912805468,120.315745349,52.7365711973,45.7988795635,52.0026573063,44.224449005,34.6698166705,51.7875306185,476.419981945,120.72262134,347.856527663,91.5548483173,28.4884544987,39.1719905915,61.0852103025,78.7315241658,107.604394708,84.7038904618,5.99971778534,750.26284804,35.1866511446,49.1148000904,26.8897753086,45.4928634932,179.5509105,54.9894397073,36.0096681542,255.408505698,80.1695868885,76.2582430349,110.862795343,146.095174905,92.2970498077,80.511225858,53.1826354558,29.2979569272,72.9197793211,65.0487752884,72.7742729867,34.1838181731,192.525428365,18.94802689,76.5005191554,69.1059472479,59.1690714204,31.8500383671,92.4444568405,110.132660101,33.3901511027,36.660039534,80.0139643561,102.566053861,43.4633043209,44.1906970807,42.4153657649,38.009285913,104.850882077,131.266574971,103.466349393,34.4189906555,25.729289227,27.3003577225,24.5827730911,63.4994240376,87.2285811812,47.643822094,88.7372874872,191.065932789,95.7651311588,123.782724255,40.0689799066,34.2624037701,28.2723099014,27.2538245195,135.339423699,0.936339299049,137.112965055,52.2415574074,80.8194283106,171.712209286,101.251596863,54.1888796151,84.6558497927,46.4227967517,126.823602938,89.1673288329,176.061138541,132.846853051,0.213380554285,215.94851742,199.700129795,140.510413969,219.362058358,161.511040256,284.878508395,82.9243987381,158.002646555,2.96915311628,192.130158177,211.80347001,202.222860988,73.7764188591,15.3591526431,0.0882551369332,55.5694926449,63.7361657622,36.5687592128,63.487865759 diff --git a/PyPSA/source/examples/networks/scigrid-de/scigrid-de/loads.csv b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/loads.csv new file mode 100644 index 0000000000000000000000000000000000000000..769211bdc34b219cf12a1eea367e685ca29616ca --- /dev/null +++ b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/loads.csv @@ -0,0 +1,490 @@ +name,bus +1,1 +3,3 +4,4 +6,6 +7,7 +8,8 +9,9 +11,11 +14,14 +16,16 +17,17 +19,19 +21,21 +23,23 +25,25 +27,27 +28,28 +29,29 +31,31 +33,33 +34,34 +35,35 +36,36 +38,38 +39,39 +40,40 +41,41 +43,43 +44,44 +45,45 +47,47 +48,48 +49,49 +50,50 +51,51 +53,53 +54,54 +55,55 +57,57 +58,58 +59,59 +60,60 +62,62 +63,63 +64,64 +65,65 +66,66 +67,67 +70,70 +71,71 +72,72 +73,73 +75,75 +76,76 +77,77 +79,79 +80,80 +81,81 +82,82 +83,83 +84,84 +85,85 +88,88 +89,89 +90,90 +91,91 +92,92 +93,93 +97,97 +98,98 +101,101 +102,102 +103,103 +108,108 +109,109 +110,110 +111,111 +112,112 +113,113 +114,114 +115,115 +116,116 +118,118 +119,119 +121,121 +122,122 +126,126 +127,127 +130,130 +131,131 +132,132 +133,133 +134,134 +135,135 +136,136 +137,137 +139,139 +140,140 +141,141 +142,142 +144,144 +145,145 +146,146 +147,147 +148,148 +149,149 +150,150 +151,151 +152,152 +153,153 +154,154 +155,155 +158,158 +161,161 +162,162 +163,163 +164,164 +166,166 +167,167 +168,168 +169,169 +170,170 +171,171 +172,172 +173,173 +174,174 +175,175 +176,176 +177,177 +178,178 +179,179 +180,180 +181,181 +182,182 +183,183 +185,185 +186,186 +187,187 +188,188 +189,189 +190,190 +192,192 +193,193 +194,194 +196,196 +197,197 +198,198 +199,199 +200,200 +202,202 +203,203 +204,204 +205,205 +206,206 +207,207 +208,208 +209,209 +210,210 +211,211 +212,212 +213,213 +214,214 +215,215 +216,216 +217,217 +218,218 +219,219 +221,221 +222,222 +223,223 +224,224 +225,225 +226,226 +227,227 +228,228 +229,229 +230,230 +234,234 +235,235 +237,237 +238,238 +239,239 +240,240 +241,241 +242,242 +245,245 +246,246 +249,249 +253,253 +254,254 +255,255 +256,256 +257,257 +258,258 +259,259 +260,260 +262,262 +265,265 +266,266 +268,268 +269,269 +270,270 +271,271 +273,273 +274,274 +275,275 +276,276 +277,277 +278,278 +280,280 +283,283 +284,284 +285,285 +286,286 +287,287 +288,288 +289,289 +290,290 +291,291 +293,293 +294,294 +295,295 +296,296 +297,297 +298,298 +299,299 +300,300 +301,301 +302,302 +304,304 +305,305 +306,306 +309,309 +310,310 +311,311 +313,313 +314,314 +316,316 +318,318 +319,319 +320,320 +321,321 +323,323 +324,324 +325,325 +326,326 +327,327 +328,328 +329,329 +330,330 +331,331 +333,333 +335,335 +337,337 +339,339 +340,340 +341,341 +342,342 +343,343 +344,344 +345,345 +346,346 +347,347 +348,348 +349,349 +350,350 +352,352 +354,354 +355,355 +356,356 +357,357 +358,358 +359,359 +361,361 +363,363 +364,364 +365,365 +366,366 +367,367 +368,368 +369,369 +370,370 +371,371 +372,372 +373,373 +374,374 +375,375 +376,376 +377,377 +378,378 +379,379 +380,380 +381,381 +383,383 +386,386 +387,387 +388,388 +389,389 +390,390 +392,392 +393,393 +394,394 +395,395 +396,396 +397,397 +398,398 +399,399 +400,400 +401,401 +402,402 +405,405 +406,406 +409,409 +410,410 +411,411 +412,412 +414,414 +415,415 +416,416 +417,417 +418,418 +422,422 +423,423 +424,424 +425,425 +426,426 +427,427 +428,428 +429,429 +430,430 +431,431 +432,432 +433,433 +434,434 +435,435 +436,436 +437,437 +438,438 +439,439 +440,440 +441,441 +442,442 +443,443 +444,444 +445,445 +446,446 +447,447 +448,448 +449,449 +451,451 +452,452 +453,453 +454,454 +455,455 +456,456 +457,457 +459,459 +460,460 +461,461 +462,462 +463,463 +464,464 +465,465 +466,466 +467,467 +468,468 +469,469 +470,470 +471,471 +472,472 +473,473 +474,474 +475,475 +476,476 +477,477 +478,478 +479,479 +480,480 +481,481 +482,482 +483,483 +484,484 +485,485 +486,486 +487,487 +488,488 +489,489 +490,490 +491,491 +492,492 +493,493 +494,494 +495,495 +2_220kV,2_220kV +5_220kV,5_220kV +10_220kV,10_220kV +12_220kV,12_220kV +13_220kV,13_220kV +15_220kV,15_220kV +18_220kV,18_220kV +20_220kV,20_220kV +22_220kV,22_220kV +24_220kV,24_220kV +26_220kV,26_220kV +30_220kV,30_220kV +32_220kV,32_220kV +37_220kV,37_220kV +42_220kV,42_220kV +46_220kV,46_220kV +52_220kV,52_220kV +56_220kV,56_220kV +61_220kV,61_220kV +68_220kV,68_220kV +69_220kV,69_220kV +74_220kV,74_220kV +78_220kV,78_220kV +86_220kV,86_220kV +87_220kV,87_220kV +94_220kV,94_220kV +95_220kV,95_220kV +96_220kV,96_220kV +99_220kV,99_220kV +100_220kV,100_220kV +104_220kV,104_220kV +105_220kV,105_220kV +106_220kV,106_220kV +107_220kV,107_220kV +117_220kV,117_220kV +120_220kV,120_220kV +123_220kV,123_220kV +124_220kV,124_220kV +125_220kV,125_220kV +128_220kV,128_220kV +129_220kV,129_220kV +138_220kV,138_220kV +143_220kV,143_220kV +156_220kV,156_220kV +157_220kV,157_220kV +159_220kV,159_220kV +160_220kV,160_220kV +165_220kV,165_220kV +184_220kV,184_220kV +191_220kV,191_220kV +195_220kV,195_220kV +201_220kV,201_220kV +220_220kV,220_220kV +231_220kV,231_220kV +232_220kV,232_220kV +233_220kV,233_220kV +236_220kV,236_220kV +247_220kV,247_220kV +248_220kV,248_220kV +250_220kV,250_220kV +251_220kV,251_220kV +252_220kV,252_220kV +261_220kV,261_220kV +263_220kV,263_220kV +264_220kV,264_220kV +267_220kV,267_220kV +272_220kV,272_220kV +279_220kV,279_220kV +281_220kV,281_220kV +282_220kV,282_220kV +292_220kV,292_220kV +303_220kV,303_220kV +307_220kV,307_220kV +308_220kV,308_220kV +312_220kV,312_220kV +315_220kV,315_220kV +317_220kV,317_220kV +322_220kV,322_220kV +332_220kV,332_220kV +334_220kV,334_220kV +336_220kV,336_220kV +338_220kV,338_220kV +351_220kV,351_220kV +353_220kV,353_220kV +360_220kV,360_220kV +362_220kV,362_220kV +382_220kV,382_220kV +384_220kV,384_220kV +385_220kV,385_220kV +391_220kV,391_220kV +403_220kV,403_220kV +404_220kV,404_220kV +413_220kV,413_220kV +421_220kV,421_220kV +450_220kV,450_220kV +458_220kV,458_220kV diff --git a/PyPSA/source/examples/networks/scigrid-de/scigrid-de/meta.json b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/meta.json new file mode 100644 index 0000000000000000000000000000000000000000..9e26dfeeb6e641a33dae4961196235bdb965b21b --- /dev/null +++ b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/meta.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/PyPSA/source/examples/networks/scigrid-de/scigrid-de/network.csv b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/network.csv new file mode 100644 index 0000000000000000000000000000000000000000..1f4c5416423480de9e31d05605c185b154144971 --- /dev/null +++ b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/network.csv @@ -0,0 +1,2 @@ +name,_multi_invest,now,pypsa_version,srid +SciGrid-DE,0,now,1.0.3,4326 diff --git a/PyPSA/source/examples/networks/scigrid-de/scigrid-de/snapshots.csv b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/snapshots.csv new file mode 100644 index 0000000000000000000000000000000000000000..bfa358e49380c9b026660bdc0153622d01e3a83c --- /dev/null +++ b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/snapshots.csv @@ -0,0 +1,25 @@ +,snapshot,objective,stores,generators +0,2011-01-01 00:00:00,1.0,1.0,1.0 +1,2011-01-01 01:00:00,1.0,1.0,1.0 +2,2011-01-01 02:00:00,1.0,1.0,1.0 +3,2011-01-01 03:00:00,1.0,1.0,1.0 +4,2011-01-01 04:00:00,1.0,1.0,1.0 +5,2011-01-01 05:00:00,1.0,1.0,1.0 +6,2011-01-01 06:00:00,1.0,1.0,1.0 +7,2011-01-01 07:00:00,1.0,1.0,1.0 +8,2011-01-01 08:00:00,1.0,1.0,1.0 +9,2011-01-01 09:00:00,1.0,1.0,1.0 +10,2011-01-01 10:00:00,1.0,1.0,1.0 +11,2011-01-01 11:00:00,1.0,1.0,1.0 +12,2011-01-01 12:00:00,1.0,1.0,1.0 +13,2011-01-01 13:00:00,1.0,1.0,1.0 +14,2011-01-01 14:00:00,1.0,1.0,1.0 +15,2011-01-01 15:00:00,1.0,1.0,1.0 +16,2011-01-01 16:00:00,1.0,1.0,1.0 +17,2011-01-01 17:00:00,1.0,1.0,1.0 +18,2011-01-01 18:00:00,1.0,1.0,1.0 +19,2011-01-01 19:00:00,1.0,1.0,1.0 +20,2011-01-01 20:00:00,1.0,1.0,1.0 +21,2011-01-01 21:00:00,1.0,1.0,1.0 +22,2011-01-01 22:00:00,1.0,1.0,1.0 +23,2011-01-01 23:00:00,1.0,1.0,1.0 diff --git a/PyPSA/source/examples/networks/scigrid-de/scigrid-de/storage_units.csv b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/storage_units.csv new file mode 100644 index 0000000000000000000000000000000000000000..8c88b2fc2bf666db56ac7bb6ee9848c0bcf010bc --- /dev/null +++ b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/storage_units.csv @@ -0,0 +1,39 @@ +name,bus,p_nom,carrier,marginal_cost,max_hours,efficiency_store,efficiency_dispatch +100_220kV Pumped Hydro,100_220kV,144.5,Pumped Hydro,3.0,6.0,0.95,0.95 +114 Pumped Hydro,114,138.0,Pumped Hydro,3.0,6.0,0.95,0.95 +121 Pumped Hydro,121,238.0,Pumped Hydro,3.0,6.0,0.95,0.95 +140 Pumped Hydro,140,100.0,Pumped Hydro,3.0,6.0,0.95,0.95 +141 Pumped Hydro,141,100.0,Pumped Hydro,3.0,6.0,0.95,0.95 +158 Pumped Hydro,158,100.0,Pumped Hydro,3.0,6.0,0.95,0.95 +166 Pumped Hydro,166,1.5,Pumped Hydro,3.0,6.0,0.95,0.95 +205 Pumped Hydro,205,153.0,Pumped Hydro,3.0,6.0,0.95,0.95 +228 Pumped Hydro,228,100.0,Pumped Hydro,3.0,6.0,0.95,0.95 +235 Pumped Hydro,235,470.0,Pumped Hydro,3.0,6.0,0.95,0.95 +25 Pumped Hydro,25,196.0,Pumped Hydro,3.0,6.0,0.95,0.95 +266 Pumped Hydro,266,98.8,Pumped Hydro,3.0,6.0,0.95,0.95 +268 Pumped Hydro,268,100.0,Pumped Hydro,3.0,6.0,0.95,0.95 +279_220kV Pumped Hydro,279_220kV,1045.2,Pumped Hydro,3.0,6.0,0.95,0.95 +293 Pumped Hydro,293,377.6,Pumped Hydro,3.0,6.0,0.95,0.95 +320 Pumped Hydro,320,90.0,Pumped Hydro,3.0,6.0,0.95,0.95 +32_220kV Pumped Hydro,32_220kV,220.0,Pumped Hydro,3.0,6.0,0.95,0.95 +333 Pumped Hydro,333,525.0,Pumped Hydro,3.0,6.0,0.95,0.95 +342 Pumped Hydro,342,79.7,Pumped Hydro,3.0,6.0,0.95,0.95 +343 Pumped Hydro,343,1052.0,Pumped Hydro,3.0,6.0,0.95,0.95 +362_220kV Pumped Hydro,362_220kV,100.0,Pumped Hydro,3.0,6.0,0.95,0.95 +368 Pumped Hydro,368,910.0,Pumped Hydro,3.0,6.0,0.95,0.95 +374 Pumped Hydro,374,100.0,Pumped Hydro,3.0,6.0,0.95,0.95 +379 Pumped Hydro,379,39.8,Pumped Hydro,3.0,6.0,0.95,0.95 +389 Pumped Hydro,389,164.0,Pumped Hydro,3.0,6.0,0.95,0.95 +397 Pumped Hydro,397,144.5,Pumped Hydro,3.0,6.0,0.95,0.95 +423 Pumped Hydro,423,360.0,Pumped Hydro,3.0,6.0,0.95,0.95 +424 Pumped Hydro,424,295.0,Pumped Hydro,3.0,6.0,0.95,0.95 +479 Pumped Hydro,479,100.0,Pumped Hydro,3.0,6.0,0.95,0.95 +481 Pumped Hydro,481,100.0,Pumped Hydro,3.0,6.0,0.95,0.95 +493 Pumped Hydro,493,100.0,Pumped Hydro,3.0,6.0,0.95,0.95 +51 Pumped Hydro,51,623.0,Pumped Hydro,3.0,6.0,0.95,0.95 +63 Pumped Hydro,63,43.0,Pumped Hydro,3.0,6.0,0.95,0.95 +66 Pumped Hydro,66,127.0,Pumped Hydro,3.0,6.0,0.95,0.95 +72 Pumped Hydro,72,198.0,Pumped Hydro,3.0,6.0,0.95,0.95 +78_220kV Pumped Hydro,78_220kV,79.8,Pumped Hydro,3.0,6.0,0.95,0.95 +8 Pumped Hydro,8,119.1,Pumped Hydro,3.0,6.0,0.95,0.95 +98 Pumped Hydro,98,247.0,Pumped Hydro,3.0,6.0,0.95,0.95 diff --git a/PyPSA/source/examples/networks/scigrid-de/scigrid-de/transformers.csv b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/transformers.csv new file mode 100644 index 0000000000000000000000000000000000000000..afa18c73f76815d44c792f86f2a0ddc892285ecc --- /dev/null +++ b/PyPSA/source/examples/networks/scigrid-de/scigrid-de/transformers.csv @@ -0,0 +1,97 @@ +name,bus0,bus1,x,s_nom +2,2,2_220kV,0.1,2000.0 +5,5,5_220kV,0.1,2000.0 +10,10,10_220kV,0.1,2000.0 +12,12,12_220kV,0.1,2000.0 +13,13,13_220kV,0.1,2000.0 +15,15,15_220kV,0.1,2000.0 +18,18,18_220kV,0.1,2000.0 +20,20,20_220kV,0.1,2000.0 +22,22,22_220kV,0.1,2000.0 +24,24,24_220kV,0.1,2000.0 +26,26,26_220kV,0.1,2000.0 +30,30,30_220kV,0.1,2000.0 +32,32,32_220kV,0.1,2000.0 +37,37,37_220kV,0.1,2000.0 +42,42,42_220kV,0.1,2000.0 +46,46,46_220kV,0.1,2000.0 +52,52,52_220kV,0.1,2000.0 +56,56,56_220kV,0.1,2000.0 +61,61,61_220kV,0.1,2000.0 +68,68,68_220kV,0.1,2000.0 +69,69,69_220kV,0.1,2000.0 +74,74,74_220kV,0.1,2000.0 +78,78,78_220kV,0.1,2000.0 +86,86,86_220kV,0.1,2000.0 +87,87,87_220kV,0.1,2000.0 +94,94,94_220kV,0.1,2000.0 +95,95,95_220kV,0.1,2000.0 +96,96,96_220kV,0.1,2000.0 +99,99,99_220kV,0.1,2000.0 +100,100,100_220kV,0.1,2000.0 +104,104,104_220kV,0.1,2000.0 +105,105,105_220kV,0.1,2000.0 +106,106,106_220kV,0.1,2000.0 +107,107,107_220kV,0.1,2000.0 +117,117,117_220kV,0.1,2000.0 +120,120,120_220kV,0.1,2000.0 +123,123,123_220kV,0.1,2000.0 +124,124,124_220kV,0.1,2000.0 +125,125,125_220kV,0.1,2000.0 +128,128,128_220kV,0.1,2000.0 +129,129,129_220kV,0.1,2000.0 +138,138,138_220kV,0.1,2000.0 +143,143,143_220kV,0.1,2000.0 +156,156,156_220kV,0.1,2000.0 +157,157,157_220kV,0.1,2000.0 +159,159,159_220kV,0.1,2000.0 +160,160,160_220kV,0.1,2000.0 +165,165,165_220kV,0.1,2000.0 +184,184,184_220kV,0.1,2000.0 +191,191,191_220kV,0.1,2000.0 +195,195,195_220kV,0.1,2000.0 +201,201,201_220kV,0.1,2000.0 +220,220,220_220kV,0.1,2000.0 +231,231,231_220kV,0.1,2000.0 +232,232,232_220kV,0.1,2000.0 +233,233,233_220kV,0.1,2000.0 +236,236,236_220kV,0.1,2000.0 +247,247,247_220kV,0.1,2000.0 +248,248,248_220kV,0.1,2000.0 +250,250,250_220kV,0.1,2000.0 +251,251,251_220kV,0.1,2000.0 +252,252,252_220kV,0.1,2000.0 +261,261,261_220kV,0.1,2000.0 +263,263,263_220kV,0.1,2000.0 +264,264,264_220kV,0.1,2000.0 +267,267,267_220kV,0.1,2000.0 +272,272,272_220kV,0.1,2000.0 +279,279,279_220kV,0.1,2000.0 +281,281,281_220kV,0.1,2000.0 +282,282,282_220kV,0.1,2000.0 +292,292,292_220kV,0.1,2000.0 +303,303,303_220kV,0.1,2000.0 +307,307,307_220kV,0.1,2000.0 +308,308,308_220kV,0.1,2000.0 +312,312,312_220kV,0.1,2000.0 +315,315,315_220kV,0.1,2000.0 +317,317,317_220kV,0.1,2000.0 +322,322,322_220kV,0.1,2000.0 +332,332,332_220kV,0.1,2000.0 +334,334,334_220kV,0.1,2000.0 +336,336,336_220kV,0.1,2000.0 +338,338,338_220kV,0.1,2000.0 +351,351,351_220kV,0.1,2000.0 +353,353,353_220kV,0.1,2000.0 +360,360,360_220kV,0.1,2000.0 +362,362,362_220kV,0.1,2000.0 +382,382,382_220kV,0.1,2000.0 +384,384,384_220kV,0.1,2000.0 +385,385,385_220kV,0.1,2000.0 +391,391,391_220kV,0.1,2000.0 +403,403,403_220kV,0.1,2000.0 +404,404,404_220kV,0.1,2000.0 +413,413,413_220kV,0.1,2000.0 +421,421,421_220kV,0.1,2000.0 +450,450,450_220kV,0.1,2000.0 +458,458,458_220kV,0.1,2000.0 diff --git a/PyPSA/source/examples/networks/stochastic-network/stochastic-network.nc b/PyPSA/source/examples/networks/stochastic-network/stochastic-network.nc new file mode 100644 index 0000000000000000000000000000000000000000..2d9880f5f83cb6f68a56d21d463e53aee8f13493 --- /dev/null +++ b/PyPSA/source/examples/networks/stochastic-network/stochastic-network.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e3e515446cc38f0d86de9ae519aa922eb912381f5e62b6cfaadb6fa0d917605 +size 121222 diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc.nc b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc.nc new file mode 100644 index 0000000000000000000000000000000000000000..a5d51523337ef03658bd4c1a62b343cdc48b3f0a --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3a5df55576234087bf841b0f3ee395cdc4ac48657c60630b28812f286b4ee3b +size 131873 diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/buses.csv b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/buses.csv new file mode 100644 index 0000000000000000000000000000000000000000..c388d040f1b7eb802f65d0d4a6f00978546dbfeb --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/buses.csv @@ -0,0 +1,7 @@ +name,v_nom +0,380.0 +1,380.0 +2,380.0 +3,380.0 +4,380.0 +5,380.0 diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/carriers.csv b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/carriers.csv new file mode 100644 index 0000000000000000000000000000000000000000..4ab33a26381f0666d16973afc234eba24d70abec --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/carriers.csv @@ -0,0 +1,4 @@ +name,co2_emissions +gas,0.24 +wind,0.0 +battery,0.0 diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/crs.json b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/crs.json new file mode 100644 index 0000000000000000000000000000000000000000..49edf3439c1f2d009aeeae1606c27c86fcd50f3f --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/crs.json @@ -0,0 +1 @@ +{"_crs": "GEOGCRS[\"WGS 84\",ENSEMBLE[\"World Geodetic System 1984 ensemble\",MEMBER[\"World Geodetic System 1984 (Transit)\"],MEMBER[\"World Geodetic System 1984 (G730)\"],MEMBER[\"World Geodetic System 1984 (G873)\"],MEMBER[\"World Geodetic System 1984 (G1150)\"],MEMBER[\"World Geodetic System 1984 (G1674)\"],MEMBER[\"World Geodetic System 1984 (G1762)\"],MEMBER[\"World Geodetic System 1984 (G2139)\"],MEMBER[\"World Geodetic System 1984 (G2296)\"],ELLIPSOID[\"WGS 84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]],ENSEMBLEACCURACY[2.0]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],CS[ellipsoidal,2],AXIS[\"geodetic latitude (Lat)\",north,ORDER[1],ANGLEUNIT[\"degree\",0.0174532925199433]],AXIS[\"geodetic longitude (Lon)\",east,ORDER[2],ANGLEUNIT[\"degree\",0.0174532925199433]],USAGE[SCOPE[\"Horizontal component of 3D system.\"],AREA[\"World.\"],BBOX[-90,-180,90,180]],ID[\"EPSG\",4326]]"} \ No newline at end of file diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/generators-p_max_pu.csv b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/generators-p_max_pu.csv new file mode 100644 index 0000000000000000000000000000000000000000..ce974f76c6caf5d38a970477f81746fddc044415 --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/generators-p_max_pu.csv @@ -0,0 +1,13 @@ +,Wind 0,Wind 1,Wind 2,Wind 3,Wind 4,Wind 5 +0,0.12417331512,0.279183679011,0.585759271458,0.96959574832,0.561030219256,0.0186472893729 +1,0.800632672681,0.232974273841,0.807105195619,0.387860644064,0.863541854559,0.747121642737 +2,0.55624023399,0.136455225661,0.0599176895122,0.121343455741,0.0445518785448,0.107494129106 +3,0.225709338608,0.712988980383,0.559716982054,0.0125559801591,0.0719742796895,0.96727633 +4,0.56810046192,0.203293234661,0.25232574457,0.743825854075,0.195429481109,0.581358927273 +5,0.970019989088,0.84682880149,0.239847759148,0.493769714269,0.619955718381,0.82898089955 +6,0.156791394646,0.0185762021774,0.0700221437192,0.486345110937,0.606329461653,0.568851437086 +7,0.317362409322,0.988616154412,0.579745219246,0.380141172624,0.550948219118,0.745334430907 +8,0.669232893453,0.264919557663,0.0663348344284,0.370084197914,0.629717507022,0.210174009915 +9,0.752755553739,0.0665364813541,0.260315098579,0.804754563743,0.193434282623,0.63946088088 +10,0.524670309124,0.924807970399,0.263296770487,0.065961090684,0.735065963289,0.772178029543 +11,0.907815852504,0.931972069197,0.0139515729756,0.234362086121,0.616778357002,0.949016320688 diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/generators.csv b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/generators.csv new file mode 100644 index 0000000000000000000000000000000000000000..2e7d25f5c4d8f9ba469e51b14aa0dada4b425ae5 --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/generators.csv @@ -0,0 +1,13 @@ +name,bus,p_nom,p_nom_extendable,p_nom_min,p_min_pu,p_max_pu,carrier,marginal_cost,capital_cost,efficiency +Wind 0,0,100.0,True,100.0,0.0,1.0,wind,0.00417304802367,2558.68982845,1.0 +Gas 0,0,350.0,False,0.0,0.02,0.85,gas,2.56154775438,119.810148908,0.358007445687 +Wind 1,1,100.0,True,100.0,0.0,1.0,wind,0.00692322615669,2876.3891523,1.0 +Gas 1,1,0.0,True,0.0,0.02,0.85,gas,5.57842665402,108.504421137,0.350390547832 +Wind 2,2,150.0,False,100.0,0.0,1.0,wind,0.000983468338331,2421.10762501,1.0 +Gas 2,2,0.0,True,0.0,0.02,0.85,gas,5.8315581206,153.316528497,0.35691877114 +Wind 3,3,100.0,True,100.0,0.0,1.0,wind,0.00834625671897,2018.28827734,1.0 +Gas 3,3,0.0,True,0.0,0.02,0.85,gas,5.00057725978,198.886108891,0.357481656544 +Wind 4,4,100.0,True,100.0,0.0,1.0,wind,0.00103226006578,2447.89352618,1.0 +Gas 4,4,0.0,True,0.0,0.02,0.85,gas,5.63438201237,129.361414837,0.352877753386 +Wind 5,5,100.0,True,100.0,0.0,1.0,wind,0.0067883553294,2211.628116,1.0 +Gas 5,5,0.0,True,0.0,0.02,0.85,gas,3.06218663749,149.157315928,0.350533625451 diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/global_constraints.csv b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/global_constraints.csv new file mode 100644 index 0000000000000000000000000000000000000000..4794cc6f661bd664ad55b861a8a38e638e20bd9e --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/global_constraints.csv @@ -0,0 +1,2 @@ +name,sense,constant +co2_limit,<=,1000.0 diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/investment_periods.csv b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/investment_periods.csv new file mode 100644 index 0000000000000000000000000000000000000000..34dd42197768b92fdb696fcfe70396544f01bb62 --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/investment_periods.csv @@ -0,0 +1 @@ +period,objective,years diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/lines.csv b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/lines.csv new file mode 100644 index 0000000000000000000000000000000000000000..1908366f66bedff5bc1aeb285e8a506bdda2cc6c --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/lines.csv @@ -0,0 +1,7 @@ +name,bus0,bus1,x,s_nom,s_nom_extendable,capital_cost +0,0,1,0.417022004703,0.0,True,0.144064898688 +1,1,2,0.000114374817345,0.0,True,0.0604665145264 +2,2,0,0.146755890817,200.0,False,0.0184677189538 +3,3,4,0.186260211378,0.0,True,0.0691121454086 +4,4,5,0.396767474231,0.0,True,0.107763346801 +5,5,3,0.419194514403,0.0,True,0.137043900079 diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/links.csv b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/links.csv new file mode 100644 index 0000000000000000000000000000000000000000..f02451e5e2876e25e2a8456c19cb956316573d04 --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/links.csv @@ -0,0 +1,3 @@ +name,bus0,bus1,p_nom,p_nom_extendable,p_min_pu,p_max_pu,capital_cost +TL 0,0,3,1000.0,True,-0.9,0.9,0.0408904499463 +TL 1,1,4,300.0,False,-0.9,0.9,0.175623487278 diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/loads-p_set.csv b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/loads-p_set.csv new file mode 100644 index 0000000000000000000000000000000000000000..87972ab0f1978b3d40e7d4dbe6e95e05e86a15b0 --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/loads-p_set.csv @@ -0,0 +1,13 @@ +,0,1,2,3,4,5 +0,574.117605492,146.728574906,589.305536903,699.758360021,102.334428828,414.05598782 +1,694.400157728,414.179269527,49.9534589461,535.896405916,663.79464522,514.889112058 +2,944.594755991,586.555040502,903.401915288,137.474704146,139.276347251,807.39128871 +3,397.676836986,165.354197117,927.508580396,347.765859746,750.812103136,725.99798535 +4,883.306091206,623.672207056,750.942434027,348.898341978,269.927891765,895.886218196 +5,428.091189871,964.840047148,663.441497818,621.695720209,114.745972953,949.489258707 +6,449.91213348,578.389614387,408.136802761,237.026980243,903.379520562,573.679486672 +7,2.87032703116,617.144913621,326.644901772,527.058102258,885.942099311,357.269760002 +8,908.53515092,623.360115792,15.8212428466,929.437233744,690.896917517,997.322850451 +9,172.340508345,137.135749629,932.595463037,696.81816149,66.0001727221,755.463052602 +10,753.876188461,923.024535546,711.524758628,124.270961972,19.8801338398,26.2109868777 +11,28.3064880208,246.211067603,860.027948683,538.831064342,552.821978686,842.03089236 diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/loads.csv b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/loads.csv new file mode 100644 index 0000000000000000000000000000000000000000..1e67ea670df061397822e2e9d46e1a497a740431 --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/loads.csv @@ -0,0 +1,7 @@ +name,bus +0,0 +1,1 +2,2 +3,3 +4,4 +5,5 diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/meta.json b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/meta.json new file mode 100644 index 0000000000000000000000000000000000000000..9e26dfeeb6e641a33dae4961196235bdb965b21b --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/meta.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/network.csv b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/network.csv new file mode 100644 index 0000000000000000000000000000000000000000..9a6590ab40c680f255026e855aa07155a6716e22 --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/network.csv @@ -0,0 +1,2 @@ +name,_multi_invest,now,pypsa_version,srid +Storage-HVDC,0,now,1.0.3,4326 diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/snapshots.csv b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/snapshots.csv new file mode 100644 index 0000000000000000000000000000000000000000..272e90e3a5d81e33e08c7c8bb84a59b05f58861f --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/snapshots.csv @@ -0,0 +1,13 @@ +,snapshot,objective,stores,generators +0,2015-01-01 00:00:00,3.0,3.0,3.0 +1,2015-01-01 01:00:00,3.0,3.0,3.0 +2,2015-01-01 02:00:00,3.0,3.0,3.0 +3,2015-01-01 03:00:00,3.0,3.0,3.0 +4,2015-01-01 04:00:00,3.0,3.0,3.0 +5,2015-01-01 05:00:00,3.0,3.0,3.0 +6,2015-01-01 06:00:00,3.0,3.0,3.0 +7,2015-01-01 07:00:00,3.0,3.0,3.0 +8,2015-01-01 08:00:00,3.0,3.0,3.0 +9,2015-01-01 09:00:00,3.0,3.0,3.0 +10,2015-01-01 10:00:00,3.0,3.0,3.0 +11,2015-01-01 11:00:00,3.0,3.0,3.0 diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/storage_units-inflow.csv b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/storage_units-inflow.csv new file mode 100644 index 0000000000000000000000000000000000000000..790e87d85bb22767af1cdc562fb47c15adcfb6f7 --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/storage_units-inflow.csv @@ -0,0 +1,13 @@ +,Storage 0 +0,20.0 +1,20.0 +2,20.0 +3,20.0 +4,20.0 +5,20.0 +6,20.0 +7,20.0 +8,20.0 +9,20.0 +10,20.0 +11,20.0 diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/storage_units-state_of_charge_set.csv b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/storage_units-state_of_charge_set.csv new file mode 100644 index 0000000000000000000000000000000000000000..6867f4e192d5ad98858f0911024989bf5ca47ba2 --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/storage_units-state_of_charge_set.csv @@ -0,0 +1,13 @@ +,Storage 3,Storage 4 +0,, +1,, +2,,25.0 +3,50.0, +4,, +5,, +6,, +7,, +8,, +9,, +10,, +11,, diff --git a/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/storage_units.csv b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/storage_units.csv new file mode 100644 index 0000000000000000000000000000000000000000..090abf4a32e3098c9efb7cebd69346ee15934ff0 --- /dev/null +++ b/PyPSA/source/examples/networks/storage-hvdc/storage-hvdc/storage_units.csv @@ -0,0 +1,7 @@ +name,bus,p_nom,p_nom_extendable,carrier,marginal_cost,capital_cost,cyclic_state_of_charge,max_hours,efficiency_store,efficiency_dispatch,standing_loss +Storage 0,0,0.0,True,battery,0.109550372792,670.467510178,False,6.0,0.9,0.95,0.01 +Storage 1,1,25.0,False,battery,3.87304630288,313.424178159,False,6.0,0.9,0.95,0.01 +Storage 2,2,0.0,True,battery,0.679321678258,878.142503429,True,6.0,0.9,0.95,0.01 +Storage 3,3,0.0,True,battery,1.26206252402,686.500927682,False,6.0,0.9,0.95,0.01 +Storage 4,4,0.0,True,battery,1.12177596826,789.279328451,True,6.0,0.9,0.95,0.01 +Storage 5,5,0.0,True,battery,0.520114288473,19.3669578703,False,6.0,0.9,0.95,0.01 diff --git a/PyPSA/source/mkdocs.yml b/PyPSA/source/mkdocs.yml new file mode 100644 index 0000000000000000000000000000000000000000..afc545d1a7092b9199297351f2351716b800e65b --- /dev/null +++ b/PyPSA/source/mkdocs.yml @@ -0,0 +1,422 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +site_name: Documentation +site_url: !ENV READTHEDOCS_CANONICAL_URL +repo_url: https://github.com/PyPSA/PyPSA +repo_name: PyPSA/PyPSA +edit_uri: edit/master/docs/ + +# Navigation +nav: + +- Home: + - Home: index.md + - Overview: + - Features: home/features.md + - Release Notes: release-notes.md + - Getting Started: + - Installation: home/installation.md + - examples/example-1.ipynb + - examples/example-2.ipynb + - examples/example-3.ipynb + - Reference: + - Users: home/users.md + - Models: home/models.md + - Citing: home/citing.md + - License: home/license.md + - Support: + - FAQ: user-guide/faq.md + - Troubleshooting: user-guide/troubleshooting.md + - Support: user-guide/support.md + - Learning Materials: user-guide/learning-materials.md + +- User Guide: + - Overview: user-guide/user-guide.md + - What's new in v1.0: user-guide/v1-guide.md + - Core Design: + - Design: user-guide/design.md + - Component Types: + - Bus: user-guide/components/buses.md + - Carrier: user-guide/components/carriers.md + - Generator: user-guide/components/generators.md + - Load: user-guide/components/loads.md + - Link: user-guide/components/links.md + - Store: user-guide/components/stores.md + - Storage Unit: user-guide/components/storage-units.md + - Line: user-guide/components/lines.md + - Line Types: user-guide/components/line-types.md + - Transformer: user-guide/components/transformers.md + - Transformer Types: user-guide/components/transformer-types.md + - Shunt Impedance: user-guide/components/shunt-impedances.md + - Global Constraints: user-guide/components/global-constraints.md + - Shapes: user-guide/components/shapes.md + - Sub-Network: user-guide/components/sub-networks.md + - Optimization: + - Overview: user-guide/optimization/overview.md + - Formulations: + - Objective: user-guide/optimization/objective.md + - Energy Balance: user-guide/optimization/energy-balance.md + - Dispatch Limits: user-guide/optimization/dispatch-limits.md + - Capacity Limits: user-guide/optimization/capacity-limits.md + - Unit Commitment: user-guide/optimization/unit-commitment.md + - Storage: user-guide/optimization/storage.md + - Power Flow: user-guide/optimization/power-flow.md + - Contingencies: user-guide/optimization/contingencies.md + - Global Constraints: user-guide/optimization/global-constraints.md + - Custom Constraints: user-guide/optimization/custom-constraints.md + - Pathway Planning: user-guide/optimization/pathway-planning.md + - Stochastic Optimization: user-guide/optimization/stochastic.md + - Modelling-to-Generate-Alternatives (MGA): user-guide/optimization/modelling-to-generate-alternatives.md + - Simulation: + - Power Flow: user-guide/power-flow.md + - Linear Power Flow: user-guide/linear-power-flow.md + - Usage: + - Components: user-guide/components.md + - Optimization: user-guide/network-optimization.md + - Import and Export: user-guide/import-export.md + - Clustering: user-guide/clustering.ipynb + - Statistics: user-guide/statistics.md + - Plotting: + - Statistics: user-guide/plotting/charts.md + - Maps (Static): user-guide/plotting/static-map.ipynb + - Maps (Interactive): user-guide/plotting/explore.ipynb + - Network Collection: user-guide/collection.md + - Options: user-guide/options.md + - Logging: user-guide/logging.ipynb + +- Examples: + - Overview: examples/examples.md + - Operational Problems: + - examples/simple-electricity-market-examples.ipynb + - examples/unit-commitment.ipynb + - examples/uc-prices.ipynb + - examples/ac-dc-lopf.ipynb + - examples/scigrid-lopf-then-pf.ipynb + - examples/scigrid-sclopf.ipynb + - examples/minimal-example-pf.ipynb + - examples/negative-prices-kvl-baker.ipynb + - examples/rolling-horizon.ipynb + - Planning Problems: + - examples/capacity-expansion-planning-single-node.ipynb + - examples/3-node-cem.ipynb + - examples/multi-investment-optimisation.ipynb + - examples/myopic-pathway.ipynb + - examples/stochastic-optimization.ipynb + - examples/mga.ipynb + - examples/near-opt-space.ipynb + - examples/modular-expansion.ipynb + - Sector Coupling: + - examples/sector-coupling-single-node.ipynb + - examples/islanded-methanol-production.ipynb + - examples/battery-electric-vehicle-charging.ipynb + - examples/chp-fixed-heat-power-ratio.ipynb + - examples/power-to-gas-boiler-chp.ipynb + - examples/power-to-heat-water-tank.ipynb + - examples/biomass-synthetic-fuels-carbon-management.ipynb + - Advanced Topics: + - examples/scigrid-redispatch.ipynb + - examples/demand-elasticity.ipynb + - examples/generation-investment-screening-curve.ipynb + - examples/chained-hydro-reservoirs.ipynb + - examples/transformer-example.ipynb + - examples/reserve-power.ipynb + - Complexity Management & Troubleshooting: + - examples/time-series-aggregation.ipynb + - examples/replace-generator-storage-units-with-store.ipynb + - examples/tracing-infeasibilities.ipynb + +- API Reference: + - Networks: + - Network: + - General: api/networks/network.md + - Index: api/networks/indexing.md + - Components: api/networks/components.md + - Transform: api/networks/transform.md + - Descriptors: api/networks/descriptors.md + - IO: api/networks/io.md + - Power Flow: api/networks/power-flow.md + - Optimize: api/networks/optimize.md + - Cluster: api/networks/cluster.md + - Statistics: api/networks/statistics.md + - Plotting: api/networks/plot.md + - SubNetwork: api/networks/subnetwork.md + - Network Collection: api/networks/collection.md + - Components: + - Components: api/components/components.md + - Component Types: + - Sub-Network: api/components/types/sub-networks.md + - Bus: api/components/types/buses.md + - Carrier: api/components/types/carriers.md + - Global Constraints: api/components/types/global-constraints.md + - Generator: api/components/types/generators.md + - Store: api/components/types/stores.md + - StorageUnit: api/components/types/storage-units.md + - Load: api/components/types/loads.md + - Shunt Impedance: api/components/types/shunt-impedances.md + - Line: api/components/types/lines.md + - Transformer: api/components/types/transformers.md + - Link: api/components/types/links.md + - Shapes: api/components/types/shapes.md + - Utilities: api/components/common.md + - Other: + - Consistency Checks: api/other/consistency.md + - Examples: api/other/api-examples.md + - Utilities: api/other/common.md + - Options: api/other/options.md + +- Contributing: + - Instructions: contributing/contributing.md + - Code of Conduct: CODE_OF_CONDUCT.md + - Contributors: contributing/contributors.md + +# Theme settings +theme: + name: material + logo: assets/logo/logo-primary-dark.svg + favicon: assets/logo/logo.svg + custom_dir: docs/assets/overrides + font: + text: Overpass + features: + - navigation.tabs + - navigation.tabs.sticky + - navigation.sections + - navigation.tracking # Anchor tracking + - navigation.indexes # Section indexes + # - navigation.path # Sponsors only + - navigation.top # Back to top button + # - navigation.foot + - header.autohide + - content.action.edit + - content.action.view + - content.code.copy # Code copy button + - toc.follow + - search + - search.suggest + - search.highlight + - search.share + - announce.dismiss + + palette: + - media: "(prefers-color-scheme: light)" + primary: custom + scheme: default + toggle: + icon: material/weather-sunny + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + primary: custom + scheme: slate + toggle: + icon: material/weather-night + name: Switch to light mode + +extra_css: +- assets/stylesheets/extra.css + +extra_javascript: +- assets/javascripts/readthedocs.js +- assets/javascripts/mathjax.js +- assets/javascripts/gurubase-widget.js +- https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js + +markdown_extensions: +# ==== Standard Extensions ==== + +# Callouts/ Admonitions +# https://squidfunk.github.io/mkdocs-material/reference/admonitions/ +- admonition +- pymdownx.details +- pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + +# Grids +- md_in_html + +# Toctree with permalinks +- toc: + title: On this page + permalink: true + toc_depth: 3 +# Footnotes +# https://squidfunk.github.io/mkdocs-material/reference/footnotes/ +- footnotes + +# ==== Pymdown Extensions ==== +# Latex math +# https://facelessuser.github.io/pymdown-extensions/extensions/arithmatex/#overview +- pymdownx.arithmatex: + generic: true +# Tabbed content +# https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/ +- pymdownx.tabbed: + alternate_style: true +# Emojis +# https://facelessuser.github.io/pymdown-extensions/extensions/emoji/ +- pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg +# Formatting +- pymdownx.caret +- pymdownx.tilde + +- pymdownx.snippets: + auto_append: + - includes/abbreviations.md +- abbr +- attr_list + +plugins: +- search +- tags +- autolinks +- table-reader +- mkdocs-video +- git-revision-date-localized: + type: timeago + timezone: Europe/Berlin + enable_creation_date: true + enable_git_follow: true + strict: false # TODO: activate +- social: + cards_layout_options: + font_family: Overpass + background_color: "#292F36" + +# Docstring generation +- mkdocstrings: + enabled: true + default_handler: python + handlers: + python: + options: + docstring_style: numpy + docstring_options: + ignore_init_summary: true + warn_missing_types: false # TODO: activate + warn_unknown_params: false # TODO: activate + warnings: false # TODO: activate + docstring_section_style: list + filters: + - "!^_[^_]" + - "!logger" + heading_level: 1 + merge_init_into_class: true + parameter_headings: false + separate_signature: true + show_root_heading: true + show_root_full_path: false + show_object_full_path: true + show_source: false + show_symbol_type_heading: true + show_symbol_type_toc: true + type_parameter_headings: true + show_signature_annotations: true + summary: true + show_inheritance_diagram: true + # Insiders + signature_crossrefs: true + backlinks: tree + # Extensions + extensions: + - docs/hooks/shortcodes.py:ProcessShortcodes + - docs/hooks/dynamic_inspect.py:InspectSpecificObjects: + objects: + # n.add + - pypsa.components._types.buses.Buses.add + - pypsa.components._types.carriers.Carriers.add + - pypsa.components._types.generators.Generators.add + - pypsa.components._types.global_constraints.GlobalConstraints.add + - pypsa.components._types.lines.Lines.add + - pypsa.components._types.links.Links.add + - pypsa.components._types.shapes.Shapes.add + - pypsa.components._types.storage_units.StorageUnits.add + - pypsa.components._types.stores.Stores.add + - pypsa.components._types.sub_networks.SubNetworks.add + - pypsa.components._types.transformers.Transformers.add + # Plot Accessor + - pypsa.plot.accessor.PlotAccessor.explore + - pypsa.plot.accessor.PlotAccessor.iplot + - pypsa.plot.accessor.PlotAccessor.map + - pypsa.plot.statistics.plotter.StatisticPlotter.__call__ + - pypsa.plot.statistics.plotter.StatisticPlotter.area + - pypsa.plot.statistics.plotter.StatisticPlotter.bar + - pypsa.plot.statistics.plotter.StatisticPlotter.scatter + - pypsa.plot.statistics.plotter.StatisticPlotter.line + - pypsa.plot.statistics.plotter.StatisticPlotter.box + - pypsa.plot.statistics.plotter.StatisticPlotter.violin + - pypsa.plot.statistics.plotter.StatisticPlotter.histogram + - pypsa.plot.statistics.plotter.InteractiveStatisticPlotter.__call__ + - pypsa.plot.statistics.plotter.InteractiveStatisticPlotter.area + - pypsa.plot.statistics.plotter.InteractiveStatisticPlotter.bar + - pypsa.plot.statistics.plotter.InteractiveStatisticPlotter.scatter + - pypsa.plot.statistics.plotter.InteractiveStatisticPlotter.line + - pypsa.plot.statistics.plotter.InteractiveStatisticPlotter.box + - pypsa.plot.statistics.plotter.InteractiveStatisticPlotter.violin + - pypsa.plot.statistics.plotter.InteractiveStatisticPlotter.histogram + # Clustering Accessor + - pypsa.clustering.ClusteringAccessor.busmap_by_greedy_modularity + - pypsa.clustering.ClusteringAccessor.busmap_by_hac + - pypsa.clustering.ClusteringAccessor.busmap_by_kmeans + - pypsa.clustering.ClusteringAccessor.cluster_by_busmap + - pypsa.clustering.ClusteringAccessor.cluster_spatially_by_greedy_modularity + - pypsa.clustering.ClusteringAccessor.cluster_spatially_by_hac + - pypsa.clustering.ClusteringAccessor.cluster_spatially_by_kmeans + - pypsa.clustering.ClusteringAccessor.get_clustering_from_busmap + +# This will rebuild and execute the notebooks during any modifications when running `mkdocs serve` +# For local development, this can be commented out/ filtered to only include the notebooks of interest +- mkdocs-jupyter: + execute: true + include: ["*.ipynb"] + allow_errors: false + include_source: true + remove_tag_config: + remove_input_tags: + - hide-cell + +# Global redirects +- redirects: + redirect_maps: + 'home/release-notes.md': 'release-notes.md' + 'changelog.md': 'release-notes.md' + 'whats-new.md': 'release-notes.md' + 'code-of-conduct.md': 'CODE_OF_CONDUCT.md' + +hooks: +- docs/hooks/shortcodes.py +- docs/hooks/cleanup.py +- docs/hooks/path_aliases.py + +extra: + social: + - icon: fontawesome/brands/discord + link: https://discord.com/invite/AnuJBk23FU + - icon: fontawesome/brands/youtube + link: https://www.youtube.com/@pypsa-official + - icon: fontawesome/brands/github + link: https://github.com/PyPSA + - icon: fontawesome/solid/globe + link: https://pypsa.org + + extra: + homepage: https://pypsa.org + +validation: + nav: + omitted_files: warn + not_found: warn + absolute_links: warn + links: + not_found: warn + anchors: warn + absolute_links: warn + unrecognized_links: warn + +copyright: >- + © Copyright PyPSA Contributors | CC-BY-4.0 diff --git a/PyPSA/source/pyproject.toml b/PyPSA/source/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..3ff136f96613012655ec847fa7c1e14b8d98d173 --- /dev/null +++ b/PyPSA/source/pyproject.toml @@ -0,0 +1,266 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +[build-system] +requires = ["setuptools>=64", "setuptools_scm>=8"] +build-backend = "setuptools.build_meta" + +[project] +name="pypsa" +dynamic = ["version"] +description="Python for Power Systems Analysis" +readme="README.md" +authors=[{name = "PyPSA Developers, see https://docs.pypsa.org/latest/contributing/contributors.html", email = "t.brown@tu-berlin.de"}] +license = { file = "LICENSE" } +classifiers=[ + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Typing :: Typed", + "Operating System :: OS Independent", +] + +requires-python = ">=3.11" + +dependencies = [ + "numpy", + "scipy", + "pandas>=0.24", + "xarray", + "netcdf4", + "linopy>=0.5.5", + "matplotlib", + "plotly", + "pydeck", + "seaborn", + "geopandas>=0.9", + "shapely", + "networkx>=2", + "deprecation", + "validators", + "highspy", + "levenshtein>=0.27.1", +] + +[project.urls] +Homepage = "https://github.com/PyPSA/PyPSA" +Source = "https://github.com/PyPSA/PyPSA" +Documentation = "https://docs.pypsa.org" + +[project.optional-dependencies] +hdf5 = ["tables"] +cartopy = ["cartopy>=0.16"] +excel = ["openpyxl", "python-calamine"] +gurobipy = ["gurobipy"] +cloudpath = ["cloudpathlib"] +dev = [ + "pytest", + "pytest-cov", + "pytest-mpl", + "coverage", + "pypower", + "pandapower>=2.14.11", + "scikit-learn", + "pre-commit", + "ruff", + "mypy", + "jupyter>=1.1.1", + "typing-extensions>=4.14.0", + "reuse>=5.1.1", +] +docs = [ + "mkdocs-material", + "mkdocs-ezlinks-plugin", + "mkdocs-autolinks-plugin", + "mkdocs-redirects", + "mkdocs-video", + "mkdocs-git-revision-date-localized-plugin", + "mkdocs-table-reader-plugin", + "mkdocstrings-python", + "mkdocs-jupyter", + "cairosvg", + "pillow", + "tsam", + "gurobipy", + "openpyxl", + "python-calamine", +] + +# setuptools_scm settings + +[tool.setuptools_scm] +version_scheme = "no-guess-dev" + +[tool.setuptools.packages.find] +include = ["pypsa"] + +[tool.setuptools.package-data] +"pypsa" = ["py.typed"] + +# Pytest settings + +[tool.pytest.ini_options] +filterwarnings = [ + # Raise warnings as errors + "error::DeprecationWarning", + "error::FutureWarning", + "error::RuntimeWarning", + "error::UserWarning", + + # Ignore internal warnings + "default:With PyPSA 1.0, the API for how to access components data has changed:DeprecationWarning", + "default:When combining.*with other plots on a geographical axis.*:UserWarning", # TODO: needs to be checked + + # Ignore linopy warnings + "default:Coordinates across variables not equal. Perform outer join.:UserWarning", + "ignore:In a future version of xarray the default value for join will change:FutureWarning", +] +markers = [ + "mpl_image_compare", +] + +# Coverage settings + +[tool.coverage.run] +branch = true +source = ["pypsa"] +omit = ["test/*"] +[tool.coverage.report] +exclude_also = [ + "if TYPE_CHECKING:", +] + +# Static type checker settings + +[tool.mypy] +exclude = ['dev/*', 'examples/*', 'docs/*'] +ignore_missing_imports = true +no_implicit_optional = true +warn_unused_ignores = true +show_error_code_links = true +# Maybe activate at later stage +# disallow_any_generics = true +# warn_return_any = true + +[[tool.mypy.overrides]] +module = "pypsa.*" +disallow_untyped_defs = true +check_untyped_defs = true + +[[tool.mypy.overrides]] +module = "cloudpathlib.*" +ignore_errors = true +follow_imports = "skip" + +# Remove when typing in linopy are resolved +[[tool.mypy.overrides]] +module = "linopy.*" +ignore_errors = true +follow_imports = "skip" + +# Formatter and linter settings + +[tool.ruff] +extend-include = ['*.ipynb'] + +[tool.ruff.lint] +select = [ + 'F', # pyflakes + 'E', # pycodestyle: Error + 'W', # pycodestyle: Warning + 'I', # isort + 'D', # pydocstyle + 'UP', # pyupgrade + 'PL', # pylint + 'PERF', # perflint + 'ANN', # flake-8 annotations + 'ASYNC', # flake8-async + 'TID', # flake8-tidy-imports + 'S', # flake8-bandit + 'B', # flake8-bugbear + 'C4', # flake8-comprehensions + 'EM', # flake8-errmsg + 'G', # flake8-logging-format + 'PIE', # flake8-pie + 'T20', # flake8-print + 'PYI', # flake8-pyi + 'PT', # flake8-pytest-style + 'SIM', # flake8-simplify + 'TC', # flake8-tidy-checking + # 'ARG', # flake8-unused-arguments + 'PTH', # flake8-use-pathlib + 'NPY', # numpy + 'RUF013', # ruff + 'ERA', # eradicate + 'TRY' # tryceratops +] + +# Include once available +# https://github.com/astral-sh/ruff/issues/2310 +ignore = [ + 'ANN401', # Dynamically typed expressions are forbidden + 'E501', # line too long + 'E741', # ambiguous variable names + 'D203', # 1 blank line required before class docstring + 'D213', # Multi-line docstring summary should start at the second line + 'D400', # First line should end with a period + 'D415', # First line should end with a period, question mark, or exclamation point + 'SIM105', + 'SIM108', + 'SIM118', + 'PLR0911', + 'PLR0913', + 'PLR0915', + 'PLR2004', + # Could be implemented + 'PLW1641', + 'PLR0912', + 'PT011', + 'PLW2901', + 'ERA001', #TODO add again + ] + + +[tool.ruff.lint.per-file-ignores] +"{test,examples,doc,docs}/**" = [ + 'D', # pydocstyle + 'ERA', # eradicate + 'S', # flake8-bandit + 'B', # flake8-bugbear + 'EM', # flake8-errmsg + 'T20', # flake8-print + 'SIM', # flake8-simplify + 'ARG', # flake8-unused-arguments + 'PL', # pylint + 'TRY' # tryceratops +] + +# Ignore for test files +"{test/**}" = [ + 'D100', # Missing docstring in public module + 'D101', # Missing docstring in public class + 'D102', # Missing docstring in public method + 'D103', # Missing docstring in public function + 'D105', # Missing docstring in magic method + 'D107', # Missing docstring in __init__ + 'D200', # One-line docstring should fit on one line with quotes + 'D205', # 1 blank line required between summary line and description + 'D404', # First word of the docstring should not be "This + 'D413', # Missing blank line after last section + 'D415', # First line should end with a period, question mark, or exclamation point + 'D417', # Missing argument descriptions in the docstring + ] + +"{test,examples,docs}/**"=[ + 'ANN' # flake8-annotations +] + +[tool.ruff.lint.flake8-tidy-imports] +# Disallow all relative imports. +ban-relative-imports = "all" diff --git a/PyPSA/source/pypsa.egg-info/PKG-INFO b/PyPSA/source/pypsa.egg-info/PKG-INFO new file mode 100644 index 0000000000000000000000000000000000000000..4f02cfaed26dc9e835b4bd12c54f3a1e1785312a --- /dev/null +++ b/PyPSA/source/pypsa.egg-info/PKG-INFO @@ -0,0 +1,310 @@ +Metadata-Version: 2.4 +Name: pypsa +Version: 1.0.3.post1.dev10+gcd5d65b2a +Summary: Python for Power Systems Analysis +Author-email: "PyPSA Developers, see https://docs.pypsa.org/latest/contributing/contributors.html" +License: MIT License + + Copyright (c) + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the + following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO + EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + USE OR OTHER DEALINGS IN THE SOFTWARE. + +Project-URL: Homepage, https://github.com/PyPSA/PyPSA +Project-URL: Source, https://github.com/PyPSA/PyPSA +Project-URL: Documentation, https://docs.pypsa.org +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Science/Research +Classifier: License :: OSI Approved :: MIT License +Classifier: Natural Language :: English +Classifier: Typing :: Typed +Classifier: Operating System :: OS Independent +Requires-Python: >=3.11 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: numpy +Requires-Dist: scipy +Requires-Dist: pandas>=0.24 +Requires-Dist: xarray +Requires-Dist: netcdf4 +Requires-Dist: linopy>=0.5.5 +Requires-Dist: matplotlib +Requires-Dist: plotly +Requires-Dist: pydeck +Requires-Dist: seaborn +Requires-Dist: geopandas>=0.9 +Requires-Dist: shapely +Requires-Dist: networkx>=2 +Requires-Dist: deprecation +Requires-Dist: validators +Requires-Dist: highspy +Requires-Dist: levenshtein>=0.27.1 +Provides-Extra: hdf5 +Requires-Dist: tables; extra == "hdf5" +Provides-Extra: cartopy +Requires-Dist: cartopy>=0.16; extra == "cartopy" +Provides-Extra: excel +Requires-Dist: openpyxl; extra == "excel" +Requires-Dist: python-calamine; extra == "excel" +Provides-Extra: gurobipy +Requires-Dist: gurobipy; extra == "gurobipy" +Provides-Extra: cloudpath +Requires-Dist: cloudpathlib; extra == "cloudpath" +Provides-Extra: dev +Requires-Dist: pytest; extra == "dev" +Requires-Dist: pytest-cov; extra == "dev" +Requires-Dist: pytest-mpl; extra == "dev" +Requires-Dist: coverage; extra == "dev" +Requires-Dist: pypower; extra == "dev" +Requires-Dist: pandapower>=2.14.11; extra == "dev" +Requires-Dist: scikit-learn; extra == "dev" +Requires-Dist: pre-commit; extra == "dev" +Requires-Dist: ruff; extra == "dev" +Requires-Dist: mypy; extra == "dev" +Requires-Dist: jupyter>=1.1.1; extra == "dev" +Requires-Dist: typing-extensions>=4.14.0; extra == "dev" +Requires-Dist: reuse>=5.1.1; extra == "dev" +Provides-Extra: docs +Requires-Dist: mkdocs-material; extra == "docs" +Requires-Dist: mkdocs-ezlinks-plugin; extra == "docs" +Requires-Dist: mkdocs-autolinks-plugin; extra == "docs" +Requires-Dist: mkdocs-redirects; extra == "docs" +Requires-Dist: mkdocs-video; extra == "docs" +Requires-Dist: mkdocs-git-revision-date-localized-plugin; extra == "docs" +Requires-Dist: mkdocs-table-reader-plugin; extra == "docs" +Requires-Dist: mkdocstrings-python; extra == "docs" +Requires-Dist: mkdocs-jupyter; extra == "docs" +Requires-Dist: cairosvg; extra == "docs" +Requires-Dist: pillow; extra == "docs" +Requires-Dist: tsam; extra == "docs" +Requires-Dist: gurobipy; extra == "docs" +Requires-Dist: openpyxl; extra == "docs" +Requires-Dist: python-calamine; extra == "docs" +Dynamic: license-file + + + + + + PyPSA Banner + + +# PyPSA - Python for Power System Analysis + + +[![PyPI version](https://img.shields.io/pypi/v/pypsa.svg)](https://pypi.python.org/pypi/pypsa) +[![Conda version](https://img.shields.io/conda/vn/conda-forge/pypsa.svg)](https://anaconda.org/conda-forge/pypsa) +![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2FPyPSA%2FPyPSA%2Fmaster%2Fpyproject.toml) +[![Tests](https://github.com/PyPSA/PyPSA/actions/workflows/test.yml/badge.svg)](https://github.com/PyPSA/PyPSA/actions/workflows/test.yml) +[![Documentation Status](https://readthedocs.org/projects/pypsa/badge/?version=latest)](https://docs.pypsa.org/latest/?badge=latest) +[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/PyPSA/PyPSA/master.svg)](https://results.pre-commit.ci/latest/github/PyPSA/PyPSA/master) +[![Code coverage](https://codecov.io/gh/PyPSA/PyPSA/branch/master/graph/badge.svg?token=kCpwJiV6Jr)](https://codecov.io/gh/PyPSA/PyPSA) +[![REUSE status](https://api.reuse.software/badge/github.com/pypsa/pypsa)](https://api.reuse.software/info/github.com/pypsa/pypsa) +[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) +[![License](https://img.shields.io/pypi/l/pypsa.svg)](LICENSE) +[![Zenodo](https://zenodo.org/badge/DOI/10.5281/zenodo.3946412.svg)](https://doi.org/10.5281/zenodo.3946412) +[![Discord](https://img.shields.io/discord/911692131440148490?logo=discord)](https://discord.gg/AnuJBk23FU) +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) + +PyPSA stands for **Python for Power System Analysis**. It is pronounced +**pipes-ah**. + +PyPSA is an open-source Python framework for optimising and simulating modern +power and energy systems that include features such as conventional generators +with unit commitment, variable wind and solar generation, hydro-electricity, +inter-temporal storage, coupling to other energy sectors, elastic demands, and +linearised power flow with loss approximations in DC and AC networks. PyPSA is +designed to scale well with large networks and long time series. It is made for +researchers, planners and utilities with basic coding aptitude who need a fast, +easy-to-use and transparent tool for power and energy system analysis. + +PyPSA is maintained by the [Department of Digital Transformation in Energy +Systems](https://tu.berlin/en/ensys) at the [Technical University of +Berlin](https://www.tu.berlin). Previous versions were developed at the +[Karlsruhe Institute of Technology](http://www.kit.edu/english/index.php) funded +by the [Helmholtz Association](https://www.helmholtz.de/en/), and at +[FIAS](https://fias.uni-frankfurt.de/) funded by the [German Federal Ministry +for Education and Research (BMBF)](https://www.bmbf.de/bmbf/en/). + +## Features + +- **Economic Dispatch (ED):** Models short-term market-based dispatch including +unit commitment, renewable availability, short-duration and seasonal storage +including hydro reservoirs with inflow and spillage dynamics, elastic demands, +load shedding and conversion between energy carriers, using either perfect +operational foresight or rolling horizon time resolution. + +- **Linear Optimal Power Flow (LOPF):** Extends economic dispatch to determine +the least-cost dispatch while respecting network constraints in meshed AC-DC +networks, using a linearised representation of power flow (KVL, KCL) with +optional loss approximations. + +- **Security-Constrained LOPF (SCLOPF):** Extends LOPF by accounting for line +outage contingencies to ensure system reliability under $N-1$ conditions. + +- **Capacity Expansion Planning (CEP):** Supports least-cost +long-term system planning with investment decisions for generation, storage, +conversion, and transmission infrastructure. Handles both single and multiple +investment periods. Continuous and discrete investments are supported. + +- **Pathway Planning:** Supports co-optimisation of multiple investment periods to +plan energy system transitions over time with perfect planning foresight. + +- **Stochastic Optimisation:** Implements two-stage stochastic programming +framework with scenario-weighted uncertain inputs, with investments as +first-stage decisions and dispatch as recourse decisions. + +- **Modelling-to-Generate-Alternatives (MGA):** Explores near-optimal decision +spaces to provide insight into the range of feasible system configurations with +similar costs. + +- **Sector-Coupling:** Modelling integrated energy systems with multiple energy + carriers (electricity, heat, hydrogen, etc.) and conversion between them. + Flexible representation of technologies such as heat pumps, electrolysers, + battery electric vehicles (BEVs), direct air capture (DAC), and synthetic + fuels production. + +- **Static Power Flow Analysis:** Computes both full non-linear and linearised + load flows for meshed AC and DC grids using Newton-Raphson method. + +## Documentation + +PyPSA has extensive [documentation](https://docs.pypsa.org) with tutorials, user guides, examples and an API reference. + +## Installation + +pip: + +``` bash +pip install pypsa +``` + +conda/mamba: + +``` bash +conda install -c conda-forge pypsa +``` + +uv: + +``` bash +uv add pypsa +``` + +## Usage + +``` py +import pypsa + +# create a new network +n = pypsa.Network() +n.add("Bus", "mybus") +n.add("Load", "myload", bus="mybus", p_set=100) +n.add("Generator", "mygen", bus="mybus", p_nom=100, marginal_cost=20) + +# load an example network +n = pypsa.examples.ac_dc_meshed() + +# run the optimisation +n.optimize() + +# plot results +n.generators_t.p.plot() +n.plot() + +# get statistics +n.statistics() +n.statistics.energy_balance() +``` + +## Dependencies + +PyPSA relies heavily on other open-source Python packages. Some of them are: + +* [pandas](http://pandas.pydata.org/) for storing data about components and time series +* [numpy](http://www.numpy.org/) and [scipy](http://scipy.org/) for linear algebra and matrix calculations +* [linopy](https://github.com/PyPSA/linopy) for preparing optimisation problems +* [matplotlib](https://matplotlib.org/), [seaborn](https://seaborn.pydata.org/) and [plotly](https://plotly.com/python/) for static and interactive plotting +* [networkx](https://networkx.github.io/) for network calculations +* [pytest](http://pytest.org/) for unit testing + +Find the full list of dependencies in the [`pyproject.toml`](https://github.com/PyPSA/PyPSA/blob/master/pyproject.toml) file. + +PyPSA can be used with different solvers. For instance, the free solvers +[HiGHS](https://highs.dev/) (installed by default), [GLPK](https://www.gnu.org/software/glpk/) and +[CBC](https://github.com/coin-or/Cbc/) or commercial solvers like +[Gurobi](http://www.gurobi.com/) or [FICO Xpress](https://www.fico.com/en/products/fico-xpress-optimization) for which free academic licenses are available. + +## Contributing and Support + +We strongly welcome anyone interested in contributing to this project. If you have any ideas, suggestions or encounter problems, feel invited to file issues or make pull requests on GitHub. + +- To **discuss** with other PyPSA users, organise projects, share news, and get in touch with the community you can use the [Discord server](https://discord.gg/AnuJBk23FU). +- For **bugs and feature requests**, please [open an issue](https://github.com/PyPSA/PyPSA/issues). +- For **troubleshooting and support**, please check the [troubleshooting](https://docs.pypsa.org/latest/user-guide/support/) and [support](https://docs.pypsa.org/latest/user-guide/support/) sectionsin the documentation. + +Detailed guidelines can be found in the [Contributing](https://docs.pypsa.org/latest/contributing/contributing/) guidelines of our documentation. + +## Code of Conduct + +Please respect our [Code of Conduct](https://docs.pypsa.org/latest/contributing/code-of-conduct/). + +## Citing PyPSA + +If you use PyPSA for your research, we would appreciate it if you would +cite the following paper: + +- T. Brown, J. Hörsch, D. Schlachtberger, [PyPSA: Python for Power + System Analysis](https://arxiv.org/abs/1707.09913), 2018, [Journal + of Open Research + Software](https://openresearchsoftware.metajnl.com/), 6(1), + [arXiv:1707.09913](https://arxiv.org/abs/1707.09913), + [DOI:10.5334/jors.188](https://doi.org/10.5334/jors.188) + +Please use the following BibTeX: + + @article{PyPSA, + author = {T. Brown and J. H\"orsch and D. Schlachtberger}, + title = {{PyPSA: Python for Power System Analysis}}, + journal = {Journal of Open Research Software}, + volume = {6}, + issue = {1}, + number = {4}, + year = {2018}, + eprint = {1707.09913}, + url = {https://doi.org/10.5334/jors.188}, + doi = {10.5334/jors.188} + } + +If you want to cite a specific PyPSA version, each release of PyPSA is archived +on [Zenodo](https://zenodo.org/) with a release-specific DOI: +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3946412.svg)](https://doi.org/10.5281/zenodo.3946412) + +## Licence + +Copyright [PyPSA Contributors](https://docs.pypsa.org/latest/developers.html) + +PyPSA is licensed under the open source [MIT License](LICENSES/MIT.txt). +The documentation is licensed under [CC-BY-4.0](LICENSES/CC-BY-4.0.txt). + +The repository uses [REUSE](https://reuse.software/) to expose the licenses of its files. diff --git a/PyPSA/source/pypsa.egg-info/SOURCES.txt b/PyPSA/source/pypsa.egg-info/SOURCES.txt new file mode 100644 index 0000000000000000000000000000000000000000..db7d6d6f5225b9fb02762368a5c1ef402c8a8c99 --- /dev/null +++ b/PyPSA/source/pypsa.egg-info/SOURCES.txt @@ -0,0 +1,762 @@ +.codespell.ignore +.git-blame-ignore-revs +.gitignore +.pre-commit-config.yaml +.readthedocs.yml +CITATION.cff +CONTRIBUTING.md +LICENSE +MANIFEST.in +README.md +REUSE.toml +codecov.yml +mkdocs.yml +pyproject.toml +.github/dependabot.yml +.github/pull_request_template.md +.github/ISSUE_TEMPLATE/bug_report.yaml +.github/ISSUE_TEMPLATE/config.yml +.github/ISSUE_TEMPLATE/documentation_improvement.yaml +.github/ISSUE_TEMPLATE/feature_request.md +.github/workflows/codeql.yml +.github/workflows/release.yml +.github/workflows/test-models.yml +.github/workflows/test.yml +LICENSES/CC-BY-4.0.txt +LICENSES/MIT.txt +docs/CODE_OF_CONDUCT.md +docs/index.md +docs/release-notes.md +docs/requirements.txt +docs/api/components/common.md +docs/api/components/components.md +docs/api/components/types/buses.md +docs/api/components/types/carriers.md +docs/api/components/types/generators.md +docs/api/components/types/global-constraints.md +docs/api/components/types/lines.md +docs/api/components/types/links.md +docs/api/components/types/loads.md +docs/api/components/types/shapes.md +docs/api/components/types/shunt-impedances.md +docs/api/components/types/storage-units.md +docs/api/components/types/stores.md +docs/api/components/types/sub-networks.md +docs/api/components/types/transformers.md +docs/api/networks/cluster.md +docs/api/networks/collection.md +docs/api/networks/components.md +docs/api/networks/descriptors.md +docs/api/networks/indexing.md +docs/api/networks/io.md +docs/api/networks/network.md +docs/api/networks/optimize.md +docs/api/networks/plot.md +docs/api/networks/power-flow.md +docs/api/networks/statistics.md +docs/api/networks/subnetwork.md +docs/api/networks/transform.md +docs/api/other/api-examples.md +docs/api/other/common.md +docs/api/other/consistency.md +docs/api/other/options.md +docs/assets/images/ac_dc_meshed-energy_balance-area_plot.jpg +docs/assets/images/ac_dc_meshed-energy_balance-area_plot.png +docs/assets/images/ac_dc_meshed-energy_balance-bar_plot.jpg +docs/assets/images/ac_dc_meshed-energy_balance-bar_plot.png +docs/assets/images/ac_dc_meshed-energy_balance-box_plot.png +docs/assets/images/ac_dc_meshed-energy_balance-custom_area_plot.png +docs/assets/images/ac_dc_meshed-energy_balance-histogram_plot.jpg +docs/assets/images/ac_dc_meshed-energy_balance-histogram_plot.png +docs/assets/images/ac_dc_meshed-energy_balance-line_plot.jpg +docs/assets/images/ac_dc_meshed-energy_balance-line_plot.png +docs/assets/images/ac_dc_meshed-energy_balance-map_plot.png +docs/assets/images/ac_dc_meshed-energy_balance-scatter_plot.jpg +docs/assets/images/ac_dc_meshed-energy_balance-scatter_plot.png +docs/assets/images/ac_dc_meshed-energy_balance-violin_plot.png +docs/assets/images/ac_dc_meshed-installed_capacity-bar_plot.jpg +docs/assets/images/ac_dc_meshed-installed_capacity-bar_plot.png +docs/assets/images/ac_dc_meshed.png +docs/assets/images/buses.png +docs/assets/images/carbon_management-energy_balance-area_plot-AC-bus_carrier.png +docs/assets/images/carbon_management-energy_balance-area_plot.png +docs/assets/images/elec_s_256_lv1.09_Co2L-3H.png +docs/assets/images/line-equivalent.png +docs/assets/images/lmp_and_line-loading.png +docs/assets/images/load-distribution.png +docs/assets/images/p_max_pu.png +docs/assets/images/pypsa-animation.png +docs/assets/images/pypsa-logo.png +docs/assets/images/reactive-power.png +docs/assets/images/reserve-power-funding.gif +docs/assets/images/reserve-power-graph.png +docs/assets/images/scigrid-curtailment.png +docs/assets/images/stacked-gen.png +docs/assets/images/stacked-gen_and_storage-scigrid.png +docs/assets/images/statistics_advanced_usage.png +docs/assets/images/storage-scigrid.png +docs/assets/images/transformer-pi-equivalent-tap-hv.png +docs/assets/images/transformer-pi-equivalent-tap-lv.png +docs/assets/images/transformer-t-equivalent-tap-hv.png +docs/assets/images/transformer-t-equivalent-tap-lv.png +docs/assets/interactive/ac_dc_meshed-energy_balance-area_iplot.html +docs/assets/interactive/carbon_management-energy_balance-area_iplot-AC-bus_carrier.html +docs/assets/interactive/carbon_management-energy_balance-area_iplot.html +docs/assets/javascripts/gurubase-widget.js +docs/assets/javascripts/matjax.js +docs/assets/javascripts/readthedocs.js +docs/assets/logo/logo-primary-dark.png +docs/assets/logo/logo-primary-dark.svg +docs/assets/logo/logo-primary-light-white-bg.png +docs/assets/logo/logo-primary-light.png +docs/assets/logo/logo-primary-light.svg +docs/assets/logo/logo-secondary-dark.png +docs/assets/logo/logo-secondary-dark.svg +docs/assets/logo/logo-secondary-light.png +docs/assets/logo/logo-secondary-light.svg +docs/assets/logo/logo-white-bg.png +docs/assets/logo/logo.png +docs/assets/logo/logo.svg +docs/assets/overrides/main.html +docs/assets/stylesheets/extra.css +docs/contributing/contributing.md +docs/contributing/contributors.md +docs/examples/3-node-cem.ipynb +docs/examples/ac-dc-lopf.ipynb +docs/examples/battery-electric-vehicle-charging.ipynb +docs/examples/biomass-synthetic-fuels-carbon-management.ipynb +docs/examples/capacity-expansion-planning-single-node.ipynb +docs/examples/chained-hydro-reservoirs.ipynb +docs/examples/chp-fixed-heat-power-ratio.ipynb +docs/examples/demand-elasticity.ipynb +docs/examples/example-1.ipynb +docs/examples/example-2.ipynb +docs/examples/example-3.ipynb +docs/examples/examples.md +docs/examples/generation-investment-screening-curve.ipynb +docs/examples/islanded-methanol-production.ipynb +docs/examples/mga.ipynb +docs/examples/minimal-example-pf.ipynb +docs/examples/modular-expansion.ipynb +docs/examples/multi-investment-optimisation.ipynb +docs/examples/myopic-pathway.ipynb +docs/examples/near-opt-space.ipynb +docs/examples/negative-prices-kvl-baker.ipynb +docs/examples/power-to-gas-boiler-chp.ipynb +docs/examples/power-to-heat-water-tank.ipynb +docs/examples/replace-generator-storage-units-with-store.ipynb +docs/examples/reserve-power.ipynb +docs/examples/rh_example.png +docs/examples/rolling-horizon.ipynb +docs/examples/scigrid-lopf-then-pf.ipynb +docs/examples/scigrid-redispatch.ipynb +docs/examples/scigrid-sclopf.ipynb +docs/examples/sector-coupling-single-node.ipynb +docs/examples/simple-electricity-market-examples.ipynb +docs/examples/stochastic-optimization.ipynb +docs/examples/time-series-aggregation.ipynb +docs/examples/tracing-infeasibilities.ipynb +docs/examples/transformer-example.ipynb +docs/examples/uc-prices.ipynb +docs/examples/unit-commitment.ipynb +docs/home/citing.md +docs/home/features.md +docs/home/installation.md +docs/home/license.md +docs/home/models.md +docs/home/users.md +docs/hooks/cleanup.py +docs/hooks/dynamic_inspect.py +docs/hooks/path_aliases.py +docs/hooks/shortcodes.py +docs/user-guide/clustering.ipynb +docs/user-guide/collection.md +docs/user-guide/components.md +docs/user-guide/design.md +docs/user-guide/faq.md +docs/user-guide/import-export.md +docs/user-guide/learning-materials.md +docs/user-guide/linear-power-flow.md +docs/user-guide/logging.ipynb +docs/user-guide/network-optimization.md +docs/user-guide/options.md +docs/user-guide/power-flow.md +docs/user-guide/statistics.md +docs/user-guide/support.md +docs/user-guide/troubleshooting.md +docs/user-guide/user-guide.md +docs/user-guide/v1-guide.md +docs/user-guide/components/buses.md +docs/user-guide/components/carriers.md +docs/user-guide/components/generators.md +docs/user-guide/components/global-constraints.md +docs/user-guide/components/line-types.md +docs/user-guide/components/lines.md +docs/user-guide/components/links.md +docs/user-guide/components/loads.md +docs/user-guide/components/shapes.md +docs/user-guide/components/shunt-impedances.md +docs/user-guide/components/storage-units.md +docs/user-guide/components/stores.md +docs/user-guide/components/sub-networks.md +docs/user-guide/components/transformer-types.md +docs/user-guide/components/transformers.md +docs/user-guide/optimization/capacity-limits.md +docs/user-guide/optimization/contingencies.md +docs/user-guide/optimization/custom-constraints.md +docs/user-guide/optimization/dispatch-limits.md +docs/user-guide/optimization/energy-balance.md +docs/user-guide/optimization/global-constraints.md +docs/user-guide/optimization/modelling-to-generate-alternatives.md +docs/user-guide/optimization/objective.md +docs/user-guide/optimization/overview.md +docs/user-guide/optimization/pathway-planning.md +docs/user-guide/optimization/power-flow.md +docs/user-guide/optimization/stochastic.md +docs/user-guide/optimization/storage.md +docs/user-guide/optimization/unit-commitment.md +docs/user-guide/plotting/charts.md +docs/user-guide/plotting/explore.ipynb +docs/user-guide/plotting/static-map.ipynb +examples/networks/ac-dc-meshed/ac-dc-meshed.nc +examples/networks/ac-dc-meshed/ac-dc-meshed/buses.csv +examples/networks/ac-dc-meshed/ac-dc-meshed/carriers.csv +examples/networks/ac-dc-meshed/ac-dc-meshed/crs.json +examples/networks/ac-dc-meshed/ac-dc-meshed/generators-p_max_pu.csv +examples/networks/ac-dc-meshed/ac-dc-meshed/generators.csv +examples/networks/ac-dc-meshed/ac-dc-meshed/global_constraints.csv +examples/networks/ac-dc-meshed/ac-dc-meshed/investment_periods.csv +examples/networks/ac-dc-meshed/ac-dc-meshed/lines.csv +examples/networks/ac-dc-meshed/ac-dc-meshed/links.csv +examples/networks/ac-dc-meshed/ac-dc-meshed/loads-p_set.csv +examples/networks/ac-dc-meshed/ac-dc-meshed/loads.csv +examples/networks/ac-dc-meshed/ac-dc-meshed/meta.json +examples/networks/ac-dc-meshed/ac-dc-meshed/network.csv +examples/networks/ac-dc-meshed/ac-dc-meshed/snapshots.csv +examples/networks/model-energy/model-energy.nc +examples/networks/model-energy/model-energy/buses.csv +examples/networks/model-energy/model-energy/carriers.csv +examples/networks/model-energy/model-energy/crs.json +examples/networks/model-energy/model-energy/generators-p_max_pu.csv +examples/networks/model-energy/model-energy/generators.csv +examples/networks/model-energy/model-energy/investment_periods.csv +examples/networks/model-energy/model-energy/links.csv +examples/networks/model-energy/model-energy/loads-p_set.csv +examples/networks/model-energy/model-energy/loads.csv +examples/networks/model-energy/model-energy/meta.json +examples/networks/model-energy/model-energy/network.csv +examples/networks/model-energy/model-energy/snapshots.csv +examples/networks/model-energy/model-energy/storage_units.csv +examples/networks/model-energy/model-energy/stores.csv +examples/networks/scigrid-de/scigrid-de.nc +examples/networks/scigrid-de/scigrid-de/buses.csv +examples/networks/scigrid-de/scigrid-de/carriers.csv +examples/networks/scigrid-de/scigrid-de/crs.json +examples/networks/scigrid-de/scigrid-de/generators-p_max_pu.csv +examples/networks/scigrid-de/scigrid-de/generators.csv +examples/networks/scigrid-de/scigrid-de/investment_periods.csv +examples/networks/scigrid-de/scigrid-de/lines.csv +examples/networks/scigrid-de/scigrid-de/loads-p_set.csv +examples/networks/scigrid-de/scigrid-de/loads.csv +examples/networks/scigrid-de/scigrid-de/meta.json +examples/networks/scigrid-de/scigrid-de/network.csv +examples/networks/scigrid-de/scigrid-de/snapshots.csv +examples/networks/scigrid-de/scigrid-de/storage_units.csv +examples/networks/scigrid-de/scigrid-de/transformers.csv +examples/networks/stochastic-network/stochastic-network.nc +examples/networks/storage-hvdc/storage-hvdc.nc +examples/networks/storage-hvdc/storage-hvdc/buses.csv +examples/networks/storage-hvdc/storage-hvdc/carriers.csv +examples/networks/storage-hvdc/storage-hvdc/crs.json +examples/networks/storage-hvdc/storage-hvdc/generators-p_max_pu.csv +examples/networks/storage-hvdc/storage-hvdc/generators.csv +examples/networks/storage-hvdc/storage-hvdc/global_constraints.csv +examples/networks/storage-hvdc/storage-hvdc/investment_periods.csv +examples/networks/storage-hvdc/storage-hvdc/lines.csv +examples/networks/storage-hvdc/storage-hvdc/links.csv +examples/networks/storage-hvdc/storage-hvdc/loads-p_set.csv +examples/networks/storage-hvdc/storage-hvdc/loads.csv +examples/networks/storage-hvdc/storage-hvdc/meta.json +examples/networks/storage-hvdc/storage-hvdc/network.csv +examples/networks/storage-hvdc/storage-hvdc/snapshots.csv +examples/networks/storage-hvdc/storage-hvdc/storage_units-inflow.csv +examples/networks/storage-hvdc/storage-hvdc/storage_units-state_of_charge_set.csv +examples/networks/storage-hvdc/storage-hvdc/storage_units.csv +pypsa/__init__.py +pypsa/_options.py +pypsa/collection.py +pypsa/common.py +pypsa/consistency.py +pypsa/constants.py +pypsa/deprecations.py +pypsa/descriptors.py +pypsa/examples.py +pypsa/geo.py +pypsa/guards.py +pypsa/networks.py +pypsa/py.typed +pypsa/type_utils.py +pypsa/version.py +pypsa.egg-info/PKG-INFO +pypsa.egg-info/SOURCES.txt +pypsa.egg-info/dependency_links.txt +pypsa.egg-info/requires.txt +pypsa.egg-info/top_level.txt +pypsa/clustering/__init__.py +pypsa/clustering/spatial.py +pypsa/clustering/temporal.py +pypsa/components/__init__.py +pypsa/components/abstract.py +pypsa/components/array.py +pypsa/components/common.py +pypsa/components/components.py +pypsa/components/descriptors.py +pypsa/components/index.py +pypsa/components/legacy.py +pypsa/components/store.py +pypsa/components/transform.py +pypsa/components/types.py +pypsa/components/_types/__init__.py +pypsa/components/_types/_patch.py +pypsa/components/_types/buses.py +pypsa/components/_types/carriers.py +pypsa/components/_types/generators.py +pypsa/components/_types/global_constraints.py +pypsa/components/_types/line_types.py +pypsa/components/_types/lines.py +pypsa/components/_types/links.py +pypsa/components/_types/loads.py +pypsa/components/_types/shapes.py +pypsa/components/_types/shunt_impedances.py +pypsa/components/_types/storage_units.py +pypsa/components/_types/stores.py +pypsa/components/_types/sub_networks.py +pypsa/components/_types/transformer_types.py +pypsa/components/_types/transformers.py +pypsa/data/components.csv +pypsa/data/variables.csv +pypsa/data/component_attrs/buses.csv +pypsa/data/component_attrs/carriers.csv +pypsa/data/component_attrs/generators.csv +pypsa/data/component_attrs/global_constraints.csv +pypsa/data/component_attrs/line_types.csv +pypsa/data/component_attrs/lines.csv +pypsa/data/component_attrs/links.csv +pypsa/data/component_attrs/loads.csv +pypsa/data/component_attrs/networks.csv +pypsa/data/component_attrs/shapes.csv +pypsa/data/component_attrs/shunt_impedances.csv +pypsa/data/component_attrs/storage_units.csv +pypsa/data/component_attrs/stores.csv +pypsa/data/component_attrs/sub_networks.csv +pypsa/data/component_attrs/transformer_types.csv +pypsa/data/component_attrs/transformers.csv +pypsa/data/standard_types/line_types.csv +pypsa/data/standard_types/transformer_types.csv +pypsa/definitions/components.py +pypsa/definitions/structures.py +pypsa/network/__init__.py +pypsa/network/abstract.py +pypsa/network/components.py +pypsa/network/descriptors.py +pypsa/network/graph.py +pypsa/network/index.py +pypsa/network/io.py +pypsa/network/power_flow.py +pypsa/network/transform.py +pypsa/optimization/__init__.py +pypsa/optimization/abstract.py +pypsa/optimization/common.py +pypsa/optimization/constraints.py +pypsa/optimization/expressions.py +pypsa/optimization/global_constraints.py +pypsa/optimization/mga.py +pypsa/optimization/optimize.py +pypsa/optimization/variables.py +pypsa/plot/__init__.py +pypsa/plot/accessor.py +pypsa/plot/maps/__init__.py +pypsa/plot/maps/common.py +pypsa/plot/maps/interactive.py +pypsa/plot/maps/static.py +pypsa/plot/statistics/__init__.py +pypsa/plot/statistics/base.py +pypsa/plot/statistics/charts.py +pypsa/plot/statistics/maps.py +pypsa/plot/statistics/plotter.py +pypsa/plot/statistics/schema.py +pypsa/statistics/__init__.py +pypsa/statistics/abstract.py +pypsa/statistics/expressions.py +pypsa/statistics/grouping.py +test/conftest.py +test/test_bugs.py +test/test_collection.py +test/test_collection_statistics.py +test/test_common.py +test/test_common_deprecations.py +test/test_components.py +test/test_components_array.py +test/test_components_custom.py +test/test_components_store.py +test/test_components_utils.py +test/test_consistency_check.py +test/test_constants.py +test/test_deprecations.py +test/test_descriptors.py +test/test_docs.py +test/test_examples.py +test/test_graph.py +test/test_guards.py +test/test_indices.py +test/test_io.py +test/test_io_cloudpathlib.py +test/test_link_post_discretization.py +test/test_lopf_ac_dc.py +test/test_lopf_activity_mask.py +test/test_lopf_and_subsequent_power_flow.py +test/test_lopf_basic_constraints.py +test/test_lopf_global_constraints.py +test/test_lopf_iteratively.py +test/test_lopf_losses.py +test/test_lopf_mga.py +test/test_lopf_modularity.py +test/test_lopf_multiinvest.py +test/test_lopf_multilink.py +test/test_lopf_quadratic_costs.py +test/test_lopf_rolling_horizon.py +test/test_lopf_storage.py +test/test_lopf_unit_commitment.py +test/test_lopf_varying_inputs.py +test/test_lpf_ac_dc.py +test/test_lpf_against_pypower.py +test/test_meshed_threshold.py +test/test_network.py +test/test_network_cycles.py +test/test_network_index.py +test/test_network_transform.py +test/test_optimization_expressions.py +test/test_optional_dependencies.py +test/test_options.py +test/test_pf_activity_mask.py +test/test_pf_against_pandapower.py +test/test_pf_against_pypower.py +test/test_pf_distributed_slack.py +test/test_plot_maps_common.py +test/test_plot_maps_interactive.py +test/test_plot_maps_static.py +test/test_riskaversion.py +test/test_sclopf_scigrid.py +test/test_snapshot_weightings.py +test/test_spatial_clustering.py +test/test_stand_by_cost.py +test/test_statistics.py +test/test_statistics_plot.py +test/test_statistics_plot_interactive.py +test/test_statistics_plot_schema.py +test/test_stochastic.py +test/test_stochastic_not_implemented.py +test/test_subnetwork.py +test/test_version.py +test/test_warnings_cyclic_storage.py +test/baseline/test_plot_types_area-capacity_factor.png +test/baseline/test_plot_types_area-capex.png +test/baseline/test_plot_types_area-curtailment.png +test/baseline/test_plot_types_area-energy_balance.png +test/baseline/test_plot_types_area-expanded_capacity.png +test/baseline/test_plot_types_area-expanded_capex.png +test/baseline/test_plot_types_area-installed_capacity.png +test/baseline/test_plot_types_area-installed_capex.png +test/baseline/test_plot_types_area-market_value.png +test/baseline/test_plot_types_area-opex.png +test/baseline/test_plot_types_area-optimal_capacity.png +test/baseline/test_plot_types_area-prices.png +test/baseline/test_plot_types_area-revenue.png +test/baseline/test_plot_types_area-supply.png +test/baseline/test_plot_types_area-system_cost.png +test/baseline/test_plot_types_area-transmission.png +test/baseline/test_plot_types_area-withdrawal.png +test/baseline/test_plot_types_area_capacity_factor.png +test/baseline/test_plot_types_area_capex.png +test/baseline/test_plot_types_area_curtailment.png +test/baseline/test_plot_types_area_energy_balance.png +test/baseline/test_plot_types_area_expanded_capacity.png +test/baseline/test_plot_types_area_expanded_capex.png +test/baseline/test_plot_types_area_installed_capacity.png +test/baseline/test_plot_types_area_installed_capex.png +test/baseline/test_plot_types_area_market_value.png +test/baseline/test_plot_types_area_opex.png +test/baseline/test_plot_types_area_optimal_capacity.png +test/baseline/test_plot_types_area_revenue.png +test/baseline/test_plot_types_area_supply.png +test/baseline/test_plot_types_area_system_cost.png +test/baseline/test_plot_types_area_transmission.png +test/baseline/test_plot_types_area_withdrawal.png +test/baseline/test_plot_types_bar-capacity_factor.png +test/baseline/test_plot_types_bar-capex.png +test/baseline/test_plot_types_bar-curtailment.png +test/baseline/test_plot_types_bar-energy_balance.png +test/baseline/test_plot_types_bar-expanded_capacity.png +test/baseline/test_plot_types_bar-expanded_capex.png +test/baseline/test_plot_types_bar-installed_capacity.png +test/baseline/test_plot_types_bar-installed_capex.png +test/baseline/test_plot_types_bar-market_value.png +test/baseline/test_plot_types_bar-opex.png +test/baseline/test_plot_types_bar-optimal_capacity.png +test/baseline/test_plot_types_bar-prices.png +test/baseline/test_plot_types_bar-revenue.png +test/baseline/test_plot_types_bar-supply.png +test/baseline/test_plot_types_bar-system_cost.png +test/baseline/test_plot_types_bar-transmission.png +test/baseline/test_plot_types_bar-withdrawal.png +test/baseline/test_plot_types_bar_capacity_factor.png +test/baseline/test_plot_types_bar_capex.png +test/baseline/test_plot_types_bar_curtailment.png +test/baseline/test_plot_types_bar_energy_balance.png +test/baseline/test_plot_types_bar_expanded_capacity.png +test/baseline/test_plot_types_bar_expanded_capex.png +test/baseline/test_plot_types_bar_installed_capacity.png +test/baseline/test_plot_types_bar_installed_capex.png +test/baseline/test_plot_types_bar_market_value.png +test/baseline/test_plot_types_bar_opex.png +test/baseline/test_plot_types_bar_optimal_capacity.png +test/baseline/test_plot_types_bar_revenue.png +test/baseline/test_plot_types_bar_supply.png +test/baseline/test_plot_types_bar_system_cost.png +test/baseline/test_plot_types_bar_transmission.png +test/baseline/test_plot_types_bar_withdrawal.png +test/baseline/test_plot_types_box-capacity_factor.png +test/baseline/test_plot_types_box-capex.png +test/baseline/test_plot_types_box-curtailment.png +test/baseline/test_plot_types_box-energy_balance.png +test/baseline/test_plot_types_box-expanded_capacity.png +test/baseline/test_plot_types_box-expanded_capex.png +test/baseline/test_plot_types_box-installed_capacity.png +test/baseline/test_plot_types_box-installed_capex.png +test/baseline/test_plot_types_box-market_value.png +test/baseline/test_plot_types_box-opex.png +test/baseline/test_plot_types_box-optimal_capacity.png +test/baseline/test_plot_types_box-prices.png +test/baseline/test_plot_types_box-revenue.png +test/baseline/test_plot_types_box-supply.png +test/baseline/test_plot_types_box-system_cost.png +test/baseline/test_plot_types_box-transmission.png +test/baseline/test_plot_types_box-withdrawal.png +test/baseline/test_plot_types_histogram-capacity_factor.png +test/baseline/test_plot_types_histogram-capex.png +test/baseline/test_plot_types_histogram-curtailment.png +test/baseline/test_plot_types_histogram-energy_balance.png +test/baseline/test_plot_types_histogram-expanded_capacity.png +test/baseline/test_plot_types_histogram-expanded_capex.png +test/baseline/test_plot_types_histogram-installed_capacity.png +test/baseline/test_plot_types_histogram-installed_capex.png +test/baseline/test_plot_types_histogram-market_value.png +test/baseline/test_plot_types_histogram-opex.png +test/baseline/test_plot_types_histogram-optimal_capacity.png +test/baseline/test_plot_types_histogram-prices.png +test/baseline/test_plot_types_histogram-revenue.png +test/baseline/test_plot_types_histogram-supply.png +test/baseline/test_plot_types_histogram-system_cost.png +test/baseline/test_plot_types_histogram-transmission.png +test/baseline/test_plot_types_histogram-withdrawal.png +test/baseline/test_plot_types_line-capacity_factor.png +test/baseline/test_plot_types_line-capex.png +test/baseline/test_plot_types_line-curtailment.png +test/baseline/test_plot_types_line-energy_balance.png +test/baseline/test_plot_types_line-expanded_capacity.png +test/baseline/test_plot_types_line-expanded_capex.png +test/baseline/test_plot_types_line-installed_capacity.png +test/baseline/test_plot_types_line-installed_capex.png +test/baseline/test_plot_types_line-market_value.png +test/baseline/test_plot_types_line-opex.png +test/baseline/test_plot_types_line-optimal_capacity.png +test/baseline/test_plot_types_line-prices.png +test/baseline/test_plot_types_line-revenue.png +test/baseline/test_plot_types_line-supply.png +test/baseline/test_plot_types_line-system_cost.png +test/baseline/test_plot_types_line-transmission.png +test/baseline/test_plot_types_line-withdrawal.png +test/baseline/test_plot_types_line_capacity_factor.png +test/baseline/test_plot_types_line_capex.png +test/baseline/test_plot_types_line_curtailment.png +test/baseline/test_plot_types_line_energy_balance.png +test/baseline/test_plot_types_line_expanded_capacity.png +test/baseline/test_plot_types_line_expanded_capex.png +test/baseline/test_plot_types_line_installed_capacity.png +test/baseline/test_plot_types_line_installed_capex.png +test/baseline/test_plot_types_line_market_value.png +test/baseline/test_plot_types_line_opex.png +test/baseline/test_plot_types_line_optimal_capacity.png +test/baseline/test_plot_types_line_revenue.png +test/baseline/test_plot_types_line_supply.png +test/baseline/test_plot_types_line_system_cost.png +test/baseline/test_plot_types_line_transmission.png +test/baseline/test_plot_types_line_withdrawal.png +test/baseline/test_plot_types_map-capacity_factor.png +test/baseline/test_plot_types_map-capex.png +test/baseline/test_plot_types_map-curtailment.png +test/baseline/test_plot_types_map-energy_balance.png +test/baseline/test_plot_types_map-expanded_capacity.png +test/baseline/test_plot_types_map-expanded_capex.png +test/baseline/test_plot_types_map-installed_capacity.png +test/baseline/test_plot_types_map-installed_capex.png +test/baseline/test_plot_types_map-market_value.png +test/baseline/test_plot_types_map-opex.png +test/baseline/test_plot_types_map-optimal_capacity.png +test/baseline/test_plot_types_map-revenue.png +test/baseline/test_plot_types_map-supply.png +test/baseline/test_plot_types_map-system_cost.png +test/baseline/test_plot_types_map-transmission.png +test/baseline/test_plot_types_map-withdrawal.png +test/baseline/test_plot_types_map_capacity_factor.png +test/baseline/test_plot_types_map_capex.png +test/baseline/test_plot_types_map_curtailment.png +test/baseline/test_plot_types_map_energy_balance.png +test/baseline/test_plot_types_map_expanded_capacity.png +test/baseline/test_plot_types_map_expanded_capex.png +test/baseline/test_plot_types_map_installed_capacity.png +test/baseline/test_plot_types_map_installed_capex.png +test/baseline/test_plot_types_map_market_value.png +test/baseline/test_plot_types_map_opex.png +test/baseline/test_plot_types_map_optimal_capacity.png +test/baseline/test_plot_types_map_revenue.png +test/baseline/test_plot_types_map_supply.png +test/baseline/test_plot_types_map_transmission.png +test/baseline/test_plot_types_map_withdrawal.png +test/baseline/test_plot_types_scatter-capacity_factor.png +test/baseline/test_plot_types_scatter-capex.png +test/baseline/test_plot_types_scatter-curtailment.png +test/baseline/test_plot_types_scatter-energy_balance.png +test/baseline/test_plot_types_scatter-expanded_capacity.png +test/baseline/test_plot_types_scatter-expanded_capex.png +test/baseline/test_plot_types_scatter-installed_capacity.png +test/baseline/test_plot_types_scatter-installed_capex.png +test/baseline/test_plot_types_scatter-market_value.png +test/baseline/test_plot_types_scatter-opex.png +test/baseline/test_plot_types_scatter-optimal_capacity.png +test/baseline/test_plot_types_scatter-prices.png +test/baseline/test_plot_types_scatter-revenue.png +test/baseline/test_plot_types_scatter-supply.png +test/baseline/test_plot_types_scatter-system_cost.png +test/baseline/test_plot_types_scatter-transmission.png +test/baseline/test_plot_types_scatter-withdrawal.png +test/baseline/test_plot_types_violin-capacity_factor.png +test/baseline/test_plot_types_violin-capex.png +test/baseline/test_plot_types_violin-curtailment.png +test/baseline/test_plot_types_violin-energy_balance.png +test/baseline/test_plot_types_violin-expanded_capacity.png +test/baseline/test_plot_types_violin-expanded_capex.png +test/baseline/test_plot_types_violin-installed_capacity.png +test/baseline/test_plot_types_violin-installed_capex.png +test/baseline/test_plot_types_violin-market_value.png +test/baseline/test_plot_types_violin-opex.png +test/baseline/test_plot_types_violin-optimal_capacity.png +test/baseline/test_plot_types_violin-prices.png +test/baseline/test_plot_types_violin-revenue.png +test/baseline/test_plot_types_violin-supply.png +test/baseline/test_plot_types_violin-system_cost.png +test/baseline/test_plot_types_violin-transmission.png +test/baseline/test_plot_types_violin-withdrawal.png +test/baseline/test_simple_plot_capacity_factor.png +test/baseline/test_simple_plot_capex.png +test/baseline/test_simple_plot_curtailment.png +test/baseline/test_simple_plot_energy_balance.png +test/baseline/test_simple_plot_expanded_capacity.png +test/baseline/test_simple_plot_expanded_capex.png +test/baseline/test_simple_plot_installed_capacity.png +test/baseline/test_simple_plot_installed_capex.png +test/baseline/test_simple_plot_market_value.png +test/baseline/test_simple_plot_opex.png +test/baseline/test_simple_plot_optimal_capacity.png +test/baseline/test_simple_plot_prices.png +test/baseline/test_simple_plot_revenue.png +test/baseline/test_simple_plot_supply.png +test/baseline/test_simple_plot_system_cost.png +test/baseline/test_simple_plot_transmission.png +test/baseline/test_simple_plot_withdrawal.png +test/data/ac-dc-meshed/results-lopf/buses-marginal_price.csv +test/data/ac-dc-meshed/results-lopf/buses-p.csv +test/data/ac-dc-meshed/results-lopf/buses-v_ang.csv +test/data/ac-dc-meshed/results-lopf/buses-v_mag_pu.csv +test/data/ac-dc-meshed/results-lopf/buses.csv +test/data/ac-dc-meshed/results-lopf/carriers.csv +test/data/ac-dc-meshed/results-lopf/crs.json +test/data/ac-dc-meshed/results-lopf/generators-p.csv +test/data/ac-dc-meshed/results-lopf/generators-p_max_pu.csv +test/data/ac-dc-meshed/results-lopf/generators.csv +test/data/ac-dc-meshed/results-lopf/global_constraints.csv +test/data/ac-dc-meshed/results-lopf/investment_periods.csv +test/data/ac-dc-meshed/results-lopf/lines-p0.csv +test/data/ac-dc-meshed/results-lopf/lines-p1.csv +test/data/ac-dc-meshed/results-lopf/lines.csv +test/data/ac-dc-meshed/results-lopf/links-p0.csv +test/data/ac-dc-meshed/results-lopf/links-p1.csv +test/data/ac-dc-meshed/results-lopf/links.csv +test/data/ac-dc-meshed/results-lopf/loads-p.csv +test/data/ac-dc-meshed/results-lopf/loads-p_set.csv +test/data/ac-dc-meshed/results-lopf/loads.csv +test/data/ac-dc-meshed/results-lopf/meta.json +test/data/ac-dc-meshed/results-lopf/network.csv +test/data/ac-dc-meshed/results-lopf/snapshots.csv +test/data/ac-dc-meshed/results-lpf/buses-p.csv +test/data/ac-dc-meshed/results-lpf/buses-v_ang.csv +test/data/ac-dc-meshed/results-lpf/buses-v_mag_pu.csv +test/data/ac-dc-meshed/results-lpf/buses.csv +test/data/ac-dc-meshed/results-lpf/carriers.csv +test/data/ac-dc-meshed/results-lpf/generators-p.csv +test/data/ac-dc-meshed/results-lpf/generators-p_max_pu.csv +test/data/ac-dc-meshed/results-lpf/generators.csv +test/data/ac-dc-meshed/results-lpf/global_constraints.csv +test/data/ac-dc-meshed/results-lpf/lines-p0.csv +test/data/ac-dc-meshed/results-lpf/lines-p1.csv +test/data/ac-dc-meshed/results-lpf/lines.csv +test/data/ac-dc-meshed/results-lpf/links-p0.csv +test/data/ac-dc-meshed/results-lpf/links-p1.csv +test/data/ac-dc-meshed/results-lpf/links-p_set.csv +test/data/ac-dc-meshed/results-lpf/links.csv +test/data/ac-dc-meshed/results-lpf/loads-p.csv +test/data/ac-dc-meshed/results-lpf/loads-p_set.csv +test/data/ac-dc-meshed/results-lpf/loads.csv +test/data/ac-dc-meshed/results-lpf/network.csv +test/data/ac-dc-meshed/results-lpf/snapshots.csv +test/data/benchmark-sp/buses-marginal_price.csv +test/data/benchmark-sp/buses-p.csv +test/data/benchmark-sp/buses.csv +test/data/benchmark-sp/crs.json +test/data/benchmark-sp/generators-p-high.csv +test/data/benchmark-sp/generators-p-med.csv +test/data/benchmark-sp/generators-p.csv +test/data/benchmark-sp/generators-p_max_pu.csv +test/data/benchmark-sp/generators.csv +test/data/benchmark-sp/investment_periods.csv +test/data/benchmark-sp/loads-p.csv +test/data/benchmark-sp/loads.csv +test/data/benchmark-sp/meta.json +test/data/benchmark-sp/network.csv +test/data/benchmark-sp/snapshots.csv +test/data/storage-hvdc/results-lopf/buses-marginal_price.csv +test/data/storage-hvdc/results-lopf/buses-p.csv +test/data/storage-hvdc/results-lopf/buses-v_ang.csv +test/data/storage-hvdc/results-lopf/buses.csv +test/data/storage-hvdc/results-lopf/carriers.csv +test/data/storage-hvdc/results-lopf/generators-p.csv +test/data/storage-hvdc/results-lopf/generators-p_max_pu.csv +test/data/storage-hvdc/results-lopf/generators.csv +test/data/storage-hvdc/results-lopf/global_constraints.csv +test/data/storage-hvdc/results-lopf/lines-p0.csv +test/data/storage-hvdc/results-lopf/lines-p1.csv +test/data/storage-hvdc/results-lopf/lines.csv +test/data/storage-hvdc/results-lopf/links-p0.csv +test/data/storage-hvdc/results-lopf/links-p1.csv +test/data/storage-hvdc/results-lopf/links.csv +test/data/storage-hvdc/results-lopf/loads-p.csv +test/data/storage-hvdc/results-lopf/loads-p_set.csv +test/data/storage-hvdc/results-lopf/loads.csv +test/data/storage-hvdc/results-lopf/network.csv +test/data/storage-hvdc/results-lopf/snapshots.csv +test/data/storage-hvdc/results-lopf/storage_units-inflow.csv +test/data/storage-hvdc/results-lopf/storage_units-p.csv +test/data/storage-hvdc/results-lopf/storage_units-spill.csv +test/data/storage-hvdc/results-lopf/storage_units-state_of_charge.csv +test/data/storage-hvdc/results-lopf/storage_units-state_of_charge_set.csv +test/data/storage-hvdc/results-lopf/storage_units.csv \ No newline at end of file diff --git a/PyPSA/source/pypsa.egg-info/dependency_links.txt b/PyPSA/source/pypsa.egg-info/dependency_links.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/PyPSA/source/pypsa.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/PyPSA/source/pypsa.egg-info/requires.txt b/PyPSA/source/pypsa.egg-info/requires.txt new file mode 100644 index 0000000000000000000000000000000000000000..5920ac6068032e7b23ea7e014b0f7a8c8fe82482 --- /dev/null +++ b/PyPSA/source/pypsa.egg-info/requires.txt @@ -0,0 +1,65 @@ +numpy +scipy +pandas>=0.24 +xarray +netcdf4 +linopy>=0.5.5 +matplotlib +plotly +pydeck +seaborn +geopandas>=0.9 +shapely +networkx>=2 +deprecation +validators +highspy +levenshtein>=0.27.1 + +[cartopy] +cartopy>=0.16 + +[cloudpath] +cloudpathlib + +[dev] +pytest +pytest-cov +pytest-mpl +coverage +pypower +pandapower>=2.14.11 +scikit-learn +pre-commit +ruff +mypy +jupyter>=1.1.1 +typing-extensions>=4.14.0 +reuse>=5.1.1 + +[docs] +mkdocs-material +mkdocs-ezlinks-plugin +mkdocs-autolinks-plugin +mkdocs-redirects +mkdocs-video +mkdocs-git-revision-date-localized-plugin +mkdocs-table-reader-plugin +mkdocstrings-python +mkdocs-jupyter +cairosvg +pillow +tsam +gurobipy +openpyxl +python-calamine + +[excel] +openpyxl +python-calamine + +[gurobipy] +gurobipy + +[hdf5] +tables diff --git a/PyPSA/source/pypsa.egg-info/top_level.txt b/PyPSA/source/pypsa.egg-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..5488884ad4159ce248f9c1babb20342e9ad11066 --- /dev/null +++ b/PyPSA/source/pypsa.egg-info/top_level.txt @@ -0,0 +1 @@ +pypsa diff --git a/PyPSA/source/pypsa/__init__.py b/PyPSA/source/pypsa/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f52593d4d214e3e21ebaea23092c3d42a9e05266 --- /dev/null +++ b/PyPSA/source/pypsa/__init__.py @@ -0,0 +1,110 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Python for Power Systems Analysis (PyPSA). + +Energy system modelling library. +""" + +__author__ = ( + "PyPSA Developers, see https://docs.pypsa.org/latest/contributing/contributors.html" +) +__copyright__ = ( + "Copyright 2015-2025 PyPSA Developers, see https://docs.pypsa.org/latest/contributing/contributors.html, " + "MIT License" +) + + +from typing import NoReturn + +from pypsa import ( + clustering, + common, + components, + descriptors, + examples, + geo, + optimization, + plot, + statistics, +) +from pypsa._options import ( + option_context, + options, +) +from pypsa.collection import NetworkCollection +from pypsa.components.components import Components +from pypsa.networks import Network, SubNetwork +from pypsa.version import ( + __version__, + __version_base__, + __version_major_minor__, +) + +version = __version__ # Alias for legacy access + +# Module access to options +get_option = options.get_option +set_option = options.set_option +reset_option = options.reset_option + + +__all__ = [ + "__version__", + "__version_base__", + "__version_major_minor__", + "version", + "options", + "set_option", + "get_option", + "option_context", + "clustering", + "common", + "components", + "contingency", + "descriptors", + "examples", + "geo", + "io", + "optimization", + "pf", + "plot", + "statistics", + "Network", + "NetworkCollection", + "SubNetwork", + "Components", +] + + +def __getattr__(name: str) -> NoReturn: + """Handle deprecated version attributes.""" + # Deprecated tuple versions (removed) + if name == "__version_short_tuple__": + msg = ( + "pypsa.__version_short_tuple__ has been removed. " + "Use pypsa.__version_major_minor__ with packaging.version.parse() for version comparisons." + ) + raise DeprecationWarning(msg) + + if name == "__version_semver_tuple__": + msg = ( + "pypsa.__version_semver_tuple__ has been removed. " + "Use pypsa.__version_base__ with packaging.version.parse() for version comparisons." + ) + raise DeprecationWarning(msg) + + # Deprecated version names (renamed) + if name == "__version_semver__": + msg = "pypsa.__version_semver__ is deprecated. Use pypsa.__version_base__ instead." + raise DeprecationWarning(msg) + + if name == "__version_short__": + msg = "pypsa.__version_short__ is deprecated. Use pypsa.__version_major_minor__ instead." + raise DeprecationWarning(msg) + + # Raise AttributeError for all other attributes + # __getattr__ is only called if the attribute is not found through normal lookup + msg = f"module '{__name__}' has no attribute '{name}'" + raise AttributeError(msg) diff --git a/PyPSA/source/pypsa/__pycache__/__init__.cpython-310.pyc b/PyPSA/source/pypsa/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3505c1e192c685276f3ccc52ef00ba1a0da442da Binary files /dev/null and b/PyPSA/source/pypsa/__pycache__/__init__.cpython-310.pyc differ diff --git a/PyPSA/source/pypsa/_options.py b/PyPSA/source/pypsa/_options.py new file mode 100644 index 0000000000000000000000000000000000000000..1c376613a158c7b0f7b4b7c043b7e4c0ad72c6c9 --- /dev/null +++ b/PyPSA/source/pypsa/_options.py @@ -0,0 +1,459 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import logging +from collections.abc import Generator +from contextlib import contextmanager +from typing import Any + +logger = logging.getLogger(__name__) + + +class InvalidOptionError(AttributeError): + """Custom exception for invalid options.""" + + def __init__( + self, + message: str | None = None, + option_path: str | None = None, + *args: Any, + **kwargs: Any, + ) -> None: + if message is None: + if option_path is None: + msg = "'option_path' must be provided if 'message' is not." + raise ValueError(msg) + message = f"Invalid option '{option_path}'. Check 'options.describe()' for valid options." + self.message = message + + super().__init__(self.message, *args, **kwargs) + + def __str__(self) -> str: + return self.message + + +class Option: + def __init__(self, value: Any = None, default: Any = None, docs: str = "") -> None: + self._value = value if value is not None else default + self._default = default + self._docs = docs + + @property + def value(self) -> Any: + return self._value + + @value.setter + def value(self, value: Any) -> None: + self._value = value + + def reset(self) -> None: + """Reset the value to its default.""" + self._value = self._default + + @property + def docs(self) -> str: + """Get the documentation for this option.""" + return self._docs + + +class OptionsNode: + """PyPSA package options. + + | + + This class provides a hierarchical structure for managing package options and + the functionality can be accessed via `pypsa.options`. + + """ + + def __init__(self, name: str = "") -> None: + self._name = name + self._children: dict[str, Any] = {} + + def __getattr__(self, name: str) -> Any: + """Get the value of an option at the specified path. + + Examples + -------- + >>> pypsa.options.general.allow_network_requests + True + + >>> pypsa.options.params.statistics.round + 5 + + """ + if name not in self._children: + raise InvalidOptionError(option_path=name) + + child = self._children[name] + if isinstance(child, Option): + return child.value + return child + + def __setattr__(self, name: str, value: Any) -> None: + """Set the value of an option at the specified path. + + Examples + -------- + Set the option to False: + >>> pypsa.options.general.allow_network_requests = False + >>> pypsa.options.general.allow_network_requests + False + + Reset back to default: + >>> pypsa.options.reset_all() + + """ + if name.startswith("_"): + super().__setattr__(name, value) + return + + if name not in self._children: + raise InvalidOptionError(option_path=name) + + child = self._children[name] + if isinstance(child, OptionsNode): + msg = f"Cannot set value for category '{name}'." + raise InvalidOptionError(msg) + child.value = value + + def _add_option(self, path: str, default: Any = None, docs: str = "") -> None: + """Add an option at the specified path.""" + parts = path.split(".") + node = self + + # Navigate/create the path + for _i, part in enumerate(parts[:-1]): + if part not in node._children: + node._children[part] = OptionsNode(part) + elif not isinstance(node._children[part], OptionsNode): + msg = f"Cannot add category '{part}' because an option already exists at this path." + raise ValueError(msg) + node = node._children[part] + + # Add the option at the leaf + leaf_name = parts[-1] + if leaf_name in node._children and isinstance( + node._children[leaf_name], OptionsNode + ): + msg = f"Cannot add option '{leaf_name}' because a category already exists at this path." + raise ValueError(msg) + + node._children[leaf_name] = Option(default, default, docs) + + def get_option(self, path: str) -> Any: + """Get the value of an option at the specified path. + + Parameters + ---------- + path : str + Path to the option. Must be in the format "category.option_name" or "category.subcategory.option_name" + + Returns + ------- + Any + The value of the option. + + Examples + -------- + >>> pypsa.options.get_option("general.allow_network_requests") + True + >>> pypsa.options.get_option("params.statistics.round") + 5 + + """ + parts = path.split(".") + node = self + + # Navigate to the parent + for part in parts[:-1]: + if part not in node._children or not isinstance( + node._children[part], OptionsNode + ): + raise InvalidOptionError(option_path=part) + node = node._children[part] + + # Get the option value + leaf_name = parts[-1] + if leaf_name not in node._children or isinstance( + node._children[leaf_name], OptionsNode + ): + raise InvalidOptionError(option_path=leaf_name) + + return node._children[leaf_name].value + + def set_option(self, path: str, value: Any) -> None: + """Set the value of an option at the specified path. + + Parameters + ---------- + path : str + Path to the option. Must be in the format "category.option_name" or "category.subcategory.option_name" + value : Any + Value to set for the option. + + Examples + -------- + Set the option to False: + >>> pypsa.options.set_option("general.allow_network_requests", False) + >>> pypsa.options.general.allow_network_requests + False + + Reset back to default: + >>> pypsa.options.reset_all() + >>> pypsa.options.general.allow_network_requests + True + + """ + parts = path.split(".") + node = self + + # Navigate to the parent + for part in parts[:-1]: + if part not in node._children or not isinstance( + node._children[part], OptionsNode + ): + raise InvalidOptionError(option_path=part) + node = node._children[part] + + # Set the option value + leaf_name = parts[-1] + if leaf_name not in node._children or isinstance( + node._children[leaf_name], OptionsNode + ): + raise InvalidOptionError(option_path=leaf_name) + + node._children[leaf_name].value = value + + def reset_option(self, path: str) -> None: + """Reset a single option to its default value. + + Parameters + ---------- + path : str + Path to the option. Must be in the format "category.option_name" or "category.subcategory.option_name" + + Examples + -------- + Set an option to a non-default value: + >>> pypsa.set_option("general.allow_network_requests", False) + >>> pypsa.options.general.allow_network_requests + False + + Reset just that option: + >>> pypsa.reset_option("general.allow_network_requests") + >>> pypsa.options.general.allow_network_requests + True + + """ + parts = path.split(".") + node = self + + # Navigate to the parent + for part in parts[:-1]: + if part not in node._children or not isinstance( + node._children[part], OptionsNode + ): + raise InvalidOptionError(option_path=part) + node = node._children[part] + + # Reset the option value + leaf_name = parts[-1] + if leaf_name not in node._children or isinstance( + node._children[leaf_name], OptionsNode + ): + raise InvalidOptionError(option_path=leaf_name) + + node._children[leaf_name].reset() + + def reset_all(self) -> None: + """Reset all options to their default values. + + Examples + -------- + Define some options: + >>> pypsa.options.general.allow_network_requests = False + >>> pypsa.options.params.statistics.round = 4 + + Reset all options: + >>> pypsa.options.reset_all() + >>> pypsa.options.general.allow_network_requests + True + >>> pypsa.options.params.statistics.round + 5 + + """ + for child in self._children.values(): + if isinstance(child, Option): + child.reset() + else: + child.reset_all() + + def _describe_options(self, prefix: str = "") -> None: + """Print documentation for options via path. + + Parameters + ---------- + prefix : str + Prefix for the option path. Used for nested options. + If empty, the root options are printed. + + + + """ + if not prefix: + print("PyPSA Options\n=============") # noqa: T201 + + for name, child in self._children.items(): + path = f"{prefix}.{name}" if prefix else name + + if isinstance(child, Option): + print(f"{path}:") # noqa: T201 + print(f" Default: {child._default}") # noqa: T201 + print(f" Description: {child._docs}") # noqa: T201 + else: + child._describe_options(path) + + def describe(self) -> None: + """Print documentation for options node via attribute access. + + Examples + -------- + Print all options: + + >>> pypsa.options.describe() # doctest: +ELLIPSIS + PyPSA Options + ============= + general.allow_network_requests: + Default: True + Description: Allow PyPSA to make network requests... + ... + + """ + self._describe_options() + + +options = OptionsNode() + + +@contextmanager +def option_context(*args: Any) -> Generator[None, None, None]: + """Context manager to temporarily set options. + + Parameters + ---------- + *args : str, Any + Must be passed in pairs of option_name and value. + Option_name must be in the format "category.option_name" or "category.subcategory.option_name" + + """ + if len(args) % 2 != 0: + msg = "Arguments must be paired option_names and values" + raise ValueError(msg) + + # Get the original values and set the temporary ones + pairs = [(args[i], args[i + 1]) for i in range(0, len(args), 2)] + original_values: dict[str, Any] = {} + + try: + # Store original values and set new ones + for option_name, value in pairs: + original_values[option_name] = options.get_option(option_name) + options.set_option(option_name, value) + yield + finally: + # Restore original values + for option_name, original_value in original_values.items(): + options.set_option(option_name, original_value) + + +# Setup options +# ============= + +# General category +options._add_option( + "general.allow_network_requests", + True, + "Allow PyPSA to make network requests. When False, all network requests\n\t" + "(such as checking for version updates) are disabled. This may be needed\n\t" + "in restricted environments, offline usage, or for security/privacy reasons.\n\t" + "This only controls PyPSA's own network requests, dependencies may still\n\t" + "make network requests independently.", +) + +# Parameters category + +options._add_option( + "params.statistics.nice_names", + True, + "Default value for the 'nice_names' parameter in statistics module.", +) +options._add_option( + "params.statistics.drop_zero", + True, + "Default value for the 'drop_zero' parameter in statistics module.", +) +options._add_option( + "params.statistics.round", + 5, + "Default value for the 'round' parameter in statistics module.", +) + +options._add_option( + "params.add.return_names", + False, + "Default value for the 'return_names' parameter in Network.add method.\n\t" + "If True, the add method returns the names of added components.\n\t" + "If False, it returns None.", +) + +options._add_option( + "params.optimize.model_kwargs", + {}, + "Default value for the 'model_kwargs' parameter in optimization module.", +) +options._add_option( + "params.optimize.solver_name", + "highs", + "Default value for the 'solver_name' parameter in optimization module.", +) +options._add_option( + "params.optimize.solver_options", + {}, + "Default value for the 'solver_options' parameter in optimization module.", +) + +# Warnings category +options._add_option( + "warnings.components_store_iter", + True, + "If False, suppresses the deprecation warning when iterating over components.", +) +options._add_option( + "warnings.attribute_typos", + True, + "If False, suppresses warnings about potential typos in component attribute names. " + "Note: warnings about unintended attributes (standard attributes for other components) " + "will still be shown.", +) + +# API +options._add_option( + "api.new_components_api", + False, + "Activate the new components API, which replaces the static components data access\n\t" + "with the more flexible components class. This will just change the api and not any\n\t" + "functionality. Components class features are always available.\n\t" + "See `https://go.pypsa.org/new-components-api` for more details.", +) + + +# Debugging category +options._add_option( + "debug.runtime_verification", + False, + "Enable runtime verification of PyPSA's internal state. This is useful for\n\t" + "debugging and development purposes. This will lead to overhead in\n\t" + "performance and should not be used in production.", +) diff --git a/PyPSA/source/pypsa/clustering/__init__.py b/PyPSA/source/pypsa/clustering/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..4deed9d09f4653a0911a866939af634c9f821747 --- /dev/null +++ b/PyPSA/source/pypsa/clustering/__init__.py @@ -0,0 +1,89 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Clustering functionality for PyPSA networks.""" + +from functools import wraps +from typing import TYPE_CHECKING, Any + +import pandas as pd + +from pypsa.clustering import spatial, temporal +from pypsa.common import _scenarios_not_implemented + +if TYPE_CHECKING: + from pypsa import Network + from pypsa.clustering.spatial import Clustering + + +class ClusteringAccessor: + """Clustering accessor for clustering a network spatially and temporally. + + + """ + + def __init__(self, n: "Network") -> None: + """Initialize the ClusteringAccessor.""" + self.n = n + + @_scenarios_not_implemented + @wraps(spatial.busmap_by_hac) + def busmap_by_hac(self, *args: Any, **kwargs: Any) -> pd.Series: + """Wrap [`pypsa.clustering.spatial.busmap_by_hac`][].""" + return spatial.busmap_by_hac(self.n, *args, **kwargs) + + @_scenarios_not_implemented + @wraps(spatial.busmap_by_kmeans) + def busmap_by_kmeans(self, *args: Any, **kwargs: Any) -> pd.Series: + """Wrap [`pypsa.clustering.spatial.busmap_by_kmeans`][].""" + return spatial.busmap_by_kmeans(self.n, *args, **kwargs) + + @_scenarios_not_implemented + @wraps(spatial.busmap_by_greedy_modularity) + def busmap_by_greedy_modularity(self, *args: Any, **kwargs: Any) -> pd.Series: + """Wrap [`pypsa.clustering.spatial.busmap_by_greedy_modularity`][].""" + return spatial.busmap_by_greedy_modularity(self.n, *args, **kwargs) + + @_scenarios_not_implemented + @wraps(spatial.hac_clustering) + def cluster_spatially_by_hac(self, *args: Any, **kwargs: Any) -> "Clustering": + """Wrap [`pypsa.clustering.spatial.hac_clustering`][].""" + return spatial.hac_clustering(self.n, *args, **kwargs).n + + @_scenarios_not_implemented + @wraps(spatial.kmeans_clustering) + def cluster_spatially_by_kmeans(self, *args: Any, **kwargs: Any) -> "Clustering": + """Wrap [`pypsa.clustering.spatial.kmeans_clustering`][].""" + return spatial.kmeans_clustering(self.n, *args, **kwargs).n + + @_scenarios_not_implemented + @wraps(spatial.greedy_modularity_clustering) + def cluster_spatially_by_greedy_modularity( + self, *args: Any, **kwargs: Any + ) -> "Clustering": + """Wrap [`pypsa.clustering.spatial.greedy_modularity_clustering`][].""" + return spatial.greedy_modularity_clustering(self.n, *args, **kwargs).n + + @_scenarios_not_implemented + def cluster_by_busmap(self, *args: Any, **kwargs: Any) -> "Clustering": + """Cluster the network spatially by busmap. + + This function calls [`pypsa.clustering.ClusteringAccessor.get_clustering_from_busmap`][] internally. + For more information, see the documentation of that function. + + Returns + ------- + n : pypsa.Network + + """ + return spatial.get_clustering_from_busmap(self.n, *args, **kwargs).n + + @_scenarios_not_implemented + @wraps(spatial.get_clustering_from_busmap) + def get_clustering_from_busmap(self, *args: Any, **kwargs: Any) -> "Clustering": + """Wrap [`get_clustering_from_busmap`][pypsa.clustering.ClusteringAccessor.get_clustering_from_busmap].""" + return spatial.get_clustering_from_busmap(self.n, *args, **kwargs) + + +__all__ = ["ClusteringAccessor", "spatial", "temporal"] diff --git a/PyPSA/source/pypsa/clustering/__pycache__/__init__.cpython-310.pyc b/PyPSA/source/pypsa/clustering/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a88256ba7ec9b6bdad379b89832211df4c76b326 Binary files /dev/null and b/PyPSA/source/pypsa/clustering/__pycache__/__init__.cpython-310.pyc differ diff --git a/PyPSA/source/pypsa/clustering/spatial.py b/PyPSA/source/pypsa/clustering/spatial.py new file mode 100644 index 0000000000000000000000000000000000000000..2b7050d785e23815484dceaeb6e65cd3ee65727f --- /dev/null +++ b/PyPSA/source/pypsa/clustering/spatial.py @@ -0,0 +1,1069 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Functions for computing network clusters.""" + +from __future__ import annotations + +import logging +from dataclasses import dataclass +from importlib.util import find_spec +from typing import TYPE_CHECKING, Any + +import networkx as nx +import numpy as np +import pandas as pd +import scipy.sparse as sp +from packaging.version import Version, parse +from pandas import Series + +from pypsa.geo import haversine_pts + +if TYPE_CHECKING: + from collections.abc import Callable, Collection, Iterable + + from pypsa import Network + +logger = logging.getLogger(__name__) + + +DEFAULT_ONE_PORT_STRATEGIES = { + "p": "sum", + "q": "sum", + "p_set": "sum", + "q_set": "sum", + "p_nom": pd.Series.sum, # resolve infinities, see https://github.com/pandas-dev/pandas/issues/54161 + "p_nom_max": pd.Series.sum, # resolve infinities, see https://github.com/pandas-dev/pandas/issues/54161 + "p_nom_min": "sum", + "e_nom": pd.Series.sum, # resolve infinities, see https://github.com/pandas-dev/pandas/issues/54161 + "e_nom_max": pd.Series.sum, # resolve infinities, see https://github.com/pandas-dev/pandas/issues/54161 + "e_nom_min": "sum", + "weight": "sum", + "ramp_limit_up": "mean", + "ramp_limit_down": "mean", + "ramp_limit_start_up": "mean", + "ramp_limit_shut_down": "mean", + "build_year": lambda x: 0, + "lifetime": lambda x: np.inf, + "control": lambda x: "", + "p_max_pu": "capacity_weighted_average", + "p_min_pu": "capacity_weighted_average", + "capital_cost": "capacity_weighted_average", + "marginal_cost": "capacity_weighted_average", + "efficiency": "capacity_weighted_average", + "max_hours": "capacity_weighted_average", + "inflow": "sum", +} + +DEFAULT_BUS_STRATEGIES = { + "x": "mean", + "y": "mean", + "v_nom": "max", + "v_mag_pu_max": "min", + "v_mag_pu_min": "max", + "generator": lambda x: "", +} + +DEFAULT_LINE_STRATEGIES = { + "r": "reciprocal_voltage_weighted_average", + "x": "reciprocal_voltage_weighted_average", + "g": "voltage_weighted_average", + "b": "voltage_weighted_average", + "terrain_factor": "mean", + "s_min_pu": "capacity_weighted_average", + "s_max_pu": "capacity_weighted_average", + "s_nom": pd.Series.sum, # resolve infinities, see https://github.com/pandas-dev/pandas/issues/54161 + "s_nom_min": "sum", + "s_nom_max": pd.Series.sum, # resolve infinities, see https://github.com/pandas-dev/pandas/issues/54161 + "s_nom_extendable": "any", + "num_parallel": "sum", + "capital_cost": "length_capacity_weighted_average", + "v_ang_min": "max", + "v_ang_max": "min", + "lifetime": "capacity_weighted_average", + "build_year": "capacity_weighted_average", +} + + +def normed_or_uniform(x: pd.Series) -> pd.Series: + """Normalize a series by dividing it by its sum. + + When the sum is zero, a uniform distribution is returned instead. + + Parameters + ---------- + x : pandas.Series + The input series to normalize. + + Returns + ------- + pandas.Series + The normalized series, or a uniform distribution if the input sum is zero. + + Examples + -------- + >>> x = pd.Series([1, 2, 3]) + >>> normed_or_uniform(x) + 0 0.166667 + 1 0.333333 + 2 0.500000 + dtype: float64 + + """ + if x.sum(skipna=False) > 0: + return x / x.sum() + return pd.Series(1.0 / len(x), x.index) + + +def make_consense(component: str, attr: str) -> Callable: + """Return a function to verify attribute values of a cluster in a component. + + The values should either be the same or all null. + + Parameters + ---------- + component : str + The name of the component. + attr : str + The name of the attribute to verify. + + Returns + ------- + Callable + A function that checks whether all values in the Series are the same or all null. + + Raises + ------ + AssertionError + If the attribute values in a cluster are not the same or all null. + + """ + + def consense(x: Series) -> object: + v = x.iat[0] + if not (x == v).all() and not x.isnull().all(): + msg = ( + f"In {component} cluster {x.name}, the values of attribute " + f"{attr} do not agree:\n{x}" + ) + raise ValueError(msg) + return v + + return consense + + +def align_strategies(strategies: dict, keys: Iterable, component: str) -> dict: + """Aligns the given strategies with the given keys. + + Parameters + ---------- + strategies : dict + The strategies to align. + keys : list + The keys to align the strategies with. + component : str + The component to align the strategies with. + + Returns + ------- + dict + The aligned strategies. + + """ + strategies |= { + k: make_consense(component, k) for k in set(keys).difference(strategies) + } + return {k: strategies[k] for k in keys} + + +def flatten_multiindex(m: pd.MultiIndex, join: str = " ") -> pd.Index: + """Flatten a multiindex by joining the levels with the given string. + + Parameters + ---------- + m : pd.MultiIndex + The multiindex to flatten. + join : str, optional + The string to join the levels with (default is " "). + + Returns + ------- + pd.Index + The flattened index. + + Examples + -------- + >>> m = pd.MultiIndex.from_tuples([("a", "b"), ("c", "d")]) + >>> flatten_multiindex(m) + Index(['a b', 'c d'], dtype='object') + + """ + return m if m.nlevels <= 1 else m.to_flat_index().str.join(join).str.strip() + + +def aggregateoneport( + n: Network, + busmap: dict, + component: str, + carriers: Iterable | None = None, + buses: Iterable | None = None, + with_time: bool = True, + custom_strategies: dict | None = None, +) -> tuple[pd.DataFrame, dict]: + """Aggregate one port components in the network based on the given busmap. + + Parameters + ---------- + n : Network + The network containing the generators. + busmap : dict + A dictionary mapping old bus IDs to new bus IDs. + component : str + The component to aggregate. + carriers : list, optional + List of carriers to be considered (default is all carriers). + buses : list, optional + List of buses to be considered (default is all buses). + with_time : bool, optional + Whether to include time-dependent attributes (default is True). + custom_strategies : dict, optional + Custom aggregation strategies (default is empty dict). + + Returns + ------- + static : DataFrame + DataFrame of the aggregated generators. + dynamic : dict + Dictionary of the aggregated dynamic data. + + """ + if custom_strategies is None: + custom_strategies = {} + c = component + static = n.c[c].static + attrs = n.components[c]["defaults"] + if "carrier" in static.columns: + if carriers is None: + carriers = static.carrier.unique() + to_aggregate = static.carrier.isin(carriers) + else: + to_aggregate = pd.Series(True, static.index) + + if buses is not None: + to_aggregate |= static.bus.isin(buses) + + static = static[to_aggregate] + static = static.assign(bus=static.bus.map(busmap)) + + output_columns = attrs.index[attrs.static & attrs.status.str.startswith("Output")] + columns = [c for c in static.columns if c not in output_columns] + + strategies = {**DEFAULT_ONE_PORT_STRATEGIES, **custom_strategies} + static_strategies = align_strategies(strategies, columns, c) + + grouper = ( + [static.bus, static.carrier] if "carrier" in static.columns else static.bus + ) + capacity = static.columns.intersection({"p_nom", "e_nom"}) + if len(capacity): + capacity_weights = ( + static[capacity[0]].groupby(grouper, axis=0).transform(normed_or_uniform) + ) + if "weight" in static.columns: + weights = static.weight.groupby(grouper, axis=0).transform(normed_or_uniform) + + for k, v in static_strategies.items(): + if v == "weighted_average": + static[k] = static[k] * weights + static_strategies[k] = "sum" + elif v == "capacity_weighted_average": + static[k] = static[k] * capacity_weights + static_strategies[k] = "sum" + elif v == "weighted_min": + static["p_nom_max"] /= weights + static_strategies[k] = "min" + + aggregated = static.groupby(grouper).agg(static_strategies) + aggregated.index = flatten_multiindex(aggregated.index).rename(c) + + non_aggregated = n.c[c].static[~to_aggregate] + non_aggregated = non_aggregated.assign(bus=non_aggregated.bus.map(busmap)) + + static = pd.concat([aggregated, non_aggregated], sort=False) + static.fillna(attrs.default, inplace=True) + + dynamic = {} + if with_time: + dynamic_strategies = align_strategies(strategies, n.c[c].dynamic, c) + for attr, data in n.c[c].dynamic.items(): + if data.empty: + dynamic[attr] = data + continue + strategy = dynamic_strategies[attr] + data = n.get_switchable_as_dense(c, attr) + aggregated = data.loc[:, to_aggregate] + + if strategy == "weighted_average": + aggregated = aggregated * weights + aggregated = aggregated.T.groupby(grouper).sum().T + elif strategy == "capacity_weighted_average": + aggregated = aggregated * capacity_weights + aggregated = aggregated.T.groupby(grouper).sum().T + elif strategy == "weighted_min": + aggregated = aggregated / weights + aggregated = aggregated.T.groupby(grouper).min().T + else: + aggregated = aggregated.T.groupby(grouper).agg(strategy).T + aggregated.columns = flatten_multiindex(aggregated.columns).rename(c) + + non_aggregated = data.loc[:, ~to_aggregate] + + dynamic[attr] = pd.concat([aggregated, non_aggregated], axis=1, sort=False) + + # filter out static values + if attr in static: + is_static = (dynamic[attr] == static[attr]).all() + dynamic[attr] = dynamic[attr].loc[:, ~is_static] + + return static, dynamic + + +def aggregatebuses( + n: Network, busmap: dict, custom_strategies: dict | None = None +) -> pd.DataFrame: + """Aggregate buses in the network based on the given busmap. + + Parameters + ---------- + n : Network + The network containing the buses. + busmap : dict + A dictionary mapping old bus IDs to new bus IDs. + custom_strategies : dict, optional + Custom aggregation strategies (default is empty dict). + + Returns + ------- + static : DataFrame + DataFrame of the aggregated buses. + + """ + if custom_strategies is None: + custom_strategies = {} + c = "Bus" + attrs = n.components[c]["defaults"] + + output_columns = attrs.index[attrs.static & attrs.status.str.startswith("Output")] + columns = [c for c in n.c.buses.static.columns if c not in output_columns] + + strategies = {**DEFAULT_BUS_STRATEGIES, **custom_strategies} + strategies = align_strategies(strategies, columns, c) + + aggregated = n.c.buses.static.groupby(busmap).agg(strategies) + aggregated.index = flatten_multiindex(aggregated.index).rename(c) + + return aggregated + + +def aggregatelines( + n: Network, + busmap: dict, + line_length_factor: float = 1.0, + with_time: bool = True, + custom_strategies: dict | None = None, + bus_strategies: dict | None = None, + custom_line_groupers: Iterable = [], +) -> tuple[pd.DataFrame, dict, pd.Series]: + """Aggregate lines in the network based on the given busmap. + + Parameters + ---------- + n : Network + The network containing the lines. + busmap : dict + A dictionary mapping old bus IDs to new bus IDs. + line_length_factor : float, optional + A factor to multiply the length of each line by (default is 1.0). + with_time : bool, optional + Whether to aggregate dynamic data (default is True). + custom_strategies : dict, optional + Custom aggregation strategies (default is empty dict). + bus_strategies : dict, optional + Custom aggregation strategies for buses (default is empty dict). + custom_line_groupers : list, optional + Additional custom groupers for the lines. Specifies that different column values are not aggregated. (default is empty list). + + Returns + ------- + static : DataFrame + DataFrame of the aggregated lines. + dynamic : dict + Dictionary of DataFrames of the aggregated dynamic data (if with_time is True). + + """ + if custom_strategies is None: + custom_strategies = {} + if bus_strategies is None: + bus_strategies = {} + attrs = n.components["Line"]["defaults"] + static = n.c["Line"].static + idx = static.index[static.bus0.map(busmap) != static.bus1.map(busmap)] + static = static.loc[idx] + + orig_length = static.length + orig_v_nom = static.bus0.map(n.c.buses.static.v_nom) + + bus_strategies = {**DEFAULT_BUS_STRATEGIES, **bus_strategies} + cols = ["x", "y", "v_nom"] + buses = ( + n.c.buses.static[cols].groupby(busmap).agg({c: bus_strategies[c] for c in cols}) + ) + + static = static.assign(bus0=static.bus0.map(busmap), bus1=static.bus1.map(busmap)) + reverse_order = static.bus0 > static.bus1 + reverse_values = static.loc[reverse_order, ["bus1", "bus0"]].values + static.loc[reverse_order, ["bus0", "bus1"]] = reverse_values + + output_columns = attrs.index[attrs.static & attrs.status.str.startswith("Output")] + columns = [c for c in static.columns if c not in output_columns] + + strategies = {**DEFAULT_LINE_STRATEGIES, **custom_strategies} + static_strategies = align_strategies(strategies, columns, "Line") + + grouper = ( + static.groupby(["bus0", "bus1", *custom_line_groupers]).ngroup().astype(str) + ) + + coords = buses[["x", "y"]] + length = ( + haversine_pts(coords.loc[static.bus0], coords.loc[static.bus1]) + * line_length_factor + ) + static = static.assign(length=length) + + length_factor = (static.length / orig_length).where(orig_length > 0, static.length) + v_nom = pd.concat( + [static.bus0.map(buses.v_nom), static.bus1.map(buses.v_nom)], axis=1 + ).max(1) + voltage_factor = (orig_v_nom / v_nom) ** 2 + capacity_weights = static.groupby(grouper).s_nom.transform(normed_or_uniform) + + for col, strategy in static_strategies.items(): + if strategy == "capacity_weighted_average": + static[col] = static[col] * capacity_weights + static_strategies[col] = "sum" + elif strategy == "reciprocal_voltage_weighted_average": + static[col] = voltage_factor / (length_factor * static[col]) + static_strategies[col] = lambda x: 1.0 / x.sum() + elif strategy == "voltage_weighted_average": + static[col] = voltage_factor * length_factor * static[col] + static_strategies[col] = "sum" + elif strategy == "length_capacity_weighted_average": + static[col] = static[col] * length_factor * capacity_weights + static_strategies[col] = "sum" + + static = static.groupby(grouper).agg(static_strategies) + + dynamic = {} + if with_time: + dynamic_strategies = align_strategies(strategies, n.c["Line"].dynamic, "Line") + + for attr, data in n.c.lines.dynamic.items(): + if data.empty: + dynamic[attr] = data + continue + + strategy = dynamic_strategies[attr] + data = n.get_switchable_as_dense("Line", attr, inds=idx) + + if strategy == "capacity_weighted_average": + data = data * capacity_weights + data = data.T.groupby(grouper).sum().T + else: + data = data.T.groupby(grouper).agg(strategy).T + + dynamic[attr] = data + + # filter out static values + if attr in static: + is_static = (dynamic[attr] == static[attr]).all() + dynamic[attr] = dynamic[attr].loc[:, ~is_static] + + return static, dynamic, grouper + + +@dataclass +class Clustering: + """Clustering result.""" + + n: Any + busmap: pd.Series + linemap: pd.Series + + +def get_clustering_from_busmap( + n: Network, + busmap: dict, + with_time: bool = True, + line_length_factor: float = 1.0, + aggregate_generators_weighted: bool = False, + aggregate_one_ports: dict | None = None, + aggregate_generators_carriers: Iterable | None = None, + scale_link_capital_costs: bool = True, + bus_strategies: dict | None = None, + one_port_strategies: dict | None = None, + generator_strategies: dict | None = None, + line_strategies: dict | None = None, + aggregate_generators_buses: Iterable | None = None, + custom_line_groupers: list | None = None, +) -> Clustering: + """Get a clustering result from a busmap.""" + if bus_strategies is None: + bus_strategies = {} + if one_port_strategies is None: + one_port_strategies = {} + if generator_strategies is None: + generator_strategies = {} + if line_strategies is None: + line_strategies = {} + if aggregate_one_ports is None: + aggregate_one_ports = {} + if custom_line_groupers is None: + custom_line_groupers = [] + + buses = aggregatebuses(n, busmap, custom_strategies=bus_strategies) + lines, lines_t, linemap = aggregatelines( + n, + busmap, + line_length_factor, + with_time=with_time, + custom_strategies=line_strategies, + bus_strategies=bus_strategies, + custom_line_groupers=custom_line_groupers, + ) + + clustered = n.__class__() + + clustered.add("Bus", buses.index, **buses) + clustered.add("Line", lines.index, **lines) + + # Carry forward global constraints to clustered n. + clustered.c.global_constraints.static = n.c.global_constraints.static + + if with_time: + clustered.set_snapshots(n.snapshots) + clustered.snapshot_weightings = n.snapshot_weightings.copy() + if not n.investment_periods.empty: + clustered.set_investment_periods(n.investment_periods) + clustered.investment_period_weightings = ( + n.investment_period_weightings.copy() + ) + for attr, df in lines_t.items(): + if not df.empty: + clustered._import_series_from_df(df, "Line", attr) + + one_port_components = n.one_port_components.copy() + + if aggregate_generators_weighted: + # TODO: Remove this in favour of the more general approach below. + one_port_components.remove("Generator") + generators, generators_dynamic = aggregateoneport( + n, + busmap, + "Generator", + carriers=aggregate_generators_carriers, + buses=aggregate_generators_buses, + with_time=with_time, + custom_strategies=generator_strategies, + ) + clustered.add("Generator", generators.index, **generators) + if with_time: + for attr, df in generators_dynamic.items(): + if not df.empty: + clustered._import_series_from_df(df, "Generator", attr) + + for one_port in aggregate_one_ports: + one_port_components.remove(one_port) + new_static, new_dynamic = aggregateoneport( + n, + busmap, + component=one_port, + with_time=with_time, + custom_strategies=one_port_strategies.get(one_port, {}), + ) + clustered.add(one_port, new_static.index, **new_static) + for attr, df in new_dynamic.items(): + clustered._import_series_from_df(df, one_port, attr) + + # Collect remaining one ports + + for c in n.components: + if c.name not in one_port_components: + continue + remaining_one_port_data = c.static.assign(bus=c.static.bus.map(busmap)).dropna( + subset=["bus"] + ) + clustered.add(c.name, remaining_one_port_data.index, **remaining_one_port_data) + + if with_time: + for c in n.components: + if c.name not in one_port_components: + continue + for attr, df in c.dynamic.items(): + if not df.empty: + clustered._import_series_from_df(df, c.name, attr) + + bus_mappings = { + "bus0": n.c.links.static.bus0.map(busmap), + "bus1": n.c.links.static.bus1.map(busmap), + } + + # Also add additional ports if they exist + for port in n.c.links.additional_ports: + col = f"bus{port}" + if col in n.c.links.static.columns: + bus_mappings[col] = n.c.links.static[col].map(busmap) + + new_links = ( + n.c.links.static.assign(**bus_mappings) + .dropna(subset=["bus0", "bus1"]) # Only require bus0 and bus1 to be non-NaN + .loc[lambda df: df.bus0 != df.bus1] + ) + + new_links["length"] = np.where( + new_links.length.notnull() & (new_links.length > 0), + line_length_factor + * haversine_pts( + buses.loc[new_links["bus0"], ["x", "y"]], + buses.loc[new_links["bus1"], ["x", "y"]], + ), + 0, + ) + if scale_link_capital_costs: + new_links["capital_cost"] *= ( + new_links.length / n.c.links.static.length + ).fillna(1) + + clustered.add("Link", new_links.index, **new_links) + + if with_time: + for attr, df in n.c.links.dynamic.items(): + if not df.empty: + clustered._import_series_from_df(df, "Link", attr) + + clustered.add("Carrier", n.c.carriers.static.index, **n.c.carriers.static) + + clustered.determine_network_topology() + + return Clustering(clustered, busmap, linemap) + + +################ +# k-Means clustering based on bus properties + + +def busmap_by_kmeans( + n: Network, + bus_weightings: pd.Series, + n_clusters: int, + buses_i: pd.Index | None = None, + **kwargs: Any, +) -> pd.Series: + """Create a bus map from the clustering of buses in space with a weighting. + + Parameters + ---------- + n : pypsa.Network + The buses must have coordinates x, y. + bus_weightings : pandas.Series + Series of integer weights for buses, indexed by bus names. + n_clusters : int + Final number of clusters desired. + buses_i : None|pandas.Index + If not None (default), subset of buses to cluster. + kwargs + Any remaining arguments to be passed to KMeans (e.g. n_init, n_jobs). + + Returns + ------- + busmap : pandas.Series + Mapping of n.buses to k-means clusters (indexed by + non-negative integers). + + """ + if find_spec("sklearn") is None: + msg = ( + "Optional dependency 'sklearn' not found." + "Install via 'conda install -c conda-forge scikit-learn' " + "or 'pip install scikit-learn'" + ) + raise ModuleNotFoundError(msg) + + from sklearn.cluster import KMeans # noqa: PLC0415 + + if buses_i is None: + buses_i = n.c.buses.static.index + + # since one cannot weight points directly in the scikit-learn + # implementation of k-means, just add additional points at + # same position + points = n.c.buses.static.loc[buses_i, ["x", "y"]].values.repeat( + bus_weightings.reindex(buses_i).astype(int), axis=0 + ) + + kwargs.setdefault("n_init", "auto") + kmeans = KMeans(init="k-means++", n_clusters=n_clusters, **kwargs) + + kmeans.fit(points) + + return pd.Series( + data=kmeans.predict(n.c.buses.static.loc[buses_i, ["x", "y"]].values), + index=buses_i, + ).astype(str) + + +def kmeans_clustering( + n: Network, + bus_weightings: pd.Series, + n_clusters: int, + line_length_factor: float = 1.0, + **kwargs: Any, +) -> Clustering: + """Cluster the network according to k-means clustering of the buses. + + Buses can be weighted by an integer in the series `bus_weightings`. + + Note that this clustering method completely ignores the branches of the network. + + Parameters + ---------- + n : pypsa.Network + The buses must have coordinates x, y. + bus_weightings : pandas.Series + Series of integer weights for buses, indexed by bus names. + n_clusters : int + Final number of clusters desired. + line_length_factor : float + Factor to multiply the spherical distance between new buses in order to get new + line lengths. + kwargs + Any remaining arguments to be passed to KMeans (e.g. n_init, n_jobs) + + Returns + ------- + Clustering : named tuple + A named tuple containing network, busmap and linemap + + """ + busmap = busmap_by_kmeans(n, bus_weightings, n_clusters, **kwargs) + + return get_clustering_from_busmap(n, busmap, line_length_factor=line_length_factor) + + +################ +# Hierarchical Clustering +def busmap_by_hac( + n: Network, + n_clusters: int, + buses_i: pd.Index | None = None, + branch_components: Collection[str] | None = None, + feature: pd.DataFrame | None = None, + affinity: str | Callable = "euclidean", + linkage: str = "ward", + **kwargs: Any, +) -> pd.Series: + """Create a busmap according to Hierarchical Agglomerative Clustering. + + Parameters + ---------- + n : pypsa.Network + Network instance. + n_clusters : int + Final number of clusters desired. + buses_i: None | pandas.Index, default=None + Subset of buses to cluster. If None, all buses are considered. + branch_components: List, default=None + Subset of all branch_components in the network. If None, all branch_components are considered. + feature: None | pandas.DataFrame, default=None + Feature to be considered for the clustering. + The DataFrame must be indexed with buses_i. + If None, all buses have the same similarity. + affinity: str or Callable, default=’euclidean’ + Metric used to compute the linkage. + Can be “euclidean”, “l1”, “l2”, “manhattan”, “cosine”, or “precomputed”. + If linkage is “ward”, only “euclidean” is accepted. + If “precomputed”, a distance matrix (instead of a similarity matrix) is needed as input for the fit method. + linkage: ‘ward’, ‘complete’, ‘average’ or ‘single’, default=’ward’ + Which linkage criterion to use. + The linkage criterion determines which distance to use between sets of observation. + The algorithm will merge the pairs of cluster that minimize this criterion. + - ‘ward’ minimizes the variance of the clusters being merged. + - ‘average’ uses the average of the distances of each observation of the two sets. + - ‘complete’ or ‘maximum’ linkage uses the maximum distances between all observations of the two sets. + - ‘single’ uses the minimum of the distances between all observations of the two sets. + kwargs: + Any remaining arguments to be passed to Hierarchical Clustering (e.g. memory, connectivity). + + Returns + ------- + busmap : pandas.Series + Mapping of n.buses to clusters (indexed by + non-negative integers). + + """ + if find_spec("sklearn") is None: + msg = ( + "Optional dependency 'sklearn' not found." + "Install via 'conda install -c conda-forge scikit-learn' " + "or 'pip install scikit-learn'" + ) + raise ModuleNotFoundError(msg) + + from sklearn.cluster import AgglomerativeClustering as HAC # noqa: PLC0415 + + if buses_i is None: + buses_i = n.c.buses.static.index + + if branch_components is None: + branch_components = n.branch_components + + if feature is None: + logger.warning( + "No feature is specified for Hierarchical Clustering. " + "Falling back to default, where all buses have equal similarity. " + "You can specify a feature as pandas.DataFrame indexed with buses_i." + ) + + feature = pd.DataFrame(index=buses_i, columns=[""], data=0) + + buses_x = n.c.buses.static.index.get_indexer(buses_i) + + adjacency_df = n.adjacency_matrix( + branch_components=branch_components, return_dataframe=True + ) + A = sp.csr_matrix(adjacency_df.values).tocsc()[buses_x][:, buses_x] + + labels = HAC( + n_clusters=n_clusters, + connectivity=A, + metric=affinity, + linkage=linkage, + **kwargs, + ).fit_predict(feature) + + return pd.Series(labels, index=buses_i, dtype=str) + + +def hac_clustering( # noqa: D417 + n: Network, + n_clusters: int, + buses_i: pd.Index | None = None, + branch_components: Collection[str] | None = None, + feature: pd.DataFrame | None = None, + affinity: str | Callable = "euclidean", + linkage: str = "ward", + line_length_factor: float = 1.0, + **kwargs: Any, +) -> Clustering: + """Cluster the network using Hierarchical Agglomerative Clustering. + + Parameters + ---------- + n_clusters : int + Final number of clusters desired. + buses_i: None | pandas.Index, default=None + Subset of buses to cluster. If None, all buses are considered. + branch_components: List, default=["Line", "Link"] + Subset of all branch_components in the network. + feature: None | pandas.DataFrame, default=None + Feature to be considered for the clustering. + The DataFrame must be indexed with buses_i. + If None, all buses have the same similarity. + affinity: str or Callable, default=’euclidean’ + Metric used to compute the linkage. + Can be “euclidean”, “l1”, “l2”, “manhattan”, “cosine”, or “precomputed”. + If linkage is “ward”, only “euclidean” is accepted. + If “precomputed”, a distance matrix (instead of a similarity matrix) is needed as input for the fit method. + linkage: ‘ward’, ‘complete’, ‘average’ or ‘single’, default=’ward’ + Which linkage criterion to use. + The linkage criterion determines which distance to use between sets of observation. + The algorithm will merge the pairs of cluster that minimize this criterion. + - ‘ward’ minimizes the variance of the clusters being merged. + - ‘average’ uses the average of the distances of each observation of the two sets. + - ‘complete’ or ‘maximum’ linkage uses the maximum distances between all observations of the two sets. + - ‘single’ uses the minimum of the distances between all observations of the two sets. + line_length_factor: float, default=1.0 + Factor to multiply the spherical distance between two new buses in order to get new line lengths. + kwargs: + Any remaining arguments to be passed to Hierarchical Clustering (e.g. memory, connectivity). + + + Returns + ------- + Clustering : named tuple + A named tuple containing network, busmap and linemap + + """ + busmap = busmap_by_hac( + n, + n_clusters, + buses_i, + branch_components, + feature, + affinity, + linkage, + **kwargs, + ) + + return get_clustering_from_busmap(n, busmap, line_length_factor=line_length_factor) + + +def busmap_by_greedy_modularity( # noqa: D417 + n: Network, n_clusters: int, buses_i: pd.Index | None = None +) -> pd.Series: + """Create a busmap according to Clauset-Newman-Moore greedy modularity maximization. + + See [CNM2004_1]_ for more details. + + Parameters + ---------- + n_clusters : int + Final number of clusters desired. + buses_i: None | pandas.Index, default=None + Subset of buses to cluster. If None, all buses are considered. + + Returns + ------- + busmap : pandas.Series + Mapping of n.buses to clusters (indexed by + non-negative integers). + + References + ---------- + [CNM2004_1] Clauset, A., Newman, M. E., & Moore, C. + "Finding community structure in very large networks." + Physical Review E 70(6), 2004. + + """ + if parse(nx.__version__) < Version("2.8"): + msg = ( + "The fuction `busmap_by_greedy_modularity` requires `networkx>=2.8`, " + f"but version `networkx={nx.__version__}` is installed." + ) + raise NotImplementedError(msg) + + if buses_i is None: + buses_i = n.c.buses.static.index + + n.calculate_dependent_values() + + lines = n.c.lines.static.query("bus0 in @buses_i and bus1 in @buses_i") + lines = ( + lines[["bus0", "bus1"]] + .assign(weight=lines.s_nom / abs(lines.r + 1j * lines.x)) + .set_index(["bus0", "bus1"]) + ) + + G = nx.Graph() + G.add_nodes_from(buses_i) + G.add_edges_from((u, v, {"weight": w}) for (u, v), w in lines.itertuples()) + + communities = nx.community.greedy_modularity_communities( + G, best_n=n_clusters, cutoff=n_clusters, weight="weight" + ) + busmap = pd.Series(buses_i, buses_i) + for c in np.arange(len(communities)): + busmap.loc[list(communities[c])] = str(c) + busmap.index = busmap.index.astype(str) + + return busmap + + +def greedy_modularity_clustering( # noqa: D417 + n: Network, + n_clusters: int, + buses_i: pd.Index | None = None, + line_length_factor: float = 1.0, +) -> Clustering: + """Create a busmap according to Clauset-Newman-Moore greedy modularity maximization. + + See [CNM2004_2]_ for more details. + + Parameters + ---------- + n_clusters : int + Final number of clusters desired. + buses_i: None | pandas.Index, default=None + Subset of buses to cluster. If None, all buses are considered. + line_length_factor: float, default=1.0 + Factor to multiply the spherical distance between two new buses to get new line lengths. + + Returns + ------- + Clustering : named tuple + A named tuple containing network, busmap and linemap. + + References + ---------- + [CNM2004_2] Clauset, A., Newman, M. E., & Moore, C. + "Finding community structure in very large networks." + Physical Review E 70(6), 2004. + + """ + busmap = busmap_by_greedy_modularity(n, n_clusters, buses_i) + + return get_clustering_from_busmap(n, busmap, line_length_factor=line_length_factor) + + +################ +# Reduce stubs/dead-ends, i.e. nodes with valency 1, iteratively to remove tree-like structures + + +def busmap_by_stubs( + n: Network, matching_attrs: Iterable[str] | None = None +) -> pd.Series: + """Create a busmap by reducing stubs and stubby trees. + + In other words sequentially reducing dead-ends. + + Parameters + ---------- + n : pypsa.Network + Network instance. + matching_attrs : None|[str] + bus attributes clusters have to agree on + + Returns + ------- + busmap : pandas.Series + Mapping of n.c.buses.static to k-means clusters (indexed by + non-negative integers). + + """ + busmap = pd.Series(n.c.buses.static.index, n.c.buses.static.index) + + G = n.graph() + + def attrs_match(u: str, v: str) -> bool: + return ( + matching_attrs is None + or ( + n.c.buses.static.loc[u, matching_attrs] + == n.c.buses.static.loc[v, matching_attrs] + ).all() + ) + + while True: + stubs = [] + for u in G.nodes: + neighbours = list(G.adj[u].keys()) + if len(neighbours) == 1: + (v,) = neighbours + if attrs_match(u, v): + busmap[busmap == u] = v + stubs.append(u) + G.remove_nodes_from(stubs) + if not stubs: + break + return busmap diff --git a/PyPSA/source/pypsa/clustering/temporal.py b/PyPSA/source/pypsa/clustering/temporal.py new file mode 100644 index 0000000000000000000000000000000000000000..ef7cd0f7ed1458960490368caf5f6964d3a6f229 --- /dev/null +++ b/PyPSA/source/pypsa/clustering/temporal.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Functions for temporal clustering of networks.""" diff --git a/PyPSA/source/pypsa/collection.py b/PyPSA/source/pypsa/collection.py new file mode 100644 index 0000000000000000000000000000000000000000..0003e1242a698d8204c86c27bf94adcb5413f9e8 --- /dev/null +++ b/PyPSA/source/pypsa/collection.py @@ -0,0 +1,779 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""NetworkCollection class for handling multiple PyPSA networks.""" + +import logging +import re +from collections.abc import Callable, Iterator, Sequence +from typing import Any + +import pandas as pd + +from pypsa._options import options +from pypsa.definitions.structures import Dict +from pypsa.networks import Network +from pypsa.statistics.expressions import StatisticsAccessor + +logger = logging.getLogger(__name__) + + +class NetworkCollection: + """A collection of networks that can be accessed like a single network. + + | + + Examples + -------- + Create a collection from file paths: + + >>> nc = pypsa.NetworkCollection(["network1.nc", "network2.nc"]) # doctest: +SKIP + + Create a collection from Network objects: + + >>> n + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 9 + - Carrier: 6 + - Generator: 6 + - GlobalConstraint: 1 + - Line: 7 + - Link: 4 + - Load: 6 + - SubNetwork: 3 + Snapshots: 10 + + >>> n_shuffled_load + PyPSA Network 'AC-DC-Meshed-Shuffled-Load' + ------------------------------------------ + Components: + - Bus: 9 + - Carrier: 6 + - Generator: 6 + - GlobalConstraint: 1 + - Line: 7 + - Link: 4 + - Load: 6 + - SubNetwork: 3 + Snapshots: 10 + + >>> nc = pypsa.NetworkCollection([n, n_shuffled_load]) + >>> nc + NetworkCollection + ----------------- + Networks: 2 + Index name: 'network' + Entries: ['AC-DC-Meshed', 'AC-DC-Meshed-Shuffled-Load'] + + Access component data across all networks: + + >>> nc.generators # doctest: +ELLIPSIS + bus ... p_nom_opt + network name ... + AC-DC-Meshed Manchester Wind Manchester ... ... + Manchester Gas Manchester ... ... + Norway Wind Norway ... ... + Norway Gas Norway ... ... + Frankfurt Wind Frankfurt ... ... + Frankfurt Gas Frankfurt ... ... + AC-DC-Meshed-Shuffled-Load Manchester Wind Manchester ... ... + Manchester Gas Manchester ... ... + Norway Wind Norway ... ... + Norway Gas Norway ... ... + Frankfurt Wind Frankfurt ... ... + Frankfurt Gas Frankfurt ... ... + + [12 rows x 38 columns] + + + >>> nc.statistics.energy_balance() # doctest: +ELLIPSIS + component network carrier bus_carrier + Generator AC-DC-Meshed gas AC 1465.27439 + wind AC 31082.35370 + AC-DC-Meshed-Shuffled-Load gas AC ... + wind AC ... + Load AC-DC-Meshed load AC -32547.62808 + AC-DC-Meshed-Shuffled-Load load AC -32547.62808 + dtype: float64 + + Use custom index: + + >>> import pandas as pd + >>> index = pd.Index(["scenario_A", "scenario_B"]) + >>> nc = pypsa.NetworkCollection([n, n_shuffled_load], index=index) + >>> nc + NetworkCollection + ----------------- + Networks: 2 + Index name: 'network' + Entries: ['scenario_A', 'scenario_B'] + + Notes + ----- + A single network is mirrored in two ways: + + 1. For each nested method or property of a network, the collection will + dynamically create a new MemberProxy object that wraps around it and allows for + custom processing. The '_method_patterns' dictionary in the MemberProxy class + defines which processor is used for which method or property. If no processor + is defined, a NotImplementedError is raised. + 2. Some accessors of the Network class already support Networks and + NetworkCollections, since via the step above the NetworkCollection can + already duck-type to a Network. If this is the case, the accessor is + directly initialised with a NetworkCollection instead. + + """ + + def __init__( + self, + networks: pd.Series | Sequence[Network | str], + index: pd.Index | pd.MultiIndex | Sequence | None = None, + ) -> None: + """Initialize the NetworkCollection with one or more networks. + + Parameters + ---------- + networks : pd.Series | Sequence[Network | str] + Sequence or pd.Series of Network objects or strings (file paths/ urls) to + include in the collection. If strings are provided, they will be passed to + pypsa.Network() to create Network objects. + index : pd.Index, pd.MultiIndex, Sequence, or None, optional + The index to use for the collection. If `networks` is of type `pd.Series`, + no index is allowed and it will be retrieved from the Series. If None, a + default index based on the network names will be created. + + """ + if isinstance(networks, pd.Series) and index is not None: + msg = ( + "When passing a pandas Series, the index must be None, " + "as the Series index is used as the collection index." + ) + raise ValueError(msg) + + # Validate that networks is not a single string (which would iterate char by char) + if isinstance(networks, str): + msg = "Single strings are not supported. Pass a list of strings or Network objects." + raise TypeError(msg) + + def _convert_to_network(item: Any) -> Network: + if isinstance(item, Network): + return item + elif isinstance(item, str): + return Network(item) + else: + msg = f"All values must be PyPSA Network objects or strings, got {type(item)}." + raise TypeError(msg) + + if isinstance(networks, pd.Series): + self.networks = networks.map(_convert_to_network) + else: + self.networks = [_convert_to_network(n) for n in networks] + + if not isinstance(self.networks, pd.Series): + # Format and validate index + if index is None and not isinstance(self.networks, pd.Series): + names = ["network" if not n.name else n.name for n in self.networks] + + # Check for duplicate names + if len(names) != len(set(names)): + duplicates = [name for name in set(names) if names.count(name) > 1] + msg = ( + f"Duplicate network names found: {duplicates}. " + "Please provide a custom index or ensure all networks have unique names." + ) + raise ValueError(msg) + index = pd.Index(names, name="network") + elif isinstance(index, Sequence): + if len(index) != len(self.networks): + msg = "The length of the index must match the number of networks provided." + raise ValueError(msg) + index = pd.Index(index) + elif not isinstance(index, pd.Index | pd.MultiIndex): + msg = ( + "The index must be a pandas Index or a sequence of names matching the " + "number of networks provided." + ) + raise TypeError(msg) + + self.networks = pd.Series(self.networks, index=index) + + # Only set default index name for non-MultiIndex + if ( + not isinstance(self.networks.index, pd.MultiIndex) + and self.networks.index.name is None + ): + self.networks.index.name = "network" + + # Validate index names + if isinstance(self.networks.index, pd.MultiIndex): # noqa: SIM102 + if any(name is None for name in self.networks.index.names): + msg = "All levels of MultiIndex must have names" + raise ValueError(msg) + + self._validate_network_compatibility() + + # Initialize accessors which support NetworkCollections and don't need a proxy + # member + self.statistics = StatisticsAccessor(self) + + def __getattr__(self, name: str) -> Any: + """Get attribute from all networks in the collection. + + Returns an MemberProxy that will either call the method on each network + when invoked or retrieve the property values when accessed. + """ + if not self.networks.any(): + msg = "Please provide at least one network." + raise AttributeError(msg) + + try: + return MemberProxy(self, lambda n: getattr(n, name), name) + except AttributeError as e: + msg = ( + "Only members as they are defined in any Network class can be accessed." + ) + raise AttributeError(msg) from e + + def __getitem__(self, key: Any) -> Any: + """Get a subset of networks using pandas Series indexing. + + Examples + -------- + >>> nc["AC-DC-Meshed"] + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 9 + - Carrier: 6 + - Generator: 6 + - GlobalConstraint: 1 + - Line: 7 + - Link: 4 + - Load: 6 + - SubNetwork: 3 + Snapshots: 10 + + + """ + try: + if isinstance(key, slice | pd.Series): + selected = self.networks[key] + if len(selected) == 0: + msg = f"Selection with key {key} resulted in empty collection" + raise ValueError(msg) + return NetworkCollection(selected) + return self.networks[key] + except KeyError as e: + msg = f"Key '{key}' not found in NetworkCollection index: {list(self.networks.index)}" + raise KeyError(msg) from e + + def __len__(self) -> int: + """Get the number of networks in the collection. + + Examples + -------- + >>> nc + NetworkCollection + ----------------- + Networks: 2 + Index name: 'network' + Entries: ['AC-DC-Meshed', 'AC-DC-Meshed-Shuffled-Load'] + >>> len(nc) + 2 + + """ + return len(self.networks) + + def __iter__(self) -> Iterator[Network]: + """Iterate over the Network objects in the container.""" + return iter(self.networks) + + @property + def is_collection(self) -> bool: + """Check if this is a collection of networks or a single network. + + Returns + ------- + bool + True, since this is a NetworkCollection. + + Examples + -------- + >>> nc + NetworkCollection + ----------------- + Networks: 2 + Index name: 'network' + Entries: ['AC-DC-Meshed', 'AC-DC-Meshed-Shuffled-Load'] + >>> nc.is_collection + True + + >>> n # doctest: +ELLIPSIS + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 9 + ... + Snapshots: 10 + + >>> n.is_collection + False + + See Also + -------- + [pypsa.Network][], [pypsa.NetworkCollection][] + + """ + return True + + def get_network(self, collection: Any) -> Network: + """Return a single network from the collection. + + Parameters + ---------- + collection : Any + Name or identifier of the network to retrieve. + + Returns + ------- + Network + The requested network from the collection. + + Examples + -------- + >>> nc.get_network("AC-DC-Meshed") + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 9 + - Carrier: 6 + - Generator: 6 + - GlobalConstraint: 1 + - Line: 7 + - Link: 4 + - Load: 6 + - SubNetwork: 3 + Snapshots: 10 + + + Raises + ------ + KeyError + If the collection name is not found. + + """ + try: + return self.networks.loc[collection] + except KeyError as err: + msg = f"Collection '{collection}' not found in network collection." + raise KeyError(msg) from err + + @property + def index(self) -> pd.Index: + """Get the index of the NetworkCollection. + + Returns + ------- + pd.Index + The index of the NetworkCollection. + + """ + return self.networks.index + + @property + def _index_names(self) -> list[str]: + """Get the names of the index of the NetworkCollection. + + Returns + ------- + list[str] + The names of the index of the NetworkCollection. + + """ + return self.index.names or [self.index.name] + + def __repr__(self) -> str: + """Get representation of NetworkCollection. + + Examples + -------- + >>> nc + NetworkCollection + ----------------- + Networks: 2 + Index name: 'network' + Entries: ['AC-DC-Meshed', 'AC-DC-Meshed-Shuffled-Load'] + + """ + n_networks = len(self.networks) + + # Show index information + if isinstance(self.networks.index, pd.MultiIndex): + index_info = f"MultiIndex with {self.networks.index.nlevels} levels: {list(self.networks.index.names)}" + # Show first few entries of the MultiIndex + if n_networks > 0: + sample_size = min(5, n_networks) + sample_entries = list(self.networks.index[:sample_size]) + index_info += f"\n First {sample_size} entries: {sample_entries}" + if n_networks > sample_size: + index_info += f"\n ... and {n_networks - sample_size} more" + else: + index_name = self.networks.index.name or "network" + index_info = f"Index name: '{index_name}'" + if n_networks > 0: + sample_size = min(5, n_networks) + sample_entries = list(self.networks.index[:sample_size]) + index_info += f"\nEntries: {sample_entries}" + if n_networks > sample_size: + index_info += f" ... and {n_networks - sample_size} more" + + return f"NetworkCollection\n-----------------\nNetworks: {n_networks}\n{index_info}" + + def _validate_network_compatibility(self) -> None: + """Validate basic compatibility between networks in the collection. + + Raises + ------ + ValueError + If networks have incompatible structures that would prevent + meaningful aggregation. + + """ + if len(self.networks) <= 1: + return # No validation needed for single network or empty collection + + # TODO: Implement basic validation of network compatibility + + +_all_components = ( + r"sub_networks|buses|carriers|global_constraints|lines|line_types|" + r"transformers|transformer_types|links|loads|generators|storage_units|" + r"stores|shunt_impedances|shapes" +) + + +def _get_method_patterns() -> dict[str, str]: + new_api = options.api.new_components_api + _all_component_names = ( + r"SubNetwork|Bus|Carrier|GlobalConstraint|Line|LineType|" + "Transformer|TransformerType|Link|Load|Generator|StorageUnit|" + "Store|ShuntImpedance|Shape" + ) + + _component_classes = ( + ("(" if new_api else "") + + rf"(components|c)((\['({_all_components}|{_all_component_names})'])|(\.({_all_components})))" + + ("|" + _all_components + ")" if new_api else "") + ) + + _method_patterns = { + # run_per_network + # --------------- + "run_per_network": r"^(" + r"(consistency_check_plots)+" + r")$", + # --------------- + "vertical_concat": rf"^(" + rf"({_all_components if not new_api else ''})|" + rf"({_component_classes}.static)|" + rf"({_component_classes}.get_active_assets)|" + rf"static|" + rf"get_active_assets" + rf")$", + # --------------- + "horizontal_concat": rf"^(" + rf"({'(' + _all_components + ')_t' if not new_api else ''})|" + rf"({_component_classes}.dynamic)|" + rf"dynamic|" + rf"get_switchable_as_dense" + rf")$", + # --------------- + "return_from_first": r"^(" + r"\S+_components|" + r"snapshots|" + r"snapshot_weightings|" + r"bus_carrier_unit|" + r")$", + # --------------- + "index_concat": r"^(" + rf"({_component_classes}).committables|" + r"get_committable_i|" + r")$", + # --------------- + "continue_proxy": rf"^(" + rf"components|c|" + rf"{_component_classes}" + rf")$", + } + return _method_patterns + + +class MemberProxy: + """Wrapper for network accessors that combines results from multiple networks. + + This class handles arbitrary nesting of accessor methods and properties, + dynamically proxying calls to the underlying network objects. + """ + + collection: NetworkCollection + + def __new__(cls, *args: Any, **kwargs: Any) -> Any: + """Create new instance of MemberProxy. + + If no Wrapper is needed, since the return value is not a callable, immediately + return the result of the default processor function. + """ + instance = super().__new__(cls) + cls.__init__(instance, *args, **kwargs) + + # Immediately end recursion for non callable returns, unless it's an intermediate path + first_accessor = instance.accessor_func(instance.collection.networks.iloc[0]) + if not callable(first_accessor): + # Check if this is an intermediate path that should continue as MemberProxy + if instance._is_intermediate_path(): + return instance + processor = instance.get_processor() + return processor(is_call=False) + + return instance + + def _is_intermediate_path(self) -> bool: + """Check if this accessor path is an intermediate path that should continue as MemberProxy.""" + # Check if this path could be part of a longer supported path + for processor_name, pattern in _get_method_patterns().items(): + if processor_name == "continue_proxy" and re.match( + pattern, self.accessor_path + ): + return True + return False + + def __init__( + self, + collection: NetworkCollection, + accessor_func: Callable, + accessor_path: str = "", + ) -> None: + """Initialize the wrapper. + + Parameters + ---------- + collection : NetworkCollection + The collection of networks to operate on + accessor_func : callable + Function that returns the appropriate accessor for a given network + accessor_path : str, optional + The dot-separated path of accessor names from NetworkCollection to this wrapper + + """ + self.collection = collection + self.accessor_func = accessor_func + self.accessor_path = accessor_path + + def __call__(self, *args: Any, **kwargs: Any) -> Any: + """Handle direct calls to the accessor. + + For methods with custom implementations defined by regex patterns, + uses the matching handler method. Otherwise uses the default behavior + of collecting results from each network. + """ + processor = self.get_processor() + return processor(True, *args, **kwargs) + + def __getattr__(self, name: str) -> Any: + """Handle attribute access on the accessor. + + This method handles three cases: + 1. The attribute is another accessor object (returns a new MemberProxy) + 2. The attribute is a method (returns a function that aggregates results) + 3. The attribute is a property (returns a ResultWrapper of property values) + """ + # Get the attribute from the first accessor to determine its type + if len(self.collection.networks) == 0: + msg = f"Cannot access attribute '{name}' on empty collection" + raise AttributeError(msg) + + # Create the new accessor path by appending the attribute name + new_path = f"{self.accessor_path}.{name}" if self.accessor_path else name + + # For any attribute, create a new accessor function that chains the attribute access + return MemberProxy( + self.collection, lambda n: getattr(self.accessor_func(n), name), new_path + ) + + def __getitem__(self, name: str) -> Any: + """Handle attribute access on the accessor. + + This method handles three cases: + 1. The attribute is another accessor object (returns a new MemberProxy) + 2. The attribute is a method (returns a function that aggregates results) + 3. The attribute is a property (returns a ResultWrapper of property values) + """ + # Get the attribute from the first accessor to determine its type + if len(self.collection.networks) == 0: + msg = f"Cannot access attribute '{name}' on empty collection" + raise AttributeError(msg) + + # Create the new accessor path by appending the attribute name + new_path = f"{self.accessor_path}['{name}']" if self.accessor_path else name + + # For any attribute, create a new accessor function that chains the attribute access + return MemberProxy( + self.collection, lambda n: getattr(self.accessor_func(n), name), new_path + ) + + def get_processor(self) -> Any: + """Determine the appropriate processor for the current accessor path.""" + # Check for pattern-based method processor + for processor_name, pattern in _get_method_patterns().items(): + if re.match(pattern, self.accessor_path): + processor = getattr(self, processor_name) + return processor + + msg = ( + f"'{self.accessor_path}' is currently not a supported method/ property for " + f"network collections. This might change in the future." + ) + raise NotImplementedError(msg) + + # Helper functions for processing + # ------------------------------- + + def _concat_indexes(self, results: dict) -> Any: + # Build names list based on the network collection index and result index names + network_names = self.collection._index_names + + # Get the names from the first result index (they should all be the same) + first_result = next(iter(results.values())) + result_names = [first_result.name] if first_result.name else [] + + # Combine network names with result names + all_names = network_names + result_names + + # Flatten the combinations to include network index values + flattened_combinations = [] + for idx, result_idx in results.items(): + # idx can be a string or tuple depending on whether collection has MultiIndex + idx_values = (idx,) if isinstance(idx, str) else idx + flattened_combinations.extend([idx_values + (val,) for val in result_idx]) + + return pd.MultiIndex.from_tuples(flattened_combinations, names=all_names) + + def _do_concat(self, results: Any, axis: int) -> Any: + # Check if values are dictionaries + if all(isinstance(v, dict) for v in results.values()): + # Get all unique keys across all dictionaries + all_keys = set().union(*[d.keys() for d in results.values()]) + + merged_results = Dict() + for key in all_keys: + key_results = { + idx: results[idx].get(key) for idx in results if key in results[idx] + } + + # Recursively call on subsets + merged_results[key] = self._do_concat(key_results, axis=axis) + return merged_results + else: + # Default case - simple concatenation + first_result = next(iter(results.values())) + + if isinstance(first_result, pd.Index): + result = self._concat_indexes(results) + else: + result = pd.concat(results, axis=axis) + if axis == 0: + result.index.names = ( + self.collection.networks.index.names or ["network"] + ) + first_result.index.names + + elif axis == 1: + result.columns.names = ( + self.collection.networks.index.names or ["network"] + ) + first_result.columns.names + else: + msg = "Axis must be 0 or 1" + raise AssertionError(msg) + return result + + # ----------------- + # Custom processors + # Any custom processor are defined below. They need to be added with the same + # signature and added to the _method_patterns list above. + # ----------------- + + def run_per_network(self, is_call: bool, *args: Any, **kwargs: Any) -> Any: + """Run the accessor function for each network in the collection.""" + results = [] + for _, network in self.collection.networks.items(): # noqa: PERF102 + accessor = self.accessor_func(network) + if is_call: + result = accessor(*args, **kwargs) + else: + result = accessor + results.append(result) + return results + + def vertical_concat(self, is_call: bool, *args: Any, **kwargs: Any) -> Any: + """Concatenate results vertically (axis=0) from all networks in the collection.""" + results = {} + + for idx, network in self.collection.networks.items(): + accessor = self.accessor_func(network) + if is_call: + result = accessor(*args, **kwargs) + else: + result = accessor + results[idx] = result + + return self._do_concat(results, axis=0) + + def horizontal_concat(self, is_call: bool, *args: Any, **kwargs: Any) -> Any: + """Concatenate results horizontally across networks.""" + results = {} + for idx, network in self.collection.networks.items(): + accessor = self.accessor_func(network) + if is_call: + result = accessor(*args, **kwargs) + else: + result = accessor + results[idx] = result + + return self._do_concat(results, axis=1) + + def return_from_first(self, is_call: bool, *args: Any, **kwargs: Any) -> Any: + """Return the result from the first network in the collection. + + This is used for properties that are expected to be the same across all networks. + """ + # Get the first network + first_network = self.collection.networks.iloc[0] + accessor = self.accessor_func(first_network) + if is_call: + result = accessor(*args, **kwargs) + else: + result = accessor + + return result + + def index_concat(self, is_call: bool, *args: Any, **kwargs: Any) -> Any: + """Concatenate indexes from all networks in the collection into a MultiIndex. + + This function collects indexes from each network and combines them into a + MultiIndex where the network identifier(s) form the first level(s) and the + original index values form the subsequent levels. + """ + results = {} + + for idx, network in self.collection.networks.items(): + accessor = self.accessor_func(network) + if is_call: + result = accessor(*args, **kwargs) + else: + result = accessor + results[idx] = result + + # Use the _concat_indexes helper which already handles MultiIndex creation + return self._concat_indexes(results) diff --git a/PyPSA/source/pypsa/common.py b/PyPSA/source/pypsa/common.py new file mode 100644 index 0000000000000000000000000000000000000000..d52941c0ebe0b99a91d55a445086ec461ad89752 --- /dev/null +++ b/PyPSA/source/pypsa/common.py @@ -0,0 +1,910 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""General utility functions for PyPSA.""" + +from __future__ import annotations + +import functools +import json +import logging +import warnings +from functools import lru_cache +from typing import TYPE_CHECKING, Any +from urllib import parse, request + +import numpy as np +import pandas as pd +import pandas.testing as pd_testing +from deprecation import deprecated +from packaging import version +from pandas.api.types import is_list_like + +from pypsa._options import options +from pypsa.definitions.structures import Dict +from pypsa.version import __version_base__ + +if TYPE_CHECKING: + from collections.abc import Callable, Sequence + + from pypsa.type_utils import NetworkType + +logger = logging.getLogger(__name__) + + +class UnexpectedError(AssertionError): + """Custom error for unexpected conditions with issue tracker reference. + + + """ + + URL_CREATE_ISSUE = "https://go.pypsa.org/report-bug" + + def __init__(self, message: str = "") -> None: + """Initialize the UnexpectedError. + + Parameters + ---------- + message : str, optional + Message to be displayed. + + Examples + -------- + >>> try: + ... raise UnexpectedError("This is an unexpected error.") + ... except UnexpectedError as e: + ... print(str(e)) # doctest: +ELLIPSIS + This is an unexpected error. + Please track this issue in our issue tracker: https://go.pypsa.org/report-bug + + """ + track_message = ( + f"Please track this issue in our issue tracker: {self.URL_CREATE_ISSUE}" + ) + + if message: + message += f"\n{track_message}" + else: + message = track_message + + super().__init__(message) + + +class MethodHandlerWrapper: + """Decorator to wrap a method with a handler class. + + This decorator wraps any method with a handler class that is used to + process the method's return value. The handler class must be a callable with + the same signature as the method to guarantee compatibility. It also must be + initialized with the method as its first argument. If so, the API is only extended + and not changed. The handler class can be used as a drop-in replacement for the + method. + + Needs to be used as a callable decorator, i.e. with parentheses: + >>> class MyHandlerClass: + ... def __init__(self, bound_method: Callable) -> None: + ... self.bound_method = bound_method + ... def __call__(self, *args: Any, **kwargs: Any) -> pd.DataFrame: + ... return self.bound_method(*args, **kwargs) + + >>> @MethodHandlerWrapper(handler_class=MyHandlerClass) + ... def my_method(self, *args, **kwargs): + ... pass + + """ + + def __init__( + self, + func: Callable | None = None, + *, + handler_class: Any = None, + inject_attrs: dict[str, str] | None = None, + ) -> None: + """Initialize the decorator. + + Parameters + ---------- + func : Callable, optional + The statistic method to wrap. + handler_class : Type, optional + The handler class to use for wrapping the method. It should be callable + with same signature as the method to guarantee compatibility. + inject_attrs : dict[str, str], optional + A mapping of instance attributes to be passed to the handler class + as keyword arguments. The keys are the names of the attributes to be + passed, and the values are the names of the attributes in the handler + class. If None, no attributes are passed. Pass only strings, not + attributes of the instance. + + """ + self.func = func + self.handler_class = handler_class + self.inject_attrs = inject_attrs or {} + + def __call__(self, func: Callable | None = None) -> MethodHandlerWrapper: + """Call the decorator with the function to wrap.""" + if func is not None: + self.func = func + return self + return self + + def __get__(self, obj: Any, objtype: Any = None) -> Any: + """Bind method to an object instance.""" + if obj is None: + return self + + if self.func is None: + msg = "Method has not been set correctly in MethodHandlerWrapper" + raise TypeError(msg) + + # Create a bound method wrapper + bound_method = self.func.__get__(obj, objtype) + + # Prepare additional arguments from instance attributes, if any + handler_kwargs = {} + for key, value in self.inject_attrs.items(): + if hasattr(obj, value): + handler_kwargs[key] = getattr(obj, value) + else: + msg = ( + f"Attribute '{key}' not found in the object instance. " + f"Please ensure it is set before using the decorator." + ) + raise AttributeError(msg) + + wrapper = self.handler_class(bound_method, **handler_kwargs) + wrapper.__name__ = self.func.__name__ + wrapper.__doc__ = self.func.__doc__ + + return wrapper + + +@lru_cache(maxsize=1) +def _check_for_update(current_version: str, repo_owner: str, repo_name: str) -> str: + """Log a message if a newer version is available. + + Checks the latest release on GitHub and compares it to the current version. Does + nothing if the latest version is not available or if the current version is up + to date. + + Parameters + ---------- + current_version : str + The current version of the package as a semantic version string. + repo_owner : str + The owner of the repository. + repo_name : str + The name of the repository. + + Returns + ------- + str + A message if a newer version is available. + + """ + # Check if network requests are allowed + if not options.get_option("general.allow_network_requests"): + return "" + + try: + url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest" + + # Validate URL scheme + parsed_url = parse.urlparse(url) + if parsed_url.scheme not in ("https", "http"): + return "" + + headers = {"User-Agent": "Python"} # GitHub API requires a user-agent + req = request.Request(url, headers=headers) # noqa: S310 + response = request.urlopen(req) # noqa: S310 + latest_version = json.loads(response.read())["tag_name"].replace("v", "") + + # Version comparison using packaging.version + latest_parsed = version.parse(latest_version) + current_parsed = version.parse(current_version) + + if latest_parsed > current_parsed: + return ( + f"New version {latest_version} available! (Current: {current_version})" + ) + + except Exception: # noqa: S110 + pass + + return "" + + +def as_index( + n: NetworkType, values: Any, network_attribute: str, force_subset: bool = True +) -> pd.Index: + """Return a pd.Index object from a list-like or scalar object. + + + + Also checks if the values are a subset of the corresponding attribute of the + network object. If values is None, it is also used as the default. + + Parameters + ---------- + n : pypsa.Network + Network object from which to extract the default values. + values : Any + List-like or scalar object or None. + network_attribute : str + Name of the network attribute to be used as the default values. Only used if + values is None. + force_subset : bool, optional + If True, the values must be a subset of the network attribute. Otherwise this + is not checked, by default True. + + Returns + ------- + pd.Index: values as a pd.Index object. + + Examples + -------- + >>> # Convert list to Index using network snapshots + >>> first_two = list(n.snapshots[:2]) + >>> pypsa.common.as_index(n, first_two, 'snapshots') # doctest: +ELLIPSIS + DatetimeIndex([..., ...], dtype='datetime64[ns]', name='snapshot', freq=None) + + >>> # Using None returns all snapshots + >>> pypsa.common.as_index(n, None, 'snapshots') # doctest: +ELLIPSIS + DatetimeIndex([..., ...], dtype='datetime64[ns]', name='snapshot', freq=None) + + """ + n_attr = getattr(n, network_attribute) + + if values is None: + values_ = n_attr + elif isinstance(values, pd.MultiIndex): + values_ = values + values_.names = n_attr.names + values_.name = n_attr.name + elif isinstance(values, pd.Index): + values_ = values + # If only timestep level is given for multiindex snapshots + # TODO: This ambiguity should be resolved + values_.names = n_attr.names[:1] + elif not is_list_like(values): + values_ = pd.Index([values], name=n_attr.names[0]) + else: + values_ = pd.Index(values, name=n_attr.names[0]) + + if force_subset and not all(val in n_attr for val in values_): + msg = ( + f"Values must be a subset of the network attribute " + f"'{network_attribute}'. Pass force_subset=False to disable this check." + ) + raise ValueError(msg) + + return values_ + + +def equals( + a: Any, + b: Any, + ignored_classes: Any = None, + log_mode: str = "silent", + path: str = "", +) -> bool: + """Check if two objects are equal and track the location of differences. + + Parameters + ---------- + a : Any + First object to compare. + b : Any + Second object to compare. + ignored_classes : Any, default=None + Classes to ignore during comparison. If None, no classes are ignored. + log_mode: str, default="silent" + Controls how differences are reported: + - 'silent': No logging, just returns True/False + - 'verbose': Prints differences but doesn't raise errors + - 'strict': Raises ValueError on first difference + path: str, default="" + Current path in the object structure (used for tracking differences). + + Raises + ------ + ValueError + If log_mode is 'strict' and components are not equal. + + Returns + ------- + bool + True if the objects are equal, False otherwise. + + Examples + -------- + >>> # Compare pandas DataFrames + >>> df1 = pd.DataFrame({'a': [1, 2], 'b': [3, 4]}) + >>> df2 = pd.DataFrame({'a': [1, 2], 'b': [3, 4]}) + >>> pypsa.common.equals(df1, df2) + True + + >>> # Compare lists + >>> pypsa.common.equals([1, 2, 3], [1, 2, 4]) + False + + >>> # Handle NaN values correctly + >>> pypsa.common.equals(np.nan, np.nan) + True + + """ + if not isinstance(log_mode, str): + msg = "'log_mode' must be a string, not {type(log_mode)}." + if log_mode not in ["silent", "verbose", "strict"]: + msg = ( + f"'log_mode' must be one of 'silent', 'verbose', 'strict'], not {log_mode}." + ) + raise ValueError(msg) + + current_path = path or "root" + + def handle_diff(message: str) -> bool: + if log_mode == "strict": + raise ValueError(message) + if log_mode == "verbose": + logger.warning(message) + return False + + if not isinstance(a, type(b)): + # Ignore if they are subtypes # TODO: remove with data validation PR + if np.issubdtype(type(a), type(b)) or np.issubdtype(type(b), type(a)): + pass + else: + msg = f"Types differ at '{current_path}'\n\n{a} ({type(a)})\n\n!=\n\n{b} ({type(b)})\n" + return handle_diff(msg) + + if ignored_classes is not None and isinstance(a, tuple(ignored_classes)): + return True + from pypsa.components.store import ComponentsStore # noqa: PLC0415 + + # Classes with equality methods + if isinstance(a, np.ndarray): + if not np.array_equal(a, b, equal_nan=True): + msg = f"numpy arrays differ at '{current_path}'\n\n{a}\n\n!=\n\n{b}\n" + return handle_diff(msg) + + elif isinstance(a, pd.DataFrame | pd.Series | pd.Index): + if a.empty and b.empty: + return True + if not a.equals(b): + # TODO: Resolve with data validation PR + # Check if dtypes are equal + try: + pd_testing.assert_frame_equal( + a, b, check_dtype=False, check_exact=False + ) + except AssertionError: + msg = f"pandas objects differ at '{current_path}'\n\n{a}\n\n!=\n\n{b}\n" + return handle_diff(msg) + + elif isinstance(a, ComponentsStore): + for k, v in a.items(): + if not hasattr(b, k): + msg = ( + f"Key '{k}' missing from second ComponentsStore at '{current_path}'" + ) + return handle_diff(msg) + if not equals(v, b[k], ignored_classes, log_mode, f"{current_path}.{k}"): + return False + # Check for extra keys in b + for k in b.keys(): + if not hasattr(a, k): + msg = ( + f"Key '{k}' missing from first ComponentsStore at '{current_path}'" + ) + return handle_diff(msg) + + # Iterators + elif isinstance(a, dict | Dict): + for k, v in a.items(): + if k not in b: + msg = f"Key '{k}' missing from second dict at '{current_path}'" + return handle_diff(msg) + if not equals(v, b[k], ignored_classes, log_mode, f"{current_path}.{k}"): + return False + # Check for extra keys in b + for k in b: + if k not in a: + msg = f"Key '{k}' missing from first dict at '{current_path}'" + return handle_diff(msg) + + elif isinstance(a, list | tuple): + if len(a) != len(b): + msg = f"Collections have different lengths at '{current_path}': {len(a)} != {len(b)}" + return handle_diff(msg) + + for i, v in enumerate(a): + if not equals(v, b[i], ignored_classes, log_mode, f"{current_path}[{i}]"): + return False + + # Nans + elif pd.isna(a) and pd.isna(b): + pass + + # Floating point numbers with tolerance + elif isinstance(a, float | int) and isinstance(b, float | int): + if not np.isclose(a, b, rtol=1e-9, atol=1e-12): + msg = f"Objects differ at '{current_path}'\n\n{a}\n\n!=\n\n{b}\n" + return handle_diff(msg) + + # Other objects + elif a != b: + msg = f"Objects differ at '{current_path}'\n\n{a}\n\n!=\n\n{b}\n" + return handle_diff(msg) + + return True + + +def rename_deprecated_kwargs( + func_name: str, + kwargs: dict[str, Any], + aliases: dict[str, str], + deprecated_in: str, + removed_in: str, +) -> None: + """Decorate functions to deprecate function parameters. + + Based on solution from [here](https://stackoverflow.com/questions/49802412). + + Parameters + ---------- + func_name : str + The name of the function. + kwargs : dict + The keyword arguments of the function. + aliases : dict + A mapping of old argument names to new argument names. + deprecated_in : str + Version in which the argument was deprecated. + removed_in : str + Version in which the argument will be removed. + + """ + for alias, new in aliases.items(): + if alias in kwargs: + if new in kwargs: + msg = ( + f"{func_name} received both {alias} and {new} as arguments!" + f" {alias} is deprecated, use {new} instead." + ) + raise DeprecationWarning(msg) + + message = f"`{alias}` is deprecated as an argument to `{func_name}`; use `{new}` instead." + if deprecated_in: + message += f" Deprecated in version {deprecated_in}." + if removed_in: + message += f" Will be removed in version {removed_in}." + + warnings.warn( + message=message, + category=DeprecationWarning, + stacklevel=3, + ) + kwargs[new] = kwargs.pop(alias) + + +def deprecated_kwargs(deprecated_in: str, removed_in: str, **aliases: str) -> Callable: + """Decorate functions and methods with deprecated arguments. + + Based on solution from [here](https://stackoverflow.com/questions/49802412). + + Parameters + ---------- + deprecated_in : str + Version in which the argument was deprecated. + removed_in : str + Version in which the argument will be removed. + aliases : dict + A mapping of old argument names to new argument names. + + Returns + ------- + Callable + A decorator that renames the old arguments to the new arguments. + + Examples + -------- + >>> @deprecated_kwargs(deprecated_in="0.32.0", removed_in="1.0", object_id="id_object") + ... def some_func(id_object): + ... print(id_object) + >>> some_func(object_id=1) # doctest: +SKIP + 1 + + """ + + def deco(f: Callable) -> Callable: + @functools.wraps(f) + def wrapper(*args: Any, **kwargs: Any) -> Any: + rename_deprecated_kwargs( + f.__name__, kwargs, aliases, deprecated_in, removed_in + ) + return f(*args, **kwargs) + + return wrapper + + return deco + + +def deprecated_common_kwargs(f: Callable) -> Callable: + """Decorate functions with predefined common kwarg deprecations. + + Backwards compatibility is given and just adds more deprecated kwargs without + having to specify them in each decorator call. + + This allows its usage as `@deprecated_ab` without parentheses. + + Parameters + ---------- + f : Callable + The function we are decorating. + + Returns + ------- + Callable + A decorated function that renames 'network' to 'n'. + + """ + return deprecated_kwargs(network="n", deprecated_in="0.31", removed_in="1.0")(f) + + +def deprecated_in_next_major(details: str) -> Callable: + """Wrap the @deprecated decorator to only require specifying the details. + + Deprecates the function in the next major version and removes it in the + following major version. Currently set to deprecate in version 1.0 and remove + in version 2.0. + + Parameters + ---------- + details : str + Details about the deprecation. + + Returns + ------- + Callable + A decorator that marks the function as deprecated. + + """ + + def decorator(func: Callable) -> Callable: + return deprecated( + deprecated_in="1.0rc1", + removed_in="2.0", + current_version=__version_base__, + details=details, + )(func) + + return decorator + + +def deprecated_namespace( + func: Callable, + previous_module: str, + deprecated_in: str, + removed_in: str, +) -> Callable: + """Decorate functions that have been moved from one namespace to another. + + Parameters + ---------- + func : Callable + The function that has been moved. + previous_module : str + The previous module path where the function was located. + deprecated_in : str + Version in which the namespace was deprecated. + removed_in : str + Version in which the namespace will be removed. + + Returns + ------- + Callable + A wrapper function that warns about the deprecated namespace. + + """ + current_version = version.parse(__version_base__) + if version.parse(deprecated_in) > current_version and __version_base__ != "0.0": + msg = ( + "'deprecated_namespace' can only be used in a version >= deprecated_in " + f"(current version: {__version_base__}, deprecated_in: {deprecated_in})." + ) + raise ValueError(msg) + + @functools.wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> Any: + # Build the warning message with version information + message = ( + f"`{previous_module}.{func.__name__}` is deprecated and will be removed in a future version. " + f"Please use `{func.__module__}.{func.__name__}` instead." + ) + + if deprecated_in: + message += f" Deprecated since version {deprecated_in}." + if removed_in: + message += f" Will be removed in version {removed_in}." + + warnings.warn( + message, + DeprecationWarning, + stacklevel=2, + ) + return func(*args, **kwargs) + + return wrapper + + +def list_as_string( + list_: Sequence | dict | set, prefix: str = "", style: str = "comma-separated" +) -> str: + """Convert a list to a formatted string. + + Parameters + ---------- + list_ : Sequence + The input sequence to be converted. + prefix : str, optional + String to prepend to each line, by default "". + style : {'same-line', 'bullet-list'}, optional + Output format style, by default "same-line". + + Returns + ------- + str + Formatted string representation of the input sequence. + + Raises + ------ + ValueError + If an invalid style is provided. + + Examples + -------- + >>> list_as_string(['a', 'b', 'c']) + 'a, b, c' + + >>> list_as_string(['x', 'y'], prefix=' ') + ' x, y' + + """ + if isinstance(list_, dict): + list_ = list(list_.keys()) + if len(list_) == 0: + return "" + + if style == "comma-separated": + return prefix + ", ".join(list_) + if style == "bullet-list": + return prefix + "- " + f"\n{prefix}- ".join(list_) + msg = f"Style '{style}' not recognized. Use 'comma-separated' or 'bullet-list'." + raise ValueError(msg) + + +def pass_none_if_keyerror(func: Callable) -> Callable: + """Decorate functions to pass None if a KeyError or AttributeError is raised. + + Parameters + ---------- + func : Callable + The function to decorate. + + Returns + ------- + Callable + The decorated function. + + """ + + @functools.wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> Any: + try: + return func(*args, **kwargs) + except (KeyError, AttributeError): + return None + + return wrapper + + +def pass_empty_series_if_keyerror(func: Callable) -> Callable: + """Decorate functions to pass an empty series if a KeyError or AttributeError is raised. + + Parameters + ---------- + func : Callable + The function to decorate. + + Returns + ------- + Callable + The decorated function. + + """ + + @functools.wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> pd.Series: + try: + return func(*args, **kwargs) + except (KeyError, AttributeError): + return pd.Series([], dtype=float) + + return wrapper + + +def check_optional_dependency(module_name: str, install_message: str) -> None: + """Check if an optional dependency is installed. + + If not, raise an ImportError with an install message. + """ + try: + __import__(module_name) + except ImportError as e: + raise ImportError(install_message) from e + + +def _convert_to_series(variable: dict | Sequence | float, index: pd.Index) -> pd.Series: + """Convert a variable to a pandas Series with the given index. + + Parameters + ---------- + variable : dict | Sequence | float | int + The variable to convert. + index : pd.Index + The index to use for the Series. + + Examples + -------- + >>> _convert_to_series([1, 2, 3], pd.Index(['a', 'b', 'c'])) + a 1 + b 2 + c 3 + dtype: int64 + + >>> _convert_to_series({'a': 10, 'c': 30}, pd.Index(['a', 'b', 'c'])) + a 10 + c 30 + dtype: int64 + + >>> _convert_to_series(5.0, pd.Index(['x', 'y'])) + x 5.0 + y 5.0 + dtype: float64 + + """ + if isinstance(variable, dict): + return pd.Series(variable) + if not isinstance(variable, pd.Series): + return pd.Series(variable, index=index) + return variable + + +def resample_timeseries( + df: pd.DataFrame, freq: str, numeric_columns: list[str] | None = None +) -> pd.DataFrame: + """Resample a DataFrame with proper handling of numeric and non-numeric columns. + + Parameters + ---------- + df : pd.DataFrame + DataFrame to resample, must have a datetime index + freq : str + Frequency string for resampling (e.g. 'H' for hourly) + numeric_columns : list[str] | None + List of numeric column names to resample. If None, auto-detected. + + Returns + ------- + pd.DataFrame + Resampled DataFrame with numeric columns aggregated by mean + and non-numeric columns forward-filled + + Examples + -------- + >>> # Create time series with mixed data types + >>> dates = pd.date_range('2020-01-01', periods=4, freq='15min') + >>> df = pd.DataFrame({ + ... 'value': [1.0, 2.0, 3.0, 4.0], + ... 'label': ['A', 'A', 'B', 'B'] + ... }, index=dates) + >>> resampled = pypsa.common.resample_timeseries(df, '30min') + >>> resampled['value'].iloc[0] # Mean of first two values + np.float64(1.5) + >>> resampled['label'].iloc[0] # Forward-filled non-numeric + 'A' + + """ + if not isinstance(df.index, pd.DatetimeIndex): + df = df.set_index(pd.to_datetime(df.index)) + + if numeric_columns is None: + numeric_columns = df.select_dtypes(include=["int64", "float64"]).columns + + # Handle duplicate indices by aggregating first + if not df.index.is_unique: + numeric_df = df[numeric_columns].groupby(level=0).mean() + non_numeric_df = df.drop(columns=numeric_columns).groupby(level=0).first() + df = pd.concat([numeric_df, non_numeric_df], axis=1)[df.columns] + + # Split into numeric and non-numeric columns + numeric_df = df[numeric_columns].resample(freq).mean() + non_numeric_df = df.drop(columns=numeric_columns).resample(freq).ffill() + + # Combine the results + return pd.concat([numeric_df, non_numeric_df], axis=1)[df.columns] + + +def expand_series(ser: pd.Series, columns: Sequence[str]) -> pd.DataFrame: + """Expand a series to a dataframe quickly. + + Columns are the given series and every single column being the equal to + the given series. + + Parameters + ---------- + ser : pd.Series + Input series to expand. + columns : Sequence[str] + Column names for the resulting DataFrame. + + Returns + ------- + pd.DataFrame + DataFrame with all columns containing the same values as the input series. + + Examples + -------- + >>> ser = pd.Series([1, 2, 3], index=['a', 'b', 'c']) + >>> df = pypsa.common.expand_series(ser, ['col1', 'col2']) + >>> df + col1 col2 + a 1.0 1.0 + b 2.0 2.0 + c 3.0 3.0 + + """ + return ser.to_frame(columns[0]).reindex(columns=columns).ffill(axis=1) + + +def _scenarios_not_implemented(func: Callable) -> Callable: + """Raise ValueError when used with stochastic networks.""" + + @functools.wraps(func) + def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any: + # Check if self is the network or has an 'n' attribute pointing to the network + network = getattr(self, "n", self) + if network.has_scenarios: + msg = f"Method '{func.__name__}' is not yet implemented for stochastic networks." + raise ValueError(msg) + return func(self, *args, **kwargs) + + return wrapper + + +def annuity(r: float | pd.Series, n: int | pd.Series) -> float | pd.Series: + """Calculate the annuity factor for a given discount rate and lifetime. + + According to formula $r / (1 - (1 + r)^{-n})$. + + Parameters + ---------- + r : float | pd.Series + Discount rate (as a decimal, e.g. 0.05 for 5%). + n : int | pd.Series + Lifetime of loan or asset (in years). + + Returns + ------- + float | pd.Series + The annuity factor. + + Examples + -------- + >>> pypsa.common.annuity(0.05, 10) # 5% discount rate over 10 years + 0.12950457496545661 + + """ + return r / (1.0 - 1.0 / (1.0 + r) ** n) diff --git a/PyPSA/source/pypsa/components/__init__.py b/PyPSA/source/pypsa/components/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9e066d9e90ea3908fd5370ce9f13c376014d7344 --- /dev/null +++ b/PyPSA/source/pypsa/components/__init__.py @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Package for component specific functionality in PyPSA.""" + +from typing import Any + +from pypsa.components._types import ( + Buses, + Carriers, + Generators, + GlobalConstraints, + Lines, + LineTypes, + Links, + Loads, + Shapes, + ShuntImpedances, + StorageUnits, + Stores, + SubNetworks, + Transformers, + TransformerTypes, +) +from pypsa.components.components import Components +from pypsa.components.legacy import Component + + +def __getattr__(name: str) -> Any: + if name in ["Network", "SubNetwork"]: + msg = ( + f"Cannot import '{name}' from 'pypsa.components'. " + "Import it from 'pypsa' instead." + ) + raise ImportError(msg) + return getattr(Components, name) + + +__all__ = [ + "Component", + "Components", + "Buses", + "Carriers", + "Generators", + "GlobalConstraints", + "LineTypes", + "Lines", + "Links", + "Loads", + "Shapes", + "SubNetworks", + "ShuntImpedances", + "StorageUnits", + "Stores", + "TransformerTypes", + "Transformers", +] diff --git a/PyPSA/source/pypsa/components/_types/__init__.py b/PyPSA/source/pypsa/components/_types/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..423090244e5daed211e6c318aadcad789a37b463 --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/__init__.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Components types package. + +Contains all classes for specific component types. They all inherit from the Components +base class and might add additional functionality or override existing methods. +""" + +from pypsa.components._types.buses import Buses +from pypsa.components._types.carriers import Carriers +from pypsa.components._types.generators import Generators +from pypsa.components._types.global_constraints import GlobalConstraints +from pypsa.components._types.line_types import LineTypes +from pypsa.components._types.lines import Lines +from pypsa.components._types.links import Links +from pypsa.components._types.loads import Loads +from pypsa.components._types.shapes import Shapes +from pypsa.components._types.shunt_impedances import ShuntImpedances +from pypsa.components._types.storage_units import StorageUnits +from pypsa.components._types.stores import Stores +from pypsa.components._types.sub_networks import SubNetworks +from pypsa.components._types.transformer_types import TransformerTypes +from pypsa.components._types.transformers import Transformers + +__all__ = [ + "Buses", + "Carriers", + "Generators", + "GlobalConstraints", + "LineTypes", + "Lines", + "Links", + "Loads", + "Shapes", + "ShuntImpedances", + "StorageUnits", + "SubNetworks", + "Stores", + "TransformerTypes", + "Transformers", +] diff --git a/PyPSA/source/pypsa/components/_types/_patch.py b/PyPSA/source/pypsa/components/_types/_patch.py new file mode 100644 index 0000000000000000000000000000000000000000..c011205f796ea2c8767ba28849cab2f53d365296 --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/_patch.py @@ -0,0 +1,111 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Generators components module.""" + +from __future__ import annotations + +import re +from functools import wraps +from typing import Any, TypeVar + +from pypsa.components.components import Components +from pypsa.components.types import get as get_component_type + +_T = TypeVar("_T", bound=Components) + + +def create_docstring_parameters(component_name: str) -> str: + """Create docstring parameters section for all attributes of component. + + Parameters + ---------- + component_name : str + The name of the component. + + Returns + ------- + str + The docstring parameters section. + + """ + # Create the docstring parameters section + + docstring = ( + "kwargs : Any\n" + " Component attributes to add. See Other Parameters for list of default" + " attributes but any attribute could be added.\n" + "\n" + "Other Parameters\n" + "---------------------\n" + ) + ct = get_component_type(component_name) + for attribute, row in ct.defaults.iterrows(): + attr_type = row["type"] + description = row["description"] + + # Skip name attribute since it is not a additional parameter + if attribute == "name": + continue + + # Map the type to Python types + if attr_type == "string": + py_type = "str" + elif attr_type == "float": + py_type = "float" + elif attr_type == "boolean": + py_type = "bool" + elif attr_type == "int": + py_type = "int" + elif "static or series" in attr_type: + py_type = "float or pandas.Series" + else: + py_type = attr_type + if attr_type == "series": + py_type = "pandas.Series" + is_dynamic = True + else: + is_dynamic = False + + param_header = f"{attribute} : {py_type} or SeriesLike[{py_type}]" + if is_dynamic: + param_header += f" or ArrayLike[{py_type}]" + default_arg = row["default"] + if attr_type == "string": + default_arg = f"'{default_arg}'" + param_header += f", default={default_arg}\n" + + # Format the parameter line + docstring += param_header + docstring += f" {description}\n" + + return docstring + + +def patch_add_docstring(cls: type[_T]) -> type[_T]: + original_add = cls.add + original_doc = Components.add.__doc__ or "" + + @wraps(original_add) + def add(self: _T, *args: Any, **kwargs: Any) -> Any: + return original_add(self, *args, **kwargs) + + to_be_replaced = ( + "kwargs : Any\n" + " Component attributes, e.g. x=[0.1, 0.2], can be list, pandas.Series\n" + " of pandas.DataFrame for time-varying\n" + ) + + def _camel_to_snake(name: str) -> str: + return re.sub(r"(?>> n.components.buses + 'Bus' Components + ---------------- + Attached to PyPSA Network 'AC-DC-Meshed' + Components: 9 + + """ + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Wrap Components.add() and docstring is patched via decorator.""" + return super().add( + name=name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) diff --git a/PyPSA/source/pypsa/components/_types/carriers.py b/PyPSA/source/pypsa/components/_types/carriers.py new file mode 100644 index 0000000000000000000000000000000000000000..18cbfede215c50fcd7dc236395ca16f77254d314 --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/carriers.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Carriers components module.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from pypsa.components._types._patch import patch_add_docstring +from pypsa.components.components import Components + +if TYPE_CHECKING: + from collections.abc import Sequence + + import pandas as pd + + +@patch_add_docstring +class Carriers(Components): + """Carriers components class. + + This class is used for carrier components. All functionality specific to + carriers is implemented here. Functionality for all components is implemented in + the abstract base class. + + See Also + -------- + [pypsa.Components][] + + Examples + -------- + >>> n.components.carriers + 'Carrier' Components + -------------------- + Attached to PyPSA Network 'AC-DC-Meshed' + Components: 6 + + """ + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Wrap Components.add() and docstring is patched via decorator.""" + return super().add( + name=name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) diff --git a/PyPSA/source/pypsa/components/_types/generators.py b/PyPSA/source/pypsa/components/_types/generators.py new file mode 100644 index 0000000000000000000000000000000000000000..962dfefc616d06f83be6a61f5e26bc8d05dd12bf --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/generators.py @@ -0,0 +1,89 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Generators components module.""" + +from __future__ import annotations + +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any + +import pandas as pd + +from pypsa.common import list_as_string +from pypsa.components._types._patch import patch_add_docstring +from pypsa.components.components import Components + +if TYPE_CHECKING: + from collections.abc import Sequence + + import pandas as pd + import xarray as xr + + +@patch_add_docstring +class Generators(Components): + """Generators components class. + + This class is used for generator components. All functionality specific to + generators is implemented here. Functionality for all components is implemented in + the abstract base class. + + See Also + -------- + [pypsa.Components][] + + Examples + -------- + >>> n.components.generators + 'Generator' Components + ---------------------- + Attached to PyPSA Network 'AC-DC-Meshed' + Components: 6 + + """ + + _operational_variables = ["p"] + + def get_bounds_pu( + self, + attr: str = "p", + ) -> tuple[xr.DataArray, xr.DataArray]: + """Get per unit bounds for generators. + + + + Parameters + ---------- + attr : string, optional + Attribute name for the bounds, e.g. "p" + + Returns + ------- + tuple[xr.DataArray, xr.DataArray] + Tuple of (min_pu, max_pu) DataArrays. + + """ + if attr not in self._operational_variables: + msg = f"Bounds can only be retrieved for operational attributes. For generators those are: {list_as_string(self._operational_variables)}." + raise ValueError(msg) + + return self.da.p_min_pu, self.da.p_max_pu + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Wrap Components.add() and docstring is patched via decorator.""" + return super().add( + name=name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) diff --git a/PyPSA/source/pypsa/components/_types/global_constraints.py b/PyPSA/source/pypsa/components/_types/global_constraints.py new file mode 100644 index 0000000000000000000000000000000000000000..82547ae5580d5adba16b0b5bcee2caf20a6a548b --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/global_constraints.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Global constraints components module.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from pypsa.components._types._patch import patch_add_docstring +from pypsa.components.components import Components + +if TYPE_CHECKING: + from collections.abc import Sequence + + import pandas as pd + + +@patch_add_docstring +class GlobalConstraints(Components): + """Global constraints components class. + + This class is used for global constraint components. All functionality specific to + global constraints is implemented here. Functionality for all components is implemented in + the abstract base class. + + See Also + -------- + [pypsa.Components][] + + Examples + -------- + >>> n.components.global_constraints + 'GlobalConstraint' Components + ----------------------------- + Attached to PyPSA Network 'AC-DC-Meshed' + Components: 1 + + """ + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Wrap Components.add() and docstring is patched via decorator.""" + return super().add( + name=name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) diff --git a/PyPSA/source/pypsa/components/_types/line_types.py b/PyPSA/source/pypsa/components/_types/line_types.py new file mode 100644 index 0000000000000000000000000000000000000000..8c6e4789b435df999140b7c487b8aac7f0acc860 --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/line_types.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Line types components module.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from pypsa.components._types._patch import patch_add_docstring +from pypsa.components.components import Components + +if TYPE_CHECKING: + from collections.abc import Sequence + + import pandas as pd + + +@patch_add_docstring +class LineTypes(Components): + """Line types components class. + + This class is used for line type components. All functionality specific to + line types is implemented here. Functionality for all components is implemented in + the abstract base class. + + See Also + -------- + [pypsa.Components][] + + Examples + -------- + >>> n.components.line_types + 'LineType' Components + --------------------- + Attached to PyPSA Network 'AC-DC-Meshed' + Components: 59 + + """ + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Wrap Components.add() and docstring is patched via decorator.""" + return super().add( + name=name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) diff --git a/PyPSA/source/pypsa/components/_types/lines.py b/PyPSA/source/pypsa/components/_types/lines.py new file mode 100644 index 0000000000000000000000000000000000000000..80e02c5374ce6130e4bb274480dee24cabd6117e --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/lines.py @@ -0,0 +1,144 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Lines components module.""" + +from __future__ import annotations + +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any + +import numpy as np +import pandas as pd + +from pypsa.common import list_as_string +from pypsa.components._types._patch import patch_add_docstring +from pypsa.components.components import Components +from pypsa.geo import haversine_pts + +if TYPE_CHECKING: + from collections.abc import Sequence + + import xarray as xr + + +@patch_add_docstring +class Lines(Components): + """Lines components class. + + This class is used for line components. All functionality specific to + lines is implemented here. Functionality for all components is implemented in + the abstract base class. + + See Also + -------- + [pypsa.Components][] + + Examples + -------- + >>> n.components.lines + 'Line' Components + ----------------- + Attached to PyPSA Network 'AC-DC-Meshed' + Components: 7 + + """ + + _operational_variables = ["s"] + + def get_bounds_pu( + self, + attr: str = "s", + ) -> tuple[xr.DataArray, xr.DataArray]: + """Get per unit bounds for lines. + + + + For passive branch components, min_pu is the negative of max_pu. + + Parameters + ---------- + attr : string, optional + Attribute name for the bounds, e.g. "s" + + Returns + ------- + tuple[xr.DataArray, xr.DataArray] + Tuple of (min_pu, max_pu) DataArrays. + + """ + if attr not in self._operational_variables: + msg = f"Bounds can only be retrieved for operational attributes. For lines those are: {list_as_string(self._operational_variables)}." + raise ValueError(msg) + + max_pu = self.da.s_max_pu + min_pu = -max_pu # Lines specific: min_pu is the negative of max_pu + + return min_pu, max_pu + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Wrap Components.add() and docstring is patched via decorator.""" + return super().add( + name=name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) + + def calculate_line_length(self) -> pd.Series: + """Get length of the lines in meters. + + Based on coordinates of attached buses. Buses must have 'x' and 'y' attributes, + otherwise no line length can be calculated. By default the haversine formula is + used to calculate the distance between two points. + + Returns + ------- + pd.Series + Length of the lines. + + See Also + -------- + [pypsa.geo.haversine][] + + Examples + -------- + >>> c = pypsa.examples.scigrid_de().c.lines + >>> ds = c.calculate_line_length() + >>> ds.head() + 0 34432.796096 + 1 59701.666027 + 2 32242.741010 + 3 30559.154647 + 4 21574.543367 + dtype: float64 + + """ + return ( + pd.Series( + haversine_pts( + a=np.array( + [ + self.static.bus0.map(self.n_save.buses.x), + self.static.bus0.map(self.n_save.buses.y), + ] + ).T, + b=np.array( + [ + self.static.bus1.map(self.n_save.buses.x), + self.static.bus1.map(self.n_save.buses.y), + ] + ).T, + ) + ) + * 1_000 + ) diff --git a/PyPSA/source/pypsa/components/_types/links.py b/PyPSA/source/pypsa/components/_types/links.py new file mode 100644 index 0000000000000000000000000000000000000000..b09140926657284503df7f02b9a1f6422e9fe487 --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/links.py @@ -0,0 +1,118 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Links components module.""" + +from __future__ import annotations + +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any + +import pandas as pd + +from pypsa.common import list_as_string +from pypsa.components._types._patch import patch_add_docstring +from pypsa.components.components import Components +from pypsa.constants import RE_PORTS_GE_2 + +if TYPE_CHECKING: + from collections.abc import Sequence + + import pandas as pd + import xarray as xr + + +@patch_add_docstring +class Links(Components): + """Links components class. + + This class is used for link components. All functionality specific to + links is implemented here. Functionality for all components is implemented in + the abstract base class. + + See Also + -------- + [pypsa.Components][] + + Examples + -------- + >>> n.components.links + 'Link' Components + ----------------- + Attached to PyPSA Network 'AC-DC-Meshed' + Components: 4 + + """ + + _operational_variables = ["p"] + + def get_bounds_pu( + self, + attr: str = "p", + ) -> tuple[xr.DataArray, xr.DataArray]: + """Get per unit bounds for links. + + + + Parameters + ---------- + attr : string, optional + Attribute name for the bounds, e.g. "p" + + Returns + ------- + tuple[xr.DataArray, xr.DataArray] + Tuple of (min_pu, max_pu) DataArrays. + + """ + if attr not in self._operational_variables: + msg = f"Bounds can only be retrieved for operational attributes. For links those are: {list_as_string(self._operational_variables)}." + raise ValueError(msg) + + return self.da.p_min_pu, self.da.p_max_pu + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Wrap Components.add() and docstring is patched via decorator.""" + return super().add( + name=name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) + + @property + def additional_ports(self) -> list[str]: + """Identify additional link ports (bus connections) beyond predefined ones. + + Returns + ------- + list of strings + List of additional link ports. E.g. ["2", "3"] for bus2, bus3. + + Also see + --------- + pypsa.Components.ports + + Examples + -------- + >>> n = pypsa.Network() # doctest: +SKIP + >>> n.add("Link", "link1", bus0="bus1", bus1="bus2", bus2="bus3") # doctest: +SKIP + Index(['link1'], dtype='object') + >>> n.components.links.additional_ports # doctest: +SKIP + ['2'] + + """ + return [ + match.group(1) + for col in self.static.columns + if (match := RE_PORTS_GE_2.search(col)) + ] diff --git a/PyPSA/source/pypsa/components/_types/loads.py b/PyPSA/source/pypsa/components/_types/loads.py new file mode 100644 index 0000000000000000000000000000000000000000..a2d3bad3e7a423377edc25f1650b1489c8538657 --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/loads.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Loads components module.""" + +from collections.abc import Sequence +from typing import Any + +import pandas as pd + +from pypsa.components._types._patch import patch_add_docstring +from pypsa.components.components import Components + + +@patch_add_docstring +class Loads(Components): + """Loads components class. + + This class is used for load components. All functionality specific to + loads is implemented here. Functionality for all components is implemented in + the abstract base class. + + See Also + -------- + [pypsa.Components][] + + Examples + -------- + >>> n.components.loads + 'Load' Components + ----------------- + Attached to PyPSA Network 'AC-DC-Meshed' + Components: 6 + + """ + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Wrap Components.add() and docstring is patched via decorator.""" + return super().add( + name=name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) diff --git a/PyPSA/source/pypsa/components/_types/shapes.py b/PyPSA/source/pypsa/components/_types/shapes.py new file mode 100644 index 0000000000000000000000000000000000000000..46f8b43a4e13ab63e9a1d2fc4c001ade0954ff91 --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/shapes.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Shapes components module.""" + +from collections.abc import Sequence +from typing import Any + +import pandas as pd + +from pypsa.components._types._patch import patch_add_docstring +from pypsa.components.components import Components + + +@patch_add_docstring +class Shapes(Components): + """Shapes components class. + + This class is used for shape components. All functionality specific to + shapes is implemented here. Functionality for all components is implemented in + the abstract base class. + + See Also + -------- + [pypsa.Components][] + + Examples + -------- + >>> n.components.shapes + Empty 'Shape' Components + + """ + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Wrap Components.add() and docstring is patched via decorator.""" + return super().add( + name=name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) diff --git a/PyPSA/source/pypsa/components/_types/shunt_impedances.py b/PyPSA/source/pypsa/components/_types/shunt_impedances.py new file mode 100644 index 0000000000000000000000000000000000000000..4bf9cfd2a21e262111511861886fffc26d5dcd6e --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/shunt_impedances.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Shunt impedances components module.""" + +from collections.abc import Sequence +from typing import Any + +import pandas as pd + +from pypsa.components._types._patch import patch_add_docstring +from pypsa.components.components import Components + + +@patch_add_docstring +class ShuntImpedances(Components): + """Shunt impedances components class. + + This class is used for shunt impedance components. All functionality specific to + shunt impedances is implemented here. Functionality for all components is implemented in + the abstract base class. + + See Also + -------- + [pypsa.Components][] + + Examples + -------- + >>> n.components.shunt_impedances + Empty 'ShuntImpedance' Components + + """ + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Wrap Components.add() and docstring is patched via decorator.""" + return super().add( + name=name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) diff --git a/PyPSA/source/pypsa/components/_types/storage_units.py b/PyPSA/source/pypsa/components/_types/storage_units.py new file mode 100644 index 0000000000000000000000000000000000000000..29e8899c5cf609bec3f7dcb2c9bf1dc3c408ed81 --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/storage_units.py @@ -0,0 +1,91 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Storage units components module.""" + +from collections.abc import Sequence +from typing import Any + +import pandas as pd +import xarray as xr + +from pypsa.common import list_as_string +from pypsa.components._types._patch import patch_add_docstring +from pypsa.components.components import Components + + +@patch_add_docstring +class StorageUnits(Components): + """StorageUnits components class. + + This class is used for storage unit components. All functionality specific to + storage units is implemented here. Functionality for all components is implemented + in the abstract base class. + + See Also + -------- + [pypsa.Components][] + + Examples + -------- + >>> n.components.storage_units + Empty 'StorageUnit' Components + + """ + + _operational_variables = ["p_dispatch", "p_store", "state_of_charge"] + + def get_bounds_pu( + self, + attr: str = "p_store", + ) -> tuple[xr.DataArray, xr.DataArray]: + """Get per unit bounds for storage units. + + + + Parameters + ---------- + attr : string, optional + Attribute name for the bounds, e.g. "p", "p_store", "state_of_charge" + + Returns + ------- + tuple[xr.DataArray, xr.DataArray] + Tuple of (min_pu, max_pu) DataArrays. + + """ + if attr not in self._operational_variables: + msg = f"Bounds can only be retrieved for operational attributes. For storage_units those are: {list_as_string(self._operational_variables)}." + raise ValueError(msg) + + max_pu = self.da.p_max_pu + + if attr == "p_store": + max_pu = -self.da.p_min_pu + min_pu = xr.zeros_like(max_pu) + elif attr == "state_of_charge": + max_pu = self.da.max_hours + min_pu = xr.zeros_like(max_pu) + else: + max_pu = self.da.p_max_pu + min_pu = xr.zeros_like(max_pu) + + return min_pu, max_pu + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Wrap Components.add() and docstring is patched via decorator.""" + return super().add( + name=name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) diff --git a/PyPSA/source/pypsa/components/_types/stores.py b/PyPSA/source/pypsa/components/_types/stores.py new file mode 100644 index 0000000000000000000000000000000000000000..ad8b1ad9e049a856d8adea4ddcc42c8e9f30e6d5 --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/stores.py @@ -0,0 +1,86 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Stores components module.""" + +from __future__ import annotations + +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any + +import pandas as pd + +from pypsa.common import list_as_string +from pypsa.components._types._patch import patch_add_docstring +from pypsa.components.components import Components + +if TYPE_CHECKING: + from collections.abc import Sequence + + import pandas as pd + import xarray as xr + + +@patch_add_docstring +class Stores(Components): + """Stores components class. + + This class is used for store components. All functionality specific to + stores is implemented here. Functionality for all components is implemented in + the abstract base class. + + Examples + -------- + >>> n.components.stores + Empty 'Store' Components + + See Also + -------- + [pypsa.Components][] + + """ + + _operational_variables = ["e"] + + def get_bounds_pu( + self, + attr: str = "e", + ) -> tuple[xr.DataArray, xr.DataArray]: + """Get per unit bounds for stores. + + + + Parameters + ---------- + attr : string, optional + Attribute name for the bounds, e.g. "e" + + Returns + ------- + tuple[xr.DataArray, xr.DataArray] + Tuple of (min_pu, max_pu) DataArrays. + + """ + if attr not in self._operational_variables: + msg = f"Bounds can only be retrieved for operational attributes. For stores those are: {list_as_string(self._operational_variables)}." + raise ValueError(msg) + + return self.da.e_min_pu, self.da.e_max_pu + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Wrap Components.add() and docstring is patched via decorator.""" + return super().add( + name=name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) diff --git a/PyPSA/source/pypsa/components/_types/sub_networks.py b/PyPSA/source/pypsa/components/_types/sub_networks.py new file mode 100644 index 0000000000000000000000000000000000000000..483e93dddd39f0497d3a4ce89cea63b026d56b0e --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/sub_networks.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Sub networks components module.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from pypsa.components._types._patch import patch_add_docstring +from pypsa.components.components import Components + +if TYPE_CHECKING: + from collections.abc import Sequence + + import pandas as pd + + +@patch_add_docstring +class SubNetworks(Components): + """Sub networks components class. + + This class is used for sub network components. All functionality specific to + sub networks is implemented here. Functionality for all components is implemented in + the abstract base class. + + See Also + -------- + [pypsa.Components][] + + Examples + -------- + >>> n.components.sub_networks + 'SubNetwork' Components + ----------------------- + Attached to PyPSA Network 'AC-DC-Meshed' + Components: 3 + + """ + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Wrap Components.add() and docstring is patched via decorator.""" + return super().add( + name=name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) diff --git a/PyPSA/source/pypsa/components/_types/transformer_types.py b/PyPSA/source/pypsa/components/_types/transformer_types.py new file mode 100644 index 0000000000000000000000000000000000000000..aaf3459c829166a2be1d6ff0c361cdc7ce1a72ae --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/transformer_types.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Transformer types components module.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from pypsa.components._types._patch import patch_add_docstring +from pypsa.components.components import Components + +if TYPE_CHECKING: + from collections.abc import Sequence + + import pandas as pd + + +@patch_add_docstring +class TransformerTypes(Components): + """Transformer types components class. + + This class is used for transformer type components. All functionality specific to + transformer types is implemented here. Functionality for all components is implemented in + the abstract base class. + + See Also + -------- + [pypsa.Components][] + + Examples + -------- + >>> n.components.transformer_types + 'TransformerType' Components + ---------------------------- + Attached to PyPSA Network 'AC-DC-Meshed' + Components: 14 + + """ + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Wrap Components.add() and docstring is patched via decorator.""" + return super().add( + name=name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) diff --git a/PyPSA/source/pypsa/components/_types/transformers.py b/PyPSA/source/pypsa/components/_types/transformers.py new file mode 100644 index 0000000000000000000000000000000000000000..cb66d11207f9298cacd473331115cfe28e633929 --- /dev/null +++ b/PyPSA/source/pypsa/components/_types/transformers.py @@ -0,0 +1,89 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any + +import pandas as pd + +from pypsa.common import list_as_string +from pypsa.components._types._patch import patch_add_docstring +from pypsa.components.components import Components + +if TYPE_CHECKING: + from collections.abc import Sequence + + import pandas as pd + import xarray as xr + + +@patch_add_docstring +class Transformers(Components): + """Transformers components class. + + This class is used for transformer components. All functionality specific to + transformers is implemented here. Functionality for all components is implemented in + the abstract base class. + + See Also + -------- + [pypsa.Components][] + + Examples + -------- + >>> n.components.transformers + Empty 'Transformer' Components + + """ + + _operational_variables = ["s"] + + def get_bounds_pu( + self, + attr: str = "s", + ) -> tuple[xr.DataArray, xr.DataArray]: + """Get per unit bounds for transformers. + + + + For passive branch components, min_pu is the negative of max_pu. + + Parameters + ---------- + attr : string, optional + Attribute name for the bounds, e.g. "s" + + Returns + ------- + tuple[xr.DataArray, xr.DataArray] + Tuple of (min_pu, max_pu) DataArrays. + + """ + if attr not in self._operational_variables: + msg = f"Bounds can only be retrieved for operational attributes. For transformers those are: {list_as_string(self._operational_variables)}." + raise ValueError(msg) + + max_pu = self.da.s_max_pu + min_pu = -max_pu # Transformers specific: min_pu is the negative of max_pu + + return min_pu, max_pu + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Wrap Components.add() and docstring is patched via decorator.""" + return super().add( + name=name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) diff --git a/PyPSA/source/pypsa/components/abstract.py b/PyPSA/source/pypsa/components/abstract.py new file mode 100644 index 0000000000000000000000000000000000000000..78dbbac8c7862b5381f312f2d0ae6b45b11e2be4 --- /dev/null +++ b/PyPSA/source/pypsa/components/abstract.py @@ -0,0 +1,128 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Abstract components module. + +Only defines a base class for all Components helper classes which inherit to +`Components` class. +""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + import pandas as pd + + from pypsa import Network + from pypsa.components.types import ComponentType + from pypsa.definitions.structures import Dict + + +class _ComponentsABC(ABC): + ctype: ComponentType + n: Network | None + static: pd.DataFrame + dynamic: Dict + + @property + @abstractmethod + def name(self) -> str: + pass + + @property + @abstractmethod + def category(self) -> str: + pass + + @property + @abstractmethod + def n_save(self) -> pd.DataFrame: + pass + + @property + @abstractmethod + def names(self) -> pd.Index: + pass + + @property + @abstractmethod + def snapshots(self) -> pd.Index: + pass + + @property + @abstractmethod + def has_scenarios(self) -> bool: + pass + + @property + @abstractmethod + def scenarios(self) -> pd.Index: + pass + + @property + @abstractmethod + def has_investment_periods(self) -> bool: + pass + + @property + @abstractmethod + def has_periods(self) -> bool: + pass + + @property + @abstractmethod + def investment_periods(self) -> pd.Index: + pass + + @property + @abstractmethod + def extendables(self) -> pd.Index: + pass + + @property + @abstractmethod + def committables(self) -> pd.Index: + pass + + @property + @abstractmethod + def fixed(self) -> pd.Index: + pass + + @abstractmethod + def get_activity_mask(self, *args: Any, **kwargs: Any) -> pd.DataFrame: + pass + + @abstractmethod + def _as_dynamic(self, *args: Any, **kwargs: Any) -> pd.DataFrame: + pass + + @abstractmethod + def get_active_assets(self, *args: Any, **kwargs: Any) -> pd.DataFrame: + pass + + def get_bounds_pu( + self, + *args: Any, + **kwargs: Any, + ) -> tuple[pd.DataFrame, pd.DataFrame]: + """Get per unit bounds for components. + + Parameters + ---------- + args : Any + Arguments for the method + kwargs : Any + Keyword arguments for the method + + Returns + ------- + tuple[pd.DataFrame, pd.DataFrame] + Tuple of (min_pu, max_pu) DataFrames. + + """ + msg = f"Bounds can only be retrieved for components with operational attributes and not for {self.name} components." + raise AttributeError(msg) diff --git a/PyPSA/source/pypsa/components/array.py b/PyPSA/source/pypsa/components/array.py new file mode 100644 index 0000000000000000000000000000000000000000..368bc0bbdea022ac88ae1eee3915c63d6c3eabd5 --- /dev/null +++ b/PyPSA/source/pypsa/components/array.py @@ -0,0 +1,351 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Array module of PyPSA components. + +Contains logic to combine static and dynamic pandas DataFrames to single xarray +DataArray for each variable. +""" + +from __future__ import annotations + +import copy +from typing import TYPE_CHECKING + +import numpy as np +import pandas as pd +import xarray + +from pypsa._options import options +from pypsa.common import UnexpectedError, as_index, list_as_string +from pypsa.components.abstract import _ComponentsABC +from pypsa.guards import _assert_xarray_integrity + +if TYPE_CHECKING: + from collections.abc import Sequence + + from pypsa import Components + + +def _from_xarray(da: xarray.DataArray, c: Components) -> pd.DataFrame | pd.Series: + """Convert component attribute xarray view back to pandas dataframe or series. + + Based on the dimensions the method returns the pandas format as stored in Network: + + - name: single-indexed series with names as rows and single column as attribute + - name, snapshot: dataframe with snapshots as rows and names as columns + - name, scenario: multi-index series with name and scenarios as rows + - name, snapshot, scenario: multi-index dataframe with snapshots as rows and name/ scenarios as columns + + If name or scenarios (if stochastic) are missing, they will be expanded to cover them + + """ + # Add missing dimensions if needed + if "name" not in da.dims: + da = da.expand_dims(name=c.names) + if "scenario" not in da.dims and c.has_scenarios: + da = da.expand_dims(scenario=c.scenarios) + + dims = set(da.dims) + if dims in ({"name"}, {"name", "snapshot"}): + return da.transpose("snapshot", "name", missing_dims="ignore").to_pandas() + + # c.static with scenarios + elif dims == {"name", "scenario"}: + return da.transpose("scenario", "name").to_pandas().stack() + + # c.dynamic with scenarios + elif dims == {"name", "snapshot", "scenario"}: + df = ( + da.transpose("name", "scenario", "snapshot", ...) + .stack(combined=("scenario", "name")) + .to_pandas() + ) + # Always return dataframes, also for one-column data + if isinstance(df, pd.Series): + df = df.to_frame() + + df.columns.name = None + return df + + # Handle auxiliary dimensions (e.g. from security constrained optimization) + elif len(dims) > 2: + # Find auxiliary dimensions + contingency_dims = [ + d for d in dims if d not in {"snapshot", "name", "scenario"} + ] + + if contingency_dims: + # Stack auxiliary dimensions with component dimension to create combined index + if "scenario" in dims: + stack_dims = ["name", "scenario"] + contingency_dims + else: + stack_dims = ["name"] + contingency_dims + + combined_name = "combined" + df = da.stack({combined_name: stack_dims}).to_pandas() + + if hasattr(df, "columns"): + df.columns.name = None + + return df + + # Handle cases with auxiliary dimensions but no component dimension (e.g. GlobalConstraint with cycle) + elif len(dims) == 2 and "snapshot" in dims: + # For 2D cases like ('snapshot', 'cycle'), just use to_pandas() directly + return da.to_pandas() + + # Handle other cases + available_dims = list_as_string(dims) + msg = ( + f"Unexpected combination of dimensions: {available_dims}. " + f"Expected some combination of 'snapshot', 'name', and 'scenario'." + ) + raise UnexpectedError(msg) + + +class _XarrayAccessor: + """Accessor class that provides property-like xarray access to all attributes. + + Attributes are lazy evaluated via _as_xarray method of the component. + Supports both attribute access (c.da.p_max_pu) and item access (c.da['p_max_pu']). + """ + + # Use __slots__ to reduce memory footprint (no __dict__ and no dynamic attributes) + __slots__ = ("_component",) + + def __init__(self, component: ComponentsArrayMixin) -> None: + object.__setattr__(self, "_component", component) + + def _get_component(self) -> ComponentsArrayMixin: + """Safely get the component reference to avoid recursion during unpickling.""" + return object.__getattribute__(self, "_component") + + def _get_array(self, attr: str) -> xarray.DataArray: + """Get an xarray DataArray for the specified attribute.""" + component = self._get_component() + try: + return component._as_xarray(attr=attr) + except AttributeError as e: + msg = f"'{component.__class__.__name__}' components has no attribute '{attr}'." + raise AttributeError(msg) from e + + def __getattr__(self, attr: str) -> xarray.DataArray: + """Access component attributes as xarray DataArrays via dot notation.""" + return self._get_array(attr) + + def __getitem__(self, attr: str) -> xarray.DataArray: + """Access component attributes as xarray DataArrays via bracket notation.""" + return self._get_array(attr) + + def __dir__(self) -> list[str]: + """List available attributes for tab-completion.""" + component = self._get_component() + # Include all static and dynamic attributes + attrs = set(component.static.columns) + attrs.update(component.dynamic.keys()) + return sorted(attrs) + + def __str__(self) -> str: + """Get string representation of the xarray accessor.""" + component = self._get_component() + return f"'{component.ctype.name}' XarrayAccessor" + + def __repr__(self) -> str: + """Get representation of the xarray accessor.""" + component = self._get_component() + return f"'{component.ctype.name}' XarrayAccessor" + + +class ComponentsArrayMixin(_ComponentsABC): + """Helper class for components array methods. + + Class inherits to [pypsa.Components][]. All attributes and methods can be used + within any Components instance. + """ + + def __init__(self) -> None: + """Initialize the ComponentsArrayMixin.""" + self.da = _XarrayAccessor(self) + """ + xArray accessor to get component attributes as xarray DataArray. + + Examples + -------- + >>> c = n.components.generators + >>> c.da.p_max_pu + xarray.DataArray 'p_max_pu' (snapshot: 10, name: 6)> Size: 480B + array([[0.93001988, 1. , 0.9745832 , 1. , 0.5590784 , + ... + 1. ]]) + Coordinates: + * snapshot (snapshot) datetime64[ns] 80B 2015-01-01 ... 2015-01-01T09:00:00 + * name (name) object 48B 'Manchester Wind' ... 'Frankfurt Gas' + + For stochastic networks the scenarios are unstacked automatically: + >>> c = n_stoch.components.generators + >>> c.da.p_max_pu + Size: 280kB + array([[[0. , 0.1566, 1. , 1. ], + ... + [0. , 0.1082, 1. , 1. ]]], shape=(2920, 3, 4)) + Coordinates: + * snapshot (snapshot) datetime64[ns] 23kB 2015-01-01 ... 2015-12-31T21:00:00 + * scenario (scenario) object 24B 'low' 'med' 'high' + * name (name) object 32B 'solar' 'wind' 'gas' 'lignite' + + String representation: + >>> c.da + + """ + + def __deepcopy__( + self, memo: dict[int, object] | None = None + ) -> ComponentsArrayMixin: + """Create custom deepcopy which does not copy the xarray accessor.""" + cls = self.__class__ + result = cls.__new__(cls) + memo[id(self)] = result # type: ignore + for k, v in self.__dict__.items(): + setattr( + result, + k, + _XarrayAccessor(result) if k == "da" else copy.deepcopy(v, memo), + ) + return result + + def _as_dynamic( + self, + attr: str, + snapshots: Sequence | None = None, + inds: pd.Index | None = None, + ) -> pd.DataFrame: + """Get an attribute as a dynamic DataFrame. + + Parameters + ---------- + c : pypsa.Components + Components instance + component : string + Component object name, e.g. 'Generator' or 'Link' + attr : string + Attribute name + snapshots : pandas.Index + Restrict to these snapshots rather than n.snapshots. + inds : pandas.Index + Restrict to these components rather than n.components.index + + Returns + ------- + pandas.DataFrame + + Examples + -------- + >>> import pypsa + >>> n = pypsa.examples.ac_dc_meshed() + >>> n.components.generators._as_dynamic('p_max_pu', n.snapshots[:2]) + name Manchester Wind ... Frankfurt Gas + snapshot ... + 2015-01-01 00:00:00 0.930020 ... 1.0 + 2015-01-01 01:00:00 0.485748 ... 1.0 + + [2 rows x 6 columns] + + """ + sns = as_index(self.n_save, snapshots, "snapshots") + index = self.static.index + + static = self.static.get(attr, pd.Series([], index=index[:0])) + dynamic = self.dynamic.get(attr, pd.DataFrame(index=sns, columns=index[:0])) + + # Filter snapshots + if not dynamic.index.equals(sns): + dynamic = dynamic.reindex(sns, fill_value=np.nan) + + # Filter names + if inds is not None: + index = index.intersection(inds) + + # Find columns that need to be filled from static data + diff = index.difference(dynamic.columns) + + if len(diff) == 0: + # No static data needed, just slice dynamic + res = dynamic.reindex(columns=index, fill_value=np.nan) + else: + static_subset = static.reindex(diff, fill_value=np.nan) + + if len(static_subset) > 0: + static_values = static_subset.values + static_to_dynamic = pd.DataFrame( + data=static_values.reshape(1, -1).repeat(len(sns), axis=0), + index=sns, + columns=diff, + ) + else: + static_to_dynamic = pd.DataFrame(index=sns, columns=diff) + + # Concatenate only if there is existing dynamic data + if len(dynamic) > 0: + res = pd.concat([dynamic, static_to_dynamic], axis=1, copy=False) + res = res[index] + else: + res = static_to_dynamic + + res.index.name = sns.name + if self.has_scenarios: + res.columns.names = static.index.names + res.columns.name = None + else: + res.columns.name = "name" + return res + + def _as_xarray(self, attr: str) -> xarray.DataArray: + """Get an attribute as a xarray DataArray. + + Converts component data to a flexible xarray DataArray format, which is + particularly useful for optimization routines. The method provides several + conveniences: + + 1. Automatically handles both static and time-varying attributes + 2. Creates activity masks with the special "active" attribute name + 3. Properly handles scenarios if present in the network + + Parameters + ---------- + c : pypsa.Components + Components instance + attr : str + Attribute name to retrieve, can be an operational shorthand (e.g., "max_pu") + or the full attribute name (e.g., "p_max_pu") + + Returns + ------- + xarray.DataArray + The requested attribute data as an xarray DataArray with appropriate dimensions + + """ + if attr == "active": + res = xarray.DataArray(self.get_activity_mask()) + elif attr in self.dynamic.keys(): + res = xarray.DataArray(self._as_dynamic(attr)) + else: + res = xarray.DataArray(self.static[attr]) + + # Unstack the dimension that contains the scenarios + if self.has_scenarios: + res = ( + res.unstack(res.indexes["scenario"].name) + .reindex(name=self.names) + .reindex(scenario=self.scenarios) + ) + + # Set attibute name as DataArray name + res.name = attr + + # Optional runtime verification + if options.debug.runtime_verification: + _assert_xarray_integrity(self, res) + + return res diff --git a/PyPSA/source/pypsa/components/common.py b/PyPSA/source/pypsa/components/common.py new file mode 100644 index 0000000000000000000000000000000000000000..b5b1e1a53f694849510b0e5cdc65fe15987b1f25 --- /dev/null +++ b/PyPSA/source/pypsa/components/common.py @@ -0,0 +1,64 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""General utility functions for PyPSA components.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pypsa.components.components import Components +from pypsa.deprecations import COMPONENT_ALIAS_DICT + +if TYPE_CHECKING: + from pypsa.type_utils import NetworkType + + +def as_components(n: NetworkType, value: str | Components) -> Components: + """Get component instance from string. + + + + E.g. pass 'Generator', 'generators' or Components class instance to get the + corresponding Components class instance. + + Parameters + ---------- + value : str | Components + String or Components class instance. + n : pypsa.Network + Network instance to which the components are attached. + + Returns + ------- + Components + Components class instance. + + Examples + -------- + >>> # Get generators component from string + >>> generators = pypsa.components.common.as_components(n, 'generators') + >>> generators.name + 'Generator' + + >>> # Also works with singular form + >>> gen = pypsa.components.common.as_components(n, 'Generator') + >>> gen.name + 'Generator' + + """ + if isinstance(value, str): + if value in COMPONENT_ALIAS_DICT: + value = COMPONENT_ALIAS_DICT[value] + return getattr(n.components, value) + if isinstance(value, Components): + if value.n is None: + msg = "Passed component must be attached to the same network." + raise ValueError(msg) + if value.n is not n: + msg = "Passed component is attached to a different network." + raise ValueError(msg) + return value + msg = "Value must be a string or Components class instance." + raise TypeError(msg) diff --git a/PyPSA/source/pypsa/components/components.py b/PyPSA/source/pypsa/components/components.py new file mode 100644 index 0000000000000000000000000000000000000000..df5241de80496804c8e5388c67331e9f5107aad7 --- /dev/null +++ b/PyPSA/source/pypsa/components/components.py @@ -0,0 +1,926 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Components module. + +Contains classes and properties relevant to all component types in PyPSA. Also imports +logic from other modules: +- components.types + +Contains classes and logic relevant to specific component types in PyPSA. +Generic functionality is implemented in the abstract module. +ponents module. +Components module. + +Contains classes and logic relevant to specific component types in PyPSA. +Generic functionality is implemented in the abstract module. +""" + +from __future__ import annotations + +import logging +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any + +import geopandas as gpd +import pandas as pd +import xarray +from pyproj import CRS + +from pypsa.common import deprecated_in_next_major, equals +from pypsa.components.array import ComponentsArrayMixin +from pypsa.components.descriptors import ComponentsDescriptorsMixin +from pypsa.components.index import ComponentsIndexMixin +from pypsa.components.transform import ComponentsTransformMixin +from pypsa.constants import DEFAULT_EPSG, DEFAULT_TIMESTAMP, RE_PORTS +from pypsa.definitions.structures import Dict + +logger = logging.getLogger(__name__) + +if TYPE_CHECKING: + from collections.abc import Callable, Sequence + + from pypsa import Network + from pypsa.definitions.components import ComponentType + +# TODO attachment todos +# - crs +# - snapshots, investment_periods + + +@dataclass +class ComponentsData: + """Dataclass for Components. + + + + This class is used to store all data of a Components object. Other classes inherit + from this class to implement logic and methods, but do not store any data next + to the data in here. + + All attributes can therefore also be accessed directly from + any [`Components`][pypsa.Components] object (which defines all + attributes and properties which are available for all component types) as well as + in specific type classes as [`Generators`][pypsa.components.Generators] (which + define logic and methods specific to the component type). + + Attributes + ---------- + ctype : ComponentType + Component type information containing all default values and attributes. #TODO + n : Network | None + Network to which the component might be attached. + static : pd.DataFrame + Static data of components as a pandas DataFrame. Columns are the attributes + and the index is the component name. + dynamic : dict + Dynamic (time-varying) data of components as a dict-like object of pandas + DataFrames. Keys of the dict are the attribute names and each value is a pandas + DataFrame with snapshots as index and the component names as columns. + + """ + + ctype: ComponentType + n: Network | None + static: pd.DataFrame + """ + Dataframe with static data for all components of this type. + + Returns + ------- + pandas.DataFrame + Static data of the component. + + Examples + -------- + >>> c.static + """ + dynamic: Dict + """ + Dataframe with dynamic data for all components of this type. + + Returns + ------- + pandas.DataFrame + Dynamic data of the component. + + Examples + -------- + >>> c.dynamic + """ + + +class Components( + ComponentsData, + ComponentsDescriptorsMixin, + ComponentsTransformMixin, + ComponentsIndexMixin, + ComponentsArrayMixin, +): + """Components base class. + + | + + Base class for container of energy system related assets, such as + generators or transmission lines. Use the specific subclasses for concrete or + a generic component type. + All data is stored in the dataclass [ComponentsData][pypsa.components.components.ComponentsData]. + Components inherits from it, adds logic and methods, but does not store any data + itself. + + """ + + def __init__( + self, + ctype: ComponentType, + n: Network | None = None, + names: str | int | Sequence[int | str] | None = None, + suffix: str = "", + ) -> None: + """Initialize Components object. + + Parameters + ---------- + ctype : ComponentType + Component type information. + n : Network, optional + Network object to attach to, by default None. + names : str, int, Sequence[int | str], optional + Names of components to attach to, by default None. + suffix : str, optional + Suffix to add to component names, by default "". + + """ + if names is not None: + msg = "Adding components during initialisation is not yet supported." + raise NotImplementedError(msg) + if n is not None: + msg = ( + "Attaching components to Network during initialisation is not yet " + "supported." + ) + raise NotImplementedError(msg) + static, dynamic = self._get_data_containers(ctype) + ComponentsData.__init__(self, ctype, n=None, static=static, dynamic=dynamic) + ComponentsArrayMixin.__init__(self) + + def __str__(self) -> str: + """Get string representation of component. + + + + Examples + -------- + >>> str(n.components.generators) + "'Generator' Components" + + """ + return f"'{self.ctype.name}' Components" + + def __repr__(self) -> str: + """Get representation of component. + + + + Examples + -------- + >>> c = n.components.generators + >>> c + 'Generator' Components + ---------------------- + Attached to PyPSA Network 'AC-DC-Meshed' + Components: 6 + + """ + num_components = len(self.static) + if not num_components: + return f"Empty {self}" + text = f"{self}\n" + "-" * len(str(self)) + "\n" + + # Add attachment status + if self.attached: + text += f"Attached to {str(self.n)}\n" + + text += f"Components: {len(self.static)}" + + return text + + def __getitem__(self, key: str) -> Any: + """Get attribute of component. + + + + Parameters + ---------- + key : str + Attribute name to get. + + Returns + ------- + Any + Attribute value of component. + + """ + return getattr(self, key) + + def __setitem__(self, key: str, value: Any) -> None: + """Set attribute of component. + + + + Parameters + ---------- + key : str + Attribute name to set. + value : Any + Attribute value to set. + + Raises + ------ + KeyError + If the attribute is not found in component. + + """ + if key in self.__dict__: + setattr(self, key, value) + else: + msg = f"'{key}' not found in Component" + raise KeyError(msg) + + def __eq__(self, other: object) -> bool: + """Check if two Components are equal. + + + + Does not check the attached Network, but only component specific data. Therefore + two components can be equal even if they are attached to different networks. + + Parameters + ---------- + other : Any + Other object to compare with. + + Returns + ------- + bool + True if components are equal, otherwise False. + + See Also + -------- + [pypsa.Components.equals][] + + """ + return self.equals(other) + + def __len__(self) -> int: + """Get the number of components. + + + + Returns + ------- + int + Number of components. + + Examples + -------- + >>> len(n.components.generators) + 6 + + Which is the same as: + >>> n.components.generators.static.shape[0] + 6 + + """ + return len(self.static) + + def equals(self, other: Any, log_mode: str = "silent") -> bool: + """Check if two Components are equal. + + + + Does not check the attached Network, but only component specific data. Therefore + two components can be equal even if they are attached to different networks. + + Parameters + ---------- + other : Any + The other network to compare with. + log_mode: str, default="silent" + Controls how differences are reported: + - 'silent': No logging, just returns True/False + - 'verbose': Prints differences but doesn't raise errors + - 'strict': Raises ValueError on first difference + + Raises + ------ + ValueError + If log_mode is 'strict' and components are not equal. + + Returns + ------- + bool + True if components are equal, otherwise False. + + Examples + -------- + >>> n1 = pypsa.Network() + >>> n2 = pypsa.Network() + >>> n1.add("Bus", "bus1") + >>> n2.add("Bus", "bus1") + >>> n1.buses.equals(n2.buses) + True + + """ + return ( + equals(self.ctype, other.ctype, log_mode=log_mode, path="c.ctype") + and equals(self.static, other.static, log_mode=log_mode, path="c.static") + and equals(self.dynamic, other.dynamic, log_mode=log_mode, path="c.dynamic") + ) + + @staticmethod + def _get_data_containers(ct: ComponentType) -> tuple[pd.DataFrame, Dict]: + static_dtypes = ct.defaults.loc[ct.defaults.static, "dtype"].drop(["name"]) + if ct.name == "Shape": + crs = CRS.from_epsg( + DEFAULT_EPSG + ) # if n is None else n.crs #TODO attach mechanism + static = gpd.GeoDataFrame( + {k: gpd.GeoSeries(dtype=d) for k, d in static_dtypes.items()}, + columns=static_dtypes.index, + crs=crs, + ) + else: + static = pd.DataFrame( + {k: pd.Series(dtype=d) for k, d in static_dtypes.items()}, + columns=static_dtypes.index, + ) + static.index.name = "name" + + # # it's currently hard to imagine non-float series, + # but this could be generalised + dynamic = Dict() + snapshots = pd.Index( + [DEFAULT_TIMESTAMP] + ) # if n is None else n.snapshots #TODO attach mechanism + for k in ct.defaults.index[ct.defaults.varying]: + df = pd.DataFrame(index=snapshots, columns=[], dtype=float) + df.index.name = "snapshot" + df.columns.name = "name" + dynamic[k] = df + + return static, dynamic + + @property + def standard_types(self) -> pd.DataFrame | None: + """Get standard types of component. + + + + Returns + ------- + pd.DataFrame + DataFrame with standard types of component. + + Examples + -------- + >>> n.components.transformers.standard_types + + """ + return self.ctype.standard_types + + @property + def name(self) -> str: + """Name of component type. + + + + Returns + ------- + str + Name of component. + + Examples + -------- + >>> n.components.generators.name + 'Generator' + + """ + return self.ctype.name + + @property + def list_name(self) -> str: + """List name of component type. + + + + Returns + ------- + str + List name of component. + + Examples + -------- + >>> n.components.generators.list_name + 'generators' + + """ + return self.ctype.list_name + + @property + def description(self) -> str: + """Description of component. + + + + Returns + ------- + str + Description of component. + + Examples + -------- + >>> n.components.generators.description + 'Power generator for the bus carrier it attaches to.' + + """ + return self.ctype.description + + @property + def category(self) -> str: + """Category of component. + + + + Returns + ------- + str + Category of component. + + Examples + -------- + >>> n.components.generators.category + 'controllable_one_port' + + """ + return self.ctype.category + + @property + def type(self) -> str: + """Get category of component. + + + + !!! note + While not actively deprecated yet, [`category`][pypsa.Components.category] is the preferred method + to access component type. + + Returns + ------- + str + Category of component. + + """ + return self.ctype.category + + @property + @deprecated_in_next_major(details="Use `c.defaults` instead.") + def attrs(self) -> pd.DataFrame: + """Default values of corresponding component type. + + !!! warning "Deprecated in " + + Use [`c.defaults`][pypsa.Components.defaults] instead. + + Returns + ------- + pd.DataFrame + DataFrame with component attribute names as index and the information + like type, unit, default value and description as columns. + + """ + return self.ctype.defaults + + @property + def defaults(self) -> pd.DataFrame: + """Default values of corresponding component type. + + + + Returns + ------- + pd.DataFrame + DataFrame with component attribute names as index and the information + like type, unit, default value and description as columns. + + Examples + -------- + >>> n.components.generators.defaults.head() # doctest: +SKIP + type unit default description status static varying typ dtype + attribute + name string NaN Unique name Input (required) True False object + bus string NaN name of bus to which generator is attached Input (required) True False object + control string NaN PQ P,Q,V control strategy for PF, must be "PQ", "... Input (optional) True False object + type string NaN Placeholder for generator type. Not yet implem... Input (optional) True False object + p_nom float MW 0.0 Nominal power for limits in optimization. Input (optional) True False float64 + + """ + return self.ctype.defaults + + @property + def empty(self) -> bool: + """Check if component is empty. + + + + Returns + ------- + bool + True if component is empty, otherwise False. + + Examples + -------- + >>> n = pypsa.Network() + >>> n.add('Generator', 'g1') + >>> n.components.generators.empty + False + + >>> n.components.buses.empty + True + + """ + return self.static.empty + + @property + def attached(self) -> bool: + """Check if component is attached to a Network. + + + + Some functionality of the component is only available when attached to a + Network. + + Returns + ------- + bool + True if component is attached to a Network, otherwise False. + + Examples + -------- + >>> n.components.generators.attached + True + + """ + return self.n is not None + + @property + def n_save(self) -> Any: + """A save property to access the network (component must be attached). + + + + Returns + ------- + Network + Network to which the component is attached. + + Raises + ------ + AttributeError + If component is not attached to a Network. + + """ + if not self.attached: + msg = "Component must be attached to a Network." + raise AttributeError(msg) + return self.n + + @property + @deprecated_in_next_major(details="Use `c.static` instead.") + def df(self) -> pd.DataFrame: + """Get static data of all components as pandas DataFrame. + + !!! warning "Deprecated in " + Use [`c.static`][pypsa.Components.static] instead. + + Returns + ------- + pd.DataFrame + DataFrame with components as index and attributes as columns. + + """ + return self.static + + @property + @deprecated_in_next_major(details="Use `c.dynamic` instead.") + def pnl(self) -> dict: + """Get dynamic data of all components as a dictionary of pandas DataFrames. + + !!! warning "Deprecated in " + Use [`c.dynamic`][pypsa.Components.dynamic] instead. + + Returns + ------- + dict + Dictionary of dynamic components. Keys are the attribute and each value is + a pandas DataFrame with snapshots as index and the component names as + columns. + + """ + return self.dynamic + + @property + def ds(self) -> xarray.Dataset: + """Create a xarray data array view of the component. + + + + !!! note + + Note that this will create a full copy of the component data. For large networks + this may be a bottleneck. Use the [pypsa.Components.da][] accessor instead to + access a specific attribute of the component. + + Returns + ------- + xarray.Dataset + Dataset with component attributes as variables and snapshots as coordinates. + + See Also + -------- + [pypsa.Components.da][] + + Examples + -------- + >>> c = n.components.generators + >>> c.ds # doctest: +ELLIPSIS + Size: ... + Dimensions: (name: 6, snapshot: 10) + Coordinates: + * name (name) object ... 'Manchester Wind' ... 'Frankfu... + * snapshot (snapshot) datetime64[ns] ... 2015-01-01 ... 201... + Data variables: (12/39) + bus (name) object ... 'Manchester' ... 'Frankfurt' + control (name) object ... 'Slack' 'PQ' ... 'Slack' 'PQ' + type (name) object ... '' '' '' '' '' '' + p_nom (name) float64 ... 80.0 5e+04 100.0 ... 110.0 8e+04 + p_nom_mod (name) float64 ... 0.0 0.0 0.0 0.0 0.0 0.0 + p_nom_extendable (name) bool ... True True True True True True + ... + + """ + data = {} + + for attr in self.static.columns: + data[attr] = self._as_xarray(attr) + + for attr, df in self.dynamic.items(): + if not df.empty: + data[attr] = self._as_xarray(attr) + + return xarray.Dataset(data) + + @property + def units(self) -> pd.Series: + """Get units of all attributes of components. + + + + Returns + ------- + pd.Series + Series with attribute names as index and units as values. + + Examples + -------- + >>> c = n.components.generators + >>> c.units.head() # doctest: +SKIP + unit + attribute + p_nom MW + p_nom_mod MW + p_nom_min MW + p_nom_max MW + p_min_pu per unit + + """ + return self.defaults.unit[self.defaults.unit.notnull()].to_frame() + + @property + def ports(self) -> list: + """Get ports of all components. + + + + Returns + ------- + pd.Series + Series with attribute names as index and port names as values. + + Examples + -------- + >>> c = n.components.lines + >>> c.ports + ['0', '1'] + + See Also + -------- + [pypsa.components.Links.additional_ports][] + + """ + return [ + match.group(1) for col in self.static if (match := RE_PORTS.search(col)) + ] + + @property + def extendables(self) -> pd.Index: + """Get the index of extendable elements of this component. + + + + Returns + ------- + pd.Index + Single-level index of extendable elements. + + """ + extendable_col = self._operational_attrs["nom_extendable"] + if extendable_col not in self.static.columns: + return self.static.iloc[:0].index + + idx = self.static.loc[self.static[extendable_col]].index + + # Remove scenario dimension, since they cannot vary across scenarios + if self.has_scenarios: + idx = idx.get_level_values("name").drop_duplicates() + + return idx + + @property + def fixed(self) -> pd.Index: + """Get the index of non-extendable elements of this component. + + + + Returns + ------- + pd.Index + Single-level index of non-extendable elements. + + """ + extendable_col = self._operational_attrs["nom_extendable"] + if extendable_col not in self.static.columns: + return self.static.iloc[:0].index + + idx = self.static.loc[~self.static[extendable_col]].index + + # Remove scenario dimension, since they cannot vary across scenarios + if self.has_scenarios: + idx = idx.get_level_values("name").drop_duplicates() + + return idx + + @property + def committables(self) -> pd.Index: + """Get the index of committable elements of this component. + + + + Returns + ------- + pd.Index + Single-level index of committable elements. + + """ + if "committable" not in self.static: + return self.static.iloc[:0].index + + idx = self.static.loc[self.static["committable"]].index + + # Remove scenario dimension, since they cannot vary across scenarios + if self.has_scenarios: + idx = idx.get_level_values("name").drop_duplicates() + + return idx + + +class SubNetworkComponents: + """Wrapper class to allow for custom attribute handling of components. + + SubNetworkComponents are read-only and delegate attribute access to it's wrapped + Components object of the PyPSA Network. This allows for custom attribute handling + and getter functions to be implemented, e.g. to filter sub-network specific + components from the main network components. + + Also See + -------- + pypsa.Components : Base class for all PyPSA components in the + network. + """ + + def __init__(self, wrapped_data: Components, wrapped_get: Callable) -> None: + """Initialize SubNetworkComponents. + + Parameters + ---------- + wrapped_data : Components + Components object to wrap around. + wrapped_get : Callable + Custom getter function to delegate attribute access to the wrapped data + object and allow for custom attribute handling. + + """ + self._wrapped_data = wrapped_data + self._wrapper_func = wrapped_get + + def __getattr__(self, item: str) -> Any: + """Delegate attribute access to the wrapped data object. + + Parameters + ---------- + item : str + Attribute name to access. + + Returns + ------- + Any + Attribute value of the wrapped data object. + + """ + return self._wrapper_func(item, self._wrapped_data) + + def __setattr__(self, key: str, value: Any) -> None: + """Prevent setting of attributes. + + Parameters + ---------- + key : str + Attribute name to set. + value : Any + Attribute value to set. + + Raises + ------ + AttributeError + If attribute setting is attempted. + + """ + if key in {"_wrapped_data", "_wrapper_func"}: + super().__setattr__(key, value) + else: + msg = "SubNetworkComponents is read-only" + raise AttributeError(msg) + + def __delattr__(self, name: str) -> None: + """Prevent deletion of attributes. + + Parameters + ---------- + name : str + Attribute name to delete. + + Raises + ------ + AttributeError + If attribute deletion is attempted. + + """ + msg = "SubNetworkComponents is read-only" + raise AttributeError(msg) + + def __str__(self) -> str: + """Get string representation of sub-network components. + + Examples + -------- + >>> str(sub_network.components.generators) + "'Generator' SubNetworkComponents" + + """ + return f"'{self.ctype.name}' SubNetworkComponents" + + def __repr__(self) -> str: + """Get representation of sub-network components. + + Examples + -------- + >>> sub_network.components.generators + 'Generator' SubNetworkComponents + -------------------------------- + Attached to Sub-Network of PyPSA Network 'AC-DC-Meshed' + Components: 6 + + """ + num_components = len(self._wrapped_data.static) + if not num_components: + return f"Empty {self}" + text = f"{self}\n" + "-" * len(str(self)) + "\n" + + # Add attachment status + if self.attached: + text += f"Attached to Sub-Network of {str(self.n)}\n" + + text += f"Components: {len(self._wrapped_data.static)}" + + return text diff --git a/PyPSA/source/pypsa/components/descriptors.py b/PyPSA/source/pypsa/components/descriptors.py new file mode 100644 index 0000000000000000000000000000000000000000..f89c42513a112cc43c77e948e70a5fe0fdd391af --- /dev/null +++ b/PyPSA/source/pypsa/components/descriptors.py @@ -0,0 +1,283 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Components descriptor module. + +Contains single mixin class which is used to inherit to [pypsa.Components][] class. +Should not be used directly. + +Descriptor functions only describe data and do not modify it. + +""" + +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +import numpy as np +import pandas as pd + +from pypsa.common import as_index +from pypsa.components.abstract import _ComponentsABC + +logger = logging.getLogger(__name__) + +if TYPE_CHECKING: + from collections.abc import Sequence + +logger = logging.getLogger(__name__) + + +class ComponentsDescriptorsMixin(_ComponentsABC): + """Mixin class for components descriptors methods. + + Class inherits to [pypsa.Components][]. All attributes and methods can be used + within any Components instance. + + """ + + @property + def _operational_attrs(self) -> dict[str, str]: + """Get operational attributes of component for optimization. + + Provides a dictionary of attribute patterns used in optimization constraints, + based on the component type. This makes constraint formulation more modular + by avoiding hardcoded attribute names. + + Returns + ------- + dict[str, str] + Dictionary of operational attribute names + + """ + # TODO if we expose this, this needs further refinment and checks, specially + # because of edge case StorageUnit with multiple operational variables + + base = { + "Generator": "p", + "Line": "s", + "Link": "p", + "Load": "p", + "StorageUnit": "p", + "Store": "e", + "Transformer": "s", + }[self.name] + + return { + "base": base, + "nom": f"{base}_nom", + "nom_extendable": f"{base}_nom_extendable", + "nom_min": f"{base}_nom_min", + "nom_max": f"{base}_nom_max", + "nom_set": f"{base}_nom_set", + "min_pu": f"{base}_min_pu", + "max_pu": f"{base}_max_pu", + "set": f"{base}_set", + } + + def get_active_assets( + self, + investment_period: int | str | Sequence | None = None, + ) -> pd.Series: + """Get active components mask of component type in investment period(s). + + + + A component is considered active when: + + - it's active attribute is True + - it's build year + lifetime is smaller than the investment period (if given) + + Parameters + ---------- + investment_period : int, str, Sequence + Investment period(s) to check for active within build year and lifetime. If + none only the active attribute is considered and build year and lifetime are + ignored. If multiple periods are given the mask is True if component is + active in any of the given periods. + + Returns + ------- + pd.Series + Boolean mask for active components + + Examples + -------- + Without investment periods + + >>> n = pypsa.Network() + >>> n.add("Generator", "g1", active=False) + >>> n.add("Generator", "g2", active=True) + >>> n.components.generators.get_active_assets() + name + g1 False + g2 True + Name: active, dtype: bool + + With investment periods + >>> n = pypsa.Network() + >>> n.snapshots = pd.MultiIndex.from_product([[2020, 2021, 2022], ["1", "2", "3"]]) + >>> n.add("Generator", "g1", build_year=2020, lifetime=1) + >>> n.add("Generator", "g2", active=False) + >>> n.components.generators.get_active_assets() + name + g1 True + g2 False + Name: active, dtype: bool + + """ + if investment_period is None: + return self.static.active + if not {"build_year", "lifetime"}.issubset(self.static): + return self.static.active + + # Logical OR of active assets in all investment periods and + # logical AND with active attribute + active = {} + for period in np.atleast_1d(investment_period): + if period not in self.n_save.investment_periods: + msg = "Investment period not in `n.investment_periods`" + raise ValueError(msg) + active[period] = self.static.eval( + "build_year <= @period < build_year + lifetime" + ) + return pd.DataFrame(active).any(axis=1) & self.static.active + + @property + def active_assets(self) -> pd.Series: + """Get list of active assets. + + + + See corresponding [pypsa.Components.inactive_assets][] for details. + + Returns + ------- + pd.Series + List of inactive assets + + See Also + -------- + [pypsa.Components.inactive_assets][] + + """ + active_assets = self.get_active_assets() + return active_assets[active_assets].index.get_level_values("name").unique() + + @property + def inactive_assets(self) -> pd.Series: + """Get list of inactive assets. + + + + An asset is considered inactive when one of the following conditions is met: + - `active` is set to False across all dimensions (investment periods, scenarios) + - `build_year` + `lifetime` never satisfies the condition for any investment period + + Inactive assets are not considered in the optimization and are excluded from + the model entirely. + + Returns + ------- + pd.Series + List of inactive assets + + Examples + -------- + >>> n = pypsa.Network() + >>> n.snapshots = pd.MultiIndex.from_product([[2020, 2021, 2022], ["1", "2", "3"]]) + >>> n.add("Generator", "g1", build_year=2020, lifetime=1) + >>> n.add("Generator", "g2", active=False) + + List all components + >>> n.generators.index + Index(['g1', 'g2'], dtype='object', name='name') + + List of inactive components + 'g1' will be considered as active because it is active in at least one investment period + >>> n.components.generators.inactive_assets + Index(['g2'], dtype='object', name='name') + + List of active components + >>> n.components.generators.active_assets + Index(['g1'], dtype='object', name='name') + + `c.active_assets` and `c.inactive_assets` are mutually exclusive + + See Also + -------- + [pypsa.Components.get_active_assets][] + + """ + active_assets = self.get_active_assets() + return active_assets[~active_assets].index.get_level_values("name").unique() + + def get_activity_mask( + self, + sns: Sequence | None = None, + index: pd.Index | None = None, + ) -> pd.DataFrame: + """Get active components mask indexed by snapshots. + + + + Gets the boolean mask for active components, indexed by snapshots and + components instead of just components. + + Parameters + ---------- + sns : pandas.Index, default None + Set of snapshots for the mask. If None (default) all snapshots are returned. + index : pd.Index, default None + Subset of the component elements. If None (default) all components are returned. + + Examples + -------- + >>> n = pypsa.Network() + >>> n.snapshots = pd.MultiIndex.from_product([[2020, 2021, 2022], ["1", "2", "3"]]) + >>> n.add("Generator", "g1", build_year=2020, lifetime=1) + >>> n.add("Generator", "g2", active=False) + >>> n.components.generators.get_activity_mask() # doctest: +ELLIPSIS + name g1 g2 + period timestep + 2020 1 True False + 2 True False + 3 True False + 2021 1 False False + 2 False False + 3 False False + 2022 1 False False + 2 False False + 3 False False + + """ + sns_ = as_index(self.n_save, sns, "snapshots") + + if self.has_investment_periods: + active_assets_per_period = { + period: self.get_active_assets(investment_period=period) + for period in self.investment_periods + } + mask = ( + pd.concat(active_assets_per_period, axis=1) + .T.reindex(self.snapshots, level=0) + .loc[sns_] + ) + else: + active_assets = self.get_active_assets() + mask = pd.DataFrame( + np.tile(active_assets, (len(sns_), 1)), + index=sns_, + columns=active_assets.index, + ) + + if index is not None: + mask = mask.reindex(columns=index) + + mask.index.name = "snapshot" + if isinstance(mask.index, pd.MultiIndex): + mask.index.names = ["period", "timestep"] + + return mask diff --git a/PyPSA/source/pypsa/components/index.py b/PyPSA/source/pypsa/components/index.py new file mode 100644 index 0000000000000000000000000000000000000000..35ecfd9d9f049cf2bd89b2076b25c8efc71abd5c --- /dev/null +++ b/PyPSA/source/pypsa/components/index.py @@ -0,0 +1,172 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Components index module. + +Contains single mixin class which is used to inherit to [pypsa.Components][] class. +Should not be used directly. + +Index methods and properties are used to access the different index levels, based on +the attached parent network. + +""" + +from __future__ import annotations + +import logging +import warnings +from typing import TYPE_CHECKING + +from pypsa.components.abstract import _ComponentsABC + +if TYPE_CHECKING: + import pandas as pd + + +logger = logging.getLogger(__name__) + + +class ComponentsIndexMixin(_ComponentsABC): + """Mixin class for components index methods. + + Class inherits to [pypsa.Components][]. All attributes and methods can be used + within any Components instance. + + """ + + @property + def names(self) -> pd.Series: + """Get component names. + + + + The names are corresponding to [`c.static.index`][pypsa.Components.static] for + non stochastic networks. For stochastic networks the index will be multi-indexed. + + Returns + ------- + pd.Series + Series with component names as index and values. + + Examples + -------- + >>> n = pypsa.Network() + >>> n.add("Generator", "g1") + >>> n.add("Generator", "g2") + >>> n.generators.index + Index(['g1', 'g2'], dtype='object', name='name') + + """ + return self.static.index.get_level_values("name").drop_duplicates() + + @property + def component_names(self) -> pd.Series: + """Get component names. + + !!! warning "Deprecated in " + Use [`names`][pypsa.Components.names] instead. + + Returns + ------- + pd.Series + Series with component names as index and values. + + """ + warnings.warn( + "c.component_names is deprecated, use c.names instead", + DeprecationWarning, + stacklevel=2, + ) + return self.names + + # Derived from attached Network + + @property + def snapshots(self) -> pd.Index | pd.MultiIndex: + """Snapshots of the network. + + + + See Also + -------- + [pypsa.Network.snapshots][] + + """ + return self.n_save.snapshots + + @property + def timesteps(self) -> pd.Index: + """Time steps of the network. + + + + See Also + -------- + [pypsa.Network.timesteps][] + + """ + return self.n_save.timesteps + + @property + def investment_periods(self) -> pd.Index: + """Investment periods of the network. + + + + See Also + -------- + [pypsa.Network.investment_periods][] + + """ + return self.n_save.investment_periods + + @property + def has_investment_periods(self) -> bool: + """Indicator whether network has investment periods. + + + + See Also + -------- + [pypsa.Network.has_investment_periods][] + + """ + return self.n_save.has_investment_periods + + @property + def periods(self) -> pd.Index: + """Periods of the network. + + + + See Also + -------- + [pypsa.Network.periods][] + + """ + return self.n_save.periods + + @property + def has_periods(self) -> bool: + """Investment periods of the network. + + + """ + return self.n_save.has_periods + + @property + def scenarios(self) -> pd.Index: + """Scenarios of networks. + + + """ + return self.n_save.scenarios + + @property + def has_scenarios(self) -> bool: + """Boolean indicating if the network has scenarios defined. + + + """ + return len(self.scenarios) > 0 diff --git a/PyPSA/source/pypsa/components/legacy.py b/PyPSA/source/pypsa/components/legacy.py new file mode 100644 index 0000000000000000000000000000000000000000..6aa8fbaf3c0bfc03045f8b37d896bea1c3b3d20c --- /dev/null +++ b/PyPSA/source/pypsa/components/legacy.py @@ -0,0 +1,104 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Legacy functionality which is kept for backwards compatibility.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from pypsa.common import UnexpectedError +from pypsa.components._types import ( + Buses, + Carriers, + Generators, + GlobalConstraints, + Lines, + LineTypes, + Links, + Loads, + Shapes, + ShuntImpedances, + StorageUnits, + Stores, + SubNetworks, + Transformers, + TransformerTypes, +) +from pypsa.components.types import get as get_component_type + +if TYPE_CHECKING: + import pandas as pd + + from pypsa.components.components import Components + from pypsa.definitions.components import ComponentType + from pypsa.definitions.structures import Dict + +# Legacy Component Class +# ----------------------------------- + +_CLASS_MAPPING = { + "Bus": Buses, + "Carrier": Carriers, + "Generator": Generators, + "GlobalConstraint": GlobalConstraints, + "Line": Lines, + "LineType": LineTypes, + "Link": Links, + "Load": Loads, + "Shape": Shapes, + "ShuntImpedance": ShuntImpedances, + "StorageUnit": StorageUnits, + "Store": Stores, + "SubNetwork": SubNetworks, + "Transformer": Transformers, + "TransformerType": TransformerTypes, +} + + +class Component: + """Legacy component class. + + Allows to keep functionallity of previous dataclass/ named tuple and wraps + around new structure. + + !!! warning + This class is deprecated and should not be used anymore. + """ + + # ruff: noqa: D102 + def __new__( + cls, + name: str | None = None, + ctype: ComponentType | None = None, + n: Any | None = None, + static: pd.DataFrame | None = None, + dynamic: Dict | None = None, + ) -> Any: + # Deprecation warnings + if (name and ctype is not None) or (not name and ctype is None): + msg = "One out of 'name' or 'ct' must be given." + raise ValueError(msg) + + if name: + ctype_ = get_component_type(name) + else: + ctype_ = ctype # type: ignore + + component_class = _CLASS_MAPPING.get(ctype_.name, None) + instance: Components + if component_class is not None: + instance = component_class(ctype=ctype_) + else: + msg = f"Component type '{ctype_.name}' not found." + raise UnexpectedError(msg) + + if n is not None: + instance.n = n + if static is not None: + instance.static = static + if dynamic is not None: + instance.dynamic = dynamic + + return instance diff --git a/PyPSA/source/pypsa/components/store.py b/PyPSA/source/pypsa/components/store.py new file mode 100644 index 0000000000000000000000000000000000000000..eec1e5699fbd58525b4aa3391c98ae57194d29c0 --- /dev/null +++ b/PyPSA/source/pypsa/components/store.py @@ -0,0 +1,187 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Components store module. + +Contains store class which is used to store all different components in the network. +""" + +from __future__ import annotations + +import logging +import re +from typing import TYPE_CHECKING, Any + +from pypsa.deprecations import COMPONENT_ALIAS_DICT + +if TYPE_CHECKING: + from pypsa.components._types.buses import Buses + from pypsa.components._types.carriers import Carriers + from pypsa.components._types.generators import Generators + from pypsa.components._types.global_constraints import GlobalConstraints + from pypsa.components._types.line_types import LineTypes + from pypsa.components._types.lines import Lines + from pypsa.components._types.links import Links + from pypsa.components._types.loads import Loads + from pypsa.components._types.shapes import Shapes + from pypsa.components._types.shunt_impedances import ShuntImpedances + from pypsa.components._types.storage_units import StorageUnits + from pypsa.components._types.stores import Stores + from pypsa.components._types.sub_networks import SubNetworks + from pypsa.components._types.transformer_types import TransformerTypes + from pypsa.components._types.transformers import Transformers + +logger = logging.getLogger(__name__) + + +class ComponentsStore(dict): + """Component store for all components in the network.""" + + buses: Buses + carriers: Carriers + generators: Generators + global_constraints: GlobalConstraints + line_types: LineTypes + lines: Lines + links: Links + loads: Loads + shapes: Shapes + shunt_impedances: ShuntImpedances + storage_units: StorageUnits + stores: Stores + sub_networks: SubNetworks + transformer_types: TransformerTypes + transformers: Transformers + + def __repr__(self) -> str: + """Get representation of component store. + + Examples + -------- + >>> n.components + PyPSA Components Store + ====================== + - 9 'Bus' Components + - 6 'Carrier' Components + - 6 'Generator' Components + - 6 'Load' Components + - 4 'Link' Components + - 0 'Store' Components + - 0 'StorageUnit' Components + - 7 'Line' Components + - 59 'LineType' Components + - 0 'Transformer' Components + - 14 'TransformerType' Components + - 0 'ShuntImpedance' Components + - 1 'GlobalConstraint' Components + - 0 'Shape' Components + - 3 'SubNetwork' Components + + """ + return "PyPSA Components Store\n======================\n- " + "\n- ".join( + f"{len(value.static)} {value}" for value in self.values() + ) + + def __setattr__(self, name: str, value: Any) -> None: + """Is invoked when object.member = value is called.""" + if hasattr(ComponentsStore, name): + msg = f"'ComponentsStore' object attribute '{name}' can not be set." + raise AttributeError(msg) + self[name] = value + + def __getitem__(self, item: str | list | set) -> Any: + """Index single and multiple items from the dictionary. + + Similar behavior to pandas.DataFrame.__getitem__. + + Examples + -------- + >>> n.components + PyPSA Components Store + ====================== + - 9 'Bus' Components + - 6 'Carrier' Components + - 6 'Generator' Components + - 6 'Load' Components + - 4 'Link' Components + - 0 'Store' Components + - 0 'StorageUnit' Components + - 7 'Line' Components + - 59 'LineType' Components + - 0 'Transformer' Components + - 14 'TransformerType' Components + - 0 'ShuntImpedance' Components + - 1 'GlobalConstraint' Components + - 0 'Shape' Components + - 3 'SubNetwork' Components + >>> n.components["generators"] + 'Generator' Components + ---------------------- + Attached to PyPSA Network 'AC-DC-Meshed' + Components: 6 + + """ + if isinstance(item, (list | set)): + return [self[key] for key in item] + if item in COMPONENT_ALIAS_DICT: + # TODO: Activate when changing logic + # Accessing components in n.components using capitalized singular " + # name is deprecated. Use lowercase list name instead: " + # '{COMPONENT_ALIAS_DICT[item]}' instead of '{item}'. + return super().__getitem__(COMPONENT_ALIAS_DICT[item]) + return super().__getitem__(item) + + def __getattr__(self, item: str) -> Any: + """Get attribute from the dictionary. + + Examples + -------- + >>> n.components.generators + 'Generator' Components + ---------------------- + Attached to PyPSA Network 'AC-DC-Meshed' + Components: 6 + + """ + try: + return self[item] + except KeyError as e: + msg = f"Network has no components '{item}'" + raise AttributeError(msg) from e + + def __delattr__(self, name: str) -> None: + """Is invoked when del object.member is called.""" + del self[name] + + _re_pattern = re.compile("[a-zA-Z_][a-zA-Z0-9_]*") + + def __dir__(self) -> list[str]: + """Return list of object attributes including dynamic ones from the keys.""" + dict_keys = [ + k for k in self.keys() if isinstance(k, str) and self._re_pattern.match(k) + ] + obj_attrs = list(dir(super())) + return dict_keys + obj_attrs + + def __iter__(self) -> Any: + """Value iterator over components in store. + + Filters out empty components to maintain backward compatibility with + n.iterate_components() behavior. For accessing all components including + empty ones, use n.components.values() directly instead of iterating over the + store. + """ + # Filter to only return non-empty components (same behavior as iterate_components) + return iter(c for c in self.values() if not c.empty) + + def __contains__(self, item: Any) -> bool: + """Check if component is in store.""" + msg = ( + "Checking if a component is in `n.components` using the 'in' operator " + "is deprecated. Use `item in n.components.keys()` to retain the old " + "behavior. But with v0.33.0 custom components are deprecated and " + "therefore keys in `n.components` never change. Check the release " + "notes for more information." + ) + raise DeprecationWarning(msg) diff --git a/PyPSA/source/pypsa/components/transform.py b/PyPSA/source/pypsa/components/transform.py new file mode 100644 index 0000000000000000000000000000000000000000..fa99b4f43c73e610a919282852b87838562bed88 --- /dev/null +++ b/PyPSA/source/pypsa/components/transform.py @@ -0,0 +1,219 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Components transform module. + +Contains single mixin class which is used to inherit to [pypsa.Components][] class. +Should not be used directly. + +Transform methods are methods which modify and restructure data. + +""" + +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from collections.abc import Sequence + + import pandas as pd + + from pypsa.definitions.structures import Dict + +logger = logging.getLogger(__name__) + + +class ComponentsTransformMixin: + """Mixin class for components descriptors methods. + + Class inherits to [pypsa.Components][]. All attributes and methods can be used + within any Components instance. + """ + + static: pd.DataFrame + dynamic: Dict + attached: Any + n_save: Any + name: Any + + def add( + self, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Add new components. + + + + Handles addition of single and multiple components along with their attributes. + Pass a list of names to add multiple components at once or pass a single name + to add a single component. + + When a single component is added, all non-scalar attributes are assumed to be + time-varying and indexed by snapshots. + When multiple components are added, all non-scalar attributes are assumed to be + static and indexed by names. A single value sequence is treated as scalar and + broadcasted to all components. It is recommended to explicitly pass a scalar + instead. + If you want to add time-varying attributes to multiple components, you can pass + a 2D array/ DataFrame where the first dimension is snapshots and the second + dimension is names. + + Any attributes which are not specified will be given the default + value from . + + Parameters + ---------- + name : str or int or list of str or list of int + Component name(s) + suffix : str, default "" + All components are named after name with this added suffix. + overwrite : bool, default False + If True, existing components with the same names as in `name` will be + overwritten. Otherwise only new components will be added and others will be + ignored. + return_names : bool | None, default=None + Whether to return the names of the new components. Defaults to module wide + option (default: False). See `https://go.pypsa.org/options-params` for more + information. + kwargs : Any + Component attributes, e.g. x=[0.1, 0.2], can be list, pandas.Series + of pandas.DataFrame for time-varying + + Returns + ------- + new_names : pandas.index or None + Names of new components (including suffix) if return_names is `True`, + otherwise `None`. + + Examples + -------- + The example is shown for Generator component, but the same applies to all + component types. + + >>> n = pypsa.Network() + >>> c = n.components.generators + >>> c + Empty 'Generator' Components + + Add a single component: + + >>> c.add("my-generator-1", carrier="AC") + + A new generator is added to the components instance: + >>> c + 'Generator' Components + ---------------------- + Attached to PyPSA Network 'Unnamed Network' + Components: 1 + + With static data (and default values for all attributes): + >>> c.static[["carrier", "p_nom"]] + carrier p_nom + name + my-generator-1 AC 0.0 + + Add multiple components with static attributes: + + >>> c.add(["my-generator-2", "my-generator-3"], + ... carrier=["AC", "DC"], + ... p_nom=10) + + A new generator is added to the components instance: + >>> c + 'Generator' Components + ---------------------- + Attached to PyPSA Network 'Unnamed Network' + Components: 3 + + With static data: + >>> c.static[["carrier", "p_nom"]] + carrier p_nom + name + my-generator-1 AC 0.0 + my-generator-2 AC 10.0 + my-generator-3 DC 10.0 + + The single value for `p_nom` is broadcasted to all components. So you could also + pass `[10, 10]` instead of `10`. + + See Also + -------- + [pypsa.Network.add][] + + """ + if not self.attached: + msg = ( + "Currently new components can only be added when the components " + "are already attached to a network." + ) + raise NotImplementedError(msg) + + return self.n_save.add( + self.name, + name, + suffix=suffix, + overwrite=overwrite, + return_names=return_names, + **kwargs, + ) + + def rename_component_names(self, **kwargs: str) -> None: + """Rename component names. + + Rename components and also update all cross-references of the component in + the network. + + Parameters + ---------- + **kwargs + Mapping of old names to new names. + + Examples + -------- + Define some network + >>> import pypsa + >>> n = pypsa.Network() + >>> n.add("Bus", ["bus1"]) + >>> n.add("Generator", ["gen1"], bus="bus1") + >>> c = n.c.buses + + Now rename the bus + + >>> c.rename_component_names(bus1="bus2") + + Which updates the bus components + + >>> c.static.index + Index(['bus2'], dtype='object', name='name') + + and all references in the network + + >>> n.generators.bus + name + gen1 bus2 + Name: bus, dtype: object + + """ + if not all(isinstance(v, str) for v in kwargs.values()): + msg = "New names must be strings." + raise ValueError(msg) + + # Rename component name definitions + self.static = self.static.rename(index=kwargs) + for k, v in self.dynamic.items(): # Modify in place + self.dynamic[k] = v.rename(columns=kwargs) + + # Rename cross references in network (if attached to one) + if self.attached: + for component in self.n_save.components: + col_name = self.name.lower() # TODO: Generalize + cols = [f"{col_name}{port}" for port in component.ports] + if cols and not component.static.empty: + component.static[cols] = component.static[cols].replace(kwargs) diff --git a/PyPSA/source/pypsa/components/types.py b/PyPSA/source/pypsa/components/types.py new file mode 100644 index 0000000000000000000000000000000000000000..fb736bbf36e017540c28a6312b5e58bfa42cf5ce --- /dev/null +++ b/PyPSA/source/pypsa/components/types.py @@ -0,0 +1,229 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Components types module. + +Contains module wide component types. Default types are loaded from the package data. +Additional types can be added by the user. +""" + +from __future__ import annotations + +from pathlib import Path + +import numpy as np +import pandas as pd + +from pypsa.common import list_as_string +from pypsa.definitions.components import ComponentType +from pypsa.deprecations import COMPONENT_ALIAS_DICT + +# TODO better path handeling, integrate custom components +_components_path = Path(__file__).parent.parent / "data" / "components.csv" +_attrs_path = Path(__file__).parent.parent / "data" / "component_attrs" +_standard_types_path = Path(__file__).parent.parent / "data" / "standard_types" + +component_types_df = pd.read_csv(_components_path, index_col=0) +default_components = component_types_df.index.to_list() + +all_components = {} + + +def add_component_type( + name: str, + list_name: str, + description: str, + category: str, + defaults_df: pd.DataFrame, + standard_types_df: pd.DataFrame | None = None, +) -> None: + """Add component type to package wide component types library. + + The function is used to add the package default components but can also be used to + add custom components, which then again can be used during the network creation. + + Parameters + ---------- + name : str + Name of the component type. Must be unique. + list_name : str + List name of the component type. + description : str + Description of the component type. + category : str + Category of the component type. + defaults_df : pandas.DataFrame + Default attributes of the component type. Pass as a DataFrame with the same + structure as the default components in `/pypsa/data/default_components/`. + standard_types_df : pandas.DataFrame, optional + Standard types of the component type. + + + Examples + -------- + >>> import pandas as pd + + >>> defaults_data = { + ... "attribute": ["name", "attribute_a"], + ... "type": ["string", "float"], + ... "unit": ["n/a", "n/a"], + ... "default": ["n/a", 1], + ... "description": ["Unique name", "Some custom attribute"], + ... "status": ["Input (required)", "Input (optional)"] + ... } + >>> defaults_df = pd.DataFrame(defaults_data) + >>> pypsa.components.types.add_component_type( + ... name="CustomComponent", + ... list_name="custom_components", + ... description="A custom component example", + ... category="custom", + ... defaults_df=defaults_df, + ... ) + >>> # Check created component type + >>> pypsa.components.types.get("custom_components") + 'CustomComponent' Component Type + + """ + if name in all_components: + msg = f"Component type '{name}' already exists." + raise ValueError(msg) + + # Format attributes + defaults_df["default"] = defaults_df.default.astype(object) + defaults_df["static"] = defaults_df["type"] != "series" + defaults_df["varying"] = defaults_df["type"].isin({"series", "static or series"}) + defaults_df["typ"] = ( + defaults_df["type"] + .map({"boolean": bool, "int": int, "string": str, "geometry": "geometry"}) + .fillna(float) + ) + defaults_df["dtype"] = ( + defaults_df["type"] + .map( + { + "boolean": np.dtype(bool), + "int": np.dtype(int), + "string": np.dtype("O"), + } + ) + .fillna(np.dtype(float)) + ) + + bool_b = defaults_df.type == "boolean" + if bool_b.any(): + defaults_df.loc[bool_b, "default"] = defaults_df.loc[bool_b, "default"].isin( + {True, "True"} + ) + + str_b = defaults_df.typ.apply(lambda x: x is str) + defaults_df.loc[str_b, "default"] = defaults_df.loc[str_b, "default"].fillna("") + for typ in (str, float, int): + typ_b = defaults_df.typ == typ + defaults_df.loc[typ_b, "default"] = defaults_df.loc[typ_b, "default"].astype( + typ + ) + + # Initialize Component + all_components[list_name] = ComponentType( + name=name, + list_name=list_name, + description=description, + category=category, + defaults=defaults_df, + standard_types=standard_types_df, + ) + + +def _load_default_component_types( + component_df: pd.DataFrame, attrs_path: Path, standard_types_path: Path +) -> None: + """Load default component types from package data. + + Function is called during package import and should not be used otherwise. + + Parameters + ---------- + component_df : pandas.DataFrame + DataFrame which lists all default components. E.g. `/pypsa/data/components.csv`. + attrs_path : pathlib.Path + Path to the default attributes dir. E.g. `/pypsa/data/default_components/`. + standard_types_path : pathlib.Path + Path to the standard types dir. E.g. `/pypsa/data/standard_types/`. + + """ + for c_name, row in component_df.iterrows(): + # Read in defaults attributes + attrs_file_path = attrs_path / f"{row.list_name}.csv" + if not attrs_file_path.exists(): + msg = ( + f"Could not find {attrs_path}. For each component, there must be a " + "corresponding file for its attributes." + ) + raise FileNotFoundError(msg) + attrs = pd.read_csv(attrs_file_path, index_col=0, na_values="n/a") + + # Read in standard types + types_paths = standard_types_path / f"{row.list_name}.csv" + if not types_paths.exists(): + standard_types = None + else: + standard_types = pd.read_csv(types_paths, index_col=0) + + add_component_type( + name=c_name, + list_name=row.list_name, + description=row.description, + category=row.category, + defaults_df=attrs, + standard_types_df=standard_types, + ) + + +def get(name: str) -> ComponentType: + """Get component type instance from package wide component types library. + + The function is used to get the package default components but can also be used to + get custom components. During network creation, the type instance is not needed but + to pass a component type name as a string to the network constructor, a custom + component must be added to the package wide component types library first. + + Parameters + ---------- + name : str + Name of the component type. + + Returns + ------- + pypsa.components.types.ComponentType + Component type instance. + + Examples + -------- + >>> pypsa.components.types.get("Generator") + 'Generator' Component Type + + """ + if name in COMPONENT_ALIAS_DICT: + name = COMPONENT_ALIAS_DICT[name] + try: + return all_components[name] + except KeyError as e: + msg = ( + f"Component type '{name}' not found. If you use a custom component, make " + f"sure to have it added. Available types are: " + f"{list_as_string(all_components)}." + ) + raise ValueError(msg) from e + + +# Load default component types +_load_default_component_types( + component_df=component_types_df, + attrs_path=_attrs_path, + standard_types_path=_standard_types_path, +) + +all_standard_attrs_set = { + attr for component in all_components.values() for attr in component.defaults.index +} diff --git a/PyPSA/source/pypsa/consistency.py b/PyPSA/source/pypsa/consistency.py new file mode 100644 index 0000000000000000000000000000000000000000..803854aa1d9e2872ebb7042a1d33749f028fdb61 --- /dev/null +++ b/PyPSA/source/pypsa/consistency.py @@ -0,0 +1,1100 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Consistency check functions for PyPSA networks. + +Mainly used in the `Network.consistency_check()` method. +""" + +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, Any + +import numpy as np +import pandas as pd + +from pypsa._options import options +from pypsa.constants import RE_PORTS_FILTER +from pypsa.guards import _assert_data_integrity +from pypsa.network.abstract import _NetworkABC + +if TYPE_CHECKING: + from collections.abc import Sequence + + from pypsa import Network + from pypsa.components import Components + from pypsa.type_utils import NetworkType + +logger = logging.getLogger(__name__) + + +class ConsistencyError(ValueError): + """Error raised when a consistency check fails.""" + + +def _bus_columns(df: pd.DataFrame) -> pd.Index: + return df.columns[df.columns.str.contains(RE_PORTS_FILTER)] + + +def _log_or_raise(strict: bool, message: str, *args: Any) -> None: + formatted_message = message % args if args else message + if strict: + raise ConsistencyError(formatted_message) + logger.warning(message, *args) + + +def check_for_unknown_buses( + n: NetworkType, component: Components, strict: bool = False +) -> None: + """Check if buses are attached to component but are not defined in the network. + + Activate strict mode in general consistency check by passing `['unknown_buses']` to + the `strict` argument. + + Parameters + ---------- + n : pypsa.Network + The network to check. + component : pypsa.Component + The component to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + """ + for attr in _bus_columns(component.static): + missing = ~component.static[attr].astype(str).isin(n.c.buses.names) + # if bus2, bus3... contain empty strings do not warn + if component.name in n.branch_components and int(attr[-1]) > 1: + missing &= component.static[attr] != "" + # if bus contains empty strings for global constraints do not warn + if component.name == "GlobalConstraint": + missing &= component.static[attr] != "" + if missing.any(): + _log_or_raise( + strict, + "The following %s have buses which are not defined:\n%s", + component.list_name, + component.static.index[missing], + ) + + +def check_for_disconnected_buses(n: NetworkType, strict: bool = False) -> None: + """Check if network has buses that are not connected to any component. + + Activate strict mode in general consistency check by passing `['disconnected_buses']` + the `strict` argument. + + Parameters + ---------- + n : pypsa.Network + The network to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + """ + connected_buses = set() + for component in n.components: + for attr in _bus_columns(component.static): + connected_buses.update(component.static[attr]) + + disconnected_buses = set(n.c.buses.names) - connected_buses + if disconnected_buses: + _log_or_raise( + strict, + "The following buses have no attached components, which can break the lopf: %s", + disconnected_buses, + ) + + +def check_for_unknown_carriers( + n: NetworkType, component: Components, strict: bool = False +) -> None: + """Check if carriers are attached to component but are not defined in the network. + + Activate strict mode in general consistency check by passing `['unknown_carriers']` + the `strict` argument. + + Parameters + ---------- + n : pypsa.Network + The network to check. + component : pypsa.Component + The component to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + + """ + if "carrier" in component.static.columns: + missing = ( + ~component.static["carrier"].isin(n.c.carriers.names) + & component.static["carrier"].notna() + & (component.static["carrier"] != "") + ) + if missing.any(): + _log_or_raise( + strict, + "The following %s have carriers which are not defined:\n%s", + component.list_name, + component.static.index[missing], + ) + + +def check_for_zero_impedances( + n: NetworkType, component: Components, strict: bool = False +) -> None: + """Check if component has zero impedances. Only checks passive branch components. + + Activate strict mode in general consistency check by passing `['zero_impedances']` + the `strict` argument. + + Parameters + ---------- + n : pypsa.Network + The network to check. + component : pypsa.Component + The component to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + + See Also + -------- + [pypsa.Network.consistency_check][] + + + """ + if component.name in n.passive_branch_components: + for attr in ["x", "r"]: + bad = component.static[attr] == 0 + if bad.any(): + _log_or_raise( + strict, + "The following %s have zero %s, which could break the linear load flow:\n%s", + component.list_name, + attr, + component.static.index[bad], + ) + + +def check_for_zero_s_nom(component: Components, strict: bool = False) -> None: + """Check if component has zero s_nom. Only checks transformers. + + Activate strict mode in general consistency check by passing `['zero_s_nom']` to + the `strict` argument. + + Parameters + ---------- + component : pypsa.Component + The component to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + """ + if component.name in {"Transformer"}: + bad = component.static["s_nom"] == 0 + if bad.any(): + _log_or_raise( + strict, + "The following %s have zero s_nom, which is used to define the " + "impedance and will thus break the load flow:\n%s", + component.list_name, + component.static.index[bad], + ) + + +def check_time_series( + n: NetworkType, component: Components, strict: bool = False +) -> None: + """Check if time series of component are aligned with network snapshots. + + Activate strict mode in general consistency check by passing `['time_series']` to + the `strict` argument. + + Parameters + ---------- + n : pypsa.Network + The network to check. + component : pypsa.Component + The component to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + + """ + for attr in component.defaults.index[ + component.defaults.varying & component.defaults.static + ]: + attr_df = component.dynamic[attr] + + diff = attr_df.columns.difference(component.static.index) + if len(diff): + _log_or_raise( + strict, + "The following %s have time series defined for attribute %s in n.%s_t, " + "but are not defined in n.%s:\n%s", + component.list_name, + attr, + component.list_name, + component.list_name, + diff, + ) + + if not n.snapshots.equals(attr_df.index): + _log_or_raise( + strict, + "The index of the time-dependent Dataframe for attribute %s of n.%s_t " + "is not aligned with network snapshots", + attr, + component.list_name, + ) + + +def check_static_power_attributes( + n: NetworkType, component: Components, strict: bool = False +) -> None: + """Check static attrs p_now, s_nom, e_nom in any component. + + Activate strict mode in general consistency check by passing `['static_power_attrs']` + the `strict` argument. + + Parameters + ---------- + n : pypsa.Network + The network to check. + component : pypsa.Component + The component to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + + """ + static_attrs = ["p_nom", "s_nom", "e_nom"] + if component.name in n.all_components - {"TransformerType"}: + static_attr = component.defaults.query("static").index.intersection( + static_attrs + ) + if len(static_attr): + attr = static_attr[0] + bad = component.static[attr + "_max"] < component.static[attr + "_min"] + if bad.any(): + _log_or_raise( + strict, + "The following %s have smaller maximum than minimum expansion " + "limit which can lead to infeasibility:\n%s", + component.list_name, + component.static.index[bad], + ) + + attr = static_attr[0] + for col in [attr + "_min", attr + "_max"]: + if ( + component.static[col][component.static[attr + "_extendable"]] + .isna() + .any() + ): + _log_or_raise( + strict, + "Encountered nan's in column %s of component '%s'.", + col, + component.name, + ) + + +def check_time_series_power_attributes( + n: NetworkType, component: Components, strict: bool = False +) -> None: + """Check `p_max_pu` and `e_max_pu` nan and infinite values in time series. + + Activate strict mode in general consistency check by passing `['time_series_power_attrs']` + the `strict` argument. + + Parameters + ---------- + n : pypsa.Network + The network to check. + component : pypsa.Component + The component to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + """ + varying_attrs = ["p_max_pu", "e_max_pu"] + if component.name in n.all_components - {"TransformerType"}: + varying_attr = component.defaults.query("varying").index.intersection( + varying_attrs + ) + + if len(varying_attr): + attr = varying_attr[0][0] + max_pu = n.get_switchable_as_dense(component.name, attr + "_max_pu") + min_pu = n.get_switchable_as_dense(component.name, attr + "_min_pu") + + # check for NaN values: + if max_pu.isna().to_numpy().any(): + for col in max_pu.columns[max_pu.isna().any()]: + _log_or_raise( + strict, + "The attribute %s_max_pu of element %s of %s has NaN " + "values for the following snapshots:\n%s", + attr, + col, + component.list_name, + max_pu.index[max_pu[col].isna()], + ) + if min_pu.isna().to_numpy().any(): + for col in min_pu.columns[min_pu.isna().any()]: + _log_or_raise( + strict, + "The attribute %s_min_pu of element %s of %s has NaN " + "values for the following snapshots:\n%s", + attr, + col, + component.list_name, + min_pu.index[min_pu[col].isna()], + ) + + # check for infinite values + if np.isinf(max_pu).to_numpy().any(): + for col in max_pu.columns[np.isinf(max_pu).any()]: + _log_or_raise( + strict, + "The attribute %s_max_pu of element %s of %s has infinite" + " values for the following snapshots:\n%s", + attr, + col, + component.list_name, + max_pu.index[np.isinf(max_pu[col])], + ) + if np.isinf(min_pu).to_numpy().any(): + for col in min_pu.columns[np.isinf(min_pu).any()]: + _log_or_raise( + strict, + "The attribute %s_min_pu of element %s of %s has infinite" + " values for the following snapshots:\n%s", + attr, + col, + component.list_name, + min_pu.index[np.isinf(min_pu[col])], + ) + + diff = max_pu - min_pu + diff = diff[diff < 0].dropna(axis=1, how="all") + for col in diff.columns: + _log_or_raise( + strict, + "The element %s of %s has a smaller maximum than minimum operational" + " limit which can lead to infeasibility for the following snapshots:\n%s", + col, + component.list_name, + diff[col].dropna().index, + ) + + +def check_assets(n: NetworkType, component: Components, strict: bool = False) -> None: + """Check if assets are only committable or extendable, but not both. + + Activate strict mode in general consistency check by passing `['assets']` to the + `strict` argument. + + Parameters + ---------- + n : pypsa.Network + The network to check. + component : pypsa.Component + The component to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + """ + if component.name in {"Generator", "Link"}: + committables = component.committables + extendables = component.extendables + intersection = committables.intersection(extendables) + if not intersection.empty: + _log_or_raise( + strict, + "Assets can only be committable or extendable." + " Found assets in component %s which are both:\n\n\t%s", + component.name, + ", ".join(intersection), + ) + + +def check_generators(component: Components, strict: bool = False) -> None: + """Check the consistency of generator attributes before the simulation. + + This function performs the following checks on generator components: + 1. Ensures that committable generators are not both up and down before the simulation. + 2. Verifies that the minimum total energy to be produced (e_sum_min) is not greater than the maximum total energy to be produced (e_sum_max). + + Activate strict mode in general consistency check by passing `['generators']` to the + the `strict` argument. + + Parameters + ---------- + component : Component + The generator component to be checked. + strict : bool, optional + If True, raise an error instead of logging a warning. + + + + See Also + -------- + [pypsa.Network.consistency_check][] + + """ + if component.name in {"Generator"}: + bad_uc_gens = component.static.index[ + component.static.committable + & (component.static.up_time_before > 0) + & (component.static.down_time_before > 0) + ] + if not bad_uc_gens.empty: + _log_or_raise( + strict, + "The following committable generators were both" + " up and down before the simulation: %s. This could cause an infeasibility.", + bad_uc_gens, + ) + + bad_e_sum_gens = component.static.index[ + component.static.e_sum_min > component.static.e_sum_max + ] + if not bad_e_sum_gens.empty: + _log_or_raise( + strict, + "The following generators have e_sum_min > e_sum_max," + " which can lead to infeasibility:\n%s.", + bad_e_sum_gens, + ) + + +def check_dtypes_(component: Components, strict: bool = False) -> None: + """Check if the dtypes of the attributes in the component are as expected. + + Activate strict mode in general consistency check by passing `['dtypes']` to the + `strict` argument. + + Parameters + ---------- + component : pypsa.Component + The component to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + """ + dtypes_soll = component.defaults.loc[component.defaults["static"], "dtype"].drop( + "name" + ) + unmatched = component.static.dtypes[dtypes_soll.index] != dtypes_soll + + if unmatched.any(): + _log_or_raise( + strict, + "The following attributes of the dataframe %s have" + " the wrong dtype:\n%s\nThey are:\n%s\nbut should be:\n%s", + component.list_name, + unmatched.index[unmatched], + component.static.dtypes[dtypes_soll.index[unmatched]], + dtypes_soll[unmatched], + ) + + # now check varying attributes + + types_soll = component.defaults.loc[component.defaults["varying"], ["typ", "dtype"]] + + for attr, typ, dtype in types_soll.itertuples(): + if component.dynamic[attr].empty: + continue + + unmatched = component.dynamic[attr].dtypes != dtype + + if unmatched.any(): + _log_or_raise( + strict, + "The following columns of time-varying attribute %s in %s_t" + " have the wrong dtype:\n%s\nThey are:\n%s\nbut should be:\n%s", + attr, + component.list_name, + unmatched.index[unmatched], + component.dynamic[attr].dtypes[unmatched], + typ, + ) + + +def check_investment_periods(n: NetworkType, strict: bool = False) -> None: + """Check if investment periods are aligned with snapshots. + + Activate strict mode in general consistency check by passing `['investment_periods']` + to the `strict` argument. + + Parameters + ---------- + n : pypsa.Network + The network to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + """ + constraint_periods = set( + n.c.global_constraints.static.investment_period.dropna().unique() + ) + if isinstance(n.snapshots, pd.MultiIndex): + if not constraint_periods.issubset(n.snapshots.unique("period")): + msg = ( + "The global constraints contain investment periods which " + "are not in the set of optimized snapshots." + ) + if strict: + raise ValueError(msg) + _log_or_raise(strict, msg) + elif constraint_periods: + msg = ( + "The global constraints contain investment periods but " + "snapshots are not multi-indexed." + ) + if strict: + raise ValueError(msg) + _log_or_raise(strict, msg) + + +def check_shapes(n: NetworkType, strict: bool = False) -> None: + """Check if shapes are aligned with related components. + + Activate strict mode in general consistency check by passing `['shapes']` to the + `strict` argument. + + Parameters + ---------- + n : pypsa.Network + The network to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + """ + shape_components = n.c.shapes.static.component.unique() + for c in set(shape_components) & set(n.all_components): + geos = n.c.shapes.static.query("component == @c") + not_included = geos.index[~geos.idx.isin(n.c[c].static.index)] + + if not not_included.empty: + _log_or_raise( + strict, + "The following shapes are related to component %s and" + " have idx values that are not included in the component's index:\n%s", + c, + not_included, + ) + + +def check_nans_for_component_default_attrs( + n: NetworkType, component: Components, strict: bool = False +) -> None: + """Check for missing values in component attributes. + + Activate strict mode in general consistency check by passing `['nans_for_component_default_attrs']` + the `strict` argument. + + Checks for all attributes if they are nan but have a default value, which is not + nan. + + Parameters + ---------- + n : pypsa.Network + The network to check. + component : pypsa.Component + The component to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + """ + # Get non-NA and not-empty default attributes for the current component + default = component.defaults["default"] + not_null_component_attrs = component.defaults[ + default.notna() & default.ne("") + ].index + + # Remove attributes that are not in the component's static data + relevant_static_df = component.static[ + list(set(component.static.columns).intersection(not_null_component_attrs)) + ] + + # Run the check for nan values on relevant static data + if (isna := relevant_static_df.isna().any()).any(): + nan_cols = relevant_static_df.columns[isna] + _log_or_raise( + strict, + "Encountered nan's in static data for columns %s of component '%s'.", + nan_cols.to_list(), + component.name, + ) + + # Remove attributes that are not in the component's time series data (if + # there is any) + relevant_series_dfs = { + key: value + for key, value in component.dynamic.items() + if key in not_null_component_attrs and not value.empty + } + + # Run the check for nan values on relevant data + for key, values_df in relevant_series_dfs.items(): + if (isna := values_df.isna().any()).any(): + nan_cols = values_df.columns[isna] + _log_or_raise( + strict, + "Encountered nan's in varying data '%s' for columns %s of component '%s'.", + key, + nan_cols.to_list(), + component.name, + ) + + +def check_for_missing_carrier_colors(n: Network, strict: bool = False) -> None: + """Check if carriers are missing colors. + + Parameters + ---------- + n : pypsa.Network + The network to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + """ + missing_colors = n.c.carriers.static[ + n.c.carriers.static.color.isna() | n.c.carriers.static.color.eq("") + ] + if not missing_colors.empty: + _log_or_raise( + strict, + "The following carriers are missing colors:\n%s", + missing_colors.index, + ) + + +class NetworkConsistencyMixin(_NetworkABC): + """Mixin class for network consistency checks. + + Class inherits to [pypsa.Network][]. All attributes and methods can be used + within any Network instance. + """ + + def consistency_check( + self, check_dtypes: bool = False, strict: Sequence | None = None + ) -> None: + """Check network for consistency. + + + + Runs a series of checks on the network to ensure that it is consistent, e.g. that + all components are connected to existing buses and that no impedances are singular. + + Parameters + ---------- + check_dtypes : bool, optional + If True, check the dtypes of the attributes in the components. + strict : list, optional + If some checks should raise an error instead of logging a warning, pass a list + of strings with the names of the checks to be strict about. If 'all' is passed, + all checks will be strict. The default is no strict checks. + + Raises + ------ + ConsistencyError + If any of the checks fail and strict mode is activated. + + """ + if strict is None: + strict = [] + + strict_options = [ + "unknown_buses", + "unknown_carriers", + "time_series", + "static_power_attrs", + "time_series_power_attrs", + "nans_for_component_default_attrs", + "zero_impedances", + "zero_s_nom", + "assets", + "generators", + "disconnected_buses", + "investment_periods", + "shapes", + "dtypes", + "scenarios_sum", + "scenario_invariant_attrs", + "line_types", + "slack_bus_consistency", + ] + + if "all" in strict: + strict = strict_options + if not all(s in strict_options for s in strict): + msg = ( + f"Invalid strict option(s) {set(strict) - set(strict_options)}. " + f"Valid options are {strict_options}. Please check the documentation for " + "more details on them." + ) + raise ValueError(msg) + + self.calculate_dependent_values() + + # TODO: Check for bidirectional links with efficiency < 1. + # TODO: Warn if any ramp limits are 0. + + # Per component checks + for c in self.components: + # Checks all components + check_for_unknown_buses(self, c, "unknown_buses" in strict) + check_for_unknown_carriers(self, c, "unkown_carriers" in strict) + check_time_series(self, c, "time_series" in strict) + check_static_power_attributes(self, c, "static_power_attrs" in strict) + check_time_series_power_attributes( + self, c, "time_series_power_attrs" in strict + ) + check_nans_for_component_default_attrs( + self, c, "nans_for_component_default_attrs" in strict + ) + # Checks passive_branch_components + check_for_zero_impedances(self, c, "zero_impedances" in strict) + # Checks transformers + check_for_zero_s_nom(c, "zero_s_nom" in strict) + # Checks generators and links + check_assets(self, c, "assets" in strict) + # Checks generators + check_generators(c, "generators" in strict) + + if check_dtypes: + check_dtypes_(c, "dtypes" in strict) + + # Combined checks + check_for_disconnected_buses(self, "disconnected_buses" in strict) + check_investment_periods(self, "investment_periods" in strict) + check_shapes(self, "shapes" in strict) + check_scenarios_sum_to_one(self, "scenarios_sum" in strict) + check_scenario_invariant_attributes(self, "scenario_invariant_attrs" in strict) + check_line_types_consistency(self, "line_types" in strict) + check_stochastic_slack_bus_consistency(self, "slack_bus_consistency" in strict) + + # Optional runtime verification + if options.debug.runtime_verification: + _assert_data_integrity(self) + + def consistency_check_plots(self, strict: Sequence | None = None) -> None: + """Check network for consistency for plotting functions. + + + + Parameters + ---------- + strict : list, optional + If some checks should raise an error instead of logging a warning, pass a list + of strings with the names of the checks to be strict about. If 'all' is passed, + all checks will be strict. The default is no strict checks. + + Raises + ------ + ConsistencyError + If any of the checks fail and strict mode is activated. + + See Also + -------- + [pypsa.Network.consistency_check][], [pypsa.consistency.check_for_unknown_buses][], + [pypsa.consistency.check_for_unknown_carriers][] + + """ + if strict is None: + strict = [] + + strict_options = ["unknown_carriers", "missing_carrier_colors"] + + if "all" in strict: + strict = strict_options + + if not all(s in strict_options for s in strict): + msg = ( + f"Invalid strict option(s) {set(strict) - set(strict_options)}. " + f"Valid options are {strict_options}. Please check the documentation for " + "more details on them." + ) + raise ValueError(msg) + + for c in self.components: + check_for_unknown_carriers(self, c, strict="unknown_carriers" in strict) + check_for_missing_carrier_colors( + self, # type: ignore + strict="missing_carrier_colors" in strict, + ) + + +def check_scenarios_sum_to_one(n: NetworkType, strict: bool = False) -> None: + """Check if scenarios probabilities sum to 1. + + This check verifies that scenario probabilities have not been modified after + initialization to break the constraint that they must sum to 1. + + Activate strict mode in general consistency check by passing `['scenarios_sum']` + to the `strict` argument. + + Parameters + ---------- + n : pypsa.Network + The network to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + """ + if n.has_scenarios: + total_weight = n.scenario_weightings["weight"].sum() + + if not np.isclose(total_weight, 1.0, rtol=1e-10, atol=1e-10): + _log_or_raise( + strict, + "Scenario probabilities must sum to 1.0 (got %.10g). " + "This may indicate scenarios were modified after initialization.", + total_weight, + ) + + +def check_scenario_invariant_attributes(n: NetworkType, strict: bool = False) -> None: + """Check if invariant component attributes are not changed across scenarios. + + There are some component attributes that must remain the same across scenarios. + These attributes define the topology of the network or the mathematical structure. + We raise an error if user attemps to modify them across scenarios. + Any difference in values (including NaN vs non-NaN) will trigger an error. + + Parameters + ---------- + n : pypsa.Network + The network to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + """ + # This test is for stochastic networks only + if not n.has_scenarios: + return + + # Attributes that must be identical across all scenarios + INVARIANT_ATTRS = { + "name", + "bus", + # "control" is excluded - different buses can have different control types across scenarios + # but we ensure consistent slack bus selection separately + "type", + "p_nom_extendable", # changes mathematical problem + "committable", # changes mathematical problem + "sign", + "carrier", + "weight", + "p_nom_opt", # optimization result + "build_year", + "lifetime", + "active", # theoretically can be different, but problematic with "Line" + } + + for component in n.components: + if component.static.index.nlevels < 2: + continue # No scenario dimension + + # Get attributes that exist for this component and are in invariant list + component_invariant_attrs = INVARIANT_ATTRS.intersection( + component.static.columns + ) + + if not component_invariant_attrs: + continue + + # Group by component name (second level of MultiIndex) and check for differences + grouped = component.static.groupby(level=1) # Group by component name + + for attr in component_invariant_attrs: + for comp_name, group in grouped: + # Check if all scenarios have the same value for this attribute + unique_values = group[attr].unique() + + # If there's more than one unique value, it's an error - no exceptions + if len(unique_values) > 1: + scenarios_with_diff = ( + group[group[attr] != group[attr].iloc[0]] + .index.get_level_values(0) + .tolist() + ) + _log_or_raise( + True, + "Component '%s' of type '%s' has attribute '%s' that varies across scenarios. " + "This attribute must be identical across all scenarios. " + "Scenarios with different values: %s. Values: %s", + comp_name, + component.name, + attr, + scenarios_with_diff, + group[attr].to_dict(), + ) + + +def check_line_types_consistency(n: NetworkType, strict: bool = False) -> None: + """Check that line_types are identical across all scenarios. + + In stochastic networks, line_types must be identical across all scenarios + + Parameters + ---------- + n : pypsa.Network + The network to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + """ + if not n.has_scenarios: + return + + # Check line_types consistency across scenarios + if not n.c.line_types.static.empty and len(n.scenarios) > 1: + # Get reference line_types from first scenario + reference_scenario = n.scenarios[0] + reference_line_types = n.c.line_types.static.xs( + reference_scenario, level="scenario" + ) + + # Check each other scenario + for scenario in n.scenarios[1:]: + scenario_line_types = n.c.line_types.static.xs(scenario, level="scenario") + + # Check if DataFrames are equal + if not reference_line_types.equals(scenario_line_types): + _log_or_raise( + strict, + "line_types must be identical across all scenarios. " + "Found differences between scenario '%s' and '%s'. " + "line_types define physical characteristics and cannot vary across scenarios.", + reference_scenario, + scenario, + ) + + +def check_stochastic_slack_bus_consistency( + n: NetworkType, strict: bool = False +) -> None: + """Check that the same bus is chosen as slack across all scenarios in stochastic networks. + + Ensure that the same bus is consistently chosen as the slack bus + to maintain mathematical consistency of the optimization problem. + + Parameters + ---------- + n : pypsa.Network + The network to check. + strict : bool, optional + If True, raise an error instead of logging a warning. + + See Also + -------- + [pypsa.Network.consistency_check][] + + """ + # This test is for stochastic networks only + if not n.has_scenarios: + return + + # Check that each sub-network has the same slack bus across all scenarios + if n.has_scenarios and "control" in n.c.buses.static.columns: + # Extract slack buses for each scenario + slack_buses_by_scenario = {} + + for scenario in n.scenarios: + if n.c.buses.static.index.nlevels > 1: + # MultiIndex case (stochastic network) + scenario_buses = n.c.buses.static.xs(scenario, level="scenario") + slack_buses = scenario_buses[scenario_buses.control == "Slack"] + slack_buses_by_scenario[scenario] = set(slack_buses.index) + else: + # Single scenario case, shouldn't reach here for stochastic networks + slack_buses = n.c.buses.static[n.c.buses.static.control == "Slack"] + slack_buses_by_scenario[scenario] = set(slack_buses.index) + + # Compare slack buses across scenarios + if len(slack_buses_by_scenario) > 1: + scenarios = list(slack_buses_by_scenario.keys()) + reference_slack_buses = slack_buses_by_scenario[scenarios[0]] + + for scenario in scenarios[1:]: + current_slack_buses = slack_buses_by_scenario[scenario] + if reference_slack_buses != current_slack_buses: + _log_or_raise( + strict, + "Different slack buses found across scenarios. " + "This can cause mathematical inconsistency in stochastic optimization. " + "Reference scenario '%s' has slack buses %s, " + "but scenario '%s' has slack buses %s", + scenarios[0], + reference_slack_buses, + scenario, + current_slack_buses, + ) diff --git a/PyPSA/source/pypsa/constants.py b/PyPSA/source/pypsa/constants.py new file mode 100644 index 0000000000000000000000000000000000000000..a47a864cb31c04f238120ded2319a000a154a01c --- /dev/null +++ b/PyPSA/source/pypsa/constants.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Constants.""" + +import re + +DEFAULT_EPSG = 4326 +DEFAULT_TIMESTAMP = "now" +EARTH_RADIUS = 6378137.0 # equitorial radius in meters + +RE_PORTS = re.compile(r"^bus(\d*)$") +# Pattern for filtering bus columns without capture groups +RE_PORTS_FILTER = re.compile(r"^bus\d*$") +# Pattern to get port numbers greater or equal to 2 +RE_PORTS_GE_2 = re.compile(r"^bus((?:[2-9]|[1-9]\d+))$") diff --git a/PyPSA/source/pypsa/data/component_attrs/buses.csv b/PyPSA/source/pypsa/data/component_attrs/buses.csv new file mode 100644 index 0000000000000000000000000000000000000000..9b322f166164a38f2c505c756bc394d561dc7bab --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/buses.csv @@ -0,0 +1,20 @@ +"attribute","type","unit","default","description","status" +"name","string","n/a","n/a","Unique name","Input (required)" +"v_nom","float","kV",1,"Nominal voltage","Input (optional)" +"type","string","n/a","n/a","Placeholder for bus type. Not implemented.","Input (optional)" +"x","float","n/a",0,"Longitude; the Spatial Reference System Identifier (SRID) is set in `n.srid`.","Input (optional)" +"y","float","n/a",0,"Latitude; the Spatial Reference System Identifier (SRID) is set in `n.srid`.","Input (optional)" +"carrier","string","n/a","AC","Carrier, such as ""AC"", ""DC"", ""heat"" or ""gas"".","Input (optional)" +"unit","string","n/a","None","Unit of the bus' carrier if the implicitly assumed unit (""MW"") is inappropriate (e.g. ""t/h""). Only descriptive. Does not influence any PyPSA functions.","Input (optional)" +"location","string","n/a","n/a","Location of the bus. Does not influence the optimisation model but can be used for aggregation with `n.statistics`.","Input (optional)" +"v_mag_pu_set","static or series","per unit",1,"Voltage magnitude set point, per unit of `v_nom`.","Input (optional)" +"v_mag_pu_min","float","per unit",0,"Minimum desired voltage, per unit of `v_nom`. Placeholder attribute not currently used by any functions.","Input (optional)" +"v_mag_pu_max","float","per unit","inf","Maximum desired voltage, per unit of `v_nom`. Placeholder attribute not currently used by any functions.","Input (optional)" +"control","string","n/a","PQ","P,Q,V control strategy for power flow, must be ""PQ"", ""PV"" or ""Slack"". Note that this attribute is an output inherited from the controls of the generators attached to the bus. Do not set by hand.","Output" +"generator",string,"n/a","n/a","Name of slack generator attached to slack bus.","Output" +"sub_network","string","n/a","n/a","Name of connected sub-network to which bus belongs. This attribute is set by `n.determine_network_topology()`. Do not set by hand.","Output" +"p","series","MW",0.,"active power at bus (positive if net generation at bus)","Output" +"q","series","MVar",0.,"reactive power (positive if net generation at bus)","Output" +"v_mag_pu","series","per unit",1.,"Voltage magnitude, per unit of `v_nom`","Output" +"v_ang","series","radians",0.,"Voltage angle","Output" +"marginal_price","series","currency/MWh",0.,"Shadow price from energy balance constraint","Output" diff --git a/PyPSA/source/pypsa/data/component_attrs/carriers.csv b/PyPSA/source/pypsa/data/component_attrs/carriers.csv new file mode 100644 index 0000000000000000000000000000000000000000..349fe6d006e9710209121402b8c5090e2077b78b --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/carriers.csv @@ -0,0 +1,7 @@ +attribute,type,unit,default,description,status +name,string,n/a,n/a,Unique name,Input (required) +co2_emissions,float,tonnes/MWh,0,Emissions in tCO~2~ per MWh of primary energy (e.g. methane has 0.2 tCO~2~/MWh~thermal~).,Input (optional) +color,string,n/a,n/a,Color for plotting (e.g. `matplotlib` [named colors](https://matplotlib.org/stable/gallery/color/named_colors.html) or hexadecimal color codes like "#AB9812"),Input (optional) +nice_name,string,n/a,n/a,Descriptive name for statistics and visualisations (e.g. "Natural Gas" for carrier `gas`),Input (optional) +max_growth,float,MW,inf,Maximum new installed capacity per investment period,Input (optional) +max_relative_growth,float,nan,0,Maximum capacity ratio for new installed capacity per investment period,Input (optional) diff --git a/PyPSA/source/pypsa/data/component_attrs/generators.csv b/PyPSA/source/pypsa/data/component_attrs/generators.csv new file mode 100644 index 0000000000000000000000000000000000000000..92cd511a2849e4338c1e2a4eaea9e3a4b32f25f4 --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/generators.csv @@ -0,0 +1,50 @@ +attribute,type,unit,default,description,status +name,string,n/a,n/a,Unique name,Input (required) +bus,string,n/a,n/a,Name of bus to which generator is attached,Input (required) +control,string,n/a,PQ,"P,Q,V control strategy for power flow, must be ""PQ"", ""PV"" or ""Slack"". Only relevant for ""AC"" and ""DC"" buses.",Input (optional) +type,string,n/a,n/a,Placeholder for generator type. Not implemented.,Input (optional) +p_nom,float,MW,0,Nominal power for limits on `p` in optimization. Ignored if `p_nom_extendable=True`.,Input (optional) +p_nom_mod,float,MW,0,Nominal power of the generator module (e.g. fixed unit size of a nuclear power plant). Introduces integer variables if set.,Input (optional) +p_nom_extendable,boolean,n/a,False,Switch to allow capacity `p_nom` to be extended in optimization.,Input (optional) +p_nom_min,float,MW,0,"If `p_nom` is extendable in optimization, set its minimum value.",Input (optional) +p_nom_max,float,MW,inf,"If `p_nom` is extendable in optimization, set its maximum value (e.g. limited by technical potential).",Input (optional) +p_nom_set,float,MW,n/a,"If `p_nom` is extendable in optimization, set its value.",Input (optional) +p_min_pu,static or series,per unit,0.,"The minimum output for each snapshot per unit of `p_nom` for the optimization (e.g. a minimal dispatch level for conventional power plants). Note that if `committable=False` and `p_min_pu>0`, this represents a must-run condition.",Input (optional) +p_max_pu,static or series,per unit,1,The maximum output for each snapshot per unit of `p_nom` for the optimization (e.g. changing availability of renewable generators due to weather conditions or a de-rating of conventional power plants).,Input (optional) +p_set,static or series,MW,n/a,Active power set point (for optimisation and power flow),Input (optional) +e_sum_min,float,MWh,-inf,The minimum total energy produced during a single optimization horizon.,Input (optional) +e_sum_max,float,MWh,inf,The maximum total energy produced during a single optimization horizon.,Input (optional) +q_set,static or series,MVar,0.,Reactive power set point (for power flow),Input (optional) +sign,float,n/a,1,"Sign denoting the orientation of the dispatch variable (e.g. positive for generation, negative for consumption).",Input (optional) +carrier,string,n/a,n/a,"Prime mover energy carrier (e.g. coal, gas, wind, solar); required for global constraints on primary energy in optimisation",Input (optional) +marginal_cost,static or series,currency/MWh,0.,Marginal cost of production of 1 MWh.,Input (optional) +marginal_cost_quadratic,static or series,currency/MWh,0.,Quadratic marginal cost of production of 1 MWh.,Input (optional) +active,boolean,n/a,True,"Whether to consider the component in optimization or not",Input (optional) +build_year,int,year,0,Build year of the generator.,Input (optional) +lifetime,float,years,inf,Lifetime of the generator.,Input (optional) +capital_cost,float,currency/MW,0,"Fixed period costs of extending `p_nom` by 1 MW, including periodized investment costs and periodic fixed O&M costs (e.g. annuitized investment costs).",Input (optional) +efficiency,static or series,per unit,1,"Ratio output and primary energy carrier input (e.g. 0.4 MWh~elec~/MWh~fuel~). This is required for global constraints on primary energy in optimization.",Input (optional) +committable,boolean,n/a,False,Apply unit commitment constraints. This is only possible with `p_nom_extendable=False`.,Input (optional) +start_up_cost,float,currency,0,Cost to start up the generator. Only used if `committable=True`.,Input (optional) +shut_down_cost,float,currency,0,Cost to shut down the generator. Only used if `committable=True`.,Input (optional) +stand_by_cost,static or series,currency/h,0.,Stand-by cost for running the generator. This cost is incurred whenever the status is 1 (including when the dispatch decision is zero).,Input (optional) +min_up_time,int,snapshots,0,Minimum number of snapshots for status to be 1. Only used if `committable=True`. Does not consider snapshot weightings.,Input (optional) +min_down_time,int,snapshots,0,Minimum number of snapshots for status to be 0. Only used if `committable=True`. Does not consider snapshot weightings.,Input (optional) +up_time_before,int,snapshots,1,Number of snapshots that the generator was online before `n.snapshots` start. Only used if `committable=True` and `min_up_time>0`. Does not consider snapshot weightings.,Input (optional) +down_time_before,int,snapshots,0,Number of snapshots that the generator was offline before `n.snapshots` start. Only used if `committable=True` and `min_down_time>0`. Does not consider snapshot weightings.,Input (optional) +ramp_limit_up,static or series,per unit,NaN,"Maximum active power increase from one snapshot to the next, per unit of the nominal power. Ignored if NaN. Does not consider snapshot weightings.",Input (optional) +ramp_limit_down,static or series,per unit,NaN,"Maximum active power decrease from one snapshot to the next, per unit of the nominal power. Ignored if NaN. Does not consider snapshot weightings.",Input (optional) +ramp_limit_start_up,float,per unit,1,"Maximum active power increase at start up, per unit of the nominal power. Only used if `committable=True`.",Input (optional) +ramp_limit_shut_down,float,per unit,1,"Maximum active power decrease at shut down, per unit of the nominal power. Only used if `committable=True`.",Input (optional) +weight,float,n/a,1,"Weighting of a generator. Only used for network clustering.",Input (optional) +p,series,MW,0.,Active power at bus (positive if net generation),Output +q,series,MVar,0.,Reactive power (positive if net generation),Output +p_nom_opt,float,MW,0.,Optimised nominal capacity.,Output +status,series,n/a,1,"Status in the snapshot (1 is on, 0 is off). Only returned if `committable=True`.",Output +start_up,series,n/a,1.,"Whether the unit was started in the snapshot (1 is yes, 0 is no). Only returned if `committable=True`.",Output +shut_down,series,n/a,1.,"Whether the unit was shut down in the snapshot (1 is yes, 0 is no). Only returned if `committable=True`.",Output +mu_upper,series,currency/MWh,n/a,Shadow price of upper `p_nom` limit,Output +mu_lower,series,currency/MWh,n/a,Shadow price of lower `p_nom` limit,Output +mu_p_set,series,currency/MWh,n/a,Shadow price of fixed power generation `p_set`,Output +mu_ramp_limit_up,series,currency/MWh,n/a,Shadow price of upper ramp up limit,Output +mu_ramp_limit_down,series,currency/MWh,n/a,Shadow price of lower ramp down limit,Output diff --git a/PyPSA/source/pypsa/data/component_attrs/global_constraints.csv b/PyPSA/source/pypsa/data/component_attrs/global_constraints.csv new file mode 100644 index 0000000000000000000000000000000000000000..4eecb0b25adc4c772d2543beb2640b83e17c6f8c --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/global_constraints.csv @@ -0,0 +1,9 @@ +attribute,type,unit,default,description,status +name,string,n/a,n/a,Unique name,Input (required) +type,string,n/a,primary_energy,"Type of constraint (e.g. ""primary energy"", ""tech_capacity_expansion_limit"", ""operational_limit"", ""transmission_volume_expansion_limit"", ""transmission_expansion_cost_limit"")",Input (optional) +investment_period,float,n/a,NaN,"Investment period for which the constraint is applied. If not specified, the constraint is applied to all investment periods together.",Input (optional) +bus,string,n/a,n/a,"Bus to which the global constraint applies. Only relevant for `type=""tech_expansion_limit"". If not specified, the constraint applies to all buses.",Input (optional) +carrier_attribute,string,n/a,co2_emissions,"If the global constraint is connected with a carrier, name the associated carrier attribute. This must appear as a column in `n.carriers`.",Input (optional) +sense,string,n/a,==,"Constraint sense; must be one of <=, == or >=",Input (optional) +constant,float,n/a,0,"Constant for right-hand-side of constraint for optimisation period. For a CO~2~ constraint, this would be tonnes of CO~2~.",Input (optional) +mu,float,currency/constant,0,Shadow price of global constraint,Output diff --git a/PyPSA/source/pypsa/data/component_attrs/line_types.csv b/PyPSA/source/pypsa/data/component_attrs/line_types.csv new file mode 100644 index 0000000000000000000000000000000000000000..a9fef90b30df90ddf3019bc78cadfc408c0e02b8 --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/line_types.csv @@ -0,0 +1,10 @@ +attribute,type,unit,default,description,status +name,string,n/a,n/a,Unique name,Input (required) +f_nom,float,Hz,50.,Nominal frequency,Input (required) +r_per_length,float,Ohm per km,0.,Series resistance per length,Input (required) +x_per_length,float,Ohm per km,0.,Series resistance per length,Input (required) +c_per_length,float,nF per km,0.,Shunt capacitance per length,Input (optional) +i_nom,float,kA,0.,Nominal current,Input (optional) +mounting,string,n/a,ol,"Can be ""ol"" for overhead line or ""cs"" for cable",Input (optional) +cross_section,float,mm2,0.,Wire cross-section,Input (optional) +references,string,n/a,n/a,References for electrical parameters,Input (optional) diff --git a/PyPSA/source/pypsa/data/component_attrs/lines.csv b/PyPSA/source/pypsa/data/component_attrs/lines.csv new file mode 100644 index 0000000000000000000000000000000000000000..d6b97d0fc0d1b974fa783bcd3aabfa2c7de20d63 --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/lines.csv @@ -0,0 +1,40 @@ +attribute,type,unit,default,description,status +name,string,n/a,n/a,Unique name,Input (required) +bus0,string,n/a,n/a,Name of origin bus to which branch is attached.,Input (required) +bus1,string,n/a,n/a,Name of destination bus to which branch is attached.,Input (required) +type,string,n/a,n/a,"Name of line standard type. If this is not an empty string """", the line standard type impedance parameters are multiplied with the `length` and divided/multiplied by `num_parallel` to compute `x`, `r`, etc. This will override any values set in `r`, `x, and `b`. If the string is empty, values manually provided for `r`, `x`, etc. are taken.",Input (optional) +x,float,Ohm,0,"Series reactance, must be non-zero for AC branch for linearised power flow equations. If the line has series inductance $L$ in Henries then $x = 2\\pi f L$ where $f$ is the frequency in Hertz. Series impedance $z = r + jx$ must be non-zero for non-linear power flow calculations. Ignored if `type` defined.",Input (required) +r,float,Ohm,0,"Series resistance, must be non-zero for DC branch for linearised power flow equations. Series impedance $z = r + jx$ must be non-zero for the non-linear power flow. Ignored if `type` defined.",Input (required) +g,float,Siemens,0,Shunt conductivity. Shunt admittance is $y = g + jb$.,Input (optional) +b,float,Siemens,0,Shunt susceptance. If the line has shunt capacitance $C$ in Farads then $b = 2\\pi f C$ where $f$ is the frequency in Hertz. Shunt admittance is $y = g + jb$. Ignored if `type` defined.,Input (optional) +s_nom,float,MVA,0,Limit of apparent power which can pass through branch in either direction. Ignored if `s_nom_extendable=True`.,Input (optional) +s_nom_mod,float,MVA,0,Modular unit size of line expansion of `s_nom` (e.g. fixed rating of added circuit). Introduces integer variables.,Input (optional) +s_nom_extendable,boolean,n/a,False,Switch to allow capacity `s_nom` to be extended in optimisation.,Input (optional) +s_nom_min,float,MVA,0,"If `s_nom_extendable=True`, set the minimum value of `s_nom_opt`.",Input (optional) +s_nom_max,float,MVA,inf,"If `s_nom_extendable=True`, set the maximum value of `s_nom_opt`.",Input (optional) +s_nom_set,float,MVA,n/a,"If `s_nom` is extendable in optimization, set the value of `s_nom_opt`.",Input (optional) +s_max_pu,static or series,per unit,1,"The maximum allowed absolute apparent power flow per unit of `s_nom` for the optimisation (e.g. can set `s_max_pu<1` to approximate $N-1$ contingency factor, or can be time-varying to represent weather-dependent dynamic line rating for overhead lines).",Input (optional) +capital_cost,float,currency/MVA,0,"Fixed period costs of extending `s_nom` by 1 MVA, including periodized investment costs and periodic fixed O&M costs (e.g. annuitized investment costs). Any `length` factor must already be included here.",Input (optional) +active,boolean,n/a,True,"Whether to consider the component in optimisation or not",Input (optional) +build_year,int,year,0,Build year of line.,Input (optional) +lifetime,float,years,inf,Lifetime of line.,Input (optional) +length,float,km,0,"Length of line used when `type` is set. Also useful for calculating `capital_cost`.",Input (optional) +carrier,string,"n/a","","Type of current. ""AC"" is the only valid value for lines.",Input (optional) +terrain_factor,float,per unit,1,"Terrain factor for increasing `length` for `capital_cost` calculation.",Input (optional) +num_parallel,float,n/a,1,"When `type` is set, this is the number of parallel circuits. Can also be fractional. If `type` is empty """" this value is ignored.",Input (optional) +v_ang_min,float,degrees,-inf,Minimum voltage angle difference across the line. Placeholder attribute not currently used.,Input (optional) +v_ang_max,float,degrees,inf,Maximum voltage angle difference across the line. Placeholder attribute not currently used.,Input (optional) +sub_network,string,n/a,n/a,"Name of sub-network to which lines belongs, as calculated by `n.determine_network_topology()`. Do not set by hand.",Output +p0,series,MW,0,Active power at `bus0` (positive if branch is withdrawing power from `bus0`).,Output +q0,series,MVar,0,Reactive power at `bus0` (positive if branch is withdrawing power from `bus0`).,Output +p1,series,MW,0,Active power at `bus1` (positive if branch is withdrawing power from `bus1`).,Output +q1,series,MVar,0,Reactive power at `bus1` (positive if branch is withdrawing power from `bus1`).,Output +x_pu,float,per unit,0,Per unit series reactance calculated by `n.calculate_dependent_values()` from `x` and `n.buses.v_nom`.,Output +r_pu,float,per unit,0,Per unit series resistance calculated by `n.calculate_dependent_values()` from `r` and `n.buses.v_nom`.,Output +g_pu,float,per unit,0,Per unit shunt conductivity calculated by `n.calculate_dependent_values()` from `g` and `n.buses.v_nom`.,Output +b_pu,float,per unit,0,Per unit shunt susceptance calculated by `n.calculate_dependent_values()` from `b` and `n.buses.v_nom`.,Output +x_pu_eff,float,per unit,0,"Effective per unit series reactance for linear power flow, calculated by `n.calculate_dependent_values()` from `x` and `n.buses.v_nom`",Output +r_pu_eff,float,per unit,0,"Effective per unit series resistance for linear power flow, calculated by `n.calculate_dependent_values()` from `r` and `n.buses.v_nom`",Output +s_nom_opt,float,MVA,0,Optimised nominal capacity for apparent power.,Output +mu_lower,series,currency/MVA,0,Shadow price of lower `s_nom` limit $-F \\leq f$. Always non-negative.,Output +mu_upper,series,currency/MVA,0,Shadow price of upper `s_nom` limit $f \\leq F$. Always non-negative.,Output diff --git a/PyPSA/source/pypsa/data/component_attrs/links.csv b/PyPSA/source/pypsa/data/component_attrs/links.csv new file mode 100644 index 0000000000000000000000000000000000000000..536f24c9e0249512f9c39c1d3406899b126b9474 --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/links.csv @@ -0,0 +1,47 @@ +attribute,type,unit,default,description,status +name,string,n/a,n/a,Unique name,Input (required) +bus0,string,n/a,n/a,Name of origin bus to which link is attached.,Input (required) +bus1,string,n/a,n/a,"Name of first destination bus to which link is attached. Further destination buses (`bus2`, `bus3`, etc.) are automatically expanded as needed.",Input (required) +type,string,n/a,n/a,Placeholder for link type. Not implemented.,Input (optional) +carrier,string,n/a,,"Carrier of the link describing its technology (e.g. gas boiler, electrolyser, HVDC link).",Input (optional) +efficiency,static or series,per unit,1,"Efficiency of energy transfer from `bus0` to `bus1`. Can be time-dependent (e.g. to represent temperature-dependent heat pump COP). Further efficiency attributes for further buses (e.g. `bus2`, `bus3`, etc.) are automatically expanded as needed (e.g. `efficiency2`, `efficiency3`, etc.).",Input (optional) +active,boolean,n/a,True,"Whether to consider the component in basic functionality or not",Input (optional) +build_year,int,year,0,Build year,Input (optional) +lifetime,float,years,inf,Lifetime,Input (optional) +p_nom,float,MW,0,Limit of power which can pass through link (in units of `bus0`). Ignored if `p_nom_extendable=True`.,Input (optional) +p_nom_mod,float,MW,0,Unit size of link module (e.g. fixed blocks of 100 MW).,Input (optional) +p_nom_extendable,boolean,n/a,False,Switch to allow capacity `p_nom` to be extended.,Input (optional) +p_nom_min,float,MW,0,"If `p_nom_extendable=True`, set its minimum value.",Input (optional) +p_nom_max,float,MW,inf,"If `p_nom_extendable=True`, set its maximum value.",Input (optional) +p_nom_set,float,MW,n/a,"If `p_nom` is extendable in optimization, set its value.",Input (optional) +p_set,static or series,MW,n/a,The dispatch set point for `p0` of the link (for optimisation and power flow).,Input (optional) +p_min_pu,static or series,per unit of p_nom,0.,Minimal dispatch per unit of `p_nom` for the link. Can also be negative.,Input (optional) +p_max_pu,static or series,per unit of p_nom,1.,Maximal dispatch per unit of `p_nom` for the link. Can also be negative.,Input (optional) +capital_cost,float,currency/MW,0,"Fixed period costs of extending `p_nom` by 1 MW (unit of `bus0`), including periodized investment costs and periodic fixed O&M costs (e.g. annuitized investment costs). Any `length` factor must already be included here.",Input (optional) +marginal_cost,static or series,currency/MWh,0.,Marginal cost of 1 MWh consumption from `bus0` (e.g. including variable operation and maintenance costs of an electrolyser but excluding electricity costs).,Input (optional) +marginal_cost_quadratic,static or series,currency/MWh,0.,Quadratic marginal cost for 1 MWh of consumption from `bus0`.,Input (optional) +stand_by_cost,static or series,currency/h,0.,Stand-by cost for operating the link. This cost is incurred whenever the status is 1 (including when dispatch decision is zero).,Input (optional) +length,float,km,0,"Length of the link. Useful for calculating `capital_cost` for HVDC connections.",Input (optional) +terrain_factor,float,per unit,1,Terrain factor for increasing `capital_cost` calculated from `length`.,Input (optional) +committable,boolean,n/a,False,Apply unit commitment constraints. This is only possible with `p_nom_extendable=False`.,Input (optional) +start_up_cost,float,currency,0,Cost to start up the link. Only used if `committable=True`.,Input (optional) +shut_down_cost,float,currency,0,Cost to shut down the link. Only used if `committable=True`.,Input (optional) +min_up_time,int,snapshots,0,Minimum number of snapshots for status to be 1. Only used if `committable=True`. Does not consider snapshot weightings.,Input (optional) +min_down_time,int,snapshots,0,Minimum number of snapshots for status to be 0. Only used if `committable=True`. Does not consider snapshot weightings.,Input (optional) +up_time_before,int,snapshots,1,Number of snapshots that the link was online before network.snapshots start. Only read if `committable=True` and `min_up_time>0`. Does not consider snapshot weightings.,Input (optional) +down_time_before,int,snapshots,0,Number of snapshots that the link was offline before network.snapshots start. Only read if `committable=True` and `min_down_time>0`. Does not consider snapshot weightings.,Input (optional) +ramp_limit_up,static or series,per unit,NaN,"Maximum increase from one snapshot to the next, per unit of `p_nom`. Ignored if NaN. Does not consider snapshot weightings.",Input (optional) +ramp_limit_down,static or series,per unit,NaN,"Maximum decrease from one snapshot to the next, per unit of `p_nom`. Ignored if NaN. Does not consider snapshot weightings.",Input (optional) +ramp_limit_start_up,float,per unit,1,"Maximum increase at start up, per unit of `p_nom`. Only used if `committable=True`.",Input (optional) +ramp_limit_shut_down,float,per unit,1,"Maximum decrease at shut down, per unit of `p_nom`. Only used if `committable=True`.",Input (optional) +p0,series,MW,0.,Power at `bus0` (positive if link is withdrawing from `bus0`).,Output +p1,series,MW,0.,Power at `bus1` (positive if link is withdrawing from `bus1`).,Output +p_nom_opt,float,MW,0,Optimised nominal capacity.,Output +status,series,n/a,1.,"Status in the snapshot (1 is on, 0 is off). Only returned if `committable=True`.",Output +start_up,series,n/a,1.,"Whether the unit was started in the snapshot (1 is yes, 0 is no). Only returned if `committable=True`.",Output +shut_down,series,n/a,1.,"Whether the unit was shut down in the snapshot (1 is yes, 0 is no). Only returned if `committable=True`.",Output +mu_lower,series,currency/MW,n/a,Shadow price of lower `p_nom` limit $-F \\leq f$. Always non-negative.,Output +mu_upper,series,currency/MW,n/a,Shadow price of upper `p_nom` limit $f \\leq F$. Always non-negative.,Output +mu_p_set,series,currency/MWh,n/a,Shadow price of fixed dispatch `p_set`,Output +mu_ramp_limit_up,series,currency/MWh,n/a,Shadow price of upper ramp up limit,Output +mu_ramp_limit_down,series,currency/MWh,n/a,Shadow price of lower ramp down limit,Output diff --git a/PyPSA/source/pypsa/data/component_attrs/loads.csv b/PyPSA/source/pypsa/data/component_attrs/loads.csv new file mode 100644 index 0000000000000000000000000000000000000000..f69831810357632b1096b83cc4f7d776996fb3ec --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/loads.csv @@ -0,0 +1,11 @@ +attribute,type,unit,default,description,status +name,string,n/a,n/a,Unique name,Input (required) +bus,string,n/a,n/a,Name of bus to which load is attached.,Input (required) +carrier,string,n/a,n/a,Carrier of the load.,Input (optional) +type,string,n/a,n/a,Placeholder for load type. Not implemented.,Input (optional) +p_set,static or series,MW,0,Active power consumption (positive if the load is consuming power).,Input (optional) +q_set,static or series,MVar,0,Reactive power consumption (positive if the load is inductive).,Input (optional) +sign,float,n/a,-1,Sign (opposite sign to generator),Input (optional) +p,series,MW,0,Active power at bus (positive if net load),Output +q,series,MVar,0,Reactive power (positive if net load),Output +active,boolean,n/a,True,Whether to consider the component in optimisation or not.,Input (optional) diff --git a/PyPSA/source/pypsa/data/component_attrs/networks.csv b/PyPSA/source/pypsa/data/component_attrs/networks.csv new file mode 100644 index 0000000000000000000000000000000000000000..c96544816d25b9025eb8866e3de7062b6d706989 --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/networks.csv @@ -0,0 +1,17 @@ +attribute,type,unit,default,description,status +name,string,n/a,n/a,Unique name,Input (required) +snapshots,list or pandas.Index,n/a,"[""now""]","List of snapshots or time steps. All time-dependent series quantities are indexed by `network.snapshots`. To reset the snapshots, call `network.set_snapshots(new)`.",Input (optional) +snapshot_weightings,pandas.DataFrame, hours ,1,"The weightings applied to each snapshot, so that snapshots can represent more than one hour or fractions of one hour. The objective weightings are used to weight snapshots in the objective function. The store weightings determine the state of charge change for stores and storage units. The generator weightings are used when calculating global constraints and energy balances.",Input (optional) +investment_periods,pandas.Index,years,[],"Time periods of investment. Only used for multi investment optimisation. Years have to be integer and increasing (e.g. [2025,2030,2035]). Default is an empty pd.Index([]).",Input (optional) +investment_period_weightings,pandas.DataFrame,n/a,1,Weightings applied to each investment period. Objective weightings are multiplied with all cost coefficients in the objective function of the respective investment period (e.g. used to include social discount rate). Years weighting denote the elapsed time until the subsequent investment period (e.g. used for global constraints CO2 emissions).,Input (optional) +now,any,n/a,"""now""","The current snapshot/time/scenario, relevant e.g. when `network.pf()` is called without a snapshot argument.",Input (optional) +srid,integer,n/a,4326,"Spatial Reference System Indentifier for x,y coordinates of buses. It defaults to standard longitude and latitude.",Input (optional) +crs,CRS-like,n/a,n/a,"Coordinate Reference System for the Shape components. It defaults to standard longitude and latitude, given in srid.",Input (optional) +buses,pandas.DataFrame,n/a,n/a,"All static bus information compiled by PyPSA from inputs. Index is bus names, columns are bus attributes.",Output +buses_t,dictionary of pandas.DataFrames,n/a,n/a,"All time-dependent bus information compiled by PyPSA from inputs. Dictionary keys are time-dependent series attributes, index is network.snapshots, columns are bus names.",Output +lines,pandas.DataFrame,n/a,n/a,"All static line information compiled by PyPSA from inputs. Index is line names, columns are line attributes.",Output +lines_t,dictionary of pandas.DataFrames,n/a,n/a,"All time-dependent line information compiled by PyPSA from inputs. Dictionary keys are time-dependent series attributes, index is network.snapshots, columns are line names.",Output +components,pandas.DataFrame,n/a,n/a,"For each component type (buses, lines, etc.): static component information compiled by PyPSA from inputs. Index is component names, columns are component attributes.",Output +components_t,dictionary of pandas.DataFrames,n/a,n/a,"For each component type (buses, lines, etc.): time-dependent component information compiled by PyPSA from inputs. Dictionary keys are time-dependent series attributes, index is network.snapshots, columns are component names.",Output +branches(),pandas.DataFrame,n/a,n/a,"Dynamically generated concatenation of branch DataFrames: network.lines, network.transformers and network.links. Note that this is a copy and therefore changing entries will NOT update the original.",Output +graph(),networkx. OrderedMultiGraph,n/a,n/a,Graph of network.,Output diff --git a/PyPSA/source/pypsa/data/component_attrs/shapes.csv b/PyPSA/source/pypsa/data/component_attrs/shapes.csv new file mode 100644 index 0000000000000000000000000000000000000000..97f2ac8ff6659f19b561349f58ab7e00324e740c --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/shapes.csv @@ -0,0 +1,6 @@ +"attribute","type","unit","default","description","status" +"name","string","n/a","n/a","Unique name","Input (required)" +"geometry","geometry","n/a","n/a","Geometry","Input (optional)" +"component","string","n/a","n/a","Component type","Input (optional)" +"idx","string","n/a","n/a","Name of the component in index.","Input (optional)" +"type","string","n/a","n/a","Type of geometry","Input (optional)" diff --git a/PyPSA/source/pypsa/data/component_attrs/shunt_impedances.csv b/PyPSA/source/pypsa/data/component_attrs/shunt_impedances.csv new file mode 100644 index 0000000000000000000000000000000000000000..c7259c541b9d76320eca4d66794dd555b6a68e9e --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/shunt_impedances.csv @@ -0,0 +1,11 @@ +attribute,type,unit,default,description,status +name,string,n/a,n/a,Unique name,Input (required) +bus,string,n/a,n/a,Name of bus to which shunt impedance is attached.,Input (required) +g,float,Siemens,0.,Shunt conductivity.,Input (optional) +b,float,Siemens,0.,Shunt susceptance.,Input (optional) +sign,float,n/a,-1.,Power sign (sign convention so that `g>0` withdraws `p` from bus),Input (optional) +p,series,MW,0.,Active power at bus (positive if net load),Output +q,series,MVar,0.,Reactive power (positive if net generation),Output +g_pu,float,per unit,0.,Calculated from `g` and `n.buses.v_nom`,Output +b_pu,float,per unit,0.,Calculated from `b` and `n.buses.v_nom`,Output +active,boolean,n/a,True,Whether to consider the component as active component or not,Input (optional) diff --git a/PyPSA/source/pypsa/data/component_attrs/storage_units.csv b/PyPSA/source/pypsa/data/component_attrs/storage_units.csv new file mode 100644 index 0000000000000000000000000000000000000000..11271b91d6a6279ba9810aa5aa90726312c54d11 --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/storage_units.csv @@ -0,0 +1,48 @@ +attribute,type,unit,default,description,status +name,string,n/a,n/a,Unique name,Input (required) +bus,string,n/a,n/a,Name of bus to which storage unit is attached.,Input (required) +control,string,n/a,PQ,"P,Q,V control strategy for PF, must be ""PQ"", ""PV"" or ""Slack"".",Input (optional) +type,string,n/a,n/a,Placeholder for storage unit type. Not yet implemented.,Input (optional) +p_nom,float,MW,0,Nominal power for limits on `p` in optimisation. Ignored if `p_nom_extendable=True`.,Input (optional) +p_nom_mod,float,MW,0,Nominal power of the storage unit module. Introduces integer variables if set.,Input (optional) +p_nom_extendable,boolean,n/a,False,Switch to allow capacity `p_nom` to be extended in optimisation.,Input (optional) +p_nom_min,float,MW,0,"If `p_nom_extendable=True`, set the minimum value of `p_nom_opt`.",Input (optional) +p_nom_max,float,MW,inf,"If `p_nom_extendable=True`, set the maximum value of `p_nom_opt`.",Input (optional) +p_nom_set,float,MW,n/a,"If `p_nom_extendable=True`, set the value of `p_nom_opt`.",Input (optional) +p_min_pu,static or series,per unit,-1,The minimum output for each snapshot per unit of `p_nom` for the optimisation. Negative sign implies storing mode withdrawing power from bus.,Input (optional) +p_max_pu,static or series,per unit,1,The maximum output for each snapshot per unit of `p_nom` for the optimisation. Positive sign implies discharging mode injecting power into bus.,Input (optional) +p_set,static or series,MW,n/a,Active power set point (for power flow only),Input (optional) +q_set,static or series,MVar,0,Reactive power set point (for power flow only),Input (optional) +p_dispatch_set,static or series,MW,NaN,Active power dispatch set point (for optimisation only),Input (optional) +p_store_set,static or series,MW,NaN,Active power charging set point (for optimisation only),Input (optional) +sign,float,n/a,1,Sign denoting the orientation of the dispatch variable.,Input (optional) +carrier,string,n/a,n/a,"Prime mover energy carrier (e.g. coal, gas, wind, solar); required for global constraints on primary energy in optimisation",Input (optional) +spill_cost,static or series,currency/MWh,0,Cost of spilling 1 MWh,Input (optional) +marginal_cost,static or series,currency/MWh,0,Marginal cost of production (discharge) of 1 MWh.,Input (optional) +marginal_cost_quadratic,static or series,currency/MWh,0,Quadratic marginal cost of production (discharge) of 1 MWh.,Input (optional) +marginal_cost_storage,static or series,currency/MWh/h,0,Marginal cost of energy storage of 1 MWh for one hour.,Input (optional) +capital_cost,float,currency/MW,0,"Fixed period costs of extending `p_nom` by 1 MW, including periodized investment costs and periodic fixed O&M costs (e.g. annuitized investment costs).",Input (optional) +active,boolean,n/a,True,"Whether to consider the component in optimisation or not",Input (optional) +build_year,int,year,0,Build year,Input (optional) +lifetime,float,years,inf,Lifetime,Input (optional) +state_of_charge_initial,float,MWh,0,State of charge before the snapshots in the optimisation.,Input (optional) +state_of_charge_initial_per_period,boolean,n/a,False,"Switch: if True, the state of charge at the beginning of an investment period is set to `state_of_charge_initial`.",Input (optional) +state_of_charge_set,static or series,MWh,NaN,State of charge set points for snapshots in the optimisation.,Input (optional) +cyclic_state_of_charge,boolean,n/a,False,"Switch: if True, then `state_of_charge_initial` is ignored and the initial state of charge is set to the final state of charge for the group of snapshots in the optimisation (`soc[-1] = soc[len(snapshots)-1]`).",Input (optional) +cyclic_state_of_charge_per_period,boolean,n/a,False,"Switch: if True, the cyclic constraints are applied to each investment period separately.",Input (optional) +max_hours,float,hours,1,Maximum state of charge capacity in terms of hours at full output power capacity `p_nom`,Input (optional) +efficiency_store,static or series,per unit,1,Efficiency of storage on the way into the storage.,Input (optional) +efficiency_dispatch,static or series,per unit,1,Efficiency of storage on the way out of the storage.,Input (optional) +standing_loss,static or series,per unit,0,Losses per hour to state of charge.,Input (optional) +inflow,static or series,MW,0,"Inflow to the state of charge (e.g. due to river inflow in hydro reservoir).",Input (optional) +p,series,MW,0,Active power at bus (positive if net generation),Output +p_dispatch,series,MW,0,Active power dispatch at bus,Output +p_store,series,MW,0,Active power charging at bus,Output +q,series,MVar,0,Reactive power (positive if net generation),Output +state_of_charge,series,MWh,NaN,State of charge as calculated by the optimisation.,Output +spill,series,MW,0,Spillage for each snapshot (e.g. hydro-dam letting water flow over the spillway without generating electricity).,Output +p_nom_opt,float,MW,0,Optimised nominal power.,Output +mu_upper,series,currency/MWh,n/a,Shadow price of upper p_nom limit,Output +mu_lower,series,currency/MWh,n/a,Shadow price of lower p_nom limit,Output +mu_state_of_charge_set,series,currency/MWh,n/a,Shadow price of fixed state of charge state_of_charge_set,Output +mu_energy_balance,series,currency/MWh,n/a,Shadow price of storage consistency equations,Output diff --git a/PyPSA/source/pypsa/data/component_attrs/stores.csv b/PyPSA/source/pypsa/data/component_attrs/stores.csv new file mode 100644 index 0000000000000000000000000000000000000000..efb374e3585a238ad253c5978a68f6316aed50e5 --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/stores.csv @@ -0,0 +1,36 @@ +attribute,type,unit,default,description,status +name,string,n/a,n/a,Unique name,Input (required) +bus,string,n/a,n/a,Name of bus to which store is attached.,Input (required) +type,string,n/a,n/a,Placeholder for store type. Not yet implemented.,Input (optional) +carrier,string,n/a,,"Carrier of the store.",Input (optional) +e_nom,float,MWh,0,Nominal energy capacity (i.e. limit on `e`). Ignored if `e_nom_extendable=True`.,Input (optional) +e_nom_mod,float,MWh,0,Nominal energy capacity of the store module. Introduces integer variables if set.,Input (optional) +e_nom_extendable,boolean,n/a,False,Switch to allow capacity `e_nom` to be extended in optimisation.,Input (optional) +e_nom_min,float,MWh,0,"If `e_nom_extendable=True`, set the minimum value of `e_nom_opt`.",Input (optional) +e_nom_max,float,MWh,inf,"If `e_nom_extendable=True`, set the maximum value of `e_nom_opt`.",Input (optional) +e_nom_set,float,MWh,n/a,"If `e_nom_extendable=True`, set the value of `e_nom_opt`.",Input (optional) +e_min_pu,static or series,per unit,0,Minimal value of `e` relative to `e_nom` for the optimisation.,Input (optional) +e_max_pu,static or series,per unit,1,Maximal value of `e` relative to `e_nom` for the optimisation.,Input (optional) +e_initial,float,MWh,0,Energy before the snapshots in the optimisation.,Input (optional) +e_initial_per_period,boolean,n/a,False,"Switch: if True, then at the beginning of each investment period `e` is set to `e_initial`.",Input (optional) +e_cyclic,boolean,n/a,False,"Switch: if True, then `e_initial` is ignored and the initial energy is set to the final energy for the group of snapshots in the optimisation.",Input (optional) +e_cyclic_per_period,boolean,n/a,False,"Switch: if True, then the cyclic constraints are applied to each investment period separately.",Input (optional) +p_set,static or series,MW,n/a,Active power set point (for power flow only),Input (optional) +q_set,static or series,MVar,0,Reactive power set point (for power flow only),Input (optional) +e_set,static or series,MWh,NaN,Fixed energy filling level set point (for optimisation only),Input (optional) +sign,float,n/a,1,Sign denoting orientation of the energy variable (`e`).,Input (optional) +marginal_cost,static or series,currency/MWh,0,Marginal cost applied to both charging and discharging of 1 MWh.,Input (optional) +marginal_cost_quadratic,static or series,currency/MWh,0,Quadratic marginal cost of applied to charging and discharging of 1 MWh.,Input (optional) +marginal_cost_storage,static or series,currency/MWh/h,0,Marginal cost of energy storage of 1 MWh for one hour.,Input (optional) +capital_cost,float,currency/MWh,0,"Fixed period costs of extending `e_nom` by 1 MWh, including periodized investment costs and periodic fixed O&M costs (e.g. annuitized investment costs).",Input (optional) +standing_loss,static or series,per unit,0,Losses per hour to energy level.,Input (optional) +active,boolean,n/a,True,"Whether to consider the component in optimisation or not",Input (optional) +build_year,int,year,0,Build year,Input (optional) +lifetime,float,years,inf,Lifetime,Input (optional) +p,series,MW,0,Power at bus (positive if net supply),Output +q,series,MVar,0,Reactive power (positive if net supply),Output +e,series,MWh,0,Energy level of store as calculated by the optimisation.,Output +e_nom_opt,float,MWh,0,Optimised nominal energy capacity outputed by optimisation.,Output +mu_upper,series,currency/MWh,n/a,Shadow price of upper `e_nom` limit,Output +mu_lower,series,currency/MWh,n/a,Shadow price of lower `e_nom` limit,Output +mu_energy_balance,series,currency/MWh,n/a,Shadow price of storage consistency equations (i.e. water values),Output diff --git a/PyPSA/source/pypsa/data/component_attrs/sub_networks.csv b/PyPSA/source/pypsa/data/component_attrs/sub_networks.csv new file mode 100644 index 0000000000000000000000000000000000000000..890a1fffb451c3d265376776d465a87a439802f8 --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/sub_networks.csv @@ -0,0 +1,4 @@ +attribute,type,unit,default,description,status +name,string,n/a,n/a,Unique name assigned based on order of sub-network in list of sub-networks.,Output +carrier,string,n/a,AC,"Carrier determined from the buses in the sub-network.",Output +slack_bus,string,n/a,n/a,Name of slack bus.,Output diff --git a/PyPSA/source/pypsa/data/component_attrs/transformer_types.csv b/PyPSA/source/pypsa/data/component_attrs/transformer_types.csv new file mode 100644 index 0000000000000000000000000000000000000000..d0474b363a5d11906550ebd9f91bb8471c95ffa6 --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/transformer_types.csv @@ -0,0 +1,17 @@ +attribute,type,unit,default,description,status +name,string,n/a,n/a,Unique name,Input (required) +f_nom,float,Hz,50.,Nominal frequency,Input (required) +s_nom,float,MVA,0.,Rated apparent power,Input (required) +v_nom_0,float,kV,0.,Nominal voltage on high voltage side,Input (required) +v_nom_1,float,kV,0.,Nominal voltage on low voltage side,Input (required) +vsc,float,Percent,0.,Short circuit voltage,Input (required) +vscr,float,Percent,0.,Real part of short circuit voltage,Input (required) +pfe,float,kW,0.,No load (open circuit) iron losses,Input (required) +i0,float,Percent,0.,No load (open circuit) current,Input (required) +phase_shift,float,Degrees,0.,Phase shift angle,Input (required) +tap_side,int,n/a,0,"Defines if tap changer is modelled at the primary 0 side (usually high-voltage) or the secondary 1 side (usually low voltage) (must be 0 or 1, defaults to 0)",Input (required) +tap_neutral,int,n/a,0,"Rated tap position, i.e. position at which the winding ratio corresponds to the ratio of the rated voltages",Input (required) +tap_min,int,n/a,0,Minimum tap position,Input (required) +tap_max,int,n/a,0,Maximum tap position,Input (required) +tap_step,float,Percent,0.,Tap step size in percentage of voltage change,Input (required) +references,string,n/a,n/a,References for electrical parameters,Input (optional) diff --git a/PyPSA/source/pypsa/data/component_attrs/transformers.csv b/PyPSA/source/pypsa/data/component_attrs/transformers.csv new file mode 100644 index 0000000000000000000000000000000000000000..63b099297985b2c0c8b90ace212df7f42f9b32ab --- /dev/null +++ b/PyPSA/source/pypsa/data/component_attrs/transformers.csv @@ -0,0 +1,42 @@ +attribute,type,unit,default,description,status +name,string,n/a,n/a,Unique name,Input (required) +bus0,string,n/a,n/a,Name of origin bus (typically higher voltage) to which transformer is attached.,Input (required) +bus1,string,n/a,n/a,Name of destination bus (typically lower voltage) to which transformer is attached.,Input (required) +type,string,n/a,n/a,"Name of 2-winding transformer standard type. If this is not an empty string """", the transformer type impedance parameters are taken from the standard type along with `num_parallel`. This will override any values set in `r`, `x`, `g`, `b`, `s_nom`, `tap_ratio`, `tap_side` and `phase_shift`. If the string is empty, values manually provided for `r`, `x`, etc. are taken.",Input (optional) +model,string,n/a,t,"Model used for admittance matrix; can be ""t"" or ""pi""; defaults to ""t"" following physics and DIgSILENT PowerFactory", Input (required) +x,float,per unit,0,"Series reactance (per unit, using `s_nom` as base power); must be non-zero for AC branch in linear power flow. Series impedance $z = r + jx$ must be non-zero for the non-linear power flow. Ignored if type defined.",Input (required) +r,float,per unit,0,"Series resistance (per unit, using `s_nom` as base power); must be non-zero for DC branch in linear power flow. Series impedance $z = r + jx$ must be non-zero for the non-linear power flow. Ignored if type defined.",Input (required) +g,float,per unit,0,"Shunt conductivity (per unit, using `s_nom` as base power). Ignored if type defined.",Input (optional) +b,float,per unit,0,"Shunt susceptance (per unit, using `s_nom` as base power). Ignored if type defined.",Input (optional) +s_nom,float,MVA,0,Limit of apparent power which can pass through branch in either direction. Ignored if `s_nom_extendable=True`.,Input (optional) +s_nom_mod,float,MVA,0,Modular unit size of transformer expansion of `s_nom`. Introduces integer variables.,Input (optional) +s_nom_extendable,boolean,n/a,False,Switch to allow capacity `s_nom` to be extended in optimisation.,Input (optional) +s_nom_min,float,MVA,0,"If `s_nom_extendable=True`, set the minimum value of `s_nom_opt`.",Input (optional) +s_nom_max,float,MVA,inf,"If `s_nom_extendable=True`, set the maximum value of `s_nom_opt`.",Input (optional) +s_nom_set,float,MVA,n/a,"If `s_nom` is extendable in optimization, set the value of `s_nom_opt`.",Input (optional) +s_max_pu,static or series,per unit,1,The maximum allowed absolute flow per unit of `s_nom` for the oprimisation.,Input (optional) +capital_cost,float,currency/MVA,0,"Fixed period costs of extending p_nom by 1 MVA, including periodized investment costs and periodic fixed O&M costs (e.g. annuitized investment costs).",Input (optional) +num_parallel,float,n/a,1,"When `type` is set, this is the number of parallel transformers (can also be fractional). If `type` is empty """" this value is ignored.",Input (optional) +tap_ratio,float,per unit,1,Ratio of per unit voltages at each bus for tap changed. Ignored if type defined.,Input (optional) +tap_side,int,n/a,0,"Defines if tap changer is modelled at the primary 0 side (usually high-voltage) or the secondary 1 side (usually low voltage) (must be 0 or 1, defaults to 0). Ignored if type defined.",Input (optional) +tap_position,int,n/a,0,"If the transformer has a `type`, determines position relative to the neutral tap position.",Input (optional) +phase_shift,float,Degrees,0,Voltage phase angle shift. Ignored if type defined.,Input (optional) +active,boolean,n/a,True,"Whether to consider the component in optimisation or not",Input (optional) +build_year,int,year,0,Build year of transformer.,Input (optional) +lifetime,float,years,inf,Lifetime of transformer.,Input (optional) +v_ang_min,float,Degrees,-inf,Minimum voltage angle difference across the transformer.Placeholder attribute not currently used.,Input (optional) +v_ang_max,float,Degrees,inf,Maximum voltage angle difference across the transformer.Placeholder attribute not currently used.,Input (optional) +sub_network,string,n/a,n/a,"Name of connected sub-network to which transformer belongs, as calculated by `n.determine_network_topology()`. Do not set by hand.",Output +p0,series,MW,0,Active power at `bus0` (positive if branch is withdrawing power from `bus0`).,Output +q0,series,MVar,0,Reactive power at `bus0` (positive if branch is withdrawing power from `bus0`).,Output +p1,series,MW,0,Active power at `bus1` (positive if branch is withdrawing power from `bus1`).,Output +q1,series,MVar,0,Reactive power at `bus1` (positive if branch is withdrawing power from `bus1`).,Output +x_pu,float,per unit,0,Per unit series reactance calculated by `n.calculate_dependent_values()` from `x` and `n.buses.v_nom`.,Output +r_pu,float,per unit,0,Per unit series resistance calculated by `n.calculate_dependent_values()` from `r` and `n.buses.v_nom`.,Output +g_pu,float,per unit,0,Per unit shunt conductivity calculated by `n.calculate_dependent_values()` from `g` and `n.buses.v_nom`.,Output +b_pu,float,per unit,0,Per unit shunt susceptance calculated by `n.calculate_dependent_values()` from `b` and `n.buses.v_nom`.,Output +x_pu_eff,float,per unit,0,"Effective per unit series reactance for linear power flow, calculated by `n.calculate_dependent_values()` from `x`, `tap_ratio` for transformers and `n.buses.v_nom`.",Output +r_pu_eff,float,per unit,0,"Effective per unit series resistance for linear power flow, calculated by `n.calculate_dependent_values()` from `x`, `tap_ratio` for transformers and `n.buses.v_nom`.",Output +s_nom_opt,float,MVA,0,Optimised nominal capacity for apparent power.,Output +mu_lower,series,currency/MVA,0,Shadow price of lower `s_nom` limit. Always non-negative.,Output +mu_upper,series,currency/MVA,0,Shadow price of upper `s_nom` limit. Always non-negative.,Output diff --git a/PyPSA/source/pypsa/data/components.csv b/PyPSA/source/pypsa/data/components.csv new file mode 100644 index 0000000000000000000000000000000000000000..d97328c069b24839f6fca5a919f059f8fd442368 --- /dev/null +++ b/PyPSA/source/pypsa/data/components.csv @@ -0,0 +1,16 @@ +component,list_name,description,category +Bus,buses,"Fundamental node where all components attach." +Carrier,carriers,"Energy carriers of buses (e.g. ""AC"" for alternating current, ""DC"" for direct current, ""hydrogen"", or ""heat"") or technologies of other components (e.g. ""wind"", ""gas turbine"", ""electrolyser"", or ""heat pump"")" +Generator,generators,"Power generator for the bus carrier it attaches to.",controllable_one_port +Load,loads,"Loads represent a demand at the bus they are connected to (e.g. PQ power consumer).",controllable_one_port +Link,links,"Links are used for controllable directed flows between two or more buses with arbitrary energy carriers (e.g. HVDC links, converters, conversions between carriers).",controllable_branch +Store,stores,"Stores provide fundamental inter-temporal storage functionality not limited in charging or discharging power.",controllable_one_port +StorageUnit,storage_units,"Storage units enable inter-temporal energy shifting with fixed nominal-energy-to-nominal-power ratio.",controllable_one_port +Line,lines,"Lines include distribution and transmission lines, overhead lines and cables.",passive_branch +LineType,line_types,"Standard line types with per length values for impedances.",standard_type +Transformer,transformers,"2-winding transformer.",passive_branch +TransformerType,transformer_types,"Standard 2-winding transformer types.",standard_type +ShuntImpedance,shunt_impedances,"Shunt with voltage-dependent admittance.",passive_one_port +GlobalConstraint,global_constraints,"Constraints in the optimisation problem that apply to multiple components at once." +Shape,shapes,"Geographical shapes of network components.",shape +SubNetwork,sub_networks,"Subsets of buses and passive branches (i.e. lines and transformers) that are connected (i.e. synchronous areas)." diff --git a/PyPSA/source/pypsa/data/standard_types/line_types.csv b/PyPSA/source/pypsa/data/standard_types/line_types.csv new file mode 100644 index 0000000000000000000000000000000000000000..0097b1b467024c2e28afdf96a29ef5c58ea20d96 --- /dev/null +++ b/PyPSA/source/pypsa/data/standard_types/line_types.csv @@ -0,0 +1,60 @@ +name,f_nom,r_per_length,x_per_length,c_per_length,i_nom,mounting,cross_section,references +149-AL1/24-ST1A 10.0,50.,0.194,0.315,11.25,0.47,ol,149,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +149-AL1/24-ST1A 110.0,50.,0.194,0.41,8.75,0.47,ol,149,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +149-AL1/24-ST1A 20.0,50.,0.194,0.337,10.5,0.47,ol,149,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +15-AL1/3-ST1A 0.4,50.,1.8769,0.35,11,0.105,ol,16,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +184-AL1/30-ST1A 110.0,50.,0.1571,0.4,8.8,0.535,ol,184,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +184-AL1/30-ST1A 20.0,50.,0.1571,0.33,10.75,0.535,ol,184,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +24-AL1/4-ST1A 0.4,50.,1.2012,0.335,11.25,0.14,ol,24,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +243-AL1/39-ST1A 110.0,50.,0.1188,0.39,9,0.645,ol,243,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +243-AL1/39-ST1A 20.0,50.,0.1188,0.32,11,0.645,ol,243,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +305-AL1/39-ST1A 110.0,50.,0.0949,0.38,9.2,0.74,ol,305,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +34-AL1/6-ST1A 10.0,50.,0.8342,0.36,9.7,0.17,ol,34,pandapower +34-AL1/6-ST1A 20.0,50.,0.8342,0.382,9.15,0.17,ol,34,pandapower +48-AL1/8-ST1A 0.4,50.,0.5939,0.3,12.2,0.21,ol,48,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +48-AL1/8-ST1A 10.0,50.,0.5939,0.35,10.1,0.21,ol,48,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +48-AL1/8-ST1A 20.0,50.,0.5939,0.372,9.5,0.21,ol,48,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +48-AL1/8-ST1A 110.0,50.,0.5939,0.46,8.0,0.21,ol,48,pandapower +70-AL1/11-ST1A 10.0,50.,0.4132,0.339,10.4,0.29,ol,70,pandapower +70-AL1/11-ST1A 20.0,50.,0.4132,0.36,9.7,0.29,ol,70,pandapower +70-AL1/11-ST1A 110.0,50.,0.4132,0.45,8.4,0.29,ol,70,pandapower +94-AL1/15-ST1A 0.4,50.,0.306,0.29,13.2,0.35,ol,94,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +94-AL1/15-ST1A 10.0,50.,0.306,0.33,10.75,0.35,ol,94,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +94-AL1/15-ST1A 20.0,50.,0.306,0.35,10,0.35,ol,94,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013) +94-AL1/15-ST1A 110.0,50.,0.306,0.44,8.65,0.35,ol,94,pandapower +122-AL1/20-ST1A 10.0,50.,0.2376,0.323,11.1,0.41,ol,122,pandapower +122-AL1/20-ST1A 20.0,50.,0.2376,0.344,10.3,0.41,ol,122,pandapower +122-AL1/20-ST1A 110.0,50.,0.2376,0.43,8.5,0.41,ol,122,pandapower +490-AL1/64-ST1A 220.0,50.,0.059,0.285,10,0.96,ol,490,pandapower +490-AL1/64-ST1A 380.0,50.,0.059,0.253,11,0.96,ol,490,pandapower +490-AL1/64-ST1A 110.0,50.,0.059,0.37,9.75,0.96,ol,490,pandapower +679-AL1/86-ST1A 110.0,50.,0.042,0.36,9.95,1.15,ol,679,pandapower +679-AL1/86-ST1A 220.0,50.,0.042,0.275,11.7,1.15,ol,679,pandapower +679-AL1/86-ST1A 380.0,50.,0.042,0.25,14.6,1.15,ol,679,pandapower +N2XS(FL)2Y 1x120 RM/35 64/110 kV,50.,0.153,0.166,112,0.366,cs,120,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013);Werth Netzberechnung mit Erzeugungsprofilen +N2XS(FL)2Y 1x185 RM/35 64/110 kV,50.,0.099,0.156,125,0.457,cs,185,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013);Werth Netzberechnung mit Erzeugungsprofilen +N2XS(FL)2Y 1x240 RM/35 64/110 kV,50.,0.075,0.149,135,0.526,cs,240,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013);Werth Netzberechnung mit Erzeugungsprofilen +N2XS(FL)2Y 1x300 RM/35 64/110 kV,50.,0.06,0.144,144,0.588,cs,300,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013);Werth Netzberechnung mit Erzeugungsprofilen +NA2XS2Y 1x95 RM/25 6/10 kV,50.,0.313,0.123,315,0.249,cs,95,pandapower +NA2XS2Y 1x185 RM/25 6/10 kV,50.,0.161,0.11,406,0.358,cs,185,pandapower +NA2XS2Y 1x240 RM/25 6/10 kV,50.,0.122,0.105,456,0.416,cs,240,pandapower +NA2XS2Y 1x150 RM/25 12/20 kV,50.,0.206,0.116,250,0.319,cs,150,pandapower +NA2XS2Y 1x185 RM/25 12/20 kV,50.,0.161,0.117,273,0.362,cs,185,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013);Werth Netzberechnung mit Erzeugungsprofilen +NA2XS2Y 1x240 RM/25 12/20 kV,50.,0.122,0.112,304,0.421,cs,240,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013);Werth Netzberechnung mit Erzeugungsprofilen +NA2XS2Y 1x120 RM/25 12/20 kV,50.,0.253,0.119,230,0.283,cs,120,pandapower +NA2XS2Y 1x70 RM/25 12/20 kV,50.,0.443,0.132,190,0.22,cs,70,pandapower +NA2XS2Y 1x95 RM/25 12/20 kV,50.,0.313,0.132,216,0.252,cs,95,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013);Werth Netzberechnung mit Erzeugungsprofilen +NA2XS2Y 1x150 RM/25 6/10 kV,50.,0.206,0.11,360,0.315,cs,150,pandapower +NA2XS2Y 1x120 RM/25 6/10 kV,50.,0.253,0.113,340,0.28,cs,120,pandapower +NA2XS2Y 1x70 RM/25 6/10 kV,50.,0.443,0.123,280,0.217,cs,70,pandapower +NAYY 4x120 SE,50.,0.225,0.08,264,0.242,cs,120,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner +NAYY 4x150 SE,50.,0.208,0.08,261,0.27,cs,150,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013);Werth Netzberechnung mit Erzeugungsprofilen +NAYY 4x50 SE,50.,0.642,0.083,210,0.142,cs,50,pandapower;Heuck et al. Elektrische Energieversorgung 8. Auflage (2010); Vierweg+Teubner (2013);Werth Netzberechnung mit Erzeugungsprofilen +Al/St 240/40 2-bundle 220.0,50.,0.06,0.301,12.5,1.29,ol,240,"Oeding and Oswald ""Elektrische Kraftwerke und Netze"", 2011, Chapter 9" +Al/St 240/40 3-bundle 300.0,50.,0.04,0.265,13.2,1.935,ol,240,"Oeding and Oswald ""Elektrische Kraftwerke und Netze"", 2011, Chapter 9" +Al/St 240/40 4-bundle 380.0,50.,0.03,0.246,13.8,2.58,ol,240,"Oeding and Oswald ""Elektrische Kraftwerke und Netze"", 2011, Chapter 9" +Al/St 490/64 4-bundle 380.0,50.,0.015,0.16,16.5,3.84,ol,490,Calculation similar to Al/St 240/40 4-bundle 380.0 using the conductor 490-AL1/64-ST1A 380.0 +HTLS 4-bundle 380.0,50.,0.02,0.26,13.9,4.0,ol,240,"HTLS based on line Oudon-Taute line in JAO Static Grid model, compared also to similar lines in Germany" +Al/St 560/50 4-bundle 750.0,50.,0.013,0.276,13.13,4.16,ol,560,"Oeding and Oswald ""Elektrische Kraftwerke und Netze"", 2011, Appendix" +HVDC XLPE 1000,0.,0.0022,0.000001,10000,1.638,,2500,"Regional Group North Sea, “Offshore Transmission Technology,”ENTSO-E AISBL, Brussels, Belgium, Tech. Rep., 2011. P.21-22" +HVDC Oil filled 1400,0.,0.00264,0.000001,10000,2.8,,3000,"Regional Group North Sea, “Offshore Transmission Technology,”ENTSO-E AISBL, Brussels, Belgium, Tech. Rep., 2011. P.19-20" diff --git a/PyPSA/source/pypsa/data/standard_types/transformer_types.csv b/PyPSA/source/pypsa/data/standard_types/transformer_types.csv new file mode 100644 index 0000000000000000000000000000000000000000..433ae311cc955199638089c873392b5980f19665 --- /dev/null +++ b/PyPSA/source/pypsa/data/standard_types/transformer_types.csv @@ -0,0 +1,15 @@ +name,f_nom,s_nom,v_nom_0,v_nom_1,vsc,vscr,pfe,i0,phase_shift,tap_side,tap_neutral,tap_min,tap_max,tap_step,references +0.25 MVA 10/0.4 kV,50.,0.250,10,0.4,4,1.2,0.6,0.24,150,0,0,-2,2,2.5,pandapower;Oswald - Transformatoren - Vorlesungsskript Elektrische Energieversorgung I;Werth Netzberechnung mit Erzeugungsprofilen +0.25 MVA 20/0.4 kV,50.,0.250,20,0.4,6,1.44,0.8,0.32,150,0,0,-2,2,2.5,pandapowe;Oswald - Transformatoren - Vorlesungsskript Elektrische Energieversorgung I;Werth Netzberechnung mit Erzeugungsprofilenr +0.4 MVA 10/0.4 kV,50.,0.400,10,0.4,4,1.325,0.95,0.2375,150,0,0,-2,2,2.5,pandapower;Oswald - Transformatoren - Vorlesungsskript Elektrische Energieversorgung I;Werth Netzberechnung mit Erzeugungsprofilen +0.4 MVA 20/0.4 kV,50.,0.400,20,0.4,6,1.425,1.35,0.3375,150,0,0,-2,2,2.5,pandapower;Oswald - Transformatoren - Vorlesungsskript Elektrische Energieversorgung I;Werth Netzberechnung mit Erzeugungsprofilen +0.63 MVA 10/0.4 kV,50.,0.630,10,0.4,4,1.0794,1.18,0.1873,150,0,0,-2,2,2.5,pandapower;Oswald - Transformatoren - Vorlesungsskript Elektrische Energieversorgung I;Werth Netzberechnung mit Erzeugungsprofilen +0.63 MVA 20/0.4 kV,50.,0.630,20,0.4,6,1.206,1.65,0.2619,150,0,0,-2,2,2.5,pandapower;Oswald - Transformatoren - Vorlesungsskript Elektrische Energieversorgung I;Werth Netzberechnung mit Erzeugungsprofilen +100 MVA 220/110 kV,50.,100.0,220.0,110.0,12.0,0.26,55,0.06,0,0,0,-9,9,1.5,pandapower;Oswald - Transformatoren - Vorlesungsskript Elektrische Energieversorgung I;Werth Netzberechnung mit Erzeugungsprofilen +160 MVA 380/110 kV,50.,160.0,380.0,110.0,12.2,0.25,60,0.06,0,0,0,-9,9,1.5,pandapower;Oswald - Transformatoren - Vorlesungsskript Elektrische Energieversorgung I;Werth Netzberechnung mit Erzeugungsprofilen +25 MVA 110/10 kV,50.,25.,110,10,10.04,0.276,28.51,0.073,150,0,0,-9,9,1.5,pandapower;Oswald - Transformatoren - Vorlesungsskript Elektrische Energieversorgung I;Werth Netzberechnung mit Erzeugungsprofilen +25 MVA 110/20 kV,50.,25.,110.0,20.0,11.2,0.282,29,0.071,150,0,0,-9,9,1.5,pandapower;Oswald - Transformatoren - Vorlesungsskript Elektrische Energieversorgung I;Werth Netzberechnung mit Erzeugungsprofilen +40 MVA 110/10 kV,50.,40.,110,10,10.04,0.295,30.45,0.076,150,0,0,-9,9,1.5,pandapower;Oswald - Transformatoren - Vorlesungsskript Elektrische Energieversorgung I;Werth Netzberechnung mit Erzeugungsprofilen +40 MVA 110/20 kV,50.,40.,110.0,20.0,11.2,0.302,31,0.08,150,0,0,-9,9,1.5,pandapower;Oswald - Transformatoren - Vorlesungsskript Elektrische Energieversorgung I;Werth Netzberechnung mit Erzeugungsprofilen +63 MVA 110/10 kV,50.,63.,110,10,10.04,0.31,31.51,0.078,150,0,0,-9,9,1.5,pandapower;Oswald - Transformatoren - Vorlesungsskript Elektrische Energieversorgung I;Werth Netzberechnung mit Erzeugungsprofilen +63 MVA 110/20 kV,50.,63.,110.0,20.0,11.2,0.322,33,0.086,150,0,0,-9,9,1.5,pandapower;Oswald - Transformatoren - Vorlesungsskript Elektrische Energieversorgung I;Werth Netzberechnung mit Erzeugungsprofilen diff --git a/PyPSA/source/pypsa/data/variables.csv b/PyPSA/source/pypsa/data/variables.csv new file mode 100644 index 0000000000000000000000000000000000000000..c79daa9dc9464fc7cd84bed6a8e1af502f26b5e3 --- /dev/null +++ b/PyPSA/source/pypsa/data/variables.csv @@ -0,0 +1,29 @@ +component,variable,marginal_cost,marginal_cost_quadratic,spill_cost,marginal_cost_storage,nominal,handle_separately +Generator,p,True,True,False,False,False,False +Generator,status,False,False,False,False,False,True +Generator,start_up,False,False,False,False,False,True +Generator,shut_down,False,False,False,False,False,True +Generator,p_nom,False,False,False,False,True,False +Generator,n_mod,False,False,False,False,False,True +Line,s,False,False,False,False,False,False +Line,s_nom,False,False,False,False,True,False +Line,n_mod,False,False,False,False,False,True +Transformer,s,False,False,False,False,False,False +Transformer,s_nom,False,False,False,False,True,False +Transformer,n_mod,False,False,False,False,False,True +Link,p,True,True,False,False,False,False +Link,status,False,False,False,False,False,True +Link,start_up,False,False,False,False,False,True +Link,shut_down,False,False,False,False,False,True +Link,p_nom,False,False,False,False,True,False +Link,n_mod,False,False,False,False,False,True +Store,e,False,False,False,True,False,False +Store,p,True,True,False,False,False,True +Store,e_nom,False,False,False,False,True,False +Store,n_mod,False,False,False,False,False,True +StorageUnit,p_dispatch,True,True,False,False,False,False +StorageUnit,p_store,False,False,False,False,False,False +StorageUnit,state_of_charge,False,False,False,True,False,False +StorageUnit,p_nom,False,False,False,False,True,False +StorageUnit,n_mod,False,False,False,False,False,True +StorageUnit,spill,False,False,True,False,False,True diff --git a/PyPSA/source/pypsa/definitions/components.py b/PyPSA/source/pypsa/definitions/components.py new file mode 100644 index 0000000000000000000000000000000000000000..4a0935af11379de39b9b8ba3616cd211e2f8f2a6 --- /dev/null +++ b/PyPSA/source/pypsa/definitions/components.py @@ -0,0 +1,76 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Definitions for network components.""" + +from __future__ import annotations + +import logging +from dataclasses import dataclass +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import pandas as pd + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True) +class ComponentType: + """Dataclass for network component type. + + Contains all information about a component type, such as its name and defaults + attributes. Two different types are for example 'Generator' and 'Carrier'. + + Attributes + ---------- + name : str + Name of component type, e.g. 'Generator'. + list_name : str + Name of component type in list form, e.g. 'generators'. + description : str + Description of the component type. + category : str + Category of the component type, e.g. 'passive_branch'. + defaults : pd.DataFrame + Default values for the component type. + standard_types : pd.DataFrame | None + Standard types for the component type. + + """ + + name: str + list_name: str + description: str + category: str + defaults: pd.DataFrame + standard_types: pd.DataFrame | None = None + + def __eq__(self, other: object) -> bool: + """Check if two component types are equal. + + Parameters + ---------- + other : Any + The other object to compare to. + + Returns + ------- + bool + + """ + if not isinstance(other, ComponentType): + return NotImplemented + return ( + self.name == other.name + and self.list_name == other.list_name + and self.description == other.description + and str(self.category) == str(other.category) + and self.defaults.equals(other.defaults) + ) + + def __repr__(self) -> str: + """Get representation of component type.""" + # TODO make this actually for the REPL + return f"'{self.name}' Component Type" diff --git a/PyPSA/source/pypsa/definitions/structures.py b/PyPSA/source/pypsa/definitions/structures.py new file mode 100644 index 0000000000000000000000000000000000000000..cb38dbea9cfb35d57c6a511b3f83760e75bff975 --- /dev/null +++ b/PyPSA/source/pypsa/definitions/structures.py @@ -0,0 +1,71 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Descriptors for component attributes.""" + +from __future__ import annotations + +import logging +import re +from copy import deepcopy +from typing import Any + +logger = logging.getLogger(__name__) + + +class Dict(dict): + """Subclass of dict, which allows to use attribute syntax to get and set items. + + Stripped down from addict `https://github.com/mewwts/addict/`. + """ + + def __copy__(self) -> Dict: # e.g. copy.copy() + """Return a shallow copy of the Dict.""" + return Dict(self) + + def __deepcopy__(self, memo: dict[int, Any]) -> Dict: # e.g. copy.deepcopy() + """Return a deep copy of the Dict.""" + return Dict({k: deepcopy(v, memo) for k, v in self.items()}) + + def copy(self) -> Dict: # e.g. d.copy() + """Return a shallow copy of the Dict, preserving the Dict type.""" + return Dict(self) + + def __setattr__(self, name: str, value: Any) -> None: + """Setattr is called when the syntax a.b = 2 is used to set a value.""" + if hasattr(Dict, name): + msg = f"'Dict' object attribute '{name}' is read-only" + raise AttributeError(msg) + self[name] = value + + def __getattr__(self, item: str) -> Any: + """Getattr is called when the syntax a.b is used to get a value.""" + try: + return self.__getitem__(item) + except KeyError as e: + raise AttributeError(e.args[0]) from e + + def __delattr__(self, name: str) -> None: + """Is invoked when del some_addict.b is called.""" + del self[name] + + _re_pattern = re.compile("[a-zA-Z_][a-zA-Z0-9_]*") + + def __dir__(self) -> list[str]: + """Return a list of object attributes. + + This includes key names of any dict entries, filtered to the + subset of valid attribute names (e.g. alphanumeric strings + beginning with a letter or underscore). Also includes + attributes of parent dict class. + """ + dict_keys = [ + m.string + for k in self.keys() + if isinstance(k, str) and (m := self._re_pattern.match(k)) + ] + + obj_attrs = list(dir(Dict)) + + return dict_keys + obj_attrs diff --git a/PyPSA/source/pypsa/deprecations.py b/PyPSA/source/pypsa/deprecations.py new file mode 100644 index 0000000000000000000000000000000000000000..7aeb2af348ddec0435fbab24514374dd6578243a --- /dev/null +++ b/PyPSA/source/pypsa/deprecations.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Deprecated functionality.""" + +COMPONENT_ALIAS_DICT = { + "SubNetwork": "sub_networks", + "Bus": "buses", + "Carrier": "carriers", + "GlobalConstraint": "global_constraints", + "Line": "lines", + "LineType": "line_types", + "Transformer": "transformers", + "TransformerType": "transformer_types", + "Link": "links", + "Load": "loads", + "Generator": "generators", + "StorageUnit": "storage_units", + "Store": "stores", + "ShuntImpedance": "shunt_impedances", + "Shape": "shapes", +} diff --git a/PyPSA/source/pypsa/descriptors.py b/PyPSA/source/pypsa/descriptors.py new file mode 100644 index 0000000000000000000000000000000000000000..0793c0f2ef4f823e6c4821771ef10095787a1309 --- /dev/null +++ b/PyPSA/source/pypsa/descriptors.py @@ -0,0 +1,291 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Descriptors for component attributes.""" + +from __future__ import annotations + +import logging +import warnings +from itertools import product +from typing import TYPE_CHECKING, Any + +import pandas as pd + +from pypsa.common import deprecated_in_next_major +from pypsa.constants import RE_PORTS_GE_2 + +if TYPE_CHECKING: + from collections.abc import Iterable, Sequence + + from pypsa import Network, SubNetwork + from pypsa.type_utils import NetworkType + +logger = logging.getLogger(__name__) + + +@deprecated_in_next_major(details="Use `n.get_switchable_as_dense` instead.") +def get_switchable_as_dense( + n: Network, + component: str, + attr: str, + snapshots: Sequence | None = None, + inds: pd.Index | None = None, +) -> pd.DataFrame: + """Return a Dataframe for a time-varying component attribute . + + Deprecation + ------------ + Use `n.get_switchable_as_dense` instead. + + """ + return n.get_switchable_as_dense(component, attr, snapshots, inds) + + +@deprecated_in_next_major(details="Use `n.get_switchable_as_iter` instead.") +def get_switchable_as_iter( + n: Network, + component: str, + attr: str, + snapshots: Sequence, + inds: pd.Index | None = None, +) -> pd.DataFrame: + """Return an iterator over snapshots for a time-varying component attribute. + + Deprecation + ------------ + Use `n.get_switchable_as_iter` instead. + + """ + return n.get_switchable_as_iter(component, attr, snapshots, inds) + + +# Perhaps this should rather go into components.py +nominal_attrs = { + "Generator": "p_nom", + "Line": "s_nom", + "Transformer": "s_nom", + "Link": "p_nom", + "Store": "e_nom", + "StorageUnit": "p_nom", +} + + +@deprecated_in_next_major(details="Use `n.components[c].extendables` instead.") +def get_extendable_i(n: Network, c: str) -> pd.Index: + """Get the index of extendable elements of a given component.""" + return n.components[c].extendables + + +@deprecated_in_next_major(details="Use `n.components[c].fixed` instead.") +def get_non_extendable_i(n: Network, c: str) -> pd.Index: + """Getter function. + + Get the index of non-extendable elements of a given component. + + Deprecated: Use n.components[c].self.fixed instead. + """ + return n.components[c].fixed + + +@deprecated_in_next_major(details="Use `n.components[c].committables` instead.") +def get_committable_i(n: Network, c: str) -> pd.Index: + """Getter function. + + Get the index of commitable elements of a given component. + + Deprecated: Use n.components[c].get_committable_i() instead. + """ + return n.components[c].committables + + +@deprecated_in_next_major(details="Use `n.components[c].get_active_assets` instead.") +def get_active_assets( + n: Network | SubNetwork, + c: str, + investment_period: int | str | Sequence | None = None, +) -> pd.Series: + """Get active assets. Use `c.get_active_assets`. + + See the [`pypsa.descriptors.components.Component.get_active_assets`][]. + + Parameters + ---------- + n : pypsa.Network + Network instance + c : string + Component name + investment_period : int, str, Sequence + Investment period(s) to check + + Returns + ------- + pd.Series + Boolean mask for active components + + """ + return n.components[c].get_active_assets(investment_period=investment_period) + + +@deprecated_in_next_major(details="Use `n.components[c].get_activity_mask` instead.") +def get_activity_mask( + n: Network, + c: str, + sns: Sequence | None = None, + index: pd.Index | None = None, +) -> pd.DataFrame: + """Get active components mask indexed by snapshots. + + Wrapper around the + [`pypsa.descriptors.components.Componenet.get_active_assets`][] method. + Get's the boolean mask for active components, but indexed by snapshots and + components instead of just components. + + Parameters + ---------- + n : pypsa.Network + Network instance + c : string + Component name + sns : pandas.Index, default None + Set of snapshots for the mask. If None (default) all snapshots are returned. + index : pd.Index, default None + Subset of the component elements. If None (default) all components are returned. + + """ + return n.components[c].get_activity_mask(sns, index) + + +@deprecated_in_next_major(details="Use `n.components[c].get_bounds_pu` instead.") +def get_bounds_pu( + n: Network, + c: str, + sns: Sequence, + index: pd.Index | None = None, + attr: str | None = None, +) -> tuple[pd.DataFrame, pd.DataFrame]: + """Retrieve per unit bounds of a given component. + + Getter function to retrieve the per unit bounds of a given compoent for + given snapshots and possible subset of elements (e.g. non-extendables). + Depending on the attr you can further specify the bounds of the variable + you are looking at, e.g. p_store for storage units. + + Parameters + ---------- + n : pypsa.Network + Network instance. + c : string + Component name, e.g. "Generator", "Line". + attr : string, default None + attribute name for the bounds, e.g. "p", "s", "p_store" + sns : pandas.Index/pandas.DateTimeIndex + Deprecated. + index : pd.Index, default None + Deprecated. + + """ + min_bounds, max_bounds = n.components[c].get_bounds_pu(attr) + sel_kwargs = {} + if sns is not None: + sel_kwargs["snapshot"] = sns + if index is not None: + sel_kwargs["name"] = index + return ( + min_bounds.sel(**sel_kwargs).to_dataframe().unstack(level=0), + max_bounds.sel(**sel_kwargs).to_dataframe().unstack(level=0), + ) + + +def _update_linkports_doc_changes(s: Any, i: int, j: str) -> Any: + """Update components documentation for link ports. + + Multi-linkports require the following changes: + 1. Replaces every occurrence of the substring `j` with `i`. + 2. Make attribute required + + Parameters + ---------- + s : An + String to update. + i : int + Integer to replace `j` with. + j : string + Substring to replace. + + Returns + ------- + Any : Updated string or original value if not a string. + + """ + if not isinstance(s, str) or len(s) == 1: + return s + return s.replace(j, str(i)).replace("required", "optional") + + +def _additional_linkports( + n: NetworkType, where: Iterable[str] | None = None +) -> list[str]: + """Identify additional link ports (bus connections) beyond predefined ones. + + Parameters + ---------- + n : pypsa.Network + Network instance. + where : iterable of strings, default None + Subset of columns to consider. Takes link columns by default. + + Returns + ------- + list of strings + List of additional link ports. E.g. ["2", "3"] for bus2, bus3. + + """ + if where is None: + where = n.c.links.static.columns + return [match.group(1) for col in where if (match := RE_PORTS_GE_2.search(col))] + + +def _update_linkports_component_attrs( + n: NetworkType, where: Iterable[str] | None = None +) -> None: + """Update the Link components attributes to add the additional ports. + + Parameters + ---------- + n : Network + Network instance to which additional ports will be added. + where : Iterable[str] or None, optional + Filters for specific subsets of data by providing an iterable of tags + or identifiers. If None, no filtering is applied and additional link + ports are considered for all connectors. + + """ + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + ports = _additional_linkports(n, where) + ports.sort(reverse=True) + c = "Link" + + for i, attr in product(ports, ["bus", "efficiency", "p"]): + target = f"{attr}{i}" + if target in n.components[c]["defaults"].index: + continue + j = "1" if attr != "efficiency" else "" + base_attr = attr + j + base_attr_index = n.components[c]["defaults"].index.get_loc(base_attr) + n.components[c]["defaults"].index.insert(base_attr_index + 1, target) + n.components[c]["defaults"].loc[target] = ( + n.components[c]["defaults"] + .loc[attr + j] + .apply(_update_linkports_doc_changes, args=("1", i)) + ) + # Also update container for varying attributes + if attr in ["efficiency", "p"] and target not in n.c[c].dynamic: + df = pd.DataFrame( + index=n.snapshots, columns=n.c.links.static.index[:0], dtype=float + ) + n.c[c].dynamic[target] = df + elif attr == "bus" and target not in n.c[c].static.columns: + n.c[c].static[target] = n.components[c]["defaults"].loc[target, "default"] diff --git a/PyPSA/source/pypsa/examples.py b/PyPSA/source/pypsa/examples.py new file mode 100644 index 0000000000000000000000000000000000000000..5b211ddc60816f5132f3b5b2bbb1603648444bd2 --- /dev/null +++ b/PyPSA/source/pypsa/examples.py @@ -0,0 +1,266 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Retrieve PyPSA example networks.""" + +from __future__ import annotations + +import logging +import warnings +from pathlib import Path +from urllib.error import HTTPError, URLError +from urllib.request import urlopen + +from packaging.version import parse as parse_version + +from pypsa.networks import Network +from pypsa.version import __version_base__ + +logger = logging.getLogger(__name__) + + +def _repo_url( + master: bool = False, url: str = "https://github.com/PyPSA/PyPSA/raw/" +) -> str: + if master or parse_version(__version_base__) < parse_version( + "0.35.0" + ): # Feature was added in 0.35.0 + return f"{url}master/" + return f"{url}v{__version_base__}/" + + +def _check_url_availability(url: str) -> bool: + """Check if a URL is available by making a HEAD request.""" + if not url.startswith(("http://", "https://")): + return False + try: + with urlopen(url) as response: # noqa: S310 + return response.status == 200 + except (HTTPError, URLError, OSError): + return False + + +def _retrieve_if_not_local(path: str | Path) -> Network: + if not (Path.cwd() / path).exists(): + path = _repo_url() + str(path) + Path.cwd() + + # Suppress warning which occurs due to numpy version mismatch + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + message="numpy.ndarray size changed, may indicate binary incompatibility", + category=RuntimeWarning, + ) + return Network(path) + + +def ac_dc_meshed() -> Network: + """Load the meshed AC-DC example network. + + + + Returns + ------- + pypsa.Network + AC-DC meshed network. + + Examples + -------- + >>> n = pypsa.examples.ac_dc_meshed() + >>> n + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 9 + - Carrier: 6 + - Generator: 6 + - GlobalConstraint: 1 + - Line: 7 + - Link: 4 + - Load: 6 + Snapshots: 10 + + """ + return _retrieve_if_not_local("examples/networks/ac-dc-meshed/ac-dc-meshed.nc") + + +def storage_hvdc() -> Network: + """Load the storage network example of PyPSA. + + + + Returns + ------- + pypsa.Network + Storage network example network. + + Examples + -------- + >>> n = pypsa.examples.storage_hvdc() + >>> n + PyPSA Network 'Storage-HVDC' + ---------------------------- + Components: + - Bus: 6 + - Carrier: 3 + - Generator: 12 + - GlobalConstraint: 1 + - Line: 6 + - Link: 2 + - Load: 6 + - StorageUnit: 6 + Snapshots: 12 + + """ + return _retrieve_if_not_local("examples/networks/storage-hvdc/storage-hvdc.nc") + + +def scigrid_de() -> Network: + """Load the SciGrid network example of PyPSA. + + | + + Returns + ------- + pypsa.Network + SciGrid network example network. + + Examples + -------- + >>> n = pypsa.examples.scigrid_de() + >>> n + PyPSA Network 'SciGrid-DE' + -------------------------- + Components: + - Bus: 585 + - Carrier: 16 + - Generator: 1423 + - Line: 852 + - Load: 489 + - StorageUnit: 38 + - Transformer: 96 + Snapshots: 24 + + """ + return _retrieve_if_not_local("examples/networks/scigrid-de/scigrid-de.nc") + + +def model_energy() -> Network: + """Load the single-node capacity expansion model in style of model.energy. + + + + Check out the [model.energy website](https://model.energy/) for more information. + + + Returns + ------- + pypsa.Network + Single-node capacity expansion model in style of model.energy. + + Examples + -------- + >>> n = pypsa.examples.model_energy() + >>> n + PyPSA Network 'Model-Energy' + ---------------------------- + Components: + - Bus: 2 + - Carrier: 9 + - Generator: 3 + - Link: 2 + - Load: 1 + - StorageUnit: 1 + - Store: 1 + Snapshots: 2920 + + References + ---------- + [^1]: See https://model.energy/ + + """ + return _retrieve_if_not_local("examples/networks/model-energy/model-energy.nc") + + +def stochastic_network() -> Network: + """Load the stochastic network example. + + | + + Returns + ------- + pypsa.Network + Stochastic network example network. + + Examples + -------- + >>> n = pypsa.examples.stochastic_network() + >>> n + Stochastic PyPSA Network 'Stochastic-Network' + --------------------------------------------- + Components: + - Bus: 3 + - Generator: 12 + - Load: 3 + Snapshots: 2920 + Scenarios: 3 + + """ + n = _retrieve_if_not_local( + "examples/networks/stochastic-network/stochastic-network.nc" + ) + + return n + + +def carbon_management() -> Network: + """Load the carbon management network example of PyPSA. + + + + The Carbon Management Network has 20 days of data on the hybrid case from a + recently published paper on carbon management based on PyPSA-Eur. It is + sector-coupled and currently the most complex example network within PyPSA, + making it ideal for exploring the plotting and statistical functionality. + + References + ---------- + [^1]: Hofmann, F., Tries, C., Neumann, F. et al. H2 and CO2 network strategies for + the European energy system. Nat Energy 10, 715–724 (2025). + https://doi.org/10.1038/s41560-025-01752-6 + + Examples + -------- + >>> n = pypsa.examples.carbon_management() + >>> n + PyPSA Network 'Hybrid Scenario from https://www.nature.com/articles/s41560-025-01752-6' + --------------------------------------------------------------------------------------- + Components: + - Bus: 2164 + - Carrier: 89 + - Generator: 1489 + - GlobalConstraint: 4 + - Line: 157 + - Link: 6830 + - Load: 1357 + - StorageUnit: 106 + - Store: 1263 + Snapshots: 168 + + + """ + primary_url = ( + "https://tubcloud.tu-berlin.de/s/4nsj6XSAbnq8skc/download/carbon-management.nc" + ) + + if _check_url_availability(primary_url): + return Network(primary_url) + else: + msg = ( + "The carbon management example is currently unavailable. Please check " + "your internet connection and make sure you are on the latest version of " + "PyPSA." + ) + raise RuntimeError(msg) diff --git a/PyPSA/source/pypsa/geo.py b/PyPSA/source/pypsa/geo.py new file mode 100644 index 0000000000000000000000000000000000000000..c716b8c07cab90f2c9539999b148bede2776ae6d --- /dev/null +++ b/PyPSA/source/pypsa/geo.py @@ -0,0 +1,235 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Functionality to help with georeferencing and calculate distances/areas.""" + +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +import numpy as np +from numpy.typing import ArrayLike + +from pypsa.constants import DEFAULT_EPSG + +if TYPE_CHECKING: + import cartopy.crs as ccrs + from cartopy.mpl.geoaxes import GeoAxes + from numpy.typing import ArrayLike + + +logger = logging.getLogger(__name__) + + +def haversine_pts(a: ArrayLike, b: ArrayLike) -> np.ndarray: + """Determine crow-flies distance between points in a and b. + + ie. distance[i] = crow-fly-distance between a[i] and b[i] + + Parameters + ---------- + a, b : N x 2 - array of dtype float + Geographical coordinates in longitude, latitude ordering + + Returns + ------- + c : N - array of dtype float + Distance in km + + + Examples + -------- + >>> a = np.array([[10.1, 52.6], [10.8, 52.1]]) + >>> b = np.array([[10.8, 52.1], [-34, 56.]]) + >>> haversine_pts(a, b) + array([ 73.15416698, 2903.73511621]) + + """ + lon0, lat0 = np.deg2rad(np.asarray(a, dtype=float)).T + lon1, lat1 = np.deg2rad(np.asarray(b, dtype=float)).T + + c = ( + np.sin((lat1 - lat0) / 2.0) ** 2 + + np.cos(lat0) * np.cos(lat1) * np.sin((lon0 - lon1) / 2.0) ** 2 + ) + return 6371.000 * 2 * np.arctan2(np.sqrt(c), np.sqrt(1 - c)) + + +def haversine(a: ArrayLike, b: ArrayLike) -> np.ndarray: + """Compute the distance in km between two sets of points in long/lat. + + One dimension of a* should be 2; longitude then latitude. Uses haversine + formula. + + Parameters + ---------- + a : N x 2 - array of dtype float + Coordinates of first point, dimensions (N, 2) + b : array-like of at most 2 dimensions + Coordinates of second point, dimensions (M, 2) + + Returns + ------- + distance_km : array + 2-dimensional array of distances in km between points in a, b + + Examples + -------- + >>> haversine([10.1, 52.6], [[10.8, 52.1], [-34, 56.]]) + array([[ 73.15416698, 2836.6707696 ]]) + + """ + + def ensure_dimensions(a: np.ndarray | ArrayLike) -> np.ndarray: + """Ensure correct shape for haversine calculation. + + Parameters + ---------- + a : array-like + Array to check + + Returns + ------- + array: + N x 2 array + + """ + a = np.asarray(a) + + if a.ndim == 1: + a = a[np.newaxis, :] + + if a.shape[1] != 2: + msg = "Array must have shape (N, 2)" + raise ValueError(msg) + + return a + + a = ensure_dimensions(a) + b = ensure_dimensions(b) + + return haversine_pts(a[np.newaxis, :], b[:, np.newaxis]) + + +def compute_bbox( + x: ArrayLike, y: ArrayLike, margin: float = 0 +) -> tuple[tuple[float, float], tuple[float, float]]: + """Compute bounding box for given x, y coordinates. + + Also adds a margin around the bounding box, if desired. Defaults to 0. + + Parameters + ---------- + x, y : array-like + Arrays of x and y coordinates + margin : float, optional + Margin around the bounding box, by default 0 + + Returns + ------- + tuple + Tuple of two tuples, representing the lower left (x1, y1) and upper right + (x2, y2) corners of the bounding box + + Examples + -------- + >>> x = np.array([1, 2, 3]) + >>> y = np.array([4, 5, 6]) + >>> compute_bbox(x, y) # doctest: +ELLIPSIS + ((np.int64(1), np.int64(4)), (np.int64(3), np.int64(6))) + + >>> # With margin to expand the bounding box + >>> compute_bbox(x, y, margin=0.1) # doctest: +ELLIPSIS + ((..., ...), (..., ...)) + + """ + # set margins + pos = np.asarray((x, y)) + minxy, maxxy = np.nanmin(pos, axis=1), np.nanmax(pos, axis=1) + xy1 = minxy - margin * (maxxy - minxy) + xy2 = maxxy + margin * (maxxy - minxy) + return tuple(xy1), tuple(xy2) + + +def get_projection_from_crs(crs: int | str) -> ccrs.Projection: + """Get cartopy projection from EPSG code or proj4 string. + + If the projection is not found, a warning is issued and the default + PlateCarree projection is returned. + + Parameters + ---------- + crs : int | str + EPSG code or proj4 string + + Returns + ------- + projection : cartopy.crs.Projection + Cartopy projection object + + Examples + -------- + >>> get_projection_from_crs(4326) + + Name: unknown + Axis Info [cartesian]: + - E[east]: Easting (unknown) + - N[north]: Northing (unknown) + - h[up]: Ellipsoidal height (metre) + Area of Use: + - undefined + Coordinate Operation: + - name: unknown + - method: Equidistant Cylindrical + Datum: Unknown based on WGS 84 ellipsoid + - Ellipsoid: WGS 84 + - Prime Meridian: Greenwich + + + """ + import cartopy.crs as ccrs # noqa: PLC0415 + + try: + return ccrs.epsg(crs) + except ValueError: + pass + + if crs != 4326 and not str(crs).endswith("4326"): + logger.warning( + "Could not find projection for '%s'. Falling back to latlong.", crs + ) + + return ccrs.PlateCarree() + + +def get_projected_area_factor( + ax: GeoAxes, original_crs: int | str = DEFAULT_EPSG +) -> float: + """Get scale of current vs original projection in terms of area. + + The default 'original crs' is assumed to be 4326, which translates + to the cartopy default cartopy.crs.PlateCarree() + + Examples + -------- + >>> import cartopy.crs as ccrs + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots(subplot_kw={"projection": ccrs.Mercator()}) + >>> ax.set_extent([-10, 10, 40, 60], crs=ccrs.PlateCarree()) + >>> area_factor = get_projected_area_factor(ax) + >>> area_factor + np.float64(140056.26937534288) + + """ + if not hasattr(ax, "projection"): + return 1 + x1, x2, y1, y2 = ax.get_extent() + pbounds = get_projection_from_crs(original_crs).transform_points( + ax.projection, np.array([x1, x2]), np.array([y1, y2]) + ) + + return np.sqrt( + abs((x2 - x1) * (y2 - y1)) / abs((pbounds[0] - pbounds[1])[:2].prod()) + ) diff --git a/PyPSA/source/pypsa/guards.py b/PyPSA/source/pypsa/guards.py new file mode 100644 index 0000000000000000000000000000000000000000..da213d5d5ca98fcf09575380a7bd958c8593cee7 --- /dev/null +++ b/PyPSA/source/pypsa/guards.py @@ -0,0 +1,189 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Assertion guards for runtime verification of PyPSA. + +Methods of this module should only be called when +pypsa.options.debug.runtime_verification is True. By default and in production, +this is False to avoid overhead. In development and testing, it can be enabled +to catch errors early. +""" + +from __future__ import annotations + +import functools +from typing import TYPE_CHECKING, Any + +import pandas as pd + +if TYPE_CHECKING: + from collections.abc import Callable + +from pypsa.common import UnexpectedError + +if TYPE_CHECKING: + from collections.abc import Callable + + import xarray + + from pypsa import Network + + +def _guard_error_handler(func: Callable) -> Callable: + """Decorate guard functions to handle unexpected errors.""" + + @functools.wraps(func) + def _wrapper(*args: Any, **kwargs: Any) -> None: + try: + return func(*args, **kwargs) + except UnexpectedError: + raise # Re-raise UnexpectedError as is + except Exception as e: + msg = f"Unexpected error in guard function {func.__name__}: {e}. " + raise UnexpectedError(msg) from e + + return _wrapper + + +# Sub guards called by other guards + + +@_guard_error_handler +def _network_components_data_verification(n: Network) -> None: + """Assert that for all components, dynamic attribute columns are subsets of static index. + + Internal guard function - should only be called by other guard functions. + + Parameters + ---------- + n : Network + The PyPSA Network instance to validate. + + Raises + ------ + UnexpectedError + If any dynamic attribute columns are not a subset of the static component index. + + """ + for c in n.components: + if c.static.index.name != "name" if not c.has_scenarios else None: + msg = f"Unexpected static index name for component '{c.name}': {c.static.index.name}" + raise UnexpectedError(msg) + # if not c.static.index.names == ( + # ["name"] if not c.has_scenarios else ["scenario", "name"] + # ): + # msg = f"Unexpected static index names for component '{c.name}': {c.static.index.names}" + # raise UnexpectedError(msg) + # TODO n.sub_networks needs a fix + # if not c.static.columns.name == None: + # msg = f"Unexpected static columns name for component '{c.name}': {c.static.columns.name}" + # raise UnexpectedError(msg) + for attr_name, dynamic_df in c.dynamic.items(): + # if not dynamic_df.index.equals(c.snapshots): + # msg = f"`n.c.dynamic['{attr_name}']` of component '{c.name}' has index that does not match snapshots. Expected: {c.snapshots}, Found: {dynamic_df.index}" + # raise UnexpectedError(msg) + # if not dynamic_df.columns.isin(c.static.index).all(): + # msg = f"`n.c.dynamic['{attr_name}']` of component '{c.name}' has columns that are not in the static index. Found columns: {dynamic_df.columns[~dynamic_df.columns.isin(c.static.index)]}" + # raise UnexpectedError(msg) + if not dynamic_df.empty: + # Check if all dynamic columns exist in static index + if not isinstance(dynamic_df, pd.DataFrame): + msg = ( + f"Dynamic attribute '{attr_name}' of component '{c.name}' " + f"is not a DataFrame. Found type: {type(dynamic_df)}." + ) + raise UnexpectedError(msg) + missing_columns = dynamic_df.columns.difference(c.static.index) + if not missing_columns.empty: + msg = ( + f"Dynamic attribute '{attr_name}' of component '{c.name}' " + f"has columns {list(missing_columns)} that are not in the static index. " + f"Static index: {list(c.static.index)}" + ) + + +@_guard_error_handler +def _network_index_data_verification(n: Network) -> None: + """Assert that network index data is consistent and valid. + + Internal guard function - should only be called by other guard functions. + + Parameters + ---------- + n : Network + The PyPSA Network instance to validate. + + Raises + ------ + UnexpectedError + If any network index data is inconsistent or invalid. + + """ + # Verify snapshots index consistency + if len(n.snapshots) == 0: + msg = "Network snapshots must not be empty." + raise UnexpectedError(msg) + + if n.snapshots.name != "snapshot": + msg = f"Snapshots index must be named 'snapshot', found: {n.snapshots.name}" + raise UnexpectedError(msg) + + # Verify MultiIndex structure for investment periods + if isinstance(n.snapshots, pd.MultiIndex): + if n.snapshots.nlevels != 2: + msg = f"Snapshots MultiIndex must have exactly 2 levels, found: {n.snapshots.nlevels}" + raise UnexpectedError(msg) + + expected_names = ["period", "timestep"] + if list(n.snapshots.names) != expected_names: + msg = f"Snapshots MultiIndex must have names {expected_names}, found: {list(n.snapshots.names)}" + raise UnexpectedError(msg) + + required_weighting_cols = ["objective", "stores", "generators"] + missing_cols = set(required_weighting_cols) - set(n._snapshots_data.columns) + if missing_cols: + msg = f"Snapshot weightings missing required columns: {missing_cols}" + raise UnexpectedError(msg) + + # Verify investment period weightings if periods exist + if n.has_periods: + required_period_cols = ["objective", "years"] + missing_period_cols = set(required_period_cols) - set( + n._investment_periods_data.columns + ) + if missing_period_cols: + msg = f"Investment period weightings missing required columns: {missing_period_cols}" + raise UnexpectedError(msg) + + # Verify scenarios consistency if they exist + if n.has_scenarios: + if n._scenarios_data.index.name != "scenario": + msg = f"Scenarios index must be named 'scenario', found: {n._scenarios_data.index.name}" + raise UnexpectedError(msg) + + if "weight" not in n._scenarios_data.columns: + msg = "Scenarios data must have 'weight' column." + raise UnexpectedError(msg) + + +# Guards to be used in runtime verification + + +@_guard_error_handler +def _assert_xarray_integrity(component: Any, res: xarray.DataArray) -> None: + if component.has_scenarios and list(res.scenario.values) != list( + component.scenarios + ): + msg = f"Scenario order mismatch: {list(res.scenario.values)} != {list(component.scenarios)}" + raise UnexpectedError(msg) + + if list(res.coords["name"].values) != list(component.names): + msg = f"Component order mismatch: {list(res.coords['name'].values)} != {list(component.names)}" + raise UnexpectedError(msg) + + +@_guard_error_handler +def _assert_data_integrity(n: Network) -> None: + _network_components_data_verification(n) + _network_index_data_verification(n) diff --git a/PyPSA/source/pypsa/network/__init__.py b/PyPSA/source/pypsa/network/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d3b7bb45b1994dd2e40d717a637efcc2fa8e6301 --- /dev/null +++ b/PyPSA/source/pypsa/network/__init__.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Package for network specific functionality in PyPSA.""" diff --git a/PyPSA/source/pypsa/network/abstract.py b/PyPSA/source/pypsa/network/abstract.py new file mode 100644 index 0000000000000000000000000000000000000000..1ba73f8d8ab77fac6194821ae97054ce56b96fe2 --- /dev/null +++ b/PyPSA/source/pypsa/network/abstract.py @@ -0,0 +1,116 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Abstract network module. + +Only defines a base class for all Network mixin classes which inherit to +`Network` class. +""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Callable + from typing import Any + + import pandas as pd + + from pypsa.components.store import ComponentsStore + + +class _NetworkABC(ABC): + # Indexing + snapshots: pd.Index | pd.MultiIndex + snapshot_weightings: pd.DataFrame + _snapshots_data: pd.DataFrame + investment_periods: pd.Index + investment_period_weightings: pd.DataFrame + _investment_periods_data: pd.DataFrame + scenarios: pd.Series + scenario_weightings: pd.DataFrame + _scenarios_data: pd.DataFrame + _risk_preference: dict[str, float] | None + static: pd.DataFrame + dynamic: Callable + _import_series_from_df: Callable + add: Callable + crs: Any + standard_type_components: pd.DataFrame + srid: Any + set_snapshots: Callable + remove: Callable + iterate_components: Callable + copy: Callable + _import_components_from_df: Callable + + sub_networks: pd.DataFrame + buses: pd.DataFrame + generators: pd.DataFrame + lines: pd.DataFrame + line_types: pd.DataFrame + links: pd.DataFrame + transformers: pd.DataFrame + stores: pd.DataFrame + shunt_impedances: pd.DataFrame + carriers: pd.DataFrame + get_switchable_as_dense: Callable + get_committable_i: Callable + get_extendable_i: Callable + shapes: pd.DataFrame + component_attrs: pd.DataFrame + global_constraints: pd.DataFrame + calculate_dependent_values: Callable + + passive_branches: pd.DataFrame + + @property + @abstractmethod + def has_scenarios(self) -> bool: + """Read only placeholder.""" + ... + + @property + @abstractmethod + def has_periods(self) -> bool: + """Read only placeholder.""" + ... + + @property + @abstractmethod + def passive_branch_components(self) -> set[str]: + """Read only placeholder.""" + ... + + @property + @abstractmethod + def one_port_components(self) -> set[str]: + """Read only placeholder.""" + ... + + @property + @abstractmethod + def branch_components(self) -> set[str]: + """Read only placeholder.""" + ... + + @property + @abstractmethod + def all_components(self) -> set[str]: + """Read only placeholder.""" + ... + + @property + @abstractmethod + def components(self) -> ComponentsStore: + """Read only placeholder.""" + ... + + @property + @abstractmethod + def c(self) -> ComponentsStore: + """Read only placeholder.""" + ... diff --git a/PyPSA/source/pypsa/network/components.py b/PyPSA/source/pypsa/network/components.py new file mode 100644 index 0000000000000000000000000000000000000000..26760d578eb88a7247b2b9eb299362f6ae457ecb --- /dev/null +++ b/PyPSA/source/pypsa/network/components.py @@ -0,0 +1,933 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Network components module. + +Contains single mixin class which is used to inherit to [pypsa.Networks] class. +Should not be used directly. + +Adds all properties and access methods to the Components of a network. `n.components` +is already defined during the Network initialization and here just the access properties +are set. + +""" +# ruff: noqa: D102 + +from __future__ import annotations + +import logging +import warnings +from typing import TYPE_CHECKING, Any + +from pypsa._options import options +from pypsa.common import deprecated_in_next_major +from pypsa.components.legacy import Component +from pypsa.components.store import ComponentsStore +from pypsa.components.types import ( + component_types_df, +) +from pypsa.components.types import ( + get as get_component_type, +) +from pypsa.definitions.structures import Dict +from pypsa.network.abstract import _NetworkABC + +if TYPE_CHECKING: + import pandas as pd +logger = logging.getLogger(__name__) + +# TODO Change to UserWarning when they are all resolved and raised +# TODO Change types back to class with release. With legacy API type hints +# will be unsupported. + +_STATIC_SETTER_WARNING = ( + "You are overwriting the network components with a new object. This is " + "not supported, since it may lead to unexpected behavior. See #TODO. " + "for more information." +) + +_DYNAMIC_GETTER_WARNING = ( + "With PyPSA 1.0, the API for how to access components data has changed. " + "See #TODO for more information. Use `n.{0}.dynamic` as a " + "drop-in replacement for `n.{0}_t`." +) + +_DYNAMIC_SETTER_WARNING = ( + "With PyPSA 1.0, the API for how to access components data has changed. " + "See #TODO for more information. `n.{0}_t` is deprecated and " + "cannot be set." +) + + +class NetworkComponentsMixin(_NetworkABC): + """Mixin class for network components methods. + + Class inherits to [pypsa.Network][]. All attributes and methods can be used + within any Network instance. + """ + + def __init__(self) -> None: + """Initialize the NetworkComponentsMixin.""" + self._components: ComponentsStore | None = None + + def _read_in_default_standard_types(self) -> None: + """Read in the default standard types from the data folder.""" + for std_type in self.standard_type_components: + self.add( + std_type, + self.components[std_type].ctype.standard_types.index, + **self.components[std_type].ctype.standard_types, + ) + + @property + def components(self) -> ComponentsStore: + """Network components store. + + Access all components of the network via `n.components.`. + + Examples + -------- + >>> n.components # doctest: +ELLIPSIS + PyPSA Components Store + ====================== + - 9 'Bus' Components + - 6 'Carrier' Components + ... + + Access a single component: + >>> n.components.generators + 'Generator' Components + ---------------------- + Attached to PyPSA Network 'AC-DC-Meshed' + Components: 6 + + Which is the same reference when accessing the component directly: + >>> n.generators + bus control ... weight p_nom_opt + name ... + Manchester Wind Manchester Slack ... 1.0 4090.809778 + Manchester Gas Manchester PQ ... 1.0 -0.000000 + Norway Wind Norway PQ ... 1.0 1533.599858 + Norway Gas Norway PQ ... 1.0 -0.000000 + Frankfurt Wind Frankfurt Slack ... 1.0 1667.724420 + Frankfurt Gas Frankfurt PQ ... 1.0 982.034483 + + [6 rows x 38 columns] + >>> n.generators is n.components.generators.static + True + + Returns + ------- + ComponentsStore + + """ + if self._components is None: + components = component_types_df.index.to_list() + + self._components = ComponentsStore() + for c_name in components: + ctype = get_component_type(c_name) + + self._components[ctype.list_name] = Component(ctype=ctype, n=self) + return self._components + + @property + def c(self) -> ComponentsStore: + """Network components store. + + Access all components of the network via `n.c.`. Alias for + [`n.components`][pypsa.Network.components]. + + Returns + ------- + ComponentsStore + + """ + return self.components + + @property + def sub_networks(self) -> Any: + """Access to static data of [pypsa.components.SubNetworks][].""" + return ( + self.c.sub_networks.static + if not options.api.new_components_api + else self.c.sub_networks + ) + + @sub_networks.setter + def sub_networks(self, value: pd.DataFrame) -> None: + if options.api.new_components_api: + raise AttributeError(_STATIC_SETTER_WARNING) + self.c.sub_networks.static = value + + @property + def buses(self) -> Any: + """Access to static data of [pypsa.components.Buses][].""" + return ( + self.c.buses.static if not options.api.new_components_api else self.c.buses + ) + + @buses.setter + def buses(self, value: pd.DataFrame) -> None: + if options.api.new_components_api: + raise AttributeError(_STATIC_SETTER_WARNING) + self.c.buses.static = value + + @property + def carriers(self) -> Any: + """Access to static data of [pypsa.components.Carriers][].""" + return ( + self.c.carriers.static + if not options.api.new_components_api + else self.c.carriers + ) + + @carriers.setter + def carriers(self, value: pd.DataFrame) -> None: + if options.api.new_components_api: + raise AttributeError(_STATIC_SETTER_WARNING) + self.c.carriers.static = value + + @property + def global_constraints(self) -> Any: + """Access to static data of [pypsa.components.GlobalConstraints][].""" + return ( + self.c.global_constraints.static + if not options.api.new_components_api + else self.c.global_constraints + ) + + @global_constraints.setter + def global_constraints(self, value: pd.DataFrame) -> None: + if options.api.new_components_api: + raise AttributeError(_STATIC_SETTER_WARNING) + self.c.global_constraints.static = value + + @property + def lines(self) -> Any: + """Access to static data of [pypsa.components.Lines][].""" + return ( + self.c.lines.static if not options.api.new_components_api else self.c.lines + ) + + @lines.setter + def lines(self, value: pd.DataFrame) -> None: + if options.api.new_components_api: + raise AttributeError(_STATIC_SETTER_WARNING) + self.c.lines.static = value + + @property + def line_types(self) -> Any: + """Access to static data of [pypsa.components.LineTypes][].""" + return ( + self.c.line_types.static + if not options.api.new_components_api + else self.c.line_types + ) + + @line_types.setter + def line_types(self, value: pd.DataFrame) -> None: + if options.api.new_components_api: + raise AttributeError(_STATIC_SETTER_WARNING) + self.c.line_types.static = value + + @property + def transformers(self) -> Any: + """Access to static data of [pypsa.components.Transformers][].""" + return ( + self.c.transformers.static + if not options.api.new_components_api + else self.c.transformers + ) + + @transformers.setter + def transformers(self, value: pd.DataFrame) -> None: + if options.api.new_components_api: + raise AttributeError(_STATIC_SETTER_WARNING) + self.c.transformers.static = value + + @property + def transformer_types(self) -> Any: + """Access to static data of [pypsa.components.TransformerTypes][].""" + return ( + self.c.transformer_types.static + if not options.api.new_components_api + else self.c.transformer_types + ) + + @transformer_types.setter + def transformer_types(self, value: pd.DataFrame) -> None: + if options.api.new_components_api: + raise AttributeError(_STATIC_SETTER_WARNING) + self.c.transformer_types.static = value + + @property + def links(self) -> Any: + """Access to static data of [pypsa.components.Links][].""" + return ( + self.c.links.static if not options.api.new_components_api else self.c.links + ) + + @links.setter + def links(self, value: pd.DataFrame) -> None: + if options.api.new_components_api: + raise AttributeError(_STATIC_SETTER_WARNING) + self.c.links.static = value + + @property + def loads(self) -> Any: + """Access to static data of [pypsa.components.Loads][].""" + return ( + self.c.loads.static if not options.api.new_components_api else self.c.loads + ) + + @loads.setter + def loads(self, value: pd.DataFrame) -> None: + if options.api.new_components_api: + raise AttributeError(_STATIC_SETTER_WARNING) + self.c.loads.static = value + + @property + def generators(self) -> Any: + """Access to static data of [pypsa.components.Generators][].""" + return ( + self.c.generators.static + if not options.api.new_components_api + else self.c.generators + ) + + @generators.setter + def generators(self, value: pd.DataFrame) -> None: + if options.api.new_components_api: + raise AttributeError(_STATIC_SETTER_WARNING) + self.c.generators.static = value + + @property + def storage_units(self) -> Any: + """Access to static data of [pypsa.components.StorageUnits][].""" + return ( + self.c.storage_units.static + if not options.api.new_components_api + else self.c.storage_units + ) + + @storage_units.setter + def storage_units(self, value: pd.DataFrame) -> None: + if options.api.new_components_api: + raise AttributeError(_STATIC_SETTER_WARNING) + self.c.storage_units.static = value + + @property + def stores(self) -> Any: + """Access to static data of [pypsa.components.Stores][].""" + return ( + self.c.stores.static + if not options.api.new_components_api + else self.c.stores + ) + + @stores.setter + def stores(self, value: pd.DataFrame) -> None: + if options.api.new_components_api: + raise AttributeError(_STATIC_SETTER_WARNING) + self.c.stores.static = value + + @property + def shunt_impedances(self) -> Any: + """Access to static data of [pypsa.components.ShuntImpedances][].""" + return ( + self.c.shunt_impedances.static + if not options.api.new_components_api + else self.c.shunt_impedances + ) + + @shunt_impedances.setter + def shunt_impedances(self, value: pd.DataFrame) -> None: + if options.api.new_components_api: + raise AttributeError(_STATIC_SETTER_WARNING) + self.c.shunt_impedances.static = value + + @property + def shapes(self) -> Any: + """Access to static data of [pypsa.components.Shapes][].""" + return ( + self.c.shapes.static + if not options.api.new_components_api + else self.c.shapes + ) + + @shapes.setter + def shapes(self, value: pd.DataFrame) -> None: + if options.api.new_components_api: + raise AttributeError(_STATIC_SETTER_WARNING) + self.c.shapes.static = value + + @property + def sub_networks_t(self) -> Dict: + """Access to dynamic data of [pypsa.components.SubNetworks][].""" + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_GETTER_WARNING.format("sub_networks"), + DeprecationWarning, + stacklevel=2, + ) + return self.c.sub_networks.dynamic + + @sub_networks_t.setter + def sub_networks_t(self, value: Dict) -> None: + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_SETTER_WARNING.format("sub_networks"), + DeprecationWarning, + stacklevel=2, + ) + self.c.sub_networks.dynamic = value + + @property + def buses_t(self) -> Dict: + """Access to dynamic data of [pypsa.components.Buses][].""" + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_GETTER_WARNING.format("buses"), + DeprecationWarning, + stacklevel=2, + ) + return self.c.buses.dynamic + + @buses_t.setter + def buses_t(self, value: Dict) -> None: + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_SETTER_WARNING.format("buses"), + DeprecationWarning, + stacklevel=2, + ) + self.c.buses.dynamic = value + + @property + def carriers_t(self) -> Dict: + """Access to dynamic data of [pypsa.components.Carriers][].""" + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_GETTER_WARNING.format("carriers"), + DeprecationWarning, + stacklevel=2, + ) + return self.c.carriers.dynamic + + @carriers_t.setter + def carriers_t(self, value: Dict) -> None: + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_SETTER_WARNING.format("carriers"), + DeprecationWarning, + stacklevel=2, + ) + self.c.carriers.dynamic = value + + @property + def global_constraints_t(self) -> Dict: + """Access to dynamic data of [pypsa.components.GlobalConstraints][].""" + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_GETTER_WARNING.format("global_constraints"), + DeprecationWarning, + stacklevel=2, + ) + return self.c.global_constraints.dynamic + + @global_constraints_t.setter + def global_constraints_t(self, value: Dict) -> None: + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_SETTER_WARNING.format("global_constraints"), + DeprecationWarning, + stacklevel=2, + ) + self.c.global_constraints.dynamic = value + + @property + def lines_t(self) -> Dict: + """Access to dynamic data of [pypsa.components.Lines][].""" + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_GETTER_WARNING.format("lines"), + DeprecationWarning, + stacklevel=2, + ) + return self.c.lines.dynamic + + @lines_t.setter + def lines_t(self, value: Dict) -> None: + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_SETTER_WARNING.format("lines"), + DeprecationWarning, + stacklevel=2, + ) + self.c.lines.dynamic = value + + @property + def line_types_t(self) -> Dict: + """Access to dynamic data of [pypsa.components.LineTypes][].""" + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_GETTER_WARNING.format("line_types"), + DeprecationWarning, + stacklevel=2, + ) + return self.c.line_types.dynamic + + @line_types_t.setter + def line_types_t(self, value: Dict) -> None: + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_SETTER_WARNING.format("line_types"), + DeprecationWarning, + stacklevel=2, + ) + self.c.line_types.dynamic = value + + @property + def transformers_t(self) -> Dict: + """Access to dynamic data of [pypsa.components.Transformers][].""" + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_GETTER_WARNING.format("transformers"), + DeprecationWarning, + stacklevel=2, + ) + return self.c.transformers.dynamic + + @transformers_t.setter + def transformers_t(self, value: Dict) -> None: + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_SETTER_WARNING.format("transformers"), + DeprecationWarning, + stacklevel=2, + ) + self.c.transformers.dynamic = value + + @property + def transformer_types_t(self) -> Dict: + """Access to dynamic data of [pypsa.components.TransformerTypes][].""" + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_GETTER_WARNING.format("transformer_types"), + DeprecationWarning, + stacklevel=2, + ) + return self.c.transformer_types.dynamic + + @transformer_types_t.setter + def transformer_types_t(self, value: Dict) -> None: + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_SETTER_WARNING.format("transformer_types"), + DeprecationWarning, + stacklevel=2, + ) + self.c.transformer_types.dynamic = value + + @property + def links_t(self) -> Dict: + """Access to dynamic data of [pypsa.components.Links][].""" + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_GETTER_WARNING.format("links"), + DeprecationWarning, + stacklevel=2, + ) + return self.c.links.dynamic + + @links_t.setter + def links_t(self, value: Dict) -> None: + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_SETTER_WARNING.format("links"), + DeprecationWarning, + stacklevel=2, + ) + self.c.links.dynamic = value + + @property + def loads_t(self) -> Dict: + """Access to dynamic data of [pypsa.components.Loads][].""" + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_GETTER_WARNING.format("loads"), + DeprecationWarning, + stacklevel=2, + ) + return self.c.loads.dynamic + + @loads_t.setter + def loads_t(self, value: Dict) -> None: + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_SETTER_WARNING.format("loads"), + DeprecationWarning, + stacklevel=2, + ) + self.c.loads.dynamic = value + + @property + def generators_t(self) -> Dict: + """Access to dynamic data of [pypsa.components.Generators][].""" + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_GETTER_WARNING.format("generators"), + DeprecationWarning, + stacklevel=2, + ) + return self.c.generators.dynamic + + @generators_t.setter + def generators_t(self, value: Dict) -> None: + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_SETTER_WARNING.format("generators"), + DeprecationWarning, + stacklevel=2, + ) + self.c.generators.dynamic = value + + @property + def storage_units_t(self) -> Dict: + """Access to dynamic data of [pypsa.components.StorageUnits][].""" + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_GETTER_WARNING.format("storage_units"), + DeprecationWarning, + stacklevel=2, + ) + return self.c.storage_units.dynamic + + @storage_units_t.setter + def storage_units_t(self, value: Dict) -> None: + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_SETTER_WARNING.format("storage_units"), + DeprecationWarning, + stacklevel=2, + ) + self.c.storage_units.dynamic = value + + @property + def stores_t(self) -> Dict: + """Access to dynamic data of [pypsa.components.Stores][].""" + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_GETTER_WARNING.format("stores"), + DeprecationWarning, + stacklevel=2, + ) + return self.c.stores.dynamic + + @stores_t.setter + def stores_t(self, value: Dict) -> None: + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_SETTER_WARNING.format("stores"), + DeprecationWarning, + stacklevel=2, + ) + self.c.stores.dynamic = value + + @property + def shunt_impedances_t(self) -> Dict: + """Access to dynamic data of [pypsa.components.ShuntImpedances][].""" + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_GETTER_WARNING.format("shunt_impedances"), + DeprecationWarning, + stacklevel=2, + ) + return self.c.shunt_impedances.dynamic + + @shunt_impedances_t.setter + def shunt_impedances_t(self, value: Dict) -> None: + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_SETTER_WARNING.format("shunt_impedances"), + DeprecationWarning, + stacklevel=2, + ) + self.c.shunt_impedances.dynamic = value + + @property + def shapes_t(self) -> Dict: + """Access to dynamic data of [pypsa.components.Shapes][].""" + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_GETTER_WARNING.format("shapes"), + DeprecationWarning, + stacklevel=2, + ) + return self.c.shapes.dynamic + + @shapes_t.setter + def shapes_t(self, value: Dict) -> None: + if options.api.new_components_api: + warnings.warn( + _DYNAMIC_SETTER_WARNING.format("shapes"), + DeprecationWarning, + stacklevel=2, + ) + self.c.shapes.dynamic = value + + @property + def controllable_branch_components(self) -> set[str]: + """Controllable branch components of the network as set of strings. + + Examples + -------- + >>> sorted(n.controllable_branch_components) + ['Link'] + + """ + return {"Link"} + + @property + def controllable_one_port_components(self) -> set[str]: + """Controllable one port components of the network as set of strings. + + Examples + -------- + >>> sorted(n.controllable_one_port_components) + ['Generator', 'Load', 'StorageUnit', 'Store'] + + """ + return {"StorageUnit", "Store", "Generator", "Load"} + + @property + def passive_branch_components(self) -> set[str]: + """Passive branch components of the network as set of strings. + + Examples + -------- + >>> sorted(n.passive_branch_components) + ['Line', 'Transformer'] + + """ + return {"Transformer", "Line"} + + @property + def passive_one_port_components(self) -> set[str]: + """Passive one port components of the network as set of strings. + + Examples + -------- + >>> sorted(n.passive_one_port_components) + ['ShuntImpedance'] + + """ + return {"ShuntImpedance"} + + @property + def standard_type_components(self) -> set[str]: + """Standard type components of the network as set of strings. + + Examples + -------- + >>> sorted(n.standard_type_components) + ['LineType', 'TransformerType'] + + """ + return {"LineType", "TransformerType"} + + @property + def one_port_components(self) -> set[str]: + """One port components of the network as set of strings. + + Examples + -------- + >>> sorted(n.one_port_components) + ['Generator', 'Load', 'ShuntImpedance', 'StorageUnit', 'Store'] + + """ + return self.passive_one_port_components | self.controllable_one_port_components + + @property + def branch_components(self) -> set[str]: + """Branch components of the network as set of strings. + + Examples + -------- + >>> sorted(n.branch_components) + ['Line', 'Link', 'Transformer'] + + """ + return self.passive_branch_components | self.controllable_branch_components + + @property + def all_components(self) -> set[str]: + """All components of the network as set of strings. + + Examples + -------- + >>> sorted(n.all_components) + ['Bus', 'Carrier', 'Generator', 'GlobalConstraint', 'Line', 'LineType', 'Link', 'Load', 'Shape', 'ShuntImpedance', 'StorageUnit', 'Store', 'SubNetwork', 'Transformer', 'TransformerType'] + + """ + return { + "Carrier", + "Line", + "Transformer", + "Shape", + "Generator", + "StorageUnit", + "Store", + "ShuntImpedance", + "Link", + "GlobalConstraint", + "SubNetwork", + "TransformerType", + "LineType", + "Bus", + "Load", + } + + @property + def _index_names(self) -> list[str]: + """Compatibility property for NetworkCollection object. + + Returns + ------- + list of str + Empty list, since the Network class does not have any index names. + + """ + return [] + + @property + @deprecated_in_next_major( + details="Use `self.components..defaults` instead.", + ) + def component_attrs(self) -> pd.DataFrame: + """Component attributes. + + !!! warning "Deprecated in " + Use the [Components Class][pypsa.Components] to access components attributes. + As a drop in replacement you can use either + [`n.components[].defaults`][pypsa.Components.defaults] or + `n.components..defaults`. You can also use the alias + [`n.c`][pypsa.Network.c] for [`n.components`][pypsa.Network.components]. + + Parameters + ---------- + component_name : string + + Returns + ------- + pandas.DataFrame + Component attributes informations. + + """ + return Dict({value.name: value.defaults for value in self.components}) + + @deprecated_in_next_major( + details="Use `self.components[].static` instead." + ) + def df(self, component_name: str) -> pd.DataFrame: + """Alias for [`n.static`][pypsa.Network.static]. + + !!! warning "Deprecated in " + Use the [Components Class][pypsa.Components] to access components attributes. + As a drop in replacement you can use either + [`n.components[].static`][pypsa.Components.static] or + `n.components..static`. You can also use the alias + [`n.c`][pypsa.Network.c] for [`n.components`][pypsa.Network.components]. + + Parameters + ---------- + component_name : string + Name of the component. + + Returns + ------- + pandas.DataFrame + Static data of the component. + + """ + return self.components[component_name].static + + @deprecated_in_next_major( + details="Use `self.components..static` instead." + ) + def static(self, component_name: str) -> pd.DataFrame: + """Return the DataFrame of static components for component_name. + + !!! warning "Deprecated in " + Use the [Components Class][pypsa.Components] to access components attributes. + As a drop in replacement you can use either + [`n.components[].static`][pypsa.Components.static] or + `n.components..static`. You can also use the alias + [`n.c`][pypsa.Network.c] for [`n.components`][pypsa.Network.components]. + + Parameters + ---------- + component_name : string + Name of the component. + + Returns + ------- + pandas.DataFrame + Static data of the component. + + """ + return self.components[component_name].static + + @deprecated_in_next_major( + details="Use `self.components..dynamic` instead.", + ) + def pnl(self, component_name: str) -> Dict: + """Alias for [`n.dynamic`][pypsa.Network.dynamic]. + + !!! warning "Deprecated in " + Use the [Components Class][pypsa.Components] to access components attributes. + As a drop in replacement you can use either + [`n.components[].dynamic`][pypsa.Components.dynamic] or + `n.components..dynamic`. You can also use the alias + [`n.c`][pypsa.Network.c] for [`n.components`][pypsa.Network.components]. + + Parameters + ---------- + component_name : string + Name of the component. + + Returns + ------- + dict of pandas.DataFrame + Dynamic data of the component. + + """ + return self.components[component_name].dynamic + + @deprecated_in_next_major( + details="Use `self.components..dynamic` instead.", + ) + def dynamic(self, component_name: str) -> Dict: + """Return the dictionary of DataFrames of varying components. + + !!! warning "Deprecated in " + Use the [Components Class][pypsa.Components] to access components attributes. + As a drop in replacement you can use either + [`n.components[].dynamic`][pypsa.Components.dynamic] or + `n.components..dynamic`. You can also use the alias + [`n.c`][pypsa.Network.c] for [`n.components`][pypsa.Network.components]. + + Parameters + ---------- + component_name : string + Name of the component. + + Returns + ------- + dict of pandas.DataFrame + Dynamic data of the component. + + """ + return self.components[component_name].dynamic diff --git a/PyPSA/source/pypsa/network/descriptors.py b/PyPSA/source/pypsa/network/descriptors.py new file mode 100644 index 0000000000000000000000000000000000000000..44c14002bf4f2e92f5eef535dbe63bbf14a1102f --- /dev/null +++ b/PyPSA/source/pypsa/network/descriptors.py @@ -0,0 +1,255 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Network descriptors module. + +Contains single mixin class which is used to inherit to [pypsa.Networks] class. +Should not be used directly. + +Descriptor functions only describe data and do not modify it. + +""" + +from __future__ import annotations + +import logging +from itertools import repeat +from typing import TYPE_CHECKING + +import pandas as pd + +from pypsa.common import deprecated_in_next_major +from pypsa.components.common import as_components +from pypsa.network.abstract import _NetworkABC + +if TYPE_CHECKING: + from collections.abc import Sequence + + +logger = logging.getLogger(__name__) + + +class NetworkDescriptorsMixin(_NetworkABC): + """Mixin class for network descriptor methods. + + Class inherits to [pypsa.Network][]. All attributes and methods can be used + within any Network instance. + """ + + @deprecated_in_next_major( + details="Use `n.components[c].extendables` instead.", + ) + def get_extendable_i(self, c: str) -> pd.Index: + """Getter function. + + Get the index of extendable elements of a given component. + + Deprecated: Use n.components[c].get_extendable_i() instead. + """ + return self.components[c].extendables + + @deprecated_in_next_major(details="Use `n.components[c].fixed` instead.") + def get_non_extendable_i(self, c: str) -> pd.Index: + """Getter function. + + Get the index of non-extendable elements of a given component. + + Deprecated: Use n.components[c].self.fixed instead. + """ + return self.components[c].fixed + + @deprecated_in_next_major(details="Use `n.components[c].committables` instead.") + def get_committable_i(self, c: str) -> pd.Index: + """Getter function. + + Get the index of commitable elements of a given component. + + Deprecated: Use n.components[c].get_committable_i() instead. + """ + return self.components[c].committables + + @deprecated_in_next_major( + details="Use `n.components[c].get_active_assets` instead." + ) + def get_active_assets( + self, + c: str, + investment_period: int | str | Sequence | None = None, + ) -> pd.Series: + """Get active components mask of component type in investment period(s). + + See the [`pypsa.components.descriptors.ComponentsDescriptorsMixin.get_active_assets`][]. + + Parameters + ---------- + c : string + Component name + investment_period : int, str, Sequence + Investment period(s) to check + + Returns + ------- + pd.Series + Boolean mask for active components + + """ + return self.components[c].get_active_assets(investment_period=investment_period) + + def get_switchable_as_dense( + self, + component: str, + attr: str, + snapshots: Sequence | None = None, + inds: pd.Index | None = None, + ) -> pd.DataFrame: + """Return a Dataframe for a time-varying component attribute . + + Values for all non-time-varying components are filled in with the default + values for the attribute. + + Parameters + ---------- + component : string + Component object name, e.g. 'Generator' or 'Link' + attr : string + Attribute name + snapshots : pandas.Index + Restrict to these snapshots rather than n.snapshots. + inds : pandas.Index + Restrict to these components rather than n.components.index + + Returns + ------- + pandas.DataFrame + + Examples + -------- + >>> n.get_switchable_as_dense('Generator', 'p_max_pu', n.snapshots[:2]) # doctest: +SKIP + Generator Manchester Wind Manchester Gas Norway Wind Norway Gas Frankfurt Wind Frankfurt Gas + snapshot + 2015-01-01 00:00:00 0.930020 1.0 0.974583 1.0 0.559078 1.0 + 2015-01-01 01:00:00 0.485748 1.0 0.481290 1.0 0.752910 1.0 + + """ + return as_components(self, component)._as_dynamic(attr, snapshots, inds) + + def get_switchable_as_iter( + self, + component: str, + attr: str, + snapshots: Sequence, + inds: pd.Index | None = None, + ) -> pd.DataFrame: + """Return an iterator over snapshots for a time-varying component attribute. + + Values for all non-time-varying components are filled in with the default + values for the attribute. + + Parameters + ---------- + component : string + Component object name, e.g. 'Generator' or 'Link' + attr : string + Attribute name + snapshots : pandas.Index + Restrict to these snapshots rather than n.snapshots. + inds : pandas.Index + Restrict to these items rather than all of n.{generators, ..}.index + + Returns + ------- + pandas.DataFrame + + Examples + -------- + >>> gen = n.get_switchable_as_iter('Generator', 'p_max_pu', n.snapshots[:2]) + >>> next(gen) # doctest: +ELLIPSIS + name + Manchester Wind 0.930020 + Manchester Gas 1.000000 + Norway Wind 0.974583 + Norway Gas 1.000000 + Frankfurt Wind 0.559078 + Frankfurt Gas 1.000000 + dtype: float64 + + """ + static = self.c[component].static + dynamic = self.c[component].dynamic + + index = static.index + varying_i = dynamic[attr].columns + fixed_i = static.index.difference(varying_i) + + if inds is not None: + inds = pd.Index(inds) + index = inds.intersection(index) + varying_i = inds.intersection(varying_i) + fixed_i = inds.intersection(fixed_i) + + # Short-circuit only fixed + if len(varying_i) == 0: + return repeat(static.loc[fixed_i, attr], len(snapshots)) + + def is_same_indices(i1: pd.Index, i2: pd.Index) -> bool: + return len(i1) == len(i2) and (i1 == i2).all() + + if is_same_indices(fixed_i.append(varying_i), index): + + def reindex_maybe(s: pd.Series | pd.DataFrame) -> pd.Series | pd.DataFrame: + return s + + else: + + def reindex_maybe(s: pd.Series | pd.DataFrame) -> pd.Series | pd.DataFrame: + return s.reindex(index) + + return ( + reindex_maybe( + pd.concat( + [static.loc[fixed_i, attr], dynamic[attr].loc[sn, varying_i]], + axis=0, + ) + ) + for sn in snapshots + ) + + def bus_carrier_unit(self, bus_carrier: str | Sequence[str] | None) -> str: + """Determine the unit associated with a specific bus carrier in the network. + + Parameters + ---------- + bus_carrier : str | Sequence[str] | None + The carrier type of the bus to query. + + Returns + ------- + str: + The unit associated with the specified bus carrier. If no bus carrier is + provided, returns `"carrier dependent"`. + + Raises + ------ + ValueError + If the specified bus carrier is not found in the network or if multiple + units are found for the specified bus carrier. + + """ + if bus_carrier is None: + return "carrier dependent" + + if isinstance(bus_carrier, str): + bus_carrier = [bus_carrier] + + not_included = set(bus_carrier) - set(self.c.buses.static.carrier.unique()) + if not_included: + msg = f"Bus carriers {not_included} not in network" + raise ValueError(msg) + unit = self.c.buses.static[ + self.c.buses.static.carrier.isin(bus_carrier) + ].unit.unique() + if len(unit) > 1: + logger.warning("Multiple units found for carrier %s: %s", bus_carrier, unit) + return "carrier dependent" + return unit.item() diff --git a/PyPSA/source/pypsa/network/graph.py b/PyPSA/source/pypsa/network/graph.py new file mode 100644 index 0000000000000000000000000000000000000000..dd9ecd1797ede3f06faa4ac3c9e223df60a1988b --- /dev/null +++ b/PyPSA/source/pypsa/network/graph.py @@ -0,0 +1,297 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Graph helper functions, which are attached to network and sub_network.""" + +from __future__ import annotations + +import warnings +from collections import OrderedDict +from typing import TYPE_CHECKING, Any + +import networkx as nx +import numpy as np +import pandas as pd +import scipy as sp + +if TYPE_CHECKING: + from collections.abc import Collection, Iterable + + +class OrderedGraph(nx.MultiGraph): + """Ordered graph.""" + + node_dict_factory = OrderedDict + adjlist_dict_factory = OrderedDict + + +class NetworkGraphMixin: + """Mixin class for network graph methods. + + Class inherits to [pypsa.Network][]/[pypsa.SubNetwork][]. All attributes and + methods can be used within any Network/SubNetwork instance. + + """ + + c: Any + components: Any + iterate_components: Any + passive_branches: pd.DataFrame + has_scenarios: Any + scenarios: pd.DataFrame + + def graph( + self, + branch_components: Collection[str] | None = None, + weight: str | None = None, + inf_weight: bool | float = False, + include_inactive: bool = True, + ) -> OrderedGraph: + """Build NetworkX graph. + + Parameters + ---------- + branch_components : [str] + Components to use as branches. The default are + passive_branch_components in the case of a SubNetwork and + branch_components in the case of a Network. + weight : str + Branch attribute to use as weight + inf_weight : bool|float + How to treat infinite weights (default: False). True keeps the infinite + weight. False skips edges with infinite weight. If a float is given it + is used instead. + include_inactive : bool + Whether to include inactive components in the graph. + + Returns + ------- + graph : OrderedGraph + NetworkX graph + + """ + n = self + from pypsa import Network, SubNetwork # noqa: PLC0415 + + if branch_components is not None: + branch_components = set(branch_components) + elif isinstance(n, Network): + branch_components = n.branch_components + elif isinstance(n, SubNetwork): + branch_components = n.n.passive_branch_components + else: + msg = "graph must be called with a Network or a SubNetwork" + raise TypeError(msg) + + buses_i = n.c.buses.static.index + + if n.has_scenarios: + buses_i = buses_i.unique("name") + + graph = OrderedGraph() + + # add nodes first, in case there are isolated buses not connected with branches + graph.add_nodes_from(buses_i) + + # Multigraph uses the branch type and name as key + def gen_edges() -> Iterable[tuple[str, str, tuple[str, int], dict]]: + for c in n.iterate_components(branch_components): + static = c.static + if n.has_scenarios: + static = c.static.loc[n.scenarios[0]] + + for branch in static.loc[ + slice(None) if include_inactive else static.query("active").index + ].itertuples(): + if weight is None: + data = {} + else: + data = {"weight": getattr(branch, weight, 0)} + if np.isinf(data["weight"]) and inf_weight is not True: + if inf_weight is False: + continue + data["weight"] = inf_weight + yield (branch.bus0, branch.bus1, (c.name, branch.Index), data) + + with warnings.catch_warnings(): + # TODO Resolve + warnings.filterwarnings( + "ignore", + message=".*iterate_components is deprecated.*", + category=DeprecationWarning, + ) + graph.add_edges_from(gen_edges()) + + return graph + + def adjacency_matrix( + self, + branch_components: Collection[str] | None = None, + investment_period: int | str | None = None, + busorder: pd.Index | None = None, + weights: pd.Series | None = None, + return_dataframe: bool | None = None, + ) -> pd.DataFrame | sp.sparse.coo_matrix: + """Construct an adjacency matrix (directed) as a pandas DataFrame or sparse matrix. + + Parameters + ---------- + branch_components : iterable sublist of `branch_components` + Buses connected by any of the selected branches are adjacent + (default: branch_components (network) or passive_branch_components (sub_network)) + investment_period : int | str | None, default None + If given, only assets active in the given investment period are considered + in the network topology. + busorder : pd.Index subset of n.buses.index + Basis to use for the matrix representation of the adjacency matrix + (default: buses.index (network) or buses_i() (sub_network)) + weights : pd.Series or None (default) + If given must provide a weight for each branch, multi-indexed + on branch_component name and branch name. + return_dataframe : bool | None, default None + If True, returns a pandas DataFrame. If False, returns a sparse coo_matrix + for backwards compatibility. If None (default), returns a sparse coo_matrix + with a deprecation warning. + + Returns + ------- + adjacency_matrix : pd.DataFrame or sp.sparse.coo_matrix + Directed adjacency matrix as DataFrame (if return_dataframe=True) or + sparse matrix (if return_dataframe=False) with bus indices + + """ + from pypsa.networks import Network, SubNetwork # noqa: PLC0415 + + n = self + if not isinstance(n, Network | SubNetwork): + msg = "graph must be called with a Network or a SubNetwork" + raise TypeError(msg) + + if branch_components is not None: + branch_components = set(branch_components) + elif isinstance(n, Network): + branch_components = n.branch_components + elif isinstance(n, SubNetwork): + branch_components = n.n.passive_branch_components + else: + msg = " must be called with a Network or a SubNetwork" + raise TypeError(msg) + + if busorder is None: + busorder = n.c.buses.static.index + + # Initialize empty DataFrame with buses as both rows and columns + if n.has_scenarios: + busorder = busorder.unique("name") + + dtype = int if weights is None else float + adjacency_df = pd.DataFrame(0, index=busorder, columns=busorder, dtype=dtype) + + # Build adjacency matrix component by component + for c in n.components: + if c.name not in branch_components: + continue + active = c.get_active_assets(investment_period) + sel = c.static[active].index.unique("name") + static = c.static.reindex(sel, level="name") + + # Skip if no branches in this component + if len(static) == 0: + continue + + # Get bus0 and bus1 from static data + bus0 = static.bus0 + bus1 = static.bus1 + + # Set weights for these connections + if weights is None: + # Set default weights of 1 for all branches + for b0, b1 in zip(bus0, bus1, strict=False): + adjacency_df.at[b0, b1] = 1 + else: + # Use provided weights + for b0, b1, idx in zip(bus0, bus1, sel, strict=False): + adjacency_df.at[b0, b1] = weights[c.name][idx] + + # Handle deprecation warning for None case + if return_dataframe is None: + warnings.warn( + "In future versions, adjacency_matrix will return a pandas DataFrame by default. " + "To maintain the current behavior, explicitly set return_dataframe=False. " + "To adopt the new behavior and silence this warning, set return_dataframe=True.", + FutureWarning, + stacklevel=2, + ) + return_dataframe = False + + if return_dataframe: + return adjacency_df + else: + # Convert to sparse matrix for backwards compatibility + return sp.sparse.coo_matrix(adjacency_df.values) + + def incidence_matrix( + self, + branch_components: Collection[str] | None = None, + busorder: pd.Index | None = None, + ) -> sp.sparse.csr_matrix: + """Construct a sparse incidence matrix (directed). + + Parameters + ---------- + branch_components : iterable sublist of `branch_components` + Buses connected by any of the selected branches are adjacent + (default: branch_components (network) or passive_branch_components (sub_network)) + busorder : pd.Index subset of n.buses.index + Basis to use for the matrix representation of the adjacency matrix + (default: buses.index (network) or buses_i() (sub_network)) + + Returns + ------- + incidence_matrix : sp.sparse.csr_matrix + Directed incidence matrix + + Examples + -------- + >>> n.incidence_matrix() + + + """ + from pypsa.networks import Network, SubNetwork # noqa: PLC0415 + + if branch_components is not None: + branch_components = set(branch_components) + elif isinstance(self, Network): + branch_components = self.branch_components + elif isinstance(self, SubNetwork): + branch_components = self.n.passive_branch_components + else: + msg = " must be called with a Network or a SubNetwork" + raise TypeError(msg) + + if busorder is None: + busorder = self.c.buses.static.index + + no_buses = len(busorder) + no_branches = 0 + bus0_inds = [] + bus1_inds = [] + for c in self.components: + if c.name not in branch_components: + continue + sel = c.static.query("active").index + no_branches += len(c.static.loc[sel]) + bus0_inds.append(busorder.get_indexer(c.static.loc[sel, "bus0"])) + bus1_inds.append(busorder.get_indexer(c.static.loc[sel, "bus1"])) + bus0_inds = np.concatenate(bus0_inds) + bus1_inds = np.concatenate(bus1_inds) + + return sp.sparse.csr_matrix( + ( + np.r_[np.ones(no_branches), -np.ones(no_branches)], + (np.r_[bus0_inds, bus1_inds], np.r_[:no_branches, :no_branches]), + ), + (no_buses, no_branches), + ) diff --git a/PyPSA/source/pypsa/network/index.py b/PyPSA/source/pypsa/network/index.py new file mode 100644 index 0000000000000000000000000000000000000000..43b6cf93c38e336d3e7cd524dfbafa6187cde1af --- /dev/null +++ b/PyPSA/source/pypsa/network/index.py @@ -0,0 +1,1195 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Network index module. + +Contains single mixin class which is used to inherit to [pypsa.Networks] class. +Should not be used directly. + +Index methods and properties are used to access the different index levels, set them +and convert the Network accordingly. +""" + +from __future__ import annotations + +import logging +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any + +import numpy as np +import pandas as pd + +from pypsa._options import options +from pypsa.guards import _assert_data_integrity +from pypsa.network.abstract import _NetworkABC + +if TYPE_CHECKING: + from pypsa import Network + + +logger = logging.getLogger(__name__) + + +class NetworkIndexMixin(_NetworkABC): + """Mixin class for network index methods. + + Class inherits to [pypsa.Network][]. All attributes and methods can be used + within any Network instance. + """ + + _risk_preference: dict[str, float] | None + + # ---------------- + # Snapshots + # ---------------- + + def set_snapshots( + self, + snapshots: Sequence, + default_snapshot_weightings: float = 1.0, + weightings_from_timedelta: bool = False, + ) -> None: + """Set the snapshots/time steps and reindex all time-dependent data. + + Snapshot weightings, typically representing the hourly length of each snapshot, + is filled with the `default_snapshot_weighintgs` value, or uses the timedelta + of the snapshots if `weightings_from_timedelta` flag is True, and snapshots are + of type `pd.DatetimeIndex`. + + This will reindex all components time-dependent DataFrames + ([`pypsa.Network.dynamic`][]). NaNs are filled with the default value for + that quantity. + + Parameters + ---------- + snapshots : list, pandas.Index or pd.MultiIndex + All time steps. + default_snapshot_weightings: float + The default weight for each snapshot. Defaults to 1.0. + weightings_from_timedelta: bool + Wheter to use the timedelta of `snapshots` as `snapshot_weightings` if + `snapshots` is of type `pd.DatetimeIndex`. Defaults to False. + + Examples + -------- + >>> n = pypsa.Network() + >>> n.set_snapshots(pd.date_range("2015-01-01", freq="h", periods=3)) + >>> n.snapshots + DatetimeIndex(['2015-01-01 00:00:00', '2015-01-01 01:00:00', + '2015-01-01 02:00:00'], + dtype='datetime64[ns]', name='snapshot', freq='h') + + """ + # Check if snapshots contain timezones + if isinstance(snapshots, pd.DatetimeIndex) and snapshots.tz is not None: + msg = ( + "Numpy datetime64[ns] objects with timezones are not supported and are " + "thus not allowed in snapshots. Please pass timezone-naive timestamps " + "(e.g. via ds.values)." + ) + raise ValueError(msg) + + # Always create normal pd.Index, never pd.RangeIndex + if isinstance(snapshots, range): + snapshots = list(snapshots) + + if isinstance(snapshots, pd.MultiIndex): + if snapshots.nlevels != 2: + msg = "Maximally two levels of MultiIndex supported" + raise ValueError(msg) + sns = snapshots.rename(["period", "timestep"]) + sns.name = "snapshot" + else: + sns = pd.Index(snapshots, name="snapshot") + + if len(sns) == 0: + msg = "Snapshots must not be empty." + raise ValueError(msg) + + self._snapshots_data = self._snapshots_data.reindex( + sns, fill_value=default_snapshot_weightings + ) + + if isinstance(snapshots, pd.DatetimeIndex) and weightings_from_timedelta: + hours_per_step = ( + snapshots.to_series() + .diff(periods=1) + .shift(-1) + .ffill() # fill last value by assuming same as the one before + .apply(lambda x: x.total_seconds() / 3600) + ) + self._snapshots_data = pd.DataFrame( + dict.fromkeys(self._snapshots_data.columns, hours_per_step), index=sns + ) + elif not isinstance(snapshots, pd.DatetimeIndex) and weightings_from_timedelta: + logger.info( + "Skipping `weightings_from_timedelta` as `snapshots`is not of type `pd.DatetimeIndex`." + ) + + for component in self.all_components: + dynamic = self.c[component].dynamic + attrs = self.components[component]["defaults"] + + for k in dynamic: + if dynamic[k].empty: # avoid expensive reindex operation + dynamic[k].index = self.snapshots + elif k in attrs.default[attrs.varying]: + if isinstance(dynamic[k].index, pd.MultiIndex): + dynamic[k] = dynamic[k].reindex( + self.snapshots, fill_value=attrs.default[attrs.varying][k] + ) + else: + # Make sure to keep timestep level in case of MultiIndex + dynamic[k] = dynamic[k].reindex( + self.snapshots, + fill_value=attrs.default[attrs.varying][k], + level="timestep", + ) + else: + dynamic[k] = dynamic[k].reindex(self.snapshots) + + # Synchronize investment_periods_data when snapshots have a period level + if isinstance(sns, pd.MultiIndex): + self.investment_period_weightings = ( + self.investment_period_weightings.reindex( + self.periods, fill_value=1.0 + ).astype(float) + ) + + if options.debug.runtime_verification: + _assert_data_integrity(self) + + @property + def snapshots(self) -> pd.Index | pd.MultiIndex: + """Snapshots dimension of the network. + + If snapshots are a pandas.MultiIndex, the first level are investment periods + and the second level are timesteps. If snapshots are single indexed, the only + level is timesteps. + + Returns + ------- + pd.Index or pd.MultiIndex + Snapshots of the network, either as a single index or a multi-index. + + See Also + -------- + [pypsa.Network.timesteps][], [pypsa.Network.periods][] + + Notes + ----- + Note that Snapshots are a dimension, while timesteps and and periods are + only levels of the snapshots dimension, similar to coords in xarray. + This is because timesteps and periods are not necessarily unique or complete + across snapshots. + + Examples + -------- + >>> n.snapshots # doctest: +ELLIPSIS + DatetimeIndex(['2015-01-01 00:00:00', '2015-01-01 01:00:00', + '2015-01-01 02:00:00', '2015-01-01 03:00:00', + ... + dtype='datetime64[ns]', name='snapshot', freq=None) + + """ + return self._snapshots_data.index + + @snapshots.setter + def snapshots(self, snapshots: Sequence) -> None: + """Setter for snapshots dimension. + + Parameters + ---------- + snapshots : Sequence + Snapshots to be set. + + See Also + -------- + [pypsa.Network.snapshots][], [pypsa.Network.set_snapshots][] + + """ + self.set_snapshots(snapshots) + + # Timesteps (Coordinate of Snapshots) + # --------- + @property + def timesteps(self) -> pd.Index: + """Timestep level of snapshots dimension. + + If snapshots is single indexed, timesteps and snapshots yield the same result. + Otherwise only the timestep level will be returned. + + Returns + ------- + pd.Index + Timesteps of the network. + + See Also + -------- + [pypsa.Network.snapshots][], [pypsa.Network.periods][] + + Examples + -------- + >>> n = pypsa.Network() + >>> n.set_snapshots(pd.date_range("2015-01-01", freq="h", periods=3)) + + For a Network without investment periods, the timesteps are identical to the + snapshots: + >>> n.timesteps + DatetimeIndex(['2015-01-01 00:00:00', '2015-01-01 01:00:00', + '2015-01-01 02:00:00'], + dtype='datetime64[ns]', name='snapshot', freq='h') + >>> n.snapshots + DatetimeIndex(['2015-01-01 00:00:00', '2015-01-01 01:00:00', + '2015-01-01 02:00:00'], + dtype='datetime64[ns]', name='snapshot', freq='h') + + For a Network with investment periods, the timesteps are are the unqiue set + of timesteps in across all investment periods: + >>> n.investment_periods = [1, 2] + >>> n.timesteps + DatetimeIndex(['2015-01-01 00:00:00', '2015-01-01 01:00:00', + '2015-01-01 02:00:00'], + dtype='datetime64[ns]', name='timestep', freq=None) + >>> n.snapshots + MultiIndex([(1, '2015-01-01 00:00:00'), + (1, '2015-01-01 01:00:00'), + (1, '2015-01-01 02:00:00'), + (2, '2015-01-01 00:00:00'), + (2, '2015-01-01 01:00:00'), + (2, '2015-01-01 02:00:00')], + name='snapshot') + + """ + if "timestep" in self.snapshots.names: + return self.snapshots.get_level_values("timestep").drop_duplicates() + return self.snapshots + + @timesteps.setter + def timesteps(self, timesteps: Sequence) -> None: + """Setter for timesteps level of snapshots dimension. + + !!! warning + Setting `timesteps` is not supported. Please set `snapshots` instead. + + Parameters + ---------- + timesteps : Sequence + Timesteps to be set. + + Also see + -------- + pypsa.Network.timesteps : Getter method + + """ + msg = "Setting `timesteps` is not supported. Please set `snapshots` instead." + raise NotImplementedError(msg) + + # Investment Periods (Coordinate of Snapshots) + # --------- + + def set_investment_periods(self, periods: Sequence) -> None: + """Set the investment periods of the network. + + If the network snapshots are a pandas.MultiIndex, the investment periods + have to be a subset of the first level. If snapshots are a single index, + they and all time-series are repeated for all periods. This changes + the network snapshots to be a MultiIndex (inplace operation) with the first + level being the investment periods and the second level the snapshots. + + Parameters + ---------- + periods : list + List of periods to be selected/initialized. + + Examples + -------- + >>> n = pypsa.Network() + >>> n.set_snapshots(pd.date_range("2015-01-01", freq="h", periods=3)) + >>> n.snapshots + DatetimeIndex(['2015-01-01 00:00:00', '2015-01-01 01:00:00', + '2015-01-01 02:00:00'], + dtype='datetime64[ns]', name='snapshot', freq='h') + >>> n.investment_periods = [1, 2] + >>> n.snapshots + MultiIndex([(1, '2015-01-01 00:00:00'), + (1, '2015-01-01 01:00:00'), + (1, '2015-01-01 02:00:00'), + (2, '2015-01-01 00:00:00'), + (2, '2015-01-01 01:00:00'), + (2, '2015-01-01 02:00:00')], + name='snapshot') + + """ + periods_ = pd.Index(periods, name="period") + if periods_.empty: + return + if not ( + pd.api.types.is_integer_dtype(periods_) + and periods_.is_unique + and periods_.is_monotonic_increasing + ): + msg = ( + "Investment periods are not strictly increasing integers, " + "which is required for multi-period investment optimisation." + ) + raise ValueError(msg) + if isinstance(self.snapshots, pd.MultiIndex): + if not periods_.isin(self.snapshots.unique("period")).all(): + msg = "Not all investment periods are in level `period` of snapshots." + raise ValueError(msg) + if len(periods_) < len(self.snapshots.unique(level="period")): + msg = "Investment periods do not equal first level values of snapshots." + raise NotImplementedError(msg) + else: + # Convenience case: + logger.info( + "Repeating time-series for each investment period and " + "converting snapshots to a pandas.MultiIndex." + ) + names = ["period", "timestep"] + for component in self.all_components: + dynamic = self.c[component].dynamic + + for k in dynamic: + dynamic[k] = pd.concat( + dict.fromkeys(periods_, dynamic[k]), names=names + ) + dynamic[k].index.name = "snapshot" + + sns = pd.MultiIndex.from_product([periods_, self.snapshots], names=names) + sns.name = "snapshot" + self._snapshots_data = pd.concat( + dict.fromkeys(periods_, self.snapshot_weightings), names=names + ) + self._snapshots_data.index.name = "snapshot" + + self.investment_period_weightings = self.investment_period_weightings.reindex( + self.periods, fill_value=1.0 + ).astype(float) + + if options.debug.runtime_verification: + _assert_data_integrity(self) + + @property + def periods(self) -> pd.Index: + """Periods level of snapshots dimension. + + If snapshots is single indexed, periods will always be empty, since there no + investment periods without timesteps are defined. Otherwise only the period + level will be returned. + + Returns + ------- + pd.Index + Periods of the network. + + See Also + -------- + [pypsa.Network.snapshots][], [pypsa.Network.timesteps][] + + Examples + -------- + >>> n = pypsa.Network() + >>> n.add("Bus", "bus") # doctest: +SKIP + >>> n.snapshots = pd.date_range("2015-01-01", freq="h", periods=3) + >>> n.snapshots + DatetimeIndex(['2015-01-01 00:00:00', '2015-01-01 01:00:00', + '2015-01-01 02:00:00'], + dtype='datetime64[ns]', name='snapshot', freq='h') + + Add investment periods: + >>> n.periods = [1, 2] + >>> n.periods + Index([1, 2], dtype='int64', name='period') + + Which will also cast snapshots to a MultiIndex: + >>> n.snapshots + MultiIndex([(1, '2015-01-01 00:00:00'), + (1, '2015-01-01 01:00:00'), + (1, '2015-01-01 02:00:00'), + (2, '2015-01-01 00:00:00'), + (2, '2015-01-01 01:00:00'), + (2, '2015-01-01 02:00:00')], + name='snapshot') + + """ + if "period" in self.snapshots.names: + return self.snapshots.get_level_values("period").drop_duplicates() + return pd.Index([], name="period") + + @periods.setter + def periods(self, periods: Sequence) -> None: + """Setter for periods level of snapshots dimension. + + Parameters + ---------- + periods : Sequence + Investment periods to be set. + Also see + -------- + pypsa.Network.periods : Getter method + pypsa.Network.set_investment_periods : Setter method + + """ + self.set_investment_periods(periods) + + @property + def has_periods(self) -> bool: + """Check if network has investment periods assigned to snapshots dimension. + + Returns + ------- + bool + True if network has investment periods, otherwise False. + + See Also + -------- + [pypsa.Network.snapshots][] + + Examples + -------- + >>> n = pypsa.Network() + >>> n.add("Bus", "bus") # doctest: +SKIP + >>> n.snapshots = pd.date_range("2015-01-01", freq="h", periods=3) + >>> n.has_periods + False + + Add investment periods: + >>> n.periods = [1, 2] + >>> n.has_periods + True + + """ + return not self.periods.empty + + @property + def investment_periods(self) -> pd.Index: + """Periods level of snapshots dimension. + + If snapshots is single indexed, periods will always be empty, since there no + investment periods without timesteps are defined. Otherwise only the period + level will be returned. + + !!! note + Alias for [`pypsa.Network.periods`][]. + + Returns + ------- + pd.Index + Investment periods of the network. + + See Also + -------- + [pypsa.Network.snapshots][], [pypsa.Network.periods][], + [pypsa.Network.timesteps][] + + Examples + -------- + >>> n = pypsa.Network() + >>> n.add("Bus", "bus") # doctest: +SKIP + >>> n.snapshots = pd.date_range("2015-01-01", freq="h", periods=3) + >>> n.snapshots + DatetimeIndex(['2015-01-01 00:00:00', '2015-01-01 01:00:00', + '2015-01-01 02:00:00'], + dtype='datetime64[ns]', name='snapshot', freq='h') + + Add investment periods: + >>> n.investment_periods = [1, 2] + >>> n.investment_periods + Index([1, 2], dtype='int64', name='period') + + Which will also cast snapshots to a MultiIndex: + >>> n.snapshots + MultiIndex([(1, '2015-01-01 00:00:00'), + (1, '2015-01-01 01:00:00'), + (1, '2015-01-01 02:00:00'), + (2, '2015-01-01 00:00:00'), + (2, '2015-01-01 01:00:00'), + (2, '2015-01-01 02:00:00')], + name='snapshot') + + """ + return self.periods + + @investment_periods.setter + def investment_periods(self, periods: Sequence) -> None: + """Setter for periods level of snapshots dimension. + + !!! note + Alias for [`pypsa.Network.periods`][]. + + Parameters + ---------- + periods : Sequence + Investment periods to be set. + + Also see + -------- + pypsa.Network.periods : Getter method + pypsa.Network.set_investment_periods : Setter method + + """ + self.periods = periods + + @property + def has_investment_periods(self) -> bool: + """Check if network has investment periods assigned to snapshots dimension. + + !!! note + Alias for [`pypsa.Network.has_periods`][]. + + Returns + ------- + bool + True if network has investment periods, otherwise False. + + See Also + -------- + [pypsa.Network.snapshots][], [pypsa.Network.periods][] + + Examples + -------- + >>> n = pypsa.Network() + >>> n.add("Bus", "bus") # doctest: +SKIP + >>> n.snapshots = pd.date_range("2015-01-01", freq="h", periods=3) + >>> n.has_investment_periods + False + + Add investment periods: + >>> n.periods = [1, 2] + >>> n.has_investment_periods + True + + """ + return self.has_periods + + # Snapshot weightings + # ------------------- + + @property + def snapshot_weightings(self) -> pd.DataFrame: + """Weightings applied to each snapshots during the optimization. + + * Objective weightings are factors on the operational cost in the + objective function. + + * Store weightings define the elapsed hours for the charge, discharge + standing loss and spillage of storage units and stores in order to + determine the state of charge. + + * Generator weightings are factors for the contribution of generators + to global constraints, e.g. emission limits, and energy balances. + + Examples + -------- + >>> n = pypsa.Network() + >>> n.add("Bus", "bus") # doctest: +SKIP + >>> n.snapshots = pd.date_range("2015-01-01", freq="h", periods=3) + >>> n.snapshot_weightings + objective stores generators + snapshot + 2015-01-01 00:00:00 1.0 1.0 1.0 + 2015-01-01 01:00:00 1.0 1.0 1.0 + 2015-01-01 02:00:00 1.0 1.0 1.0 + + Change the snapshot weightings: + >>> n.snapshot_weightings.objective = [5, 7, 9] + >>> n.snapshot_weightings + objective stores generators + snapshot + 2015-01-01 00:00:00 5 1.0 1.0 + 2015-01-01 01:00:00 7 1.0 1.0 + 2015-01-01 02:00:00 9 1.0 1.0 + + """ + return self._snapshots_data + + @snapshot_weightings.setter + def snapshot_weightings(self, df: pd.DataFrame) -> None: + if not df.index.equals(self.snapshots): + msg = "Weightings not defined for all snapshots." + raise ValueError(msg) + + if isinstance(df, pd.Series): + logger.info("Applying weightings to all columns of `snapshot_weightings`") + df = pd.DataFrame(dict.fromkeys(self._snapshots_data.columns, df)) + df.index.name = self.snapshots.name + df.index.names = self.snapshots.names + self._snapshots_data = df + + if options.debug.runtime_verification: + _assert_data_integrity(self) + + @property + def investment_period_weightings(self) -> pd.DataFrame: + """Weightings applied to each investment period during the optimization (LOPF). + + Objective weightings are multiplied with all cost coefficients in the + objective function of the respective investment period (e.g. to include a + social discount rate). + Years weightings denote the elapsed time until the subsequent investment period + (e.g. used for global constraints CO2 emissions). + + Examples + -------- + Create a network with investment periods: + >>> n = pypsa.Network() + >>> n.add("Bus", "bus") # doctest: +SKIP + >>> n.snapshots = pd.date_range("2015-01-01", freq="h", periods=2) + >>> n.investment_periods = [1, 2] + + >>> n.investment_period_weightings + objective years + period + 1 1.0 1.0 + 2 1.0 1.0 + + Change the investment period weightings: + >>> n.investment_period_weightings.objective = [5, 7] + >>> n.investment_period_weightings.years = [1, 2] + >>> n.investment_period_weightings + objective years + period + 1 5 1 + 2 7 2 + + """ + return self._investment_periods_data + + @investment_period_weightings.setter + def investment_period_weightings(self, df: pd.DataFrame) -> None: + if not df.index.equals(self.investment_periods): + msg = "Weightings not defined for all investment periods." + raise ValueError(msg) + if isinstance(df, pd.Series): + logger.info( + "Applying weightings to all columns of `investment_period_weightings`" + ) + df = pd.DataFrame(dict.fromkeys(self._investment_periods_data.columns, df)) + self._investment_periods_data = df + + if options.debug.runtime_verification: + _assert_data_integrity(self) + + # ----------- + # Scenarios + # ----------- + + def set_scenarios( + self, + scenarios: dict | Sequence | pd.Series | pd.DataFrame | None = None, + **kwargs: Any, + ) -> None: + """Set scenarios for the network to create a stochastic network. + + Parameters + ---------- + scenarios : dict, Sequence, pd.Series, optional + Scenarios to set for the network. + **kwargs : Any + Alternative way to set scenarios via keyword arguments. + E.g. `n.set_scenarios(low=0.5, high=0.5)`. + + """ + # Validate input + if self.has_scenarios: + msg = ( + "Changing scenarios on a network that already has scenarios defined is not " + "yet supported." + ) + raise NotImplementedError(msg) + if scenarios is None and not kwargs: + msg = ( + "You must pass either `scenarios` or keyword arguments " + "to set_scenarios." + ) + raise ValueError(msg) + if kwargs and scenarios is not None: + msg = ( + "You can pass scenarios either via `scenarios` or via " + "keyword arguments, but not both." + ) + raise ValueError(msg) + + if isinstance(scenarios, dict): + scenarios_ = pd.Series(scenarios) + elif isinstance(scenarios, pd.Series): + scenarios_ = scenarios + elif isinstance(scenarios, pd.DataFrame): + if scenarios.shape[1] != 1: + msg = "`scenarios` DataFrame must have exactly one column." + raise ValueError(msg) + scenarios_ = scenarios.iloc[:, 0] + elif isinstance(scenarios, Sequence): + scenarios_ = pd.Series( + [1 / len(scenarios)] * len(scenarios), index=scenarios + ) + elif kwargs: + scenarios_ = pd.Series(kwargs) + else: + msg = "Invalid type for `scenarios`. Must be dict, pd.DataFrame, pd.Series, or Sequence. " + raise TypeError(msg) + + if abs(scenarios_.sum() - 1) > 1e-5: + msg = ( + "The sum of the weights in `scenarios` must be equal to 1. " + f"Current sum: {scenarios_.sum()}" + ) + raise ValueError(msg) + + scenarios_ = scenarios_.rename("weight") + scenarios_.index = scenarios_.index.astype(str) + scenarios_.index.name = "scenario" + + for c in self.components.values(): # Loop all components, not just empty ones + c.static = pd.concat( + dict.fromkeys(scenarios_.index, c.static), names=["scenario"] + ) + for k, v in c.dynamic.items(): + c.dynamic[k] = pd.concat( + dict.fromkeys(scenarios_.index, v), names=["scenario"], axis=1 + ) + + self._scenarios_data = scenarios_.to_frame() + + if options.debug.runtime_verification: + _assert_data_integrity(self) + + @property + def scenarios(self) -> pd.Index: + """Get the scenarios index for the network. + + Returns + ------- + pd.Index + The scenarios index for the network. + + """ + return self._scenarios_data.index + + @scenarios.setter + def scenarios(self, scenarios: dict | pd.Series | Sequence) -> None: + self.set_scenarios(scenarios) + + @property + def scenario_weightings(self) -> pd.DataFrame: + """Get the scenario weightings for the network. + + Returns + ------- + pd.DataFrame + The scenario weightings as a DataFrame with 'weight' column. + + """ + return self._scenarios_data + + @property + def has_scenarios(self) -> bool: + """Boolean indicating if the network has scenarios defined.""" + return len(self._scenarios_data) > 0 + + # Risk Preferences (CVaR) + + def set_risk_preference(self, alpha: float, omega: float) -> None: + """Set risk aversion preferences for stochastic optimization using CVaR formulation. + + Parameters + ---------- + alpha : float + Confidence level in (0, 1). CVaR averages losses over the worst + (1 - alpha) probability mass (the tail). For worst 10% tail, + set alpha = 0.9 so that 1 - alpha = 0.1. Typical choices + are alpha in {0.90, 0.95, 0.99}. Higher alpha focuses on + rarer, more extreme tails; lower alpha considers a broader tail. + omega : float + Risk preference parameter (risk aversion weight). Must be between 0 and 1. + - omega = 0: Risk-neutral optimization + - omega > 0: Risk-averse optimization (more focus on the tail risk) + - omega = 1: Maximum risk aversion (optimize for the tail risk only) + Higher values indicate more risk aversion. + + Examples + -------- + >>> n = pypsa.Network() + >>> n.set_scenarios({"low": 0.3, "medium": 0.4, "high": 0.3}) + >>> n.set_risk_preference(alpha=0.95, omega=0.1) # 5% tail CVaR (1 - 0.05) + >>> n.risk_preference + {'alpha': 0.95, 'omega': 0.1} + + Notes + ----- + This method must be called after `set_scenarios()` as CVaR formulation + requires stochastic scenarios to be defined. The CVaR formulation will + add auxiliary variables and constraints to the optimization model during + the model building phase. + + """ + # Validate that scenarios are defined + if not self.has_scenarios: + msg = ( + "Risk preferences can only be set for stochastic networks. " + "Please call set_scenarios() first to define scenarios." + ) + raise ValueError(msg) + + # Validate parameters + if not (0 < alpha < 1): + msg = f"Alpha must be between 0 and 1, got {alpha}" + raise ValueError(msg) + + if not (0 <= omega <= 1): + msg = f"Omega must be between 0 and 1, got {omega}" + raise ValueError(msg) + + # Store risk preferences + self._risk_preference = {"alpha": alpha, "omega": omega} + + if options.debug.runtime_verification: + _assert_data_integrity(self) + + @property + def risk_preference(self) -> dict[str, float] | None: + """Get the risk preference parameters for the network. + + Returns + ------- + dict[str, float] | None + Dictionary containing 'alpha' and 'omega' parameters if risk preferences + are set, None otherwise. + + """ + return self._risk_preference + + @property + def has_risk_preference(self) -> bool: + """Boolean indicating if the network has risk preferences defined.""" + return self._risk_preference is not None + + # ----------- + # Collections + # ----------- + + @property + def is_collection(self) -> bool: + """Check if this is a collection of networks or a single network. + + Returns + ------- + bool + True, since this is a NetworkCollection. + + See Also + -------- + [pypsa.Network][], [pypsa.NetworkCollection][] + + """ + return False + + # ----------- + # Selectors + # ----------- + + def get_scenario(self, scenario: str) -> Network: + """Return a network for a single scenario from a stochastic network. + + Parameters + ---------- + scenario : str + Name of the scenario to extract. + + Returns + ------- + n : pypsa.Network + A new network instance containing only the selected scenario. + + Examples + -------- + >>> n_stochastic + Stochastic PyPSA Network 'Stochastic-Network' + --------------------------------------------- + Components: + - Bus: 3 + - Generator: 12 + - Load: 3 + Snapshots: 2920 + Scenarios: 3 + >>> n_high = n_stochastic.get_scenario("high") + >>> n_high + PyPSA Network 'Stochastic-Network - Scenario 'high'' + ---------------------------------------------------- + Components: + - Bus: 1 + - Generator: 4 + - Load: 1 + Snapshots: 2920 + + """ + if not self.has_scenarios: + msg = "This method can only be used on a stochastic network with scenarios." + raise ValueError(msg) + + try: + self._scenarios_data.loc[scenario] + except KeyError as e: + msg = f"Scenario '{scenario}' not found in network scenarios." + raise KeyError(msg) from e + + n = self.copy() + + n.name = f"{n.name} - Scenario '{scenario}'" + # Remove + n._scenarios_data = n._scenarios_data.iloc[:0] + + for c in n.components.values(): + if not c.static.empty: + c.static = c.static.xs(scenario, level="scenario", axis=0) + else: + c.static.index = c.static.index.droplevel("scenario") + for k, v in c.dynamic.items(): + if not c.dynamic[k].empty: + c.dynamic[k] = v.xs(scenario, level="scenario", axis=1) + else: + c.dynamic[k].columns = c.dynamic[k].columns.droplevel("scenario") + + return n + + def get_network(self, collection: str) -> None | Network: + """Return a single network from a NetworkCollection. + + Parameters + ---------- + collection : str + Name of the network to be selected from the collection. + + Returns + ------- + n : pypsa.Network + Reference to the selected network from the collection. + + Examples + -------- + >>> nc + NetworkCollection + ----------------- + Networks: 2 + Index name: 'network' + Entries: ['AC-DC-Meshed', 'AC-DC-Meshed-Shuffled-Load'] + + >>> selected = nc.get_network("AC-DC-Meshed") + >>> selected + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 9 + - Carrier: 6 + - Generator: 6 + - GlobalConstraint: 1 + - Line: 7 + - Link: 4 + - Load: 6 + - SubNetwork: 3 + Snapshots: 10 + + + A network collection does not copy the selected network, but returns + a reference to it: + >>> n1 = pypsa.Network(name="Network1") + >>> n2 = pypsa.Network(name="Network2") + >>> nc = pypsa.NetworkCollection([n1, n2]) + >>> nc.get_network("Network1") is n1 + True + + """ + if not self.is_collection: + msg = "This method can only be used on a NetworkCollection." + raise ValueError(msg) + + return None + + def slice_network( + self, + buses: str | Sequence[str] | pd.Index | slice | None = None, + snapshots: int | Sequence | pd.Index | slice | None = None, + ) -> Network: + """Return a sliced copy of the network. + + Parameters + ---------- + buses : slice, list, or str, or tuple + Used to slice a network by buses. Any valid indexer + for pandas.DataFrame.loc is allowed (refer to pandas.DataFrame.loc). + snapshots : int, slice, list or boolean array, optional + Used to slice a network by snapshots. Any valid indexer + for pandas.Index is allowed (refer to pandas.Index.__getitem__). If + None, all snapshots are used. + + Returns + ------- + n : pypsa.Network + A new network instance containing only the selected buses with attached + components and/or the selected snapshots. + + Examples + -------- + Slice network to a single bus and its connected components: + + >>> n_manchester = n.slice_network(buses="Manchester") + >>> n_manchester + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 1 + - Carrier: 6 + - Generator: 2 + - GlobalConstraint: 1 + - Load: 1 + Snapshots: 10 + + >>> n_manchester.buses.index.tolist() + ['Manchester'] + + Slice network to multiple buses: + + >>> n_subset = n.slice_network(buses=["Manchester", "Frankfurt"]) + >>> n_subset + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 2 + - Carrier: 6 + - Generator: 4 + - GlobalConstraint: 1 + - Load: 2 + Snapshots: 10 + + >>> sorted(n_subset.buses.index.tolist()) + ['Frankfurt', 'Manchester'] + + Slice network by DC carrier: + + >>> n_hv = n.slice_network(buses=n.buses.carrier == "DC") + >>> n_hv + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 3 + - Carrier: 6 + - GlobalConstraint: 1 + - Line: 3 + Snapshots: 10 + + Slice network to first 3 snapshots: + + >>> n_snap = n.slice_network(snapshots=slice(None, 3)) + >>> n_snap + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 9 + - Carrier: 6 + - Generator: 6 + - GlobalConstraint: 1 + - Line: 7 + - Link: 4 + - Load: 6 + Snapshots: 3 + + Slice both buses and snapshots: + + >>> n_both = n.slice_network(buses="Manchester", snapshots=slice(None, 2)) + >>> n_both + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 1 + - Carrier: 6 + - Generator: 2 + - GlobalConstraint: 1 + - Load: 1 + Snapshots: 2 + + """ + if buses is None and snapshots is None: + msg = "Either `buses` or `snapshots` must be provided to slice the network." + raise ValueError(msg) + + # Set defaults + if buses is None: + buses = slice(None) + if snapshots is None: + snapshots = slice(None) + + # Allow single selection without losing a dimension + if np.isscalar(buses): + buses = pd.Index([buses]) + + # Handle scalar snapshot selection + selected_snapshots = self.snapshots[snapshots] + if not isinstance(selected_snapshots, pd.Index): + # Single snapshot was selected, wrap in Index + selected_snapshots = pd.Index([selected_snapshots]) + + # Setup new network + n = self.__class__() + + n.add( + "Bus", + pd.DataFrame(self.c.buses.static.loc[buses]).assign(sub_network="").index, + **pd.DataFrame(self.c.buses.static.loc[buses]).assign(sub_network=""), + ) + + buses_i = n.c.buses.static.index + + rest_components = ( + self.all_components + - self.standard_type_components + - self.one_port_components + - self.branch_components + ) + for c in rest_components - {"Bus", "SubNetwork"}: + n.add(c, self.components[c].static.index, **self.components[c].static) + + for c in self.standard_type_components: + static = pd.DataFrame( + self.components[c].static.drop( + self.components[c]["standard_types"].index + ) + ) + n.add(c, static.index, **static) + + for c in self.one_port_components: + static = pd.DataFrame(self.c[c].static.loc[lambda df: df.bus.isin(buses_i)]) + n.add(c, static.index, **static) + + for c in self.branch_components: + static = pd.DataFrame( + self.c[c].static.loc[ + lambda df: df.bus0.isin(buses_i) & df.bus1.isin(buses_i) + ] + ) + n.add(c, static.index, **static) + + n.set_snapshots(selected_snapshots) + for c in self.all_components: + c = n.c[c] + i = c.static.index + try: + ndynamic = n.c[c.name].dynamic + dynamic = self.c[c.name].dynamic + + for k in dynamic: + ndynamic[k] = dynamic[k].loc[ + n.snapshots, i.intersection(dynamic[k].columns) + ] + except AttributeError: + pass + + # catch all remaining attributes of network + for attr in ["name", "_crs"]: + setattr(n, attr, getattr(self, attr)) + + n.snapshot_weightings = self.snapshot_weightings.loc[n.snapshots] + + return n # type: ignore[return-value] diff --git a/PyPSA/source/pypsa/network/io.py b/PyPSA/source/pypsa/network/io.py new file mode 100644 index 0000000000000000000000000000000000000000..da77d6b5d3c176c59a474d1587111efb79dedde6 --- /dev/null +++ b/PyPSA/source/pypsa/network/io.py @@ -0,0 +1,2440 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Functions for importing and exporting data.""" + +from __future__ import annotations + +import functools +import json +import logging +import math +import tempfile +import warnings +from abc import abstractmethod +from functools import partial +from typing import TYPE_CHECKING, Any, overload +from urllib.request import urlretrieve + +import geopandas as gpd +import numpy as np +import pandas as pd +import validators +import xarray as xr +from packaging.version import parse as parse_version +from pandas.errors import ParserError +from pyproj import CRS + +from pypsa._options import options +from pypsa.common import _check_for_update, check_optional_dependency +from pypsa.consistency import check_for_unknown_buses +from pypsa.descriptors import _update_linkports_component_attrs +from pypsa.network.abstract import _NetworkABC +from pypsa.version import __version_base__ + +try: + from cloudpathlib import AnyPath as Path +except ImportError: + from pathlib import Path +if TYPE_CHECKING: + from collections.abc import Callable, Iterable, Sequence + from typing import Self + + from pandapower.auxiliary import pandapowerNet + + from pypsa import Network +logger = logging.getLogger(__name__) + + +def _get_safe_excel_sheet_name(sheet_name: str) -> str: + """Convert sheet name to/from safe version for Excel's 31-character limit. + + Works bidirectionally - converts long names to short and short back to long. Only + built-in mappings are handled, other names are returned unchanged and need to be + handled by the user (via UserWarning from openpyxl). + """ + mappings = { + "storage_units-state_of_charge_set": "storage_units-soc_set", + "storage_units-efficiency_dispatch": "storage_units-eff_dispatch", + } + + if sheet_name in mappings: + return mappings[sheet_name] + + for long_name, short_name in mappings.items(): + if sheet_name == short_name: + return long_name + + return sheet_name + + +@overload +def _retrieve_from_url( + url: str, io_function: Callable[[Path], pd.read_excel] +) -> pd.DataFrame: ... + + +@overload +def _retrieve_from_url( + url: str, io_function: Callable[[Path], pd.HDFStore | xr.Dataset] +) -> Network: ... + + +@functools.lru_cache(maxsize=128) +def _retrieve_from_url(url: str, io_function: Callable) -> pd.DataFrame | Network: + # Check if network requests are allowed + if not options.get_option("general.allow_network_requests"): + msg = "Network requests are disabled. Set `pypsa.options.general.allow_network_requests = True` to enable URL loading." + raise ValueError(msg) + + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + file_path = Path(temp_file.name) + logger.info("Retrieving network data from %s.", url) + if not url.startswith("http"): + msg = f"Invalid URL: {url}" + raise ValueError(msg) + try: + urlretrieve(url, file_path) # noqa: S310 + except Exception as e: + msg = f"Failed to retrieve network data from {url}: {e}" + raise ValueError(msg) from e + return io_function(file_path) + + +class _ImpExper: + """Base class for importers and exporters.""" + + ds: Any = None + + def __enter__(self) -> Self: + """Enter the context manager.""" + if self.ds is not None: + self.ds = self.ds.__enter__() + return self + + def __exit__( + self, + exc_type: object, + exc_val: object, + exc_tb: object, + ) -> None: + """Exit the context manager.""" + if exc_type is None: + self.finish() + + if self.ds is not None: + self.ds.__exit__(exc_type, exc_val, exc_tb) + + @abstractmethod + def finish(self) -> None: + """Post-processing when process is finished.""" + + +class _Exporter(_ImpExper): + """_Exporter class.""" + + path: Path + + def remove_static(self, list_name: str) -> None: + """Remove static components data.""" + + def remove_series(self, list_name: str, attr: str) -> None: + """Remove dynamic components data.""" + + @abstractmethod + def save_attributes(self, attrs: dict) -> None: + """Save generic network attributes.""" + + @abstractmethod + def save_meta(self, meta: dict) -> None: + """Save meta data (`n.meta`).""" + + @abstractmethod + def save_crs(self, crs: dict) -> None: + """Save CRS of shapes of network.""" + + @abstractmethod + def save_snapshots(self, snapshots: Sequence) -> None: + """Save snapshots data.""" + + @abstractmethod + def save_investment_periods(self, investment_periods: pd.Index) -> None: + """Save investment periods data.""" + + @abstractmethod + def save_scenarios(self, scenarios: pd.DataFrame) -> None: + """Save scenarios data.""" + + @abstractmethod + def save_static(self, list_name: str, df: pd.DataFrame) -> None: + """Save static components data.""" + + @abstractmethod + def save_series(self, list_name: str, attr: str, df: pd.DataFrame) -> None: + """Save dynamic components data.""" + + +class _Importer(_ImpExper): + """Importer class.""" + + def get_scenarios(self) -> pd.DataFrame | None: + return None + + +class _ImporterCSV(_Importer): + """Importer class for CSV files.""" + + def __init__(self, path: str | Path, encoding: str | None, quotechar: str) -> None: + """Initialize the importer for CSV files. + + Parameters + ---------- + path : str | Path + Path to the CSV folder. + encoding : str | None + Encoding to use for the CSV files. + quotechar : str + Quote character to use for the CSV files. + + """ + self.path = Path(path) + self.encoding = encoding + self.quotechar = quotechar + + if not self.path.is_dir(): + msg = f"Directory {path} does not exist." + raise FileNotFoundError(msg) + + def get_attributes(self) -> dict | None: + """Get generic network attributes.""" + fn = self.path.joinpath("network.csv") + if not fn.is_file(): + return None + + dtypes = {"pypsa_version": str, "name": str} + return dict( + pd.read_csv( + fn, encoding=self.encoding, dtype=dtypes, quotechar=self.quotechar + ).iloc[0] + ) + + def get_meta(self) -> dict: + """Get meta data (`n.meta`).""" + fn = self.path.joinpath("meta.json") + return {} if not fn.is_file() else json.loads(fn.open().read()) + + def get_crs(self) -> dict: + """Get CRS of shapes of network.""" + fn = self.path.joinpath("crs.json") + return {} if not fn.is_file() else json.loads(fn.open().read()) + + def get_snapshots(self) -> pd.Index: + """Get snapshots data.""" + fn = self.path.joinpath("snapshots.csv") + if not fn.is_file(): + return None + df = pd.read_csv( + fn, + index_col=0, + encoding=self.encoding, + quotechar=self.quotechar, + ) + + # Convert snapshot and timestep to datetime (if possible) + if "snapshot" in df and df.snapshot.iloc[0] != "now": + try: + df["snapshot"] = pd.to_datetime(df.snapshot) + except (ValueError, ParserError): + pass + if "timestep" in df and df.timestep.iloc[0] != "now": + try: + df["timestep"] = pd.to_datetime(df.timestep) + except (ValueError, ParserError): + pass + return df + + def get_investment_periods(self) -> pd.Series: + """Get investment periods data.""" + fn = self.path.joinpath("investment_periods.csv") + if not fn.is_file(): + return None + return pd.read_csv( + fn, index_col=0, encoding=self.encoding, quotechar=self.quotechar + ) + + def get_static(self, list_name: str) -> pd.DataFrame: + """Get static components data.""" + fn = self.path.joinpath(list_name + ".csv") + if not fn.is_file(): + return None + + df = pd.read_csv( + fn, index_col=0, encoding=self.encoding, quotechar=self.quotechar + ) + + # Convert NaN to empty strings for object dtype columns to handle custom attributes + object_cols = [col for col in df.columns if df[col].dtype == "object"] + if object_cols: + df[object_cols] = df[object_cols].fillna("") + + return df + + def get_series(self, list_name: str) -> Iterable[tuple[str, pd.DataFrame]]: + """Get dynamic components data.""" + for fn in self.path.iterdir(): + if fn.name.startswith(list_name + "-") and fn.name.endswith(".csv"): + attr = fn.name[len(list_name) + 1 : -4] + df = pd.read_csv( + self.path.joinpath(fn.name), + index_col=0, + encoding=self.encoding, + quotechar=self.quotechar, + ) + yield attr, df + + def finish(self) -> None: + """Finish the import process.""" + + +class _ExporterCSV(_Exporter): + """Exporter class for CSV files.""" + + def __init__(self, path: Path | str, encoding: str | None, quotechar: str) -> None: + """Initialize the exporter for CSV files. + + Parameters + ---------- + path : Path | str + Path to the CSV folder. + encoding : str | None + Encoding to use for the CSV files. + quotechar : str + Quote character to use for the CSV files. + + """ + self.path = Path(path) + self.encoding = encoding + self.quotechar = quotechar + + # make sure directory exists + if not self.path.is_dir(): + logger.warning("Directory %s does not exist, creating it", path) + self.path.mkdir() + + def save_attributes(self, attrs: dict) -> None: + """Save generic network attributes.""" + name = attrs.pop("name") + df = pd.DataFrame(attrs, index=pd.Index([name], name="name")) + fn = self.path.joinpath("network.csv") + with fn.open("w"): + df.to_csv(fn, encoding=self.encoding, quotechar=self.quotechar) + + def save_meta(self, meta: dict) -> None: + """Save meta data (`n.meta`).""" + fn = self.path.joinpath("meta.json") + fn.open("w").write(json.dumps(meta)) + + def save_crs(self, crs: dict) -> None: + """Save CRS of shapes of network.""" + fn = self.path.joinpath("crs.json") + fn.open("w").write(json.dumps(crs)) + + def save_snapshots(self, snapshots: pd.Index) -> None: + """Save snapshots data.""" + fn = self.path.joinpath("snapshots.csv") + with fn.open("w"): + snapshots.to_csv(fn, encoding=self.encoding, quotechar=self.quotechar) + + def save_investment_periods(self, investment_periods: pd.Index) -> None: + """Save investment periods data.""" + fn = self.path.joinpath("investment_periods.csv") + with fn.open("w"): + investment_periods.to_csv( + fn, encoding=self.encoding, quotechar=self.quotechar + ) + + def save_scenarios(self, scenarios: pd.DataFrame) -> None: + """Save scenarios data.""" + msg = "Stochastic networks are not supported in the CSV exporter. Use netcdf instead." + raise NotImplementedError(msg) + + def save_static(self, list_name: str, df: pd.DataFrame) -> None: + """Save static components data.""" + fn = self.path.joinpath(list_name + ".csv") + with fn.open("w"): + df.to_csv(fn, encoding=self.encoding, quotechar=self.quotechar) + + def save_series(self, list_name: str, attr: str, df: pd.DataFrame) -> None: + """Save dynamic components data.""" + fn = self.path.joinpath(list_name + "-" + attr + ".csv") + with fn.open("w"): + df.to_csv(fn, encoding=self.encoding, quotechar=self.quotechar) + + def remove_static(self, list_name: str) -> None: + """Remove static components data. + + Needed to not have stale sheets for empty components. + """ + if fns := list(self.path.joinpath(list_name).glob("*.csv")): + for fn in fns: + fn.unlink() + logger.warning("Stale csv file(s) %s removed", ", ".join(fns)) + + def remove_series(self, list_name: str, attr: str) -> None: + """Remove dynamic components data. + + Needed to not have stale sheets for empty components. + """ + fn = self.path.joinpath(list_name + "-" + attr + ".csv") + if fn.exists(): + fn.unlink() + + def finish(self) -> None: + """Finish the export process.""" + + +class _ImporterExcel(_Importer): + """Importer class for Excel files.""" + + def __init__(self, path: str | Path, engine: str = "calamine") -> None: + """Initialize the importer for Excel files. + + Parameters + ---------- + path : str | Path + Path to the Excel file. + engine : str + Engine to use for the Excel file. + + """ + if engine == "calamine": + check_optional_dependency( + "python_calamine", + "Missing optional dependencies to use Excel files. Install them via " + "`pip install pypsa[excel]`. If you passed any other engine, " + "make sure it is installed.", + ) + if not isinstance(path, (str | Path)): + msg = f"Invalid path type. Expected str or Path, got {type(path)}." + raise TypeError(msg) + + path = Path(path) + if not path.is_file(): + msg = f"Excel file {path} does not exist." + raise FileNotFoundError(msg) + self.engine = engine + + reader = partial(pd.read_excel, sheet_name=None, engine=self.engine) + if validators.url(str(path)): + self.sheets = _retrieve_from_url(str(path), reader) + else: + self.sheets = reader(path) + self.index: dict = {} + + def get_attributes(self) -> dict | None: + """Get generic network attributes.""" + try: + # Ensure name and pypsa_version are read as strings to prevent + # automatic type conversion (e.g., numeric names like "123") + df = self.sheets["network"] + if "name" in df.columns: + df["name"] = df["name"].astype(str) + + df["name"] = df["name"].replace("nan", "") + if "pypsa_version" in df.columns: + df["pypsa_version"] = df["pypsa_version"].astype(str) + return dict(df.iloc[0]) + except (ValueError, KeyError): + return None + + def get_meta(self) -> dict: + """Get meta data (`n.meta`).""" + try: + df = self.sheets["meta"] + if not df.empty: + meta = {} + for _, row in df.iterrows(): + key = row["Key"] + value = row["Value"] + + # Try to parse JSON strings back into dictionaries + if isinstance(value, str): + try: + value = json.loads(value) + except json.JSONDecodeError: + pass + + meta[key] = value + return meta + except (ValueError, KeyError): + return {} + else: + return {} + + def get_crs(self) -> dict: + """Get CRS of shapes of network.""" + try: + df = self.sheets["crs"] + if not df.empty: + # Assuming first column is keys and second column is values + return dict(zip(df.iloc[:, 0], df.iloc[:, 1], strict=False)) + except (ValueError, KeyError): + return {} + else: + return {} + + def get_snapshots(self) -> pd.Index: + """Get snapshots data.""" + try: + df = self.sheets["snapshots"] + except KeyError: + return None + df = df.set_index(df.columns[0]) + # Convert snapshot and timestep to datetime (if possible) + if "snapshot" in df and df.snapshot.iloc[0] != "now": + try: + df["snapshot"] = pd.to_datetime(df.snapshot) + except (ValueError, ParserError): + pass + if "timestep" in df and df.timestep.iloc[0] != "now": + try: + df["timestep"] = pd.to_datetime(df.timestep) + except (ValueError, ParserError): + pass + return df + + def get_investment_periods(self) -> pd.Series: + """Get investment periods data.""" + try: + df = self.sheets["investment_periods"] + df = df.set_index(df.columns[0]) + df.index = df.index.astype(int) + except (ValueError, KeyError): + return None + else: + return df + + def get_static(self, list_name: str) -> pd.DataFrame: + """Get static components data.""" + try: + df = self.sheets[list_name] + df = df.set_index(df.columns[0]) + + # Handle DataFrames with only index values that were exported from PyPSA + # Otherwise the column row is read in as a component + if len(df.columns) == 0 and len(df.index) > 0 and df.index[0] == "name": + df = df.iloc[1:] # Remove the first row which contains the index name + + # Convert NaN to empty strings for object dtype columns to handle custom attributes + object_cols = [col for col in df.columns if df[col].dtype == "object"] + if object_cols: + df[object_cols] = df[object_cols].fillna("") + + except (ValueError, KeyError): + return None + else: + return df + + def get_series(self, list_name: str) -> Iterable[tuple[str, pd.DataFrame]]: + """Get dynamic components data.""" + for sheet_name, df in self.sheets.items(): + if sheet_name.startswith(list_name + "-"): + sheet_name = _get_safe_excel_sheet_name(sheet_name) + attr = sheet_name[len(list_name) + 1 :] + df = df.set_index(df.columns[0]) + yield attr, df + + def finish(self) -> None: + """Finish the import process.""" + + +class _ExporterExcel(_Exporter): + """Exporter class for Excel files.""" + + def __init__(self, path: Path | str, engine: str = "openpyxl") -> None: + """Initialize the exporter for Excel files. + + Parameters + ---------- + path : Path | str + Path to save the Excel file. + engine : str + Engine to use for the Excel file. + + """ + if engine == "openpyxl": + check_optional_dependency( + "openpyxl", + "Missing optional dependencies to use Excel files. Install them via " + "`pip install pypsa[excel]`. If you passed any other engine, " + "make sure it is installed.", + ) + self.engine = engine + self.path = Path(path) + # Create an empty Excel file if it doesn't exist + if not self.path.exists(): + logger.warning("Excel file %s does not exist, creating it", path) + with pd.ExcelWriter(self.path, engine=self.engine) as writer: + pd.DataFrame().to_excel(writer, sheet_name="_temp") + + # Keep track of sheets to avoid overwriting + self._writer = None + + @property + def writer(self) -> pd.ExcelWriter: + """Get the Excel writer object. + + If the writer object is not already created, create it. + """ + if self._writer is None: + self._writer = pd.ExcelWriter( + self.path, + engine=self.engine, + mode="a" if self.path.exists() else "w", + if_sheet_exists="replace", + ) + return self._writer + + def save_attributes(self, attrs: dict) -> None: + """Save generic network attributes.""" + name = attrs.pop("name") + df = pd.DataFrame(attrs, index=pd.Index([name], name="name")) + df.to_excel(self.writer, sheet_name="network") + + def save_meta(self, meta: dict) -> None: + """Save meta data (`n.meta`).""" + # Convert meta dictionary to DataFrame with proper handling of nested dicts + meta_items = [] + for key, value in meta.items(): + # If value is a dict, serialize it as JSON + if isinstance(value, dict): + value = json.dumps(value) + meta_items.append([key, value]) + + df = pd.DataFrame(meta_items, columns=["Key", "Value"]) + df.to_excel(self.writer, sheet_name="meta", index=False) + + def save_crs(self, crs: dict) -> None: + """Save CRS of shapes of network.""" + df = pd.DataFrame(list(crs.items()), columns=["Key", "Value"]) + df.to_excel(self.writer, sheet_name="crs", index=False) + + def save_snapshots(self, snapshots: pd.Index) -> None: + """Save snapshots data.""" + snapshots.to_excel(self.writer, sheet_name="snapshots") + + def save_investment_periods(self, investment_periods: pd.Index) -> None: + """Save investment periods data.""" + investment_periods.to_excel(self.writer, sheet_name="investment_periods") + + def save_scenarios(self, scenarios: pd.DataFrame) -> None: + """Save scenarios data.""" + msg = "Stochastic networks are not supported in the Excel exporter. Use netcdf instead." + raise NotImplementedError(msg) + + def save_static(self, list_name: str, df: pd.DataFrame) -> None: + """Save static components data.""" + df.to_excel(self.writer, sheet_name=list_name) + + def save_series(self, list_name: str, attr: str, df: pd.DataFrame) -> None: + """Save dynamic components data.""" + sheet_name = f"{list_name}-{attr}" + sheet_name = _get_safe_excel_sheet_name(sheet_name) + df.to_excel(self.writer, sheet_name=sheet_name) + + def remove_static(self, list_name: str) -> None: + """Remove static components data. + + Needed to not have stale sheets for empty components. + + """ + if list_name in self.writer.book.sheetnames: + del self.writer.book[list_name] + logger.warning("Stale sheet %s removed", list_name) + + def remove_series(self, list_name: str, attr: str) -> None: + """Remove dynamic components data. + + Needed to not have stale sheets for empty components. + """ + sheet_name = f"{list_name}-{attr}" + sheet_name = _get_safe_excel_sheet_name(sheet_name) + if sheet_name in self.writer.book.sheetnames: + del self.writer.book[sheet_name] + logger.warning("Stale sheet %s removed", sheet_name) + + def finish(self) -> None: + """Postprocessing of exporting process.""" + # Remove temp sheet if it exists + if "_temp" in self.writer.book.sheetnames: + del self.writer.book["_temp"] + # Close writer + if self.writer is not None: + self.writer.close() + + +class _ImporterHDF5(_Importer): + """Importer class for HDF5 files.""" + + def __init__(self, path: str | pd.HDFStore) -> None: + """Initialize the importer for HDF5 files. + + Parameters + ---------- + path : str | pd.HDFStore + Path to the HDF5 file or an hdfstore object. + + """ + check_optional_dependency( + "tables", + "Missing optional dependencies to use HDF5 files. Install them via " + "`pip install pypsa[hdf5]` or `conda install -c conda-forge pypsa[hdf5]`.", + ) + self.path = path + self.ds: pd.HDFStore + if isinstance(path, (str | Path)): + reader = partial(pd.HDFStore, mode="r") + if validators.url(str(path)): + self.ds = _retrieve_from_url(str(path), reader) + else: + self.ds = reader(Path(path)) + + self.index: dict = {} + + def get_attributes(self) -> dict: + """Get generic network attributes.""" + return dict(self.ds["/network"].reset_index().iloc[0]) + + def get_meta(self) -> dict: + """Get meta data (`n.meta`).""" + return json.loads(self.ds["/meta"][0] if "/meta" in self.ds else "{}") + + def get_crs(self) -> dict: + """Get CRS of shapes of network.""" + return json.loads(self.ds["/crs"][0] if "/crs" in self.ds else "{}") + + def get_snapshots(self) -> pd.Series: + """Get snapshots data.""" + return self.ds["/snapshots"] if "/snapshots" in self.ds else None # noqa: SIM401 + + def get_investment_periods(self) -> pd.Series: + """Get investment periods data.""" + return ( + self.ds["/investment_periods"] if "/investment_periods" in self.ds else None # noqa: SIM401 + ) + + def get_static(self, list_name: str) -> pd.DataFrame: + """Get static components data.""" + if "/" + list_name not in self.ds: + return None + + df = self.ds["/" + list_name].set_index("name") + + self.index[list_name] = df.index + return df + + def get_series(self, list_name: str) -> Iterable[tuple[str, pd.DataFrame]]: + """Get dynamic components data.""" + for tab in self.ds: + if tab.startswith("/" + list_name + "_t/"): + attr = tab[len("/" + list_name + "_t/") :] + df = self.ds[tab] + df.columns = self.index[list_name][df.columns] + yield attr, df + + def finish(self) -> None: + """Finish the import process.""" + + +class _ExporterHDF5(_Exporter): + """Exporter class for HDF5 files.""" + + def __init__(self, path: str | Path, **kwargs: Any) -> None: + """Initialize exporter for HDF5 files. + + Parameters + ---------- + path : str | Path + Path to save the HDF5 file. + **kwargs : Any + Additional keyword arguments for the HDFStore. + + """ + check_optional_dependency( + "tables", + "Missing optional dependencies to use HDF5 files. Install them via " + "`pip install pypsa[hdf5]` or `conda install -c conda-forge pypsa[hdf5]`.", + ) + self.path = Path(path) + self._hdf5_handle = self.path.open("w") + self.ds = pd.HDFStore(self.path, mode="w", **kwargs) + self.index: dict = {} + + def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: + """Exit the context manager.""" + super().__exit__(exc_type, exc_val, exc_tb) + + def save_attributes(self, attrs: dict) -> None: + """Save generic network attributes.""" + name = attrs.pop("name") + self.ds.put( + "/network", + pd.DataFrame(attrs, index=pd.Index([name], name="name")), + format="table", + index=False, + ) + + def save_meta(self, meta: dict) -> None: + """Save meta data (`n.meta`).""" + self.ds.put("/meta", pd.Series(json.dumps(meta))) + + def save_crs(self, crs: dict) -> None: + """Save CRS of shapes of network.""" + self.ds.put("/crs", pd.Series(json.dumps(crs))) + + def save_snapshots(self, snapshots: Sequence) -> None: + """Save snapshots data.""" + self.ds.put("/snapshots", snapshots, format="table", index=False) + + def save_investment_periods(self, investment_periods: pd.Index) -> None: + """Save investment periods data.""" + self.ds.put( + "/investment_periods", + investment_periods, + format="table", + index=False, + ) + + def save_scenarios(self, scenarios: pd.DataFrame) -> None: + """Save scenarios data.""" + msg = "Stochastic networks are not supported in the HDF5 exporter. Use netcdf instead." + raise NotImplementedError(msg) + + def save_static(self, list_name: str, df: pd.DataFrame) -> None: + """Save a static components data.""" + df = df.rename_axis(index="name") + self.index[list_name] = df.index + df = df.reset_index() + self.ds.put("/" + list_name, df, format="table", index=False) + + def save_series(self, list_name: str, attr: str, df: pd.DataFrame) -> None: + """Save dynamic components data.""" + df = df.set_axis(self.index[list_name].get_indexer(df.columns), axis="columns") + self.ds.put("/" + list_name + "_t/" + attr, df, format="table", index=False) + + def finish(self) -> None: + """Postprocessing of exporting process.""" + self._hdf5_handle.close() + + +class _ImporterNetCDF(_Importer): + """Importer class for netCDF files.""" + + ds: xr.Dataset + + def __init__(self, path: str | Path | xr.Dataset) -> None: + """Initialize the importer for netCDF files. + + Parameters + ---------- + path : str | Path | xr.Dataset + Path to the netCDF file or an xarray.Dataset. + + """ + self.path = path + if isinstance(path, (str | Path)): + if validators.url(str(path)): + self.ds = _retrieve_from_url(str(path), xr.open_dataset) + else: + self.ds = xr.open_dataset(Path(path)) + else: + self.ds = path + + def __enter__(self) -> Self: + """Enter the context manager.""" + if isinstance(self.path, (str | Path)): + super().__init__() + return self + + def __exit__( + self, + exc_type: object, + exc_val: object, + exc_tb: object, + ) -> None: + """Exit the context manager.""" + if isinstance(self.path, (str | Path)): + super().__exit__(exc_type, exc_val, exc_tb) + + def get_attributes(self) -> dict: + """Get generic network attributes.""" + return { + attr[len("network_") :]: val + for attr, val in self.ds.attrs.items() + if attr.startswith("network_") + } + + def get_meta(self) -> dict: + """Get meta data (`n.meta`).""" + return json.loads(self.ds.attrs.get("meta", "{}")) + + def get_crs(self) -> dict: + """Get CRS of shapes of network.""" + return json.loads(self.ds.attrs.get("crs", "{}")) + + def get_snapshots(self) -> pd.DataFrame: + """Get snapshots data.""" + return self.get_static("snapshots", "snapshots") + + def get_investment_periods(self) -> pd.DataFrame: + """Get investment periods data.""" + return self.get_static("investment_periods", "investment_periods") + + def get_scenarios(self) -> pd.DataFrame: + """Get scenarios data.""" + if "scenario_weight" in self.ds: + df = self.ds["scenario_weight"].to_pandas().rename("weight").to_frame() + df.index.name = "scenario" + return df + + def get_static(self, list_name: str, index_name: str | None = None) -> pd.DataFrame: + """Get static components data.""" + t = list_name + "_" + i = len(t) + if index_name is None: + index_name = list_name + "_i" + if index_name not in self.ds.coords: + return None + df = pd.DataFrame() + for attr in self.ds.data_vars.keys(): + if attr.startswith(t) and attr[i : i + 2] != "t_": + loaded_df = self.ds[attr].to_pandas() + if isinstance(loaded_df, pd.DataFrame): + loaded_df = loaded_df.stack() + df[attr[i:]] = loaded_df + + if df.empty: + index = self.ds.coords[index_name].to_index().rename("name") + if "scenario" in self.ds.coords: + scenario_index = self.ds.coords["scenario"].to_index() + index = pd.MultiIndex.from_product([scenario_index, index]) + df = pd.DataFrame(index=index) + return df + + def get_series(self, list_name: str) -> Iterable[tuple[str, pd.DataFrame]]: + """Get dynamic components data.""" + t = list_name + "_t_" + for attr in self.ds.data_vars.keys(): + if attr.startswith(t): + try: + df = self.ds[attr].to_pandas() + # df.index.name = "name" + df.columns.name = "name" + # Handle multi-indexed (scenarios) + except ValueError: + df = ( + self.ds[attr] + .stack(combined=("scenario", attr + "_i")) + .to_pandas() + ) + df.columns.names = ["scenario", "name"] + + yield attr[len(t) :], df + + def finish(self) -> None: + """Finish the import process.""" + + +class _ExporterNetCDF(_Exporter): + """Exporter class for netCDF files.""" + + def __init__( + self, + path: Path | str | None, + compression: dict | None = None, + float32: bool = False, + ) -> None: + """Initialize exporter for netCDF files. + + Parameters + ---------- + path : str | None + Path to save the netCDF file. + compression : dict | None, default None + Compression settings for the netCDF file. + float32 : bool, default False + If True, typecast float64 to float32. + + """ + self.path = path + if compression is None: + compression = {"zlib": True, "complevel": 4} + self.compression = compression + self.float32 = float32 + self.ds = xr.Dataset() + + def save_attributes(self, attrs: dict) -> None: + """Save generic network attributes.""" + self.ds.attrs.update(("network_" + attr, val) for attr, val in attrs.items()) + + def save_meta(self, meta: dict) -> None: + """Save meta data (`n.meta`).""" + self.ds.attrs["meta"] = json.dumps(meta) + + def save_crs(self, crs: dict) -> None: + """Save CRS of shapes of network.""" + self.ds.attrs["crs"] = json.dumps(crs) + + def save_snapshots(self, snapshots: pd.Index) -> None: + """Save snapshots data.""" + snapshots = snapshots.rename_axis(index="snapshots") + for attr in snapshots.columns: + self.ds["snapshots_" + attr] = snapshots[attr] + + def save_investment_periods(self, investment_periods: pd.Index) -> None: + """Save investment periods data.""" + investment_periods = investment_periods.rename_axis(index="investment_periods") + for attr in investment_periods.columns: + self.ds["investment_periods_" + attr] = investment_periods[attr] + + def save_scenarios(self, scenarios: pd.Index) -> None: + """Save scenarios data.""" + for attr in scenarios.columns: + self.ds["scenario_" + attr] = scenarios[attr] + + def save_static(self, list_name: str, df: pd.DataFrame) -> None: + """Save a static components data.""" + df = df.rename_axis(index={"name": list_name + "_i"}) + self.ds[list_name + "_i"] = df.index.get_level_values( + list_name + "_i" + ).drop_duplicates() + + if not df.columns.empty: + df_array = df.to_xarray().rename( + {attr: list_name + "_" + attr for attr in df.columns} + ) + self.ds = self.ds.merge(df_array, overwrite_vars=True) + + def save_series(self, list_name: str, attr: str, df: pd.DataFrame) -> None: + """Save a dynamic components data.""" + df = df.rename_axis( + index="snapshots", columns={"name": list_name + "_t_" + attr + "_i"} + ) + self.ds[list_name + "_t_" + attr] = df.stack( + level=df.columns.names, future_stack=True + ).to_xarray() + + def set_compression_encoding(self) -> None: + """Set compression encoding for all variables.""" + logger.debug("Setting compression encodings: %s", self.compression) + for v in self.ds.data_vars: + if self.ds[v].dtype.kind not in ["U", "O"]: + self.ds[v].encoding.update(self.compression) + + def typecast_float32(self) -> None: + """Typecast float64 to float32 for all variables.""" + logger.debug("Typecasting float64 to float32.") + for v in self.ds.data_vars: + if self.ds[v].dtype == np.float64: + self.ds[v] = self.ds[v].astype(np.float32) + + def finish(self) -> None: + """Finish the export process. + + Runs post-processing, compression and saving to disk. + """ + if self.float32: + self.typecast_float32() + if self.compression: + self.set_compression_encoding() + if self.path is not None: + _path = Path(self.path) + with _path.open("w"): + self.ds.to_netcdf(_path) + + +def _sort_attrs( + axis_labels: pd.Index, attrs_list: Sequence[str] | pd.Index +) -> pd.Index: + """Order axis labels to match a desired attribute sequence. + + Parameters + ---------- + axis_labels : pandas.Index + Original axis labels that should be reordered. + attrs_list : Sequence[str] | pandas.Index + Desired ordering given as an ordered collection of attribute names. + + Returns + ------- + pandas.Index + `axis_labels` with the attributes appearing in `attrs_list` first and + in the same order. Attributes missing from `attrs_list` follow in their + original order while names not present in `axis_labels` are ignored. + + """ + if axis_labels.empty or len(attrs_list) == 0: + return axis_labels + + attrs_index = ( + attrs_list if isinstance(attrs_list, pd.Index) else pd.Index(attrs_list) + ) + existing = attrs_index.intersection(axis_labels, sort=False) + if existing.empty: + return axis_labels + + remaining = axis_labels.difference(attrs_index, sort=False) + target = existing.append(remaining) + + if axis_labels.equals(target): + return axis_labels + + return target + + +class NetworkIOMixin(_NetworkABC): + """Mixin class for network I/O methods. + + + + Class inherits to [pypsa.Network][]. All attributes and methods can be used + within any Network instance. + """ + + def _export_to_exporter( + self, + exporter: _Exporter, + quotechar: str = '"', + export_standard_types: bool = False, + ) -> None: + """Export to exporter. + + Both static and series attributes of components are exported, but only + if they have non-default values. + + Parameters + ---------- + exporter : _Exporter + Initialized exporter instance + quotechar : str, default '"' + String of length 1. Character used to denote the start and end of a + quoted item. Quoted items can include "," and it will be ignored + export_standard_types : boolean, default False + If True, then standard types are exported too (upon reimporting you + should then set "ignore_standard_types" when initialising the netowrk). + + """ + # exportable component types + allowed_types = (float, int, bool, str) + tuple(np.sctypeDict.values()) + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + message=r".*component_attrs is deprecated as of 1\.0 and will be removed in 2\.0\..*", + category=DeprecationWarning, + ) + + _attrs = { + attr: getattr(self, attr) + for attr in dir(self) + if ( + not attr.startswith("__") + and attr + not in { + "component_attrs", + "df", + "pnl", + "static", + "dynamic", + "iterate_components", + } # Skip deprecated methods + and isinstance(getattr(self, attr), allowed_types) + ) + } + _attrs = {} + for attr in dir(self): + if not attr.startswith("__") and attr not in { + "component_attrs", + "df", + "pnl", + "static", + "dynamic", + "iterate_components", + }: + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + message=r".*component_attrs is deprecated as of 1\.0 and will be removed in 2\.0\..*", + category=DeprecationWarning, + ) + value = getattr(self, attr) + if isinstance(value, allowed_types): + # TODO: This needs to be refactored with NetworkData class + # Skip properties without setter, but not 'pypsa_version' + prop = getattr(self.__class__, attr, None) + if ( + isinstance(prop, property) + and prop.fset is None + and attr not in ["pypsa_version"] + ): + continue + # Skip `_name` since it is writable + if attr in ["_name", "_pypsa_version"]: + continue + _attrs[attr] = value + exporter.save_attributes(_attrs) + + crs = {} + if self.crs is not None: + crs["_crs"] = self.crs.to_wkt() + exporter.save_crs(crs) + + exporter.save_meta(self.meta) + + # export snapshots + snapshots = self.snapshot_weightings.reset_index() + exporter.save_snapshots(snapshots) + + # export investment period weightings + if self.has_periods: + investment_periods = self.investment_period_weightings + exporter.save_investment_periods(investment_periods) + + # export scenarios + if self.has_scenarios: + exporter.save_scenarios(self.scenario_weightings) + + exported_components = [] + for component in self.all_components: + c = self.components[component] + list_name = c["list_name"] + attrs = c["defaults"] + + static = c.static + dynamic = c.dynamic + + if component == "Shape": + static = pd.DataFrame(static).assign( + geometry=static["geometry"].to_wkt() + ) + + if not export_standard_types and component in self.standard_type_components: + if isinstance(static.index, pd.MultiIndex): + static = static.drop(c["standard_types"].index, level="name") + else: + static = static.drop(c["standard_types"].index) + + col_export = [] + for col in static.columns: + # do not export derived attributes and object column of subnetwork + if col in ["g_pu", "b_pu"]: + continue + if ( + col in attrs.index + and pd.isnull(attrs.at[col, "default"]) + and pd.isnull(static[col]).all() + ): + continue + if ( + col in attrs.index + and static[col].dtype == attrs.at[col, "dtype"] + and (static[col] == attrs.at[col, "default"]).all() + ): + continue + + col_export.append(col) + + # first do static attributes + if static.empty: + exporter.remove_static(list_name) + continue + + static_export = static[col_export].copy() + # Stored SubNetwork obj column is not serializable + if "obj" in col_export and component == "SubNetwork": + static_export["obj"] = np.nan + + exporter.save_static(list_name, static_export) + + # now do varying attributes + for attr in dynamic: + if attr not in attrs.index: + col_export = dynamic[attr].columns + else: + default = attrs.at[attr, "default"] + + if pd.isnull(default): + col_export = dynamic[attr].columns[ + (~pd.isnull(dynamic[attr])).any() + ] + else: + col_export = dynamic[attr].columns[ + (dynamic[attr] != default).any() + ] + + if len(col_export) > 0: + static = dynamic[attr].reset_index()[col_export] + exporter.save_series(list_name, attr, static) + else: + exporter.remove_series(list_name, attr) + + exported_components.append(list_name) + + logger.info( + "Exported network '%s'%s contains: %s", + self.name, + f" saved to '{exporter.path}" if exporter.path else "", + ", ".join(exported_components), + ) + + def _import_from_importer( + self, importer: Any, basename: str, skip_time: bool = False + ) -> None: + """Import network data from importer. + + Parameters + ---------- + importer : Any + Importer to import from. + basename : str + Name of the network. + skip_time : bool + Skip importing time + + """ + # n.meta + self.meta = importer.get_meta() + + # n.crs + crs = importer.get_crs() + crs = crs.pop("_crs", None) + if crs is not None: + crs = CRS.from_wkt(crs) + self._crs = crs + + # other network attributes + attrs = importer.get_attributes() or {} + if "name" in attrs: + name = attrs.pop("name") + if pd.notna(name): + self.name = name + + if "pypsa_version" in attrs: + pypsa_version = parse_version(attrs.pop("pypsa_version", "0.0.0")) + else: + pypsa_version = parse_version("0.0.0") + + for attr, val in attrs.items(): + if attr in ["model", "objective", "objective_constant"]: + setattr(self, f"_{attr}", val) + else: + setattr(self, attr, val) + + ## https://docs.python.org/3/tutorial/datastructures.html#comparing-sequences-and-other-types + if pypsa_version < parse_version(__version_base__): + pypsa_version_str = str(pypsa_version) + logger.warning( + "Importing network from PyPSA version v%s while current version is v%s. Read the " + "release notes at `https://go.pypsa.org/release-notes` " + "to prepare your network for import.", + pypsa_version_str, + __version_base__, + ) + + # Check for newer PyPSA version available + update_msg = _check_for_update(__version_base__, "PyPSA", "pypsa") + if update_msg: + logger.info(update_msg) + + if pypsa_version < parse_version("0.18.0"): + self._multi_invest = 0 + + # if there is snapshots.csv, read in snapshot data + df = importer.get_snapshots() + + if df is not None: + if snapshot_levels := {"period", "timestep", "snapshot"}.intersection( + df.columns + ): + df = df.set_index(sorted(snapshot_levels)) + self.set_snapshots(df.index) + + cols = ["objective", "stores", "generators"] + if not df.columns.intersection(cols).empty: + # Preserve the default column order from Network.__init__ + existing_cols = [col for col in cols if col in df.columns] + self.snapshot_weightings = df.reindex( + index=self.snapshots, columns=existing_cols + ) + elif "weightings" in df.columns: + self.snapshot_weightings = df["weightings"].reindex(self.snapshots) + + # read in investment period weightings + periods = importer.get_investment_periods() + + if periods is not None and not periods.empty: + self.periods = periods.index + + self._investment_periods_data = periods.reindex(self.investment_periods) + + scenarios = importer.get_scenarios() + if scenarios is not None: + self.scenarios = scenarios + + imported_components = [] + + # now read in other components; make sure buses and carriers come first + for component in ["Bus", "Carrier"] + sorted( + self.all_components - {"Bus", "Carrier"} + ): + list_name = self.components[component]["list_name"] + + df = importer.get_static(list_name) + if df is None: + if component == "Bus": + logger.error("Error, no buses found") + return + continue + + if component == "Link": + _update_linkports_component_attrs(self, where=df) + + self._import_components_from_df(df, component) + + if not skip_time: + for attr, df in importer.get_series(list_name): + df.set_index(self.snapshots, inplace=True) + self._import_series_from_df(df, component, attr) + + logger.debug(getattr(self, list_name)) + + imported_components.append(list_name) + + for component in self.standard_type_components: + if self.has_scenarios and not isinstance( + self.components[component].static.index, pd.MultiIndex + ): + self.components[component].static = pd.concat( + dict.fromkeys(self.scenarios, self.components[component].static), + names=["scenario"], + ) + + logger.info( + "Imported network '%s' has %s", + self.name, + ", ".join(imported_components), + ) + + def import_from_csv_folder( + self, + path: str | Path, + encoding: str | None = None, + quotechar: str = '"', + skip_time: bool = False, + ) -> None: + """Import network data from CSVs in a folder. + + The CSVs must follow the standard form, see `pypsa/examples`. + + Parameters + ---------- + path : string + Name of folder + encoding : str, default None + Encoding to use for UTF when reading (ex. 'utf-8'). See [List of Python + standard encodings](https://docs.python.org/3/library/codecs.html#standard-encodings) + quotechar : str, default '"' + String of length 1. Character used to denote the start and end of a + quoted item. Quoted items can include "," and it will be ignored + skip_time : bool, default False + Skip reading in time dependent attributes + + Examples + -------- + >>> n = pypsa.Network() + >>> n.import_from_csv_folder"./my_network") # doctest: +SKIP + + """ + basename = Path(path).name + with _ImporterCSV(path, encoding=encoding, quotechar=quotechar) as importer: + self._import_from_importer(importer, basename=basename, skip_time=skip_time) + + def export_to_csv_folder( + self, + path: Path | str, + encoding: str | None = None, + quotechar: str = '"', + export_standard_types: bool = False, + ) -> None: + """Export network and components to a folder of CSVs. + + Both static and series attributes of all components are exported, but only + if they have non-default values. + + If `path` does not already exist, it is created. + + `path` may also be a cloud object storage URI if cloudpathlib is installed. + + Static attributes are exported in one CSV file per component, + e.g. `generators.csv`. + + Series attributes are exported in one CSV file per component per + attribute, e.g. `generators-p_set.csv`. + + Parameters + ---------- + path : Path | str + Name of folder to which to export. + encoding : str, default None + Encoding to use for UTF when reading (ex. 'utf-8'). See [List of Python + standard encodings](https://docs.python.org/3/library/codecs.html#standard-encodings) + quotechar : str, default '"' + String of length 1. Character used to quote fields. + export_standard_types : boolean, default False + If True, then standard types are exported too (upon reimporting you + should then set "ignore_standard_types" when initialising the network). + + Examples + -------- + >>> n.export_to_csv_folder("my_network") # doctest: +SKIP + + See Also + -------- + [pypsa.Network.export_to_netcdf][], [pypsa.Network.export_to_hdf5][], + [pypsa.Network.export_to_excel][] + + """ + with _ExporterCSV( + path=path, encoding=encoding, quotechar=quotechar + ) as exporter: + self._export_to_exporter( + exporter, export_standard_types=export_standard_types + ) + + def import_from_excel( + self, + path: str | Path, + skip_time: bool = False, + engine: str = "calamine", + ) -> None: + """Import network data from an Excel file. + + The Excel file must follow the standard form with appropriate sheets. + + Parameters + ---------- + path : string or Path + Path to the Excel file + skip_time : bool, default False + Skip reading in time dependent attributes + engine : string, default "calamine" + The engine to use for reading the Excel file. See [pandas.read_excel + ](https://pandas.pydata.org/docs/reference/api/pandas.read_excel.html). + + Examples + -------- + >>> n = pypsa.Network() + >>> n.import_from_excel("my_network.xlsx") # doctest: +SKIP + + """ + basename = Path(path).stem + with _ImporterExcel(path, engine=engine) as importer: + self._import_from_importer(importer, basename=basename, skip_time=skip_time) + + def export_to_excel( + self, + path: str | Path, + export_standard_types: bool = False, + engine: str = "openpyxl", + ) -> None: + """Export network and components to an Excel file. + + It is recommended to only use the Excel format if needed and for small networks. + Excel files are not as efficient as other formats and can be slow to read/write. + + Both static and series attributes of all components are exported, but only + if they have non-default values. + + If `path` does not already exist, it is created. + + Static attributes are exported in one sheet per component, + e.g. a sheet named `generators`. + + Series attributes are exported in one sheet per component per + attribute, e.g. a sheet named `generators-p_set`. + + Parameters + ---------- + path : string or Path + Path to the Excel file to which to export. + export_standard_types : boolean, default False + If True, then standard types are exported too (upon reimporting you + should then set "ignore_standard_types" when initialising the network). + engine : string, default "openpyxl" + The engine to use for writing the Excel file. See [pandas.ExcelWriter + ](https://pandas.pydata.org/docs/reference/api/pandas.ExcelWriter.html). + + Examples + -------- + >>> n.export_to_excel("my_network.xlsx") # doctest: +SKIP + + See Also + -------- + [pypsa.Network.export_to_netcdf][], [pypsa.Network.export_to_hdf5][], + [pypsa.Network.export_to_csv_folder][] + + """ + with _ExporterExcel(path, engine=engine) as exporter: + self._export_to_exporter( + exporter, export_standard_types=export_standard_types + ) + + def import_from_hdf5(self, path: str | Path, skip_time: bool = False) -> None: + """Import network data from HDF5 store at `path`. + + Parameters + ---------- + path : string, Path + Name of HDF5 store. The string could be a URL. + skip_time : bool, default False + Skip reading in time dependent attributes + + Examples + -------- + >>> n = pypsa.Network() + >>> n.import_from_hdf5("my_network.h5") # doctest: +SKIP + + """ + basename = Path(path).name + + with _ImporterHDF5(path) as importer: + self._import_from_importer(importer, basename=basename, skip_time=skip_time) + + def export_to_hdf5( + self, + path: Path | str, + export_standard_types: bool = False, + **kwargs: Any, + ) -> None: + """Export network and components to an HDF store. + + Both static and series attributes of components are exported, but only + if they have non-default values. + + If path does not already exist, it is created. + + `path` may also be a cloud object storage URI if cloudpathlib is installed. + + Parameters + ---------- + path : string + Name of hdf5 file to which to export (if it exists, it is overwritten) + export_standard_types : boolean, default False + If True, then standard types are exported too (upon reimporting you + should then set "ignore_standard_types" when initialising the network). + **kwargs + Extra arguments for pd.HDFStore to specify f.i. compression + (default: complevel=4) + + Examples + -------- + >>> n.export_to_hdf5("my_network.h5") # doctest: +SKIP + + See Also + -------- + [pypsa.Network.export_to_netcdf][], [pypsa.Network.export_to_csv_folder][], + [pypsa.Network.export_to_excel][] + + """ + kwargs.setdefault("complevel", 4) + + with _ExporterHDF5(path, **kwargs) as exporter: + self._export_to_exporter( + exporter, + export_standard_types=export_standard_types, + ) + + def import_from_netcdf( + self, path: str | Path | xr.Dataset, skip_time: bool = False + ) -> None: + """Import network data from netCDF file or xarray Dataset at `path`. + + `path` may also be a cloud object storage URI if cloudpathlib is installed. + + Parameters + ---------- + path : string | Path | xr.Dataset + Path to netCDF dataset or instance of xarray Dataset. + The string could be a URL. + skip_time : bool, default False + Skip reading in time dependent attributes + + Examples + -------- + >>> n = pypsa.Network() + >>> n.import_from_netcdf("my_network.nc") # doctest: +SKIP + + """ + basename = "" if isinstance(path, xr.Dataset) else Path(path).name + with _ImporterNetCDF(path=path) as importer: + self._import_from_importer(importer, basename=basename, skip_time=skip_time) + + def export_to_netcdf( + self, + path: Path | str | None = None, + export_standard_types: bool = False, + compression: dict | None = None, + float32: bool = False, + ) -> xr.Dataset: + r"""Export network and components to a netCDF file. + + Both static and series attributes of components are exported, but only + if they have non-default values. + + If path does not already exist, it is created. + + If no path is passed, no file is exported, but the xarray.Dataset + is still returned. + + Be aware that this cannot export boolean attributes on the Network + class, e.g. n.my_bool = False is not supported by netCDF. + + Parameters + ---------- + path : Path | string | None + Name of netCDF file to which to export (if it exists, it is overwritten); + if None is passed, no file is exported and only the xarray.Dataset is returned. + export_standard_types : boolean, default False + If True, then standard types are exported too (upon reimporting you + should then set "ignore_standard_types" when initialising the network). + compression : dict|None + Compression level to use for all features which are being prepared. + The compression is handled via xarray.Dataset.to_netcdf(...). For details see: + [xarray.Dataset.to\_netcdf](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.to_netcdf.html) + An example compression directive is `{'zlib': True, 'complevel': 4}`. + The default is None which disables compression. + float32 : boolean, default False + If True, typecasts values to float32. + + Returns + ------- + ds : xarray.Dataset + + Examples + -------- + >>> n = pypsa.Network() + >>> n.export_to_netcdf("my_file.nc") # doctest: +SKIP + + See Also + -------- + [pypsa.Network.export_to_hdf5][], [pypsa.Network.export_to_csv_folder][], + [pypsa.Network.export_to_excel][] + + """ + with _ExporterNetCDF(path, compression, float32) as exporter: + self._export_to_exporter( + exporter, export_standard_types=export_standard_types + ) + return exporter.ds + + def _import_components_from_df( + self, df: pd.DataFrame, cls_name: str, overwrite: bool = False + ) -> None: + """Import components from a pandas DataFrame. + + If columns are missing then defaults are used. + + If extra columns are added, these are left in the resulting component dataframe. + + Parameters + ---------- + df : pandas.DataFrame + A DataFrame whose index is the names of the components and + whose columns are the non-default attributes. + cls_name : string + Name of class of component, e.g. `"Line", "Bus", "Generator", "StorageUnit"` + overwrite : bool, default False + If True, overwrite existing components. + + """ + attrs = self.components[cls_name]["defaults"] + + static_attrs = attrs[attrs.static].drop("name") + non_static_attrs = attrs[~attrs.static] + + if cls_name == "Link": + _update_linkports_component_attrs(self, where=df) + + # Clean dataframe and ensure correct types + df = pd.DataFrame(df) + # Handle single and multi-index + df.index = ( + df.index.astype(str) + if not isinstance(df.index, pd.MultiIndex) + else df.index.set_levels([level.astype(str) for level in df.index.levels]) + ) + + # Fill nan values with default values + df = df.fillna(attrs["default"].to_dict()) + + for k in static_attrs.index: + if k not in df.columns: + df[k] = static_attrs.at[k, "default"] + else: + if static_attrs.at[k, "type"] == "string": + df[k] = df[k].replace({np.nan: ""}) + if static_attrs.at[k, "type"] == "int": + df[k] = df[k].fillna(0) + if df[k].dtype != static_attrs.at[k, "typ"]: + if static_attrs.at[k, "type"] == "geometry": + geometry = df[k].replace({"": None, np.nan: None}) + from shapely.geometry.base import BaseGeometry # noqa: PLC0415 + + if geometry.apply(lambda x: isinstance(x, BaseGeometry)).all(): + df[k] = gpd.GeoSeries(geometry) + else: + df[k] = gpd.GeoSeries.from_wkt(geometry) + else: + df[k] = df[k].astype(static_attrs.at[k, "typ"]) + + non_static_attrs_in_df = non_static_attrs.index.intersection(df.columns) + old_static = self.c[cls_name].static + new_static = df.drop(non_static_attrs_in_df, axis=1) + + # Handle duplicates + duplicated_components = old_static.index.intersection(new_static.index) + if len(duplicated_components) > 0: + if not overwrite: + logger.warning( + "The following %s are already defined and will be skipped " + "(use overwrite=True to overwrite): %s", + self.components[cls_name]["list_name"], + ", ".join(duplicated_components), + ) + new_static = new_static.drop(duplicated_components) + else: + old_static = old_static.drop(duplicated_components) + + # Concatenate to new dataframe + if not old_static.empty: + new_static = pd.concat((old_static, new_static), sort=False) + + if cls_name == "Shape": + new_static = gpd.GeoDataFrame(new_static, crs=self.crs) + + # Align index (component names) and columns (attributes) + ordered_columns = _sort_attrs(new_static.columns, attrs.index) + if not new_static.columns.equals(ordered_columns): + if isinstance(new_static.columns, pd.MultiIndex): + new_static = new_static.loc[:, ordered_columns] + else: + indexer = new_static.columns.get_indexer(ordered_columns) + if (indexer >= 0).all(): + new_static = new_static.iloc[:, indexer] + else: + new_static = new_static.loc[:, ordered_columns] + + new_static.index.names = ( + ["name"] + if not isinstance(new_static.index, pd.MultiIndex) + else ["scenario", "name"] + ) + self.components[cls_name].static = new_static + + # Now deal with time-dependent properties + + dynamic = self.c[cls_name].dynamic + + for k in non_static_attrs_in_df: + # If reading in outputs, fill the outputs + dynamic[k] = dynamic[k].reindex( + columns=new_static.index, fill_value=non_static_attrs.at[k, "default"] + ) + if overwrite: + dynamic[k].loc[:, df.index] = df.loc[:, k].values + else: + new_components = df.index.difference(duplicated_components) + dynamic[k].loc[:, new_components] = df.loc[new_components, k].values + + self.components[cls_name].dynamic = dynamic + + # Run consistency checks + check_for_unknown_buses(self, self.c[cls_name]) + + def _import_series_from_df( + self, + df: pd.DataFrame, + cls_name: str, + attr: str, + overwrite: bool = False, + ) -> None: + """Import time series from a pandas DataFrame. + + Parameters + ---------- + df : pandas.DataFrame + A DataFrame whose index is `n.snapshots` and + whose columns are a subset of the relevant components. + cls_name : string + Name of class of component + attr : string + Name of time-varying series attribute + overwrite : bool, default False + If True, overwrite existing time series. + + """ + static = self.c[cls_name].static + dynamic = self.c[cls_name].dynamic + list_name = self.components[cls_name]["list_name"] + + if not overwrite: + try: + df = df.drop(df.columns.intersection(dynamic[attr].columns), axis=1) + except KeyError: + pass # Don't drop any columns if the data doesn't exist yet + + # df.columns.names = ["name"] if not isinstance(df.index, pd.MultiIndex) else ["scenario", "name"] + if isinstance(df.columns, pd.MultiIndex): + df.columns.names = ["scenario", "name"] + else: + df.columns.names = ["name"] + + # Check if components exist in static df + diff = df.columns.difference(static.index) + if len(diff) > 0: + logger.warning( + "Components %s for attribute %s of %s are not in main components dataframe %s", + diff, + attr, + cls_name, + list_name, + ) + + # Get all attributes for the component + attrs = self.components[cls_name]["defaults"] + + # Add all unknown attributes to the dataframe without any checks + expected_attrs = attrs[lambda ds: ds.type.str.contains("series")].index + if attr not in expected_attrs: + if overwrite or attr not in dynamic: + dynamic[attr] = df + return + + # Check if any snapshots are missing + diff = self.snapshots.difference(df.index) + if len(diff): + logger.warning( + "Snapshots %s are missing from %s of %s. Filling with default value '%s'", + diff, + attr, + cls_name, + attrs.loc[attr].default, + ) + df = df.reindex(self.snapshots, fill_value=attrs.loc[attr].default) + + if not attrs.loc[attr].static: + # Preserve static component order for consistency + ordered_columns = _sort_attrs( + df.columns.union(static.index), + static.index, + ) + dynamic[attr] = dynamic[attr].reindex( + columns=ordered_columns, + fill_value=attrs.loc[attr].default, + ) + else: + # Preserve existing dynamic order for static attrs + ordered_columns = _sort_attrs( + df.columns.union(dynamic[attr].columns), + dynamic[attr].columns, + ) + dynamic[attr] = dynamic[attr].reindex(columns=ordered_columns) + + dynamic[attr].loc[self.snapshots, df.columns] = df.loc[ + self.snapshots, df.columns + ] + + def import_from_pypower_ppc( + self, ppc: dict, overwrite_zero_s_nom: float | None = None + ) -> None: + """Import network from PYPOWER PPC dictionary format version 2. + + Converts all baseMVA to base power of 1 MVA. + + For the meaning of the pypower indices, see also pypower/idx_*. + + Parameters + ---------- + ppc : PYPOWER PPC dict + PYPOWER PPC dictionary to import from. + overwrite_zero_s_nom : Float or None, default None + If a float, all branches with s_nom of 0 will be set to this value. + + Examples + -------- + >>> from pypower.api import case30 # doctest: +SKIP + >>> ppc = case30() # doctest: +SKIP + >>> n.import_from_pypower_ppc(ppc) # doctest: +SKIP + + """ + version = ppc["version"] + if int(version) != 2: + logger.warning( + "Warning, importing from PYPOWER may not work if PPC version is not 2!" + ) + + logger.warning( + "Warning: Note that when importing from PYPOWER, some PYPOWER features not supported: areas, gencosts, component status" + ) + + baseMVA = ppc["baseMVA"] + + # add buses + + # integer numbering will be bus names + index = np.array(ppc["bus"][:, 0], dtype=int) + + columns = [ + "type", + "Pd", + "Qd", + "Gs", + "Bs", + "area", + "v_mag_pu_set", + "v_ang_set", + "v_nom", + "zone", + "v_mag_pu_max", + "v_mag_pu_min", + ] + + pdf = { + "buses": pd.DataFrame( + index=index, + columns=columns, + data=ppc["bus"][:, 1 : len(columns) + 1], + ) + } + if (pdf["buses"]["v_nom"] == 0.0).any(): + logger.warning( + "Warning, some buses have nominal voltage of 0., setting the nominal voltage of these to 1." + ) + pdf["buses"].loc[pdf["buses"]["v_nom"] == 0.0, "v_nom"] = 1.0 + + # rename controls + controls = ["", "PQ", "PV", "Slack"] + pdf["buses"]["control"] = ( + pdf["buses"].pop("type").map(lambda i: controls[int(i)]) + ) + + # add loads for any buses with Pd or Qd + pdf["loads"] = pdf["buses"].loc[ + pdf["buses"][["Pd", "Qd"]].any(axis=1), ["Pd", "Qd"] + ] + pdf["loads"]["bus"] = pdf["loads"].index + pdf["loads"].rename(columns={"Qd": "q_set", "Pd": "p_set"}, inplace=True) + pdf["loads"].index = [f"L{str(i)}" for i in range(len(pdf["loads"]))] + + # add shunt impedances for any buses with Gs or Bs + + shunt = pdf["buses"].loc[ + pdf["buses"][["Gs", "Bs"]].any(axis=1), ["v_nom", "Gs", "Bs"] + ] + + # base power for shunt is 1 MVA, so no need to rebase here + shunt["g"] = shunt["Gs"] / shunt["v_nom"] ** 2 + shunt["b"] = shunt["Bs"] / shunt["v_nom"] ** 2 + pdf["shunt_impedances"] = shunt.reindex(columns=["g", "b"]) + pdf["shunt_impedances"]["bus"] = pdf["shunt_impedances"].index + pdf["shunt_impedances"].index = [ + f"S{str(i)}" for i in range(len(pdf["shunt_impedances"])) + ] + + # add gens + + # it is assumed that the pypower p_max is the p_nom + + # could also do gen.p_min_pu = p_min/p_nom + + columns = [ + "bus", + "p_set", + "q_set", + "q_max", + "q_min", + "v_set_pu", + "mva_base", + "status", + "p_nom", + "p_min", + "Pc1", + "Pc2", + "Qc1min", + "Qc1max", + "Qc2min", + "Qc2max", + "ramp_agc", + "ramp_10", + "ramp_30", + "ramp_q", + "apf", + ] + + index_list = [f"G{str(i)}" for i in range(len(ppc["gen"]))] + + pdf["generators"] = pd.DataFrame( + index=index_list, columns=columns, data=ppc["gen"][:, : len(columns)] + ) + + # make sure bus name is an integer + pdf["generators"]["bus"] = np.array(ppc["gen"][:, 0], dtype=int) + + # add branchs + ## branch data + # fbus, tbus, r, x, b, rateA, rateB, rateC, ratio, angle, status, angmin, angmax + + columns = [ + "bus0", + "bus1", + "r", + "x", + "b", + "s_nom", + "rateB", + "rateC", + "tap_ratio", + "phase_shift", + "status", + "v_ang_min", + "v_ang_max", + ] + + pdf["branches"] = pd.DataFrame( + columns=columns, data=ppc["branch"][:, : len(columns)] + ) + + pdf["branches"]["original_index"] = pdf["branches"].index + + pdf["branches"]["bus0"] = pdf["branches"]["bus0"].astype(int) + pdf["branches"]["bus1"] = pdf["branches"]["bus1"].astype(int) + + # s_nom = 0 indicates an unconstrained line + zero_s_nom = pdf["branches"]["s_nom"] == 0.0 + if zero_s_nom.any(): + if overwrite_zero_s_nom is not None: + pdf["branches"].loc[zero_s_nom, "s_nom"] = overwrite_zero_s_nom + else: + logger.warning( + "Warning: there are %d branches with s_nom equal to zero, they will probably lead to infeasibilities and should be replaced with a high value using the `overwrite_zero_s_nom` argument.", + zero_s_nom.sum(), + ) + + # determine bus voltages of branches to detect transformers + v_nom = pdf["branches"].bus0.map(pdf["buses"].v_nom) + v_nom_1 = pdf["branches"].bus1.map(pdf["buses"].v_nom) + + # split branches into transformers and lines + transformers = ( + (v_nom != v_nom_1) + | ( + (pdf["branches"].tap_ratio != 0.0) & (pdf["branches"].tap_ratio != 1.0) + ) # NB: PYPOWER has strange default of 0. for tap ratio + | (pdf["branches"].phase_shift != 0) + ) + pdf["transformers"] = pd.DataFrame(pdf["branches"][transformers]) + pdf["lines"] = pdf["branches"][~transformers].drop( + ["tap_ratio", "phase_shift"], axis=1 + ) + + # convert transformers from base baseMVA to base s_nom + pdf["transformers"]["r"] = ( + pdf["transformers"]["r"] * pdf["transformers"]["s_nom"] / baseMVA + ) + pdf["transformers"]["x"] = ( + pdf["transformers"]["x"] * pdf["transformers"]["s_nom"] / baseMVA + ) + pdf["transformers"]["b"] = ( + pdf["transformers"]["b"] * baseMVA / pdf["transformers"]["s_nom"] + ) + + # correct per unit impedances + pdf["lines"]["r"] = v_nom**2 * pdf["lines"]["r"] / baseMVA + pdf["lines"]["x"] = v_nom**2 * pdf["lines"]["x"] / baseMVA + pdf["lines"]["b"] = pdf["lines"]["b"] * baseMVA / v_nom**2 + + if (pdf["transformers"]["tap_ratio"] == 0.0).any(): + logger.warning( + "Warning, some transformers have a tap ratio of 0., setting the tap ratio of these to 1." + ) + pdf["transformers"].loc[ + pdf["transformers"]["tap_ratio"] == 0.0, "tap_ratio" + ] = 1.0 + + # name them nicely + pdf["transformers"].index = [ + f"T{str(i)}" for i in range(len(pdf["transformers"])) + ] + pdf["lines"].index = [f"L{str(i)}" for i in range(len(pdf["lines"]))] + + # TODO + + ##----- OPF Data -----## + ## generator cost data + # 1 startup shutdown n x1 y1 ... xn yn + # 2 startup shutdown n c(n-1) ... c0 + + for component in [ + "Bus", + "Load", + "Generator", + "Line", + "Transformer", + "ShuntImpedance", + ]: + self.add( + component, + pdf[self.components[component]["list_name"]].index, + **pdf[self.components[component]["list_name"]], + ) + + self.c.generators.static["control"] = self.c.generators.static.bus.map( + self.c.buses.static["control"] + ) + + # for consistency with pypower, take the v_mag set point from the generators + self.c.buses.static.loc[self.c.generators.static.bus, "v_mag_pu_set"] = ( + np.asarray(self.c.generators.static["v_set_pu"]) + ) + + def import_from_pandapower_net( + self, + net: pandapowerNet, + extra_line_data: bool = False, + use_pandapower_index: bool = False, + ) -> None: + """Import PyPSA network from pandapower net. + + Importing from pandapower is still in beta; + not all pandapower components are supported. + + Unsupported features include: + - three-winding transformers + - switches + - in_service status and + - tap positions of transformers + + Parameters + ---------- + net : pandapower network + pandapower network to import from. + extra_line_data : boolean, default: False + if True, the line data for all parameters is imported instead of only the type + use_pandapower_index : boolean, default: False + if True, use integer numbers which is the pandapower index standard + if False, use any net.name as index (e.g. 'Bus 1' (str) or 1 (int)) + + Examples + -------- + >>> n.import_from_pandapower_net(net) # doctest: +SKIP + OR + + >>> import pandapower as pp # doctest: +SKIP + >>> import pandapower.networks as pn # doctest: +SKIP + >>> net = pn.create_cigre_network_mv(with_der='all') # doctest: +SKIP + >>> n = pypsa.Network() + >>> n.import_from_pandapower_net(net, extra_line_data=True) # doctest: +SKIP + + """ + logger.warning( + "Warning: Importing from pandapower is still in beta; not all pandapower data is supported.\nUnsupported features include: three-winding transformers, switches, in_service status, shunt impedances and tap positions of transformers." + ) + + d = { + "Bus": pd.DataFrame( + {"v_nom": net.bus.vn_kv.values, "v_mag_pu_set": 1.0}, + index=net.bus.name, + ) + } + + d["Bus"].loc[net.bus.name.loc[net.gen.bus].values, "v_mag_pu_set"] = ( + net.gen.vm_pu.values # fmt: skip + ) + + d["Bus"].loc[net.bus.name.loc[net.ext_grid.bus].values, "v_mag_pu_set"] = ( + net.ext_grid.vm_pu.values # fmt: skip + ) + + d["Load"] = pd.DataFrame( + { + "p_set": (net.load.scaling * net.load.p_mw).values, + "q_set": (net.load.scaling * net.load.q_mvar).values, + "bus": net.bus.name.loc[net.load.bus].values, + }, + index=net.load.name, + ) + + # deal with PV generators + _tmp_gen = pd.DataFrame( + { + "p_set": (net.gen.scaling * net.gen.p_mw).values, + "q_set": 0.0, + "bus": net.bus.name.loc[net.gen.bus].values, + "control": "PV", + }, + index=net.gen.name, + ) + + # deal with PQ "static" generators + _tmp_sgen = pd.DataFrame( + { + "p_set": (net.sgen.scaling * net.sgen.p_mw).values, + "q_set": (net.sgen.scaling * net.sgen.q_mvar).values, + "bus": net.bus.name.loc[net.sgen.bus].values, + "control": "PQ", + }, + index=net.sgen.name, + ) + + _tmp_ext_grid = pd.DataFrame( + { + "control": "Slack", + "p_set": 0.0, + "q_set": 0.0, + "bus": net.bus.name.loc[net.ext_grid.bus].values, + }, + index=net.ext_grid.name.fillna("External Grid"), + ) + + # concat all generators and index according to option + d["Generator"] = pd.concat( + [_tmp_gen, _tmp_sgen, _tmp_ext_grid], ignore_index=use_pandapower_index + ) + + if extra_line_data is False: + d["Line"] = pd.DataFrame( + { + "type": net.line.std_type.values, + "bus0": net.bus.name.loc[net.line.from_bus].values, + "bus1": net.bus.name.loc[net.line.to_bus].values, + "length": net.line.length_km.values, + "num_parallel": net.line.parallel.values, + }, + index=net.line.name, + ) + else: + r = net.line.r_ohm_per_km.values * net.line.length_km.values + x = net.line.x_ohm_per_km.values * net.line.length_km.values + # capacitance values from pandapower in nF; transformed here: + f = net.f_hz + b = net.line.c_nf_per_km.values * net.line.length_km.values * 1e-9 + b = b * 2 * math.pi * f + + u = net.bus.vn_kv.loc[net.line.from_bus].values + s_nom = u * net.line.max_i_ka.values + + d["Line"] = pd.DataFrame( + { + "r": r, + "x": x, + "b": b, + "s_nom": s_nom, + "bus0": net.bus.name.loc[net.line.from_bus].values, + "bus1": net.bus.name.loc[net.line.to_bus].values, + "length": net.line.length_km.values, + "num_parallel": net.line.parallel.values, + }, + index=net.line.name, + ) + + # check, if the trafo is based on a standard-type: + if net.trafo.std_type.any(): + d["Transformer"] = pd.DataFrame( + { + "type": net.trafo.std_type.values, + "bus0": net.bus.name.loc[net.trafo.hv_bus].values, + "bus1": net.bus.name.loc[net.trafo.lv_bus].values, + "tap_position": net.trafo.tap_pos.values, + }, + index=net.trafo.name, + ) + else: + s_nom = net.trafo.sn_mva.values + + # documented at https://pandapower.readthedocs.io/en/develop/elements/trafo.html?highlight=transformer#impedance-values + z = net.trafo.vk_percent.values / 100.0 / net.trafo.sn_mva.values + r = net.trafo.vkr_percent.values / 100.0 / net.trafo.sn_mva.values + x = np.sqrt(z**2 - r**2) + + y = net.trafo.i0_percent.values / 100.0 + g = ( + net.trafo.pfe_kw.values + / net.trafo.sn_mva.values + / 1000 + / net.trafo.sn_mva.values + ) + b = np.sqrt(y**2 - g**2) + + d["Transformer"] = pd.DataFrame( + { + "phase_shift": net.trafo.shift_degree.values, + "s_nom": s_nom, + "bus0": net.bus.name.loc[net.trafo.hv_bus].values, + "bus1": net.bus.name.loc[net.trafo.lv_bus].values, + "r": r, + "x": x, + "g": g, + "b": b, + "tap_position": net.trafo.tap_pos.values, + }, + index=net.trafo.name, + ) + d["Transformer"] = d["Transformer"].fillna(0) + + # documented at https://docs.pypsa.org/latest/user-guide/components/shunt-impedances + g_shunt = net.shunt.p_mw.values / net.shunt.vn_kv.values**2 + b_shunt = net.shunt.q_mvar.values / net.shunt.vn_kv.values**2 + + d["ShuntImpedance"] = pd.DataFrame( + { + "bus": net.bus.name.loc[net.shunt.bus].values, + "g": g_shunt, + "b": b_shunt, + }, + index=net.shunt.name, + ) + d["ShuntImpedance"] = d["ShuntImpedance"].fillna(0) + + for component_name in [ + "Bus", + "Load", + "Generator", + "Line", + "Transformer", + "ShuntImpedance", + ]: + self.add(component_name, d[component_name].index, **d[component_name]) + + # amalgamate buses connected by closed switches + + bus_switches = net.switch[(net.switch.et == "b") & net.switch.closed] + + bus_switches["stays"] = bus_switches.bus.map(net.bus.name) + bus_switches["goes"] = bus_switches.element.map(net.bus.name) + + to_replace = pd.Series(bus_switches.stays.values, bus_switches.goes.values) + + for i in to_replace.index: + self.remove("Bus", i) + + for component in self.components[["Generator", "Load", "ShuntImpedance"]]: + if component.empty: + continue + component.static.replace({"bus": to_replace}, inplace=True) + + for component in self.components[["Line", "Transformer"]]: + if component.empty: + continue + component.static.replace({"bus0": to_replace}, inplace=True) + component.static.replace({"bus1": to_replace}, inplace=True) diff --git a/PyPSA/source/pypsa/network/power_flow.py b/PyPSA/source/pypsa/network/power_flow.py new file mode 100644 index 0000000000000000000000000000000000000000..3ee79ce9675263e8135c035384de23731675ebbd --- /dev/null +++ b/PyPSA/source/pypsa/network/power_flow.py @@ -0,0 +1,1842 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Power flow functionality.""" + +from __future__ import annotations + +import logging +from abc import abstractmethod +from collections.abc import Sequence +from operator import itemgetter +from typing import TYPE_CHECKING, Any + +import networkx as nx +import numpy as np +import pandas as pd +from numpy import ones, r_ +from numpy.linalg import norm +from scipy.sparse import csc_matrix, csr_matrix, dok_matrix, issparse +from scipy.sparse import hstack as shstack +from scipy.sparse import vstack as svstack +from scipy.sparse.linalg import spsolve + +from pypsa.common import as_index, deprecated_common_kwargs +from pypsa.definitions.structures import Dict +from pypsa.descriptors import _update_linkports_component_attrs +from pypsa.network.abstract import _NetworkABC + +if TYPE_CHECKING: + from collections.abc import Callable + + from components import Network, SubNetwork + from scipy.sparse import spmatrix + + from pypsa.components.store import ComponentsStore + + +def zsum(s: pd.Series, *args: Any, **kwargs: Any) -> Any: + """Define a custom zsum function. + + Pandas 0.21.0 changes sum() behavior so that the result of applying sum + over an empty DataFrame is NaN. + + Meant to be set as pd.Series.zsum = zsum. + """ + # TODO Remove + return 0 if s.empty else s.sum(*args, **kwargs) + + +pd.Series.zsum = zsum + +logger = logging.getLogger(__name__) + + +def normed(s: pd.Series) -> pd.Series: + """Normalize a series by its sum.""" + return s / s.sum() + + +def real(X: pd.Series) -> pd.Series: + """Return the real part of a series.""" + return np.real(X.to_numpy()) + + +def imag(X: pd.Series) -> pd.Series: + """Return the imaginary part of a series.""" + return np.imag(X.to_numpy()) + + +def _allocate_pf_outputs(n: Network, linear: bool = False) -> None: + to_allocate = { + "Generator": ["p"], + "Load": ["p"], + "StorageUnit": ["p"], + "Store": ["p"], + "ShuntImpedance": ["p"], + "Bus": ["p", "v_ang", "v_mag_pu"], + "Line": ["p0", "p1"], + "Transformer": ["p0", "p1"], + "Link": ["p" + col[3:] for col in n.c.links.static.columns if col[:3] == "bus"], + } + + if not linear: + for component, attrs in to_allocate.items(): + if "p" in attrs: + attrs.append("q") + if "p0" in attrs and component != "Link": + attrs.extend(["q0", "q1"]) + + allocate_series_dataframes(n, to_allocate) + + +def _calculate_controllable_nodal_power_balance( + sub_network: SubNetwork, + network: Network, + snapshots: Sequence, + buses_o: Sequence, +) -> None: + for power in ("q", "p"): + # allow all one ports to dispatch as set + for c in sub_network.components: + if c.name not in network.controllable_one_port_components: + continue + c_n_set = network.get_switchable_as_dense( + c.name, + power + "_set", + snapshots, + c.static.query("active").index, + ) + network.c[c.name].dynamic[power].loc[ + snapshots, c.static.query("active").index + ] = c_n_set + + # set the power injection at each node from controllable components + network.c.buses.dynamic[power].loc[snapshots, buses_o] = sum( + ( + ( + c.dynamic[power].loc[snapshots, c.static.query("active").index] + * c.static.loc[c.static.query("active").index, "sign"] + ) + .T.groupby(c.static.loc[c.static.query("active").index, "bus"]) + .sum() + .T.reindex(columns=buses_o, fill_value=0.0) + ) + for c in sub_network.components[network.controllable_one_port_components] + if not c.empty + ) + + if power == "p": + network.c.buses.dynamic[power].loc[snapshots, buses_o] += sum( + -c.dynamic[power + str(i)] + .loc[snapshots] + .T.groupby(c.static[f"bus{str(i)}"]) + .sum() + .T.reindex(columns=buses_o, fill_value=0) + for c in network.components[network.controllable_branch_components] + for i in [int(col[3:]) for col in c.static.columns if col[:3] == "bus"] + if not c.empty + ) + + +def _network_prepare_and_run_pf( + n: Network, + snapshots: Sequence | None, + skip_pre: bool, + linear: bool = False, + distribute_slack: bool = False, + slack_weights: str = "p_set", + **kwargs: Any, +) -> Dict | None: + # TODO this needs to be refactored + if linear: + sub_network_pf_fun: Callable = SubNetworkPowerFlowMixin.lpf + sub_network_prepare_fun: Callable = SubNetworkPowerFlowMixin.calculate_B_H + else: + sub_network_pf_fun: Callable = SubNetworkPowerFlowMixin.pf # type: ignore + sub_network_prepare_fun: Callable = SubNetworkPowerFlowMixin.calculate_Y # type: ignore + + if not skip_pre: + n.determine_network_topology() + n.calculate_dependent_values() + _allocate_pf_outputs(n, linear) + + sns = as_index(n, snapshots, "snapshots") + + # deal with links + if not n.c.links.static.empty: + p_set = n.get_switchable_as_dense("Link", "p_set", sns) + n.c.links.dynamic.p0.loc[sns] = p_set.loc[sns] + for i in ["1"] + n.c.links.additional_ports: + eff_name = "efficiency" if i == "1" else f"efficiency{i}" + efficiency = n.get_switchable_as_dense("Link", eff_name, sns) + links = n.c.links.static.index[n.c.links.static[f"bus{i}"] != ""] + n.c.links.dynamic[f"p{i}"].loc[sns, links] = ( + -n.c.links.dynamic.p0.loc[sns, links] * efficiency.loc[sns, links] + ) + + itdf = pd.DataFrame(index=sns, columns=n.c.sub_networks.static.index, dtype=int) + difdf = pd.DataFrame(index=sns, columns=n.c.sub_networks.static.index) + cnvdf = pd.DataFrame(index=sns, columns=n.c.sub_networks.static.index, dtype=bool) + for sub_network in n.c.sub_networks.static.obj: + if not skip_pre: + sub_network.find_bus_controls() + + branches_i = sub_network.branches_i(active_only=True) + if len(branches_i) > 0: + sub_network_prepare_fun(sub_network, skip_pre=True) + + if isinstance(slack_weights, dict): + sn_slack_weights = slack_weights[sub_network.name] + else: + sn_slack_weights = slack_weights + + if isinstance(sn_slack_weights, dict): + sn_slack_weights = pd.Series(sn_slack_weights) + + if linear: + sub_network_pf_fun(sub_network, snapshots=sns, skip_pre=True, **kwargs) + + elif len(sub_network.c.buses.static) <= 1: + ( + itdf[sub_network.name], + difdf[sub_network.name], + cnvdf[sub_network.name], + ) = sub_network_pf_singlebus( + sub_network, + snapshots=sns, + skip_pre=True, + distribute_slack=distribute_slack, + slack_weights=sn_slack_weights, + ) + else: + ( + itdf[sub_network.name], + difdf[sub_network.name], + cnvdf[sub_network.name], + ) = sub_network_pf_fun( + sub_network, + snapshots=sns, + skip_pre=True, + distribute_slack=distribute_slack, + slack_weights=sn_slack_weights, + **kwargs, + ) + if not linear: + return Dict({"n_iter": itdf, "error": difdf, "converged": cnvdf}) + return None + + +def allocate_series_dataframes(n: Network, series: dict) -> None: + """Populate time-varying outputs with default values. + + Parameters + ---------- + n : pypsa.Network + Network instance. + series : dict + Dictionary of components and their attributes to populate (see example) + + Examples + -------- + >>> allocate_series_dataframes(n, {'Generator': ['p'], 'Load': ['p']}) + + """ + for component, attributes in series.items(): + static = n.c[component].static + dynamic = n.c[component].dynamic + + for attr in attributes: + dynamic[attr] = dynamic[attr].reindex( + columns=static.index, + fill_value=n.components[component]["defaults"].at[attr, "default"], + ) + + +def newton_raphson_sparse( + f: Callable, + guess: np.ndarray, + dfdx: Callable, + x_tol: float = 1e-10, + lim_iter: int = 100, + distribute_slack: bool = False, + slack_weights: np.ndarray | None = None, +) -> tuple[np.ndarray, int, Any, bool]: + """Solve f(x) = 0 with initial guess for x and dfdx(x). + + dfdx(x) should return a sparse Jacobian. Terminate if error on norm + of f(x) is < x_tol or there were more than lim_iter iterations. + """ + slack_args = {"distribute_slack": distribute_slack, "slack_weights": slack_weights} + converged = False + n_iter = 0 + F = f(guess, **slack_args) + diff = norm(F, np.inf) + + logger.debug("Error at iteration %d: %f", n_iter, diff) + + while diff > x_tol and n_iter < lim_iter: + n_iter += 1 + + guess = guess - spsolve(dfdx(guess, **slack_args), F) + + F = f(guess, **slack_args) + diff = norm(F, np.inf) + + logger.debug("Error at iteration %d: %f", n_iter, diff) + + if diff > x_tol: + logger.warning( + 'Warning, we didn\'t reach the required tolerance within %d iterations, error is at %f. See the section "Troubleshooting" in the documentation for tips to fix this. ', + n_iter, + diff, + ) + elif not np.isnan(diff): + converged = True + + return guess, n_iter, diff, converged + + +def sub_network_pf_singlebus( + sub_network: SubNetwork, + snapshots: Sequence | None = None, + skip_pre: float = False, + distribute_slack: bool = False, + slack_weights: str | pd.Series = "p_set", + linear: bool = False, +) -> tuple[int, float, bool]: + """Non-linear power flow for a sub-network consiting of a single bus. + + Parameters + ---------- + sub_network : pypsa.SubNetwork + The sub-network to run the power flow on. + snapshots : list-like|single snapshot + A subset or an elements of n.snapshots on which to run + the power flow, defaults to n.snapshots + skip_pre: bool, default False + Skip the preliminary steps of computing topology, calculating dependent values and finding bus controls. + distribute_slack : bool, default False + If `True`, distribute the slack power across generators proportional to generator dispatch by default + or according to the distribution scheme provided in `slack_weights`. + If `False` only the slack generator takes up the slack. + slack_weights : pandas.Series|str, default 'p_set' + Distribution scheme describing how to determine the fraction of the total slack power + a bus of the sub-network takes up. Default is to distribute proportional to generator dispatch + ('p_set'). Another option is to distribute proportional to (optimised) nominal capacity ('p_nom' or 'p_nom_opt'). + Custom weights can be provided via a pandas.Series/dict + that has the generators of the single bus as index/keys. + linear : bool, default False + If `True`, use linear power flow instead of non-linear power flow. + + """ + sns = as_index(sub_network.n, snapshots, "snapshots") + n = sub_network.n + logger.info( + "Balancing power on single-bus sub-network %s for snapshots %s", + sub_network, + snapshots, + ) + + if not skip_pre: + sub_network.find_bus_controls() + _allocate_pf_outputs(n, linear=False) + + if isinstance(slack_weights, dict): + slack_weights = pd.Series(slack_weights) + + buses_o = sub_network.buses_o + + _calculate_controllable_nodal_power_balance(sub_network, n, sns, buses_o) + + v_mag_pu_set = n.get_switchable_as_dense("Bus", "v_mag_pu_set", sns) + n.c.buses.dynamic.v_mag_pu.loc[sns, sub_network.slack_bus] = v_mag_pu_set.loc[ + :, sub_network.slack_bus + ] + n.c.buses.dynamic.v_ang.loc[sns, sub_network.slack_bus] = 0.0 + + if distribute_slack: + for bus, group in sub_network.c.generators.static.groupby("bus"): + if slack_weights in ["p_nom", "p_nom_opt"]: + if all(n.c.generators.static[slack_weights] == 0): + msg = f"Invalid slack weights! Generator attribute {slack_weights} is always zero." + raise ValueError(msg) + bus_generator_shares = ( + n.c.generators.static[slack_weights] + .loc[group.index] + .pipe(normed) + .fillna(0) + ) + elif slack_weights == "p_set": + generators_t_p_choice = n.get_switchable_as_dense( + "Generator", slack_weights, sns + ) + if generators_t_p_choice.isna().all().all(): + msg = ( + f"Invalid slack weights! Generator attribute {slack_weights}" + f" is always NaN." + ) + raise ValueError(msg) + if (generators_t_p_choice == 0).all().all(): + msg = ( + f"Invalid slack weights! Generator attribute {slack_weights}" + f" is always zero." + ) + raise ValueError(msg) + + bus_generator_shares = ( + generators_t_p_choice.loc[sns, group.index] + .apply(normed, axis=1) + .fillna(0) + ) + else: + bus_generator_shares = slack_weights.pipe(normed).fillna(0) # type: ignore + n.c.generators.dynamic.p.loc[sns, group.index] += ( + bus_generator_shares.multiply( + -n.c.buses.dynamic.p.loc[sns, bus], axis=0 + ) + ) # fmt: skip + else: + n.c.generators.dynamic.p.loc[sns, sub_network.slack_generator] -= ( + n.c.buses.dynamic.p.loc[sns, sub_network.slack_bus] + ) # fmt: skip + + n.c.generators.dynamic.q.loc[sns, sub_network.slack_generator] -= ( + n.c.buses.dynamic.q.loc[sns, sub_network.slack_bus] + ) # fmt: skip + + n.c.buses.dynamic.p.loc[sns, sub_network.slack_bus] = 0.0 + n.c.buses.dynamic.q.loc[sns, sub_network.slack_bus] = 0.0 + + return 0, 0.0, True # dummy substitute for newton raphson output + + +def apply_line_types(n: Network) -> None: + """Calculate line electrical parameters x, r, b, g from standard types.""" + lines_with_types_b = n.c.lines.static.type != "" + if lines_with_types_b.zsum() == 0: + return + + # Get unique line types from lines + line_types_used = n.c.lines.static.loc[lines_with_types_b, "type"].unique() + + missing_types = pd.Index(line_types_used).difference(n.c.line_types.names) + + if not missing_types.empty: + msg = f"The type(s) {', '.join(missing_types)} do(es) not exist in n.c.line_types.static" + raise ValueError(msg) + + lines = n.c.lines.static.loc[ + lines_with_types_b, ["type", "length", "num_parallel"] + ].copy() + + if n.has_scenarios: + # For stochastic network, use the first scenario's line types + # User changes across line type data are caught by the consistency check + line_types_to_use = n.c.line_types.static.xs( + n.c.line_types.static.index.get_level_values(0)[0], level=0 + ) + lines = lines.join(line_types_to_use, on="type") + else: + lines = lines.join(n.c.line_types.static, on="type") + + for attr in ["r", "x"]: + lines[attr] = ( + lines[attr + "_per_length"] * lines["length"] / lines["num_parallel"] + ) + lines["b"] = ( + 2 + * np.pi + * 1e-9 + * lines["f_nom"] + * lines["c_per_length"] + * lines["length"] + * lines["num_parallel"] + ) + + # now set calculated values on live lines + for attr in ["r", "x", "b"]: + n.c.lines.static.loc[lines_with_types_b, attr] = lines[attr] + + +def apply_transformer_types(n: Network) -> None: + """Calculate transformer electrical parameters x, r, b, g from standard types.""" + trafos_with_types_b = n.c.transformers.static.type != "" + if trafos_with_types_b.zsum() == 0: + return + + missing_types = pd.Index( + n.c.transformers.static.loc[trafos_with_types_b, "type"].unique() + ).difference(n.c.transformer_types.static.index) + if not missing_types.empty: + msg = ( + f"The type(s) {', '.join(missing_types)} do(es) not exist in " + f"n.transformer_types" + ) + raise ValueError(msg) + + # Get a copy of the transformers data + # (joining pulls in "phase_shift", "s_nom", "tap_side" from TransformerType) + t = n.c.transformers.static.loc[ + trafos_with_types_b, ["type", "tap_position", "num_parallel"] + ].join(n.c.transformer_types.static, on="type") + + t["r"] = t["vscr"] / 100.0 + t["x"] = np.sqrt((t["vsc"] / 100.0) ** 2 - t["r"] ** 2) + + # NB: b and g are per unit of s_nom + t["g"] = t["pfe"] / (1000.0 * t["s_nom"]) + + # for some bizarre reason, some of the standard types in pandapower have i0^2 < g^2 + t["b"] = -np.sqrt(((t["i0"] / 100.0) ** 2 - t["g"] ** 2).clip(lower=0)) + + for attr in ["r", "x"]: + t[attr] /= t["num_parallel"] + + for attr in ["b", "g"]: + t[attr] *= t["num_parallel"] + + # deal with tap positions + + t["tap_ratio"] = 1.0 + (t["tap_position"] - t["tap_neutral"]) * ( + t["tap_step"] / 100.0 + ) + + # now set calculated values on live transformers + attrs = ["r", "x", "g", "b", "phase_shift", "s_nom", "tap_side", "tap_ratio"] + n.c.transformers.static.loc[trafos_with_types_b, attrs] = t[attrs].astype( + n.c.transformers.static[attrs].dtypes + ) + + # TODO: status, rate_A + + +def wye_to_delta( + z1: float, + z2: float, + z3: float, +) -> tuple[float, float, float]: + """Follows http://home.earthlink.net/~w6rmk/math/wyedelta.html. + + Parameters + ---------- + z1 : float + First impedance value + z2 : float + Second impedance value + z3 : float + Third impedance value + + Returns + ------- + tuple[float, float, float]: + A tuple containing the three transformed impedance values + + """ + summand = z1 * z2 + z2 * z3 + z3 * z1 + return (summand / z2, summand / z1, summand / z3) + + +def apply_transformer_t_model(n: Network) -> None: + """Convert given T-model parameters to PI-model parameters. + + Notes + ----- + Uses wye-delta transformation. + + """ + z_series = n.c.transformers.static.r_pu + 1j * n.c.transformers.static.x_pu + y_shunt = n.c.transformers.static.g_pu + 1j * n.c.transformers.static.b_pu + + ts_b = (n.c.transformers.static.model == "t") & (y_shunt != 0.0) + + if ts_b.zsum() == 0: + return + + za, zb, zc = wye_to_delta( + z_series.loc[ts_b] / 2, z_series.loc[ts_b] / 2, 1 / y_shunt.loc[ts_b] + ) + + n.c.transformers.static.loc[ts_b, "r_pu"] = real(zc) + n.c.transformers.static.loc[ts_b, "x_pu"] = imag(zc) + n.c.transformers.static.loc[ts_b, "g_pu"] = real(2 / za) + n.c.transformers.static.loc[ts_b, "b_pu"] = imag(2 / za) + + +def aggregate_multi_graph(sub_network: SubNetwork) -> None: + """Aggregate branches between same buses. + + Instead a single branch with aggregated properties (e.g. s_nom is + summed, length is averaged) is created. + """ + n = sub_network.n + + count = 0 + seen = [] + graph = sub_network.graph() + for u, v in graph.edges(): + if (u, v) in seen: + continue + line_objs = list(graph.adj[u][v].keys()) + if len(line_objs) > 1: + lines = n.c.lines.static.loc[[line[1] for line in line_objs]] + attr_inv = ["x", "r"] + attr_sum = ["s_nom", "b", "g", "s_nom_max", "s_nom_min"] + attr_mean = ["capital_cost", "length", "terrain_factor"] + + aggregated = {attr: 1.0 / (1.0 / lines[attr]).sum() for attr in attr_inv} + for attr in attr_sum: + aggregated[attr] = lines[attr].sum() + + for attr in attr_mean: + aggregated[attr] = lines[attr].mean() + + count += len(line_objs) - 1 + + # remove all but first line + for line in line_objs[1:]: + n.remove("Line", line[1]) + + rep = line_objs[0] + + for key, value in aggregated.items(): + setattr(rep, key, value) + + seen.append((u, v)) + + logger.info( + "Removed %d excess lines from sub-network %s and replaced with aggregated lines", + count, + sub_network.name, + ) + + +def find_tree(sub_network: SubNetwork, weight: str = "x_pu") -> None: + """Get the spanning tree of the graph. + + Choose the node with the highest degree as a central "tree slack" and then see for + each branch which paths from the slack to each node go through the branch. + """ + branches_bus0 = sub_network.branches()["bus0"] + branches_i = branches_bus0.index + buses_i = sub_network.c.buses.static.index + + graph = sub_network.graph(weight=weight, inf_weight=1.0) + sub_network.tree = nx.minimum_spanning_tree(graph) + + # find bus with highest degree to use as slack + tree_slack_bus, slack_degree = max(sub_network.tree.degree(), key=itemgetter(1)) + logger.debug("Tree slack bus is %s with degree %d.", tree_slack_bus, slack_degree) + + # determine which buses are supplied in tree through branch from slack + + # matrix to store tree structure + sub_network.T = dok_matrix((len(branches_i), len(buses_i))) + + for j, bus in enumerate(buses_i): + path = nx.shortest_path(sub_network.tree, bus, tree_slack_bus) + for i in range(len(path) - 1): + branch = next(iter(graph[path[i]][path[i + 1]].keys())) + branch_i = branches_i.get_loc(branch) + sign = +1 if branches_bus0.iat[branch_i] == path[i] else -1 + sub_network.T[branch_i, j] = sign + + +def find_cycles(sub_network: SubNetwork, weight: str = "x_pu") -> None: + """Find all cycles in the sub_network and record them in sub_network.C. + + networkx collects the cycles with more than 2 edges; then the 2-edge + cycles from the MultiGraph must be collected separately (for cases + where there are multiple lines between the same pairs of buses). + + Cycles with infinite impedance are skipped. + """ + branches_bus0 = sub_network.branches()["bus0"] + + if sub_network.n.has_scenarios and not branches_bus0.empty: + first_scenario = sub_network.n.scenarios[0] + branches_bus0 = branches_bus0.xs(first_scenario, level="scenario") + + branches_i = branches_bus0.index + + # reduce to a non-multi-graph for cycles with > 2 edges + mgraph = sub_network.graph(weight=weight, inf_weight=False) + graph = nx.Graph(mgraph) + + cycles = nx.cycle_basis(graph) + + # number of 2-edge cycles + num_multi = len(mgraph.edges()) - len(graph.edges()) + + sub_network.C = dok_matrix((len(branches_bus0), len(cycles) + num_multi)) + + for j, cycle in enumerate(cycles): + for i in range(len(cycle)): + branch = next(iter(mgraph[cycle[i]][cycle[(i + 1) % len(cycle)]].keys())) + branch_i = branches_i.get_loc(branch) + sign = +1 if branches_bus0.iat[branch_i] == cycle[i] else -1 + sub_network.C[branch_i, j] += sign + + # counter for multis + c = len(cycles) + + # add multi-graph 2-edge cycles for multiple branches between same pairs of buses + for u, v in graph.edges(): + bs = list(mgraph[u][v].keys()) + if len(bs) > 1: + first = bs[0] + first_i = branches_i.get_loc(first) + for b in bs[1:]: + b_i = branches_i.get_loc(b) + sign = ( + -1 if branches_bus0.iat[b_i] == branches_bus0.iat[first_i] else +1 + ) + sub_network.C[first_i, c] = 1 + sub_network.C[b_i, c] = sign + c += 1 + + +@deprecated_common_kwargs +def network_batch_lpf(n: Network, snapshots: Sequence | None = None) -> None: + """Batched linear power flow with numpy.dot for several snapshots.""" + msg = "Batch linear power flow not supported yet." + raise NotImplementedError(msg) + + +class NetworkPowerFlowMixin(_NetworkABC): + """Mixin class for network power flow methods. + + Class inherits to [pypsa.Network][]. All attributes and methods can be used + within any Network instance. + """ + + def calculate_dependent_values(self) -> None: + """Calculate per unit impedances and append voltages to lines and shunt impedances.""" + apply_line_types(self) + + apply_transformer_types(self) + + buses = self.c.buses.static + + if self.has_scenarios: + buses = buses.xs(self.scenarios[0], level="scenario") + + self.c.lines.static["v_nom"] = self.c.lines.static.bus0.map(buses.v_nom) + self.c.lines.static.loc[self.c.lines.static.carrier == "", "carrier"] = ( + self.c.lines.static.bus0.map(buses.carrier) + ) + + self.c.lines.static["x_pu"] = self.c.lines.static.x / ( + self.c.lines.static.v_nom**2 + ) + self.c.lines.static["r_pu"] = self.c.lines.static.r / ( + self.c.lines.static.v_nom**2 + ) + self.c.lines.static["b_pu"] = ( + self.c.lines.static.b * self.c.lines.static.v_nom**2 + ) + self.c.lines.static["g_pu"] = ( + self.c.lines.static.g * self.c.lines.static.v_nom**2 + ) + self.c.lines.static["x_pu_eff"] = self.c.lines.static["x_pu"] + self.c.lines.static["r_pu_eff"] = self.c.lines.static["r_pu"] + + # convert transformer impedances from base power s_nom to base = 1 MVA + self.c.transformers.static["x_pu"] = ( + self.c.transformers.static.x / self.c.transformers.static.s_nom + ) + self.c.transformers.static["r_pu"] = ( + self.c.transformers.static.r / self.c.transformers.static.s_nom + ) + self.c.transformers.static["b_pu"] = ( + self.c.transformers.static.b * self.c.transformers.static.s_nom + ) + self.c.transformers.static["g_pu"] = ( + self.c.transformers.static.g * self.c.transformers.static.s_nom + ) + self.c.transformers.static["x_pu_eff"] = ( + self.c.transformers.static["x_pu"] * self.c.transformers.static["tap_ratio"] + ) + self.c.transformers.static["r_pu_eff"] = ( + self.c.transformers.static["r_pu"] * self.c.transformers.static["tap_ratio"] + ) + + apply_transformer_t_model(self) + + self.c.shunt_impedances.static["v_nom"] = self.c.shunt_impedances.static[ + "bus" + ].map(buses.v_nom) + self.c.shunt_impedances.static["b_pu"] = ( + self.c.shunt_impedances.static.b * self.c.shunt_impedances.static.v_nom**2 + ) + self.c.shunt_impedances.static["g_pu"] = ( + self.c.shunt_impedances.static.g * self.c.shunt_impedances.static.v_nom**2 + ) + + self.c.links.static.loc[self.c.links.static.carrier == "", "carrier"] = ( + self.c.links.static.bus0.map(buses.carrier) + ) + + self.c.stores.static.loc[self.c.stores.static.carrier == "", "carrier"] = ( + self.c.stores.static.bus.map(buses.carrier) + ) + + _update_linkports_component_attrs(self) + + def lpf( + n: Network, snapshots: Sequence | None = None, skip_pre: bool = False + ) -> None: + """Linear power flow for generic network. + + Parameters + ---------- + n : pypsa.Network + The network to run the power flow on. + snapshots : list-like|single snapshot + A subset or an elements of n.snapshots on which to run + the power flow, defaults to n.snapshots + skip_pre : bool, default False + Skip the preliminary steps of computing topology, calculating + dependent values and finding bus controls. + + """ + sns = as_index(n, snapshots, "snapshots") + _network_prepare_and_run_pf(n, sns, skip_pre, linear=True) + + def pf( + n: Network, + snapshots: Sequence | None = None, + skip_pre: bool = False, + x_tol: float = 1e-6, + use_seed: bool = False, + distribute_slack: bool = False, + slack_weights: str = "p_set", + ) -> Dict: + """Full non-linear power flow for generic network. + + Parameters + ---------- + n : pypsa.Network + The network to run the power flow on. + snapshots : list-like|single snapshot + A subset or an elements of n.snapshots on which to run + the power flow, defaults to n.snapshots + skip_pre : bool, default False + Skip the preliminary steps of computing topology, calculating dependent values and finding bus controls. + x_tol: float + Tolerance for Newton-Raphson power flow. + use_seed : bool, default False + Use a seed for the initial guess for the Newton-Raphson algorithm. + distribute_slack : bool, default False + If `True`, distribute the slack power across generators proportional to generator dispatch by default + or according to the distribution scheme provided in `slack_weights`. + If `alse` only the slack generator takes up the slack. + slack_weights : dict|str, default 'p_set' + Distribution scheme describing how to determine the fraction of the total slack power + (of each sub network individually) a bus of the sub-network takes up. + Default is to distribute proportional to generator dispatch ('p_set'). + Another option is to distribute proportional to (optimised) nominal capacity ('p_nom' or 'p_nom_opt'). + Custom weights can be specified via a dictionary that has a key for each + sub-network index (`n.sub_networks.index`) and a + pandas.Series/dict with buses or generators of the + corresponding sub-network as index/keys. + When specifying custom weights with buses as index/keys the slack power of a bus is distributed + among its generators in proportion to their nominal capacity (`p_nom`) if given, otherwise evenly. + + Returns + ------- + dict + Dictionary with keys 'n_iter', 'converged', 'error' and dataframe + values indicating number of iterations, convergence status, and + iteration error for each snapshot (rows) and sub_network (columns) + + """ + return _network_prepare_and_run_pf( + n, + snapshots, + skip_pre, + linear=False, + x_tol=x_tol, + use_seed=use_seed, + distribute_slack=distribute_slack, + slack_weights=slack_weights, + ) # type: ignore + + def lpf_contingency( + self, + snapshots: Sequence | str | int | pd.Timestamp | None = None, + branch_outages: Sequence | None = None, + ) -> pd.DataFrame: + """Compute linear power flow for a selection of branch outages. + + Parameters + ---------- + snapshots : list-like|single snapshot + A subset or an elements of n.snapshots on which to run + the power flow, defaults to n.snapshots + NB: currently this only works for a single snapshot + branch_outages : list-like + A list of passive branches which are to be tested for outages. + If None, it's take as all n.passive_branches_i() + + Returns + ------- + p0 : pandas.DataFrame + num_passive_branch x num_branch_outages DataFrame of new power flows + + """ + if snapshots is None: + snapshots = self.snapshots + + if isinstance(snapshots, Sequence): + logger.warning( + "Apologies LPF contingency, this only works for single snapshots at the moment, taking the first snapshot." + ) + snapshot = snapshots[0] + else: + snapshot = snapshots + + self.lpf(snapshot) + + # Store the flows from the base case + + passive_branches = self.passive_branches() + + if branch_outages is None: + branch_outages = passive_branches.index + + p0_base = pd.concat( + { + c: self.c[c].dynamic.p0.loc[snapshot] + for c in self.passive_branch_components + }, + names=["component", "name"], + ) + p0 = p0_base.to_frame("base") + + for sub_network in self.c.sub_networks.static.obj: + sub_network._branches = sub_network.branches() + sub_network.calculate_BODF() + + for branch in branch_outages: + if not isinstance(branch, tuple): + logger.warning("No type given for %s, assuming it is a line", branch) + branch = ("Line", branch) + + sub_network = self.c.sub_networks.static.obj[ + passive_branches.sub_network[branch] + ] + + branch_i = sub_network._branches.index.get_loc(branch) + p0_new = p0_base + pd.Series( + sub_network.BODF[:, branch_i] * p0_base[branch], + sub_network._branches.index, + ) + p0_new.name = branch + + p0 = pd.concat([p0, p0_new], axis=1) + + return p0 + + +class SubNetworkPowerFlowMixin: + """Mixin class for sub-network power flow methods. + + Class inherits to [pypsa.SubNetwork][]. All attributes and methods can be used + within any SubNetwork instance. + """ + + # Type Hints + n: Network + name: str + + buses_o: pd.Index + pvpqs: pd.Index + pqs: pd.Index + pvs: pd.Index + slack_bus: str + B: spmatrix + K: spmatrix + C: spmatrix + PTDF: spmatrix + BODF: spmatrix + + iterate_components: Callable + branches_i: Callable + + incidence_matrix: Callable + branches: Callable + + generators: pd.DataFrame + buses: pd.DataFrame + buses_i: pd.Index + shunt_impedances_i: pd.Index + + @property + @abstractmethod + def components(self) -> ComponentsStore: + """Read only placeholder.""" + + @property + @abstractmethod + def c(self) -> ComponentsStore: + """Read only placeholder.""" + + def calculate_BODF(self, skip_pre: bool = False) -> None: + """Calculate the Branch Outage Distribution Factor (BODF) for sub_network. + + Sets sub_network.BODF as a (dense) numpy array. + + The BODF is a num_branch x num_branch 2d array. + + For the outage of branch l, the new flow on branch k is + given in terms of the flow before the outage + + f_k^after = f_k^before + BODF_{kl} f_l^before + + Note that BODF_{ll} = -1. + + Parameters + ---------- + skip_pre : bool, default False + Skip the preliminary step of computing the PTDF. + + + """ + if not skip_pre: + self.calculate_PTDF() + + num_branches = self.PTDF.shape[0] + + # build LxL version of PTDF + branch_PTDF = self.PTDF * self.K + + with np.errstate(divide="ignore"): + denominator = csr_matrix( + (1 / (1 - np.diag(branch_PTDF)), (r_[:num_branches], r_[:num_branches])) + ) + + self.BODF = branch_PTDF * denominator + + # make sure the flow on the branch itself is zero + np.fill_diagonal(self.BODF, -1) + + def calculate_PTDF(self, skip_pre: bool = False) -> None: + """Calculate the Power Transfer Distribution Factor (PTDF) for sub_network. + + Sets sub_network.PTDF as a (dense) numpy array. + + Parameters + ---------- + skip_pre : bool, default False + Skip the preliminary steps of computing topology, calculating dependent values, + finding bus controls and computing B and H. + + """ + if not skip_pre: + self.calculate_B_H() + + # calculate inverse of B with slack removed + + n_pvpq = len(self.pvpqs) + index = np.r_[:n_pvpq] + + identity = csc_matrix((np.ones(n_pvpq), (index, index))) + + B_inverse = spsolve(csc_matrix(self.B[1:, 1:]), identity) + + # exception for two-node networks, where B_inverse is a 1d array + if issparse(B_inverse): + B_inverse = B_inverse.toarray() + elif B_inverse.shape == (1,): + B_inverse = B_inverse.reshape((1, 1)) + + # add back in zeroes for slack + B_inverse = np.hstack((np.zeros((n_pvpq, 1)), B_inverse)) + B_inverse = np.vstack((np.zeros(n_pvpq + 1), B_inverse)) + + self.PTDF = self.H * B_inverse + + def calculate_B_H(self, skip_pre: bool = False) -> None: + """Calculate B and H matrices for AC or DC sub-networks.""" + n = self.n + + if not skip_pre: + n.calculate_dependent_values() + self.find_bus_controls() + + if self.n.c.sub_networks.static.at[self.name, "carrier"] == "DC": + attribute = "r_pu_eff" + else: + attribute = "x_pu_eff" + + # following leans heavily on pypower.makeBdc + + z = np.concatenate( + [ + (c.static.loc[c.static.query("active").index, attribute]).values + for c in self.components + if c.name in n.passive_branch_components + ] + ) + # susceptances + b = np.divide(1.0, z, out=np.full_like(z, np.inf), where=z != 0) + + if np.isnan(b).any(): + logger.warning( + "Warning! Some series impedances are zero - this will cause a singularity in LPF!" + ) + b_diag = csr_matrix((b, (r_[: len(b)], r_[: len(b)]))) + + # incidence matrix + self.K = self.incidence_matrix(busorder=self.buses_o) + + self.H = b_diag * self.K.T + + # weighted Laplacian + self.B = self.K * self.H + + phase_shift = np.concatenate( + [ + ( + (c.static.loc[c.static.query("active").index, "phase_shift"]).values + * np.pi + / 180.0 + if c.name == "Transformer" + else np.zeros((len(c.static.query("active").index),)) + ) + for c in self.components + if c.name in n.passive_branch_components + ] + ) + self.p_branch_shift = np.multiply(-b, phase_shift, where=b != np.inf) + + self.p_bus_shift = self.K * self.p_branch_shift + + def calculate_Y( + self, + skip_pre: bool = False, + active_branches_only: bool = True, + ) -> None: + """Calculate bus admittance matrices for AC sub-networks.""" + if not skip_pre: + self.n.calculate_dependent_values() + + if self.n.c.sub_networks.static.at[self.name, "carrier"] != "AC": + logger.warning("Non-AC networks not supported for Y!") + return + + branches = self.branches() + buses_o = self.buses_o + + if active_branches_only: + branches = branches[branches.active] + + n = self.n + + # following leans heavily on pypower.makeYbus + # Copyright Richard Lincoln, Ray Zimmerman, BSD-style licence + + num_branches = len(branches) + num_buses = len(buses_o) + + y_se = 1 / (branches["r_pu"] + 1.0j * branches["x_pu"]) + + y_sh = branches["g_pu"] + 1.0j * branches["b_pu"] + + tau = branches["tap_ratio"].fillna(1.0) + + # catch some transformers falsely set with tau = 0 by pypower + tau[tau == 0] = 1.0 + + # define the HV tap ratios + tau_hv = pd.Series(1.0, branches.index) + tau_hv[branches.tap_side == 0] = tau[branches.tap_side == 0] + + # define the LV tap ratios + tau_lv = pd.Series(1.0, branches.index) + tau_lv[branches.tap_side == 1] = tau[branches.tap_side == 1] + + phase_shift = np.exp(1.0j * branches["phase_shift"].fillna(0.0) * np.pi / 180.0) + + # build the admittance matrix elements for each branch + Y11 = (y_se + 0.5 * y_sh) / tau_lv**2 + Y10 = -y_se / tau_lv / tau_hv / phase_shift + Y01 = -y_se / tau_lv / tau_hv / np.conj(phase_shift) + Y00 = (y_se + 0.5 * y_sh) / tau_hv**2 + + # bus shunt impedances + b_sh = ( + n.c.shunt_impedances.static.b_pu.groupby(n.c.shunt_impedances.static.bus) + .sum() + .reindex(buses_o, fill_value=0.0) + ) + g_sh = ( + n.c.shunt_impedances.static.g_pu.groupby(n.c.shunt_impedances.static.bus) + .sum() + .reindex(buses_o, fill_value=0.0) + ) + Y_sh = g_sh + 1.0j * b_sh + + # get bus indices + bus0 = buses_o.get_indexer(branches.bus0) + bus1 = buses_o.get_indexer(branches.bus1) + + # connection matrices + C0 = csr_matrix( + (ones(num_branches), (np.arange(num_branches), bus0)), + (num_branches, num_buses), + ) + C1 = csr_matrix( + (ones(num_branches), (np.arange(num_branches), bus1)), + (num_branches, num_buses), + ) + + # build Y{0, 1} such that Y{0, 1} * V is the vector complex branch currents + + i = r_[np.arange(num_branches), np.arange(num_branches)] + self.Y0 = csr_matrix( + (r_[Y00, Y01], (i, r_[bus0, bus1])), (num_branches, num_buses) + ) + self.Y1 = csr_matrix( + (r_[Y10, Y11], (i, r_[bus0, bus1])), (num_branches, num_buses) + ) + + # now build bus admittance matrix + self.Y = ( + C0.T * self.Y0 + + C1.T * self.Y1 + + csr_matrix((Y_sh, (np.arange(num_buses), np.arange(num_buses)))) + ) + + def find_slack_bus(self) -> None: + """Find the slack bus in a connected sub-network.""" + gens = self.c.generators.static + gen_names = gens.index.get_level_values("name") + + if len(gens) == 0: + self.slack_generator = None + self.slack_bus = self.c.buses.static.index.get_level_values("name")[0] + + else: + slacks = gens[gens.control == "Slack"].index.unique("name") + network_gen_names = self.n.c.generators.static.index.get_level_values( + "name" + ) + + if len(slacks) == 0: + self.slack_generator = gen_names[0] + + is_slack_generators = network_gen_names == self.slack_generator + self.n.c.generators.static.loc[is_slack_generators, "control"] = "Slack" + logger.debug( + "No slack generator found in sub-network %s, using %s as the slack generator", + self.name, + self.slack_generator, + ) + + elif len(slacks) == 1: + self.slack_generator = slacks[0] + else: + self.slack_generator = slacks[0] + non_slack_generators = network_gen_names.isin(slacks[1:]) + self.n.c.generators.static.loc[non_slack_generators, "control"] = "PV" + logger.debug( + "More than one slack generator found in sub-network %s, using %s as the slack generator", + self.name, + self.slack_generator, + ) + + if isinstance(gens.index, pd.MultiIndex): + self.slack_bus = gens.bus.xs(self.slack_generator, level="name").values[ + 0 + ] + else: + self.slack_bus = gens.bus.loc[self.slack_generator] + # also put it into the dataframe + self.n.c.sub_networks.static.at[self.name, "slack_bus"] = self.slack_bus + + logger.debug( + "Slack bus for sub-network %s is %s", + self.name, + self.slack_bus, + ) + + def find_bus_controls(self) -> None: + """Find slack and all PV and PQ buses for a sub_network. + + This function also fixes sub_network.buses_o, a DataFrame ordered by + control type. + """ + n = self.n + + self.find_slack_bus() + + gens = self.c.generators.static + buses_i = self.c.buses.static.index + + # default bus control is PQ + n.c.buses.static.loc[buses_i, "control"] = "PQ" + + # find all buses with one or more gens with PV + pvs = gens[gens.control == "PV"].index.to_series() + if len(pvs) > 0: + pvs = pvs.groupby(gens.bus).first() + n.c.buses.static.loc[pvs.index, "control"] = "PV" + n.c.buses.static.loc[pvs.index, "generator"] = pvs + + is_slack_bus = n.c.buses.static.index.get_level_values("name") == self.slack_bus + n.c.buses.static.loc[is_slack_bus, "control"] = "Slack" + n.c.buses.static.loc[is_slack_bus, "generator"] = ( + self.slack_generator if self.slack_generator is not None else "" + ) + + buses_control = n.c.buses.static.loc[buses_i, "control"] + self.pvs = buses_control.index[buses_control == "PV"] + self.pqs = buses_control.index[buses_control == "PQ"] + + self.pvpqs = self.pvs.append(self.pqs) + + # order buses + self.buses_o = self.pvpqs.insert(0, self.slack_bus) + + def pf( + self, + snapshots: Sequence | None = None, + skip_pre: bool = False, + x_tol: float = 1e-6, + use_seed: bool = False, + distribute_slack: bool = False, + slack_weights: pd.Series | dict | str = "p_set", + ) -> tuple[pd.Series, pd.Series, pd.Series]: + """Non-linear power flow for connected sub-network. + + Parameters + ---------- + snapshots : list-like|single snapshot + A subset or an elements of n.snapshots on which to run + the power flow, defaults to n.snapshots + skip_pre: bool, default False + Skip the preliminary steps of computing topology, calculating dependent values and finding bus controls. + x_tol: float + Tolerance for Newton-Raphson power flow. + use_seed : bool, default False + Use a seed for the initial guess for the Newton-Raphson algorithm. + distribute_slack : bool, default False + If `True`, distribute the slack power across generators proportional to generator dispatch by default + or according to the distribution scheme provided in `slack_weights`. + If `False` only the slack generator takes up the slack. + slack_weights : pandas.Series|str, default 'p_set' + Distribution scheme describing how to determine the fraction of the total slack power + a bus of the sub-network takes up. Default is to distribute proportional to generator dispatch + ('p_set'). Another option is to distribute proportional to (optimised) nominal capacity ('p_nom' or 'p_nom_opt'). + Custom weights can be provided via a pandas.Series/dict + that has the buses or the generators of the sub-network as index/keys. + When using custom weights with buses as index/keys the slack power of a bus is distributed + among its generators in proportion to their nominal capacity (`p_nom`) if given, otherwise evenly. + + Returns + ------- + Tuple of three pandas.Series indicating number of iterations, + remaining error, and convergence status for each snapshot + + """ + if not isinstance(slack_weights, (str | pd.Series | dict)): + msg = ( + f"Type of 'slack_weights' must be string, pd.Series or dict. Got " + f"{type(slack_weights)}." + ) + raise TypeError(msg) + + if isinstance(slack_weights, dict): + slack_weights = pd.Series(slack_weights) + elif isinstance(slack_weights, str): + valid_strings = ["p_nom", "p_nom_opt", "p_set"] + if slack_weights not in valid_strings: + msg = ( + f"String value for 'slack_weights' must be one of {valid_strings}. " + f"Is {slack_weights}." + ) + raise ValueError(msg) + + sns = as_index(self.n, snapshots, "snapshots") + logger.info( + "Performing non-linear load-flow on %s sub-network %s for snapshots %s", + self.n.c.sub_networks.static.at[self.name, "carrier"], + self, + sns, + ) + + n = self.n + + if not skip_pre: + n.calculate_dependent_values() + self.find_bus_controls() + _allocate_pf_outputs(n, linear=False) + + # get indices for the components on this sub-network + branches_i = self.branches_i(active_only=True) + buses_o = self.buses_o + sn_buses = self.c.buses.static.index + sn_generators = self.c.generators.static.index + + generator_slack_weights_b = False + bus_slack_weights_b = False + if isinstance(slack_weights, pd.Series): + if all(i in sn_generators for i in slack_weights.index): + generator_slack_weights_b = True + elif all(i in sn_buses for i in slack_weights.index): + bus_slack_weights_b = True + else: + msg = "Custom slack weights pd.Series/dict must only have the" + raise ValueError( + msg, + "generators or buses of the sub-network as index/keys.", + ) + + if not skip_pre and len(branches_i) > 0: + self.calculate_Y(skip_pre=True) + + _calculate_controllable_nodal_power_balance(self, n, sns, buses_o) + + def f( + guess: np.ndarray, + distribute_slack: bool = False, + slack_weights: np.ndarray | None = None, + ) -> np.ndarray: + last_pq = -1 if distribute_slack else None + n.c.buses.dynamic.v_ang.loc[now, self.pvpqs] = guess[: len(self.pvpqs)] + n.c.buses.dynamic.v_mag_pu.loc[now, self.pqs] = guess[ + len(self.pvpqs) : last_pq + ] + + v_mag_pu = n.c.buses.dynamic.v_mag_pu.loc[now, buses_o] + v_ang = n.c.buses.dynamic.v_ang.loc[now, buses_o] + V = v_mag_pu * np.exp(1j * v_ang) + + if distribute_slack: + slack_power = slack_weights * guess[-1] + mismatch = V * np.conj(self.Y * V) - s + slack_power + else: + mismatch = V * np.conj(self.Y * V) - s + + if distribute_slack: + F = r_[real(mismatch)[:], imag(mismatch)[1 + len(self.pvs) :]] + else: + F = r_[real(mismatch)[1:], imag(mismatch)[1 + len(self.pvs) :]] + + return F + + def dfdx( + guess: np.ndarray, + distribute_slack: bool = False, + slack_weights: np.ndarray | None = None, + ) -> csr_matrix: + last_pq = -1 if distribute_slack else None + n.c.buses.dynamic.v_ang.loc[now, self.pvpqs] = guess[: len(self.pvpqs)] + n.c.buses.dynamic.v_mag_pu.loc[now, self.pqs] = guess[ + len(self.pvpqs) : last_pq + ] + + v_mag_pu = n.c.buses.dynamic.v_mag_pu.loc[now, buses_o] + v_ang = n.c.buses.dynamic.v_ang.loc[now, buses_o] + + V = v_mag_pu * np.exp(1j * v_ang) + + index = r_[: len(buses_o)] + + # make sparse diagonal matrices + V_diag = csr_matrix((V, (index, index))) + V_norm_diag = csr_matrix((V / abs(V), (index, index))) + I_diag = csr_matrix((self.Y * V, (index, index))) + + dS_dVa = 1j * V_diag * np.conj(I_diag - self.Y * V_diag) + + dS_dVm = V_norm_diag * np.conj(I_diag) + V_diag * np.conj( + self.Y * V_norm_diag + ) + + J10 = dS_dVa[1 + len(self.pvs) :, 1:].imag + J11 = dS_dVm[1 + len(self.pvs) :, 1 + len(self.pvs) :].imag + + if distribute_slack: + J00 = dS_dVa[:, 1:].real + J01 = dS_dVm[:, 1 + len(self.pvs) :].real + J02 = csr_matrix(slack_weights, (1, 1 + len(self.pvpqs))).T + J12 = csr_matrix((1, len(self.pqs))).T + J_P_blocks = [J00, J01, J02] + J_Q_blocks = [J10, J11, J12] + else: + J00 = dS_dVa[1:, 1:].real + J01 = dS_dVm[1:, 1 + len(self.pvs) :].real + J_P_blocks = [J00, J01] + J_Q_blocks = [J10, J11] + + J = svstack([shstack(J_P_blocks), shstack(J_Q_blocks)], format="csr") + + return J + + # Set what we know: slack V and v_mag_pu for PV buses + v_mag_pu_set = n.get_switchable_as_dense("Bus", "v_mag_pu_set", sns) + n.c.buses.dynamic.v_mag_pu.loc[sns, self.pvs] = v_mag_pu_set.loc[:, self.pvs] + n.c.buses.dynamic.v_mag_pu.loc[sns, self.slack_bus] = v_mag_pu_set.loc[ + :, self.slack_bus + ] + n.c.buses.dynamic.v_ang.loc[sns, self.slack_bus] = 0.0 + + if not use_seed: + n.c.buses.dynamic.v_mag_pu.loc[sns, self.pqs] = 1.0 + n.c.buses.dynamic.v_ang.loc[sns, self.pvpqs] = 0.0 + + slack_args = {"distribute_slack": distribute_slack} + slack_variable_b = 1 if distribute_slack else 0 + + if distribute_slack: + if isinstance(slack_weights, str) and slack_weights == "p_set": + generators_t_p_choice = n.get_switchable_as_dense( + "Generator", slack_weights, sns + ) + bus_generation = generators_t_p_choice.rename( + columns=n.c.generators.static.bus + ) + slack_weights_calc = ( + pd.DataFrame( + bus_generation.T.groupby(bus_generation.columns).sum().T, + columns=buses_o, + ) + .apply(normed, axis=1) + .fillna(0) + ) + + elif isinstance(slack_weights, str) and slack_weights in [ + "p_nom", + "p_nom_opt", + ]: + if all(n.c.generators.static[slack_weights] == 0): + msg = ( + f"Invalid slack weights! Generator attribute {slack_weights} is " + f"always zero." + ) + raise ValueError(msg) + + slack_weights_calc = ( + n.c.generators.static.groupby("bus")[slack_weights] + .sum() + .reindex(buses_o) + .pipe(normed) + .fillna(0) + ) + + elif generator_slack_weights_b: + # convert generator-based slack weights to bus-based slack weights + slack_weights_calc = ( + slack_weights.rename(n.c.generators.static.bus) # type: ignore + .groupby(slack_weights.index.name) # type: ignore + .sum() + .reindex(buses_o) + .pipe(normed) + .fillna(0) + ) + + elif bus_slack_weights_b: + # take bus-based slack weights + slack_weights_calc = ( + slack_weights.reindex(buses_o).pipe(normed).fillna(0) # type: ignore + ) + + ss = np.empty((len(sns), len(buses_o)), dtype=complex) + roots = np.empty( + ( + len(sns), + len(self.pvpqs) + len(self.pqs) + slack_variable_b, + ) + ) + iters = pd.Series(0, index=sns) + diffs = pd.Series(index=sns, dtype=float) + convs = pd.Series(False, index=sns) + for i, now in enumerate(sns): + p = n.c.buses.dynamic.p.loc[now, buses_o] + q = n.c.buses.dynamic.q.loc[now, buses_o] + ss[i] = s = p + 1j * q + + # Make a guess for what we don't know: V_ang for PV and PQs and v_mag_pu for PQ buses + guess = r_[ + n.c.buses.dynamic.v_ang.loc[now, self.pvpqs], + n.c.buses.dynamic.v_mag_pu.loc[now, self.pqs], + ] + + if distribute_slack: + guess = np.append(guess, [0]) # for total slack power + if isinstance(slack_weights, str) and slack_weights == "p_set": + # snapshot-dependent slack weights + slack_args["slack_weights"] = slack_weights_calc.loc[now] + else: + slack_args["slack_weights"] = slack_weights_calc + + # Now try and solve + roots[i], n_iter, diff, converged = newton_raphson_sparse( + f, + guess, + dfdx, + x_tol=x_tol, + **slack_args, # type: ignore + ) + iters[now] = n_iter + diffs[now] = diff + convs[now] = converged + if not convs.all(): + not_converged = sns[~convs] + logger.warning("Power flow did not converge for %s.", list(not_converged)) + + # now set everything + if distribute_slack: + last_pq = -1 + else: + last_pq = None + n.c.buses.dynamic.v_ang.loc[sns, self.pvpqs] = roots[:, : len(self.pvpqs)] + n.c.buses.dynamic.v_mag_pu.loc[sns, self.pqs] = roots[ + :, len(self.pvpqs) : last_pq + ] + + v_mag_pu = n.c.buses.dynamic.v_mag_pu.loc[sns, buses_o].values + v_ang = n.c.buses.dynamic.v_ang.loc[sns, buses_o].values + + V = v_mag_pu * np.exp(1j * v_ang) + + # add voltages to branches + buses_indexer = buses_o.get_indexer + branch_bus0 = [] + branch_bus1 = [] + for c in self.components: + if c.name not in n.passive_branch_components: + continue + branch_bus0 += list(c.static.query("active").bus0) + branch_bus1 += list(c.static.query("active").bus1) + v0 = V[:, buses_indexer(branch_bus0)] + v1 = V[:, buses_indexer(branch_bus1)] + + i0 = np.empty((len(sns), self.Y0.shape[0]), dtype=complex) + i1 = np.empty((len(sns), self.Y1.shape[0]), dtype=complex) + for i, _ in enumerate(sns): + i0[i] = self.Y0 * V[i] + i1[i] = self.Y1 * V[i] + + s0 = pd.DataFrame(v0 * np.conj(i0), columns=branches_i, index=sns) + s1 = pd.DataFrame(v1 * np.conj(i1), columns=branches_i, index=sns) + for c in self.components: + if c.name not in n.passive_branch_components: + continue + s0t = s0.loc[:, c.name] + s1t = s1.loc[:, c.name] + n.c[c.name].dynamic.p0.loc[sns, s0t.columns] = s0t.values.real + n.c[c.name].dynamic.q0.loc[sns, s0t.columns] = s0t.values.imag + n.c[c.name].dynamic.p1.loc[sns, s1t.columns] = s1t.values.real + n.c[c.name].dynamic.q1.loc[sns, s1t.columns] = s1t.values.imag + + s_calc = np.empty((len(sns), len(buses_o)), dtype=complex) + for i in range(len(sns)): + s_calc[i] = V[i] * np.conj(self.Y * V[i]) + slack_index = int(buses_o.get_loc(self.slack_bus)) + if distribute_slack: + n.c.buses.dynamic.p.loc[sns, sn_buses] = s_calc.real[ + :, buses_indexer(sn_buses) + ] + else: + n.c.buses.dynamic.p.loc[sns, self.slack_bus] = s_calc[:, slack_index].real + n.c.buses.dynamic.q.loc[sns, self.slack_bus] = s_calc[:, slack_index].imag + n.c.buses.dynamic.q.loc[sns, self.pvs] = s_calc[:, buses_indexer(self.pvs)].imag + + # set shunt impedance powers + shunt_impedances_i = self.c.shunt_impedances.static.index + if len(shunt_impedances_i): + # add voltages + shunt_impedances_v_mag_pu = v_mag_pu[ + :, + buses_indexer( + n.c.shunt_impedances.static.loc[shunt_impedances_i, "bus"] + ), + ] + n.c.shunt_impedances.dynamic.p.loc[sns, shunt_impedances_i] = ( + shunt_impedances_v_mag_pu**2 + ) * n.c.shunt_impedances.static.loc[shunt_impedances_i, "g_pu"].values + n.c.shunt_impedances.dynamic.q.loc[sns, shunt_impedances_i] = ( + shunt_impedances_v_mag_pu**2 + ) * n.c.shunt_impedances.static.loc[shunt_impedances_i, "b_pu"].values + + # let slack generator take up the slack + if distribute_slack: + distributed_slack_power = ( + n.c.buses.dynamic.p.loc[sns, sn_buses] + - ss[:, buses_indexer(sn_buses)].real + ) + for bus, group in self.c.generators.static.groupby("bus"): + if isinstance(slack_weights, str) and slack_weights == "p_set": + generators_t_p_choice = n.get_switchable_as_dense( + "Generator", slack_weights, sns + ) + bus_generator_shares = ( + generators_t_p_choice.loc[sns, group.index] + .apply(normed, axis=1) + .fillna(0) + ) + n.c.generators.dynamic.p.loc[sns, group.index] += ( + bus_generator_shares.multiply( + distributed_slack_power.loc[sns, bus], axis=0 + ) + ) # fmt: skip + else: + if generator_slack_weights_b: + bus_generator_shares = ( + slack_weights.loc[group.index].pipe(normed).fillna(0) # type: ignore + ) + else: + bus_generators_p_nom = n.c.generators.static.p_nom.loc[ + group.index + ] + # distribute evenly if no p_nom given + if all(bus_generators_p_nom) == 0: + bus_generators_p_nom = 1 + bus_generator_shares = bus_generators_p_nom.pipe(normed).fillna( + 0 + ) + n.c.generators.dynamic.p.loc[sns, group.index] += ( + distributed_slack_power.loc[sns, bus].apply( + lambda row, shares=bus_generator_shares: row * shares + ) + ) + else: + n.c.generators.dynamic.p.loc[sns, self.slack_generator] += ( + n.c.buses.dynamic.p.loc[sns, self.slack_bus] - ss[:, slack_index].real + ) + + # set the Q of the slack and PV generators + n.c.generators.dynamic.q.loc[sns, self.slack_generator] += ( + n.c.buses.dynamic.q.loc[sns, self.slack_bus] - ss[:, slack_index].imag + ) + + n.c.generators.dynamic.q.loc[ + sns, n.c.buses.static.loc[self.pvs, "generator"] + ] += np.asarray( + n.c.buses.dynamic.q.loc[sns, self.pvs] - ss[:, buses_indexer(self.pvs)].imag + ) + + return iters, diffs, convs + + def lpf( + self, + snapshots: Sequence | None = None, + skip_pre: bool = False, + ) -> None: + """Linear power flow for connected sub-network. + + Parameters + ---------- + snapshots : list-like|single snapshot + A subset or an elements of n.snapshots on which to run + the power flow, defaults to n.snapshots + skip_pre : bool, default False + Skip the preliminary steps of computing topology, calculating + dependent values and finding bus controls. + + """ + sns = as_index(self.n, snapshots, "snapshots") + logger.info( + "Performing linear load-flow on %s sub-network %s for snapshot(s) %s", + self.n.c.sub_networks.static.at[self.name, "carrier"], + self, + snapshots, + ) + + n = self.n + + if not skip_pre: + n.calculate_dependent_values() + self.find_bus_controls() + _allocate_pf_outputs(n, linear=True) + + # get indices for the components on this sub-network + buses_o = self.buses_o + branches_i = self.branches_i(active_only=True) + + # allow all shunt impedances to dispatch as set + shunt_impedances_i = self.c.shunt_impedances.static.index + n.c.shunt_impedances.dynamic.p.loc[sns, shunt_impedances_i] = ( + n.c.shunt_impedances.static.g_pu.loc[shunt_impedances_i].values + ) + + # allow all one ports to dispatch as set + for c in self.components: + if c.name not in n.controllable_one_port_components: + continue + c_p_set = n.get_switchable_as_dense( + c.name, "p_set", sns, c.static.query("active").index + ) + # power flow calculations require a starting point for the algorithm, while p_set default is n/a + c_p_set = c_p_set.fillna(0) + n.c[c.name].dynamic.p.loc[sns, c.static.query("active").index] = c_p_set + + # set the power injection at each node + n.c.buses.dynamic.p.loc[sns, buses_o] = sum( + [ + ( + ( + c.dynamic.p.loc[sns, c.static.query("active").index] + * c.static.loc[c.static.query("active").index, "sign"] + ) + .T.groupby(c.static.loc[c.static.query("active").index, "bus"]) + .sum() + .T.reindex(columns=buses_o, fill_value=0.0) + ) + for c in [x for x in self.components if x.name in n.one_port_components] + ] + + [ + -c.dynamic[f"p{str(i)}"] + .loc[sns] + .T.groupby(c.static[f"bus{str(i)}"]) + .sum() + .T.reindex(columns=buses_o, fill_value=0) + for c in n.components[n.controllable_branch_components] + for i in [int(col[3:]) for col in c.static.columns if col[:3] == "bus"] + if not c.empty + ] + ) + + if not skip_pre and len(branches_i) > 0: + self.calculate_B_H(skip_pre=True) + + v_diff = np.zeros((len(sns), len(buses_o))) + if len(branches_i) > 0: + p = n.c.buses.dynamic["p"].loc[sns, buses_o].values - self.p_bus_shift + v_diff[:, 1:] = spsolve(self.B[1:, 1:], p[:, 1:].T).T + flows = ( + pd.DataFrame(v_diff * self.H.T, columns=branches_i, index=sns) + + self.p_branch_shift + ) + + for c in self.components: + if c.name not in n.passive_branch_components: + continue + f = flows.loc[:, c.name] + n.c[c.name].dynamic.p0.loc[sns, f.columns] = f + n.c[c.name].dynamic.p1.loc[sns, f.columns] = -f + + if n.c.sub_networks.static.at[self.name, "carrier"] == "DC": + n.c.buses.dynamic.v_mag_pu.loc[sns, buses_o] = 1 + v_diff + n.c.buses.dynamic.v_ang.loc[sns, buses_o] = 0.0 + else: + n.c.buses.dynamic.v_ang.loc[sns, buses_o] = v_diff + n.c.buses.dynamic.v_mag_pu.loc[sns, buses_o] = 1.0 + + # set slack bus power to pick up remained + slack_adjustment = ( + -n.c.buses.dynamic.p.loc[sns, buses_o[1:]].sum(axis=1).fillna(0.0) + - n.c.buses.dynamic.p.loc[sns, buses_o[0]] + ) + n.c.buses.dynamic.p.loc[sns, buses_o[0]] += slack_adjustment + + # let slack generator take up the slack + if self.slack_generator is not None: + n.c.generators.dynamic.p.loc[sns, self.slack_generator] += ( + slack_adjustment + ) # fmt: skip diff --git a/PyPSA/source/pypsa/network/transform.py b/PyPSA/source/pypsa/network/transform.py new file mode 100644 index 0000000000000000000000000000000000000000..0e952292cb29be52c783cd6a21ac3311ad87e353 --- /dev/null +++ b/PyPSA/source/pypsa/network/transform.py @@ -0,0 +1,564 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Network transform module. + +Contains single mixin class which is used to inherit to [pypsa.Networks] class. +Should not be used directly. + +Transform methods are methods which modify, restructure data and add or remove data. + +""" + +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, Any + +import numpy as np +import pandas as pd +from Levenshtein import distance + +from pypsa._options import options +from pypsa.components.common import as_components +from pypsa.components.types import all_standard_attrs_set +from pypsa.network.abstract import _NetworkABC +from pypsa.type_utils import is_1d_list_like + +if TYPE_CHECKING: + from collections.abc import Collection, Sequence + + from pypsa.components.components import Components + from pypsa.networks import Network + +logger = logging.getLogger(__name__) + + +def _get_potential_typos( + custom_attrs: set[str], standard_attrs: set[str] +) -> set[tuple[str, str]]: + def is_typo(custom_attr: str, default_attr: str) -> bool: + """Check if custom_attr could be a typo of default_attr. + + See according test in `test_transform.py` for detailed examples. + + Parameters + ---------- + custom_attr : str + Custom attribute name + default_attr : str + Default attribute name + + Returns + ------- + bool + True if custom_attr could be a typo of default_attr, False otherwise + + """ + dist = distance(custom_attr, default_attr) + # Basic condition + if ( + dist != 1 + or len(custom_attr) <= 1 + or len(default_attr) <= 1 + or default_attr in ["type"] + ): + return False + + # Don't catch different base_attrs (p_nom vs e_nom etc.) + if dist == 1 and custom_attr[0] != default_attr[0] and custom_attr[1] == "_": + return False + + # Don't catch different suffix (efficiency vs efficiency2, bus0 vs bus1 etc.) + if dist == 1 and (custom_attr[-1].isdigit() or default_attr[-1].isdigit()): # noqa: SIM103 + return False + + return True + + return { + (custom_attr, default_attr) + for custom_attr in custom_attrs + for default_attr in standard_attrs + if is_typo(custom_attr, default_attr) + } + + +class NetworkTransformMixin(_NetworkABC): + """Mixin class for network transform methods. + + Class inherits to [pypsa.Network][]. All attributes and methods can be used + within any Network instance. + """ + + def add( + self, + class_name: str, + name: str | int | Sequence[int | str], + suffix: str = "", + overwrite: bool = False, + return_names: bool | None = None, + **kwargs: Any, + ) -> pd.Index | None: + """Add components to the network. + + Handles addition of single and multiple components along with their attributes. + Pass a list of names to add multiple components at once or pass a single name + to add a single component. + + When a single component is added, all non-scalar attributes are assumed to be + time-varying and indexed by snapshots. + When multiple components are added, all non-scalar attributes are assumed to be + static and indexed by names. A single value sequence is treated as scalar and + broadcasted to all components. It is recommended to explicitly pass a scalar + instead. + If you want to add time-varying attributes to multiple components, you can pass + a 2D array/ DataFrame where the first dimension is snapshots and the second + dimension is names. + + Any attributes which are not specified will be given the default + value from . + + Parameters + ---------- + class_name : str + Component class name in ("Bus", "Generator", "Load", "StorageUnit", + "Store", "ShuntImpedance", "Line", "Transformer", "Link"). + name : str or int or list of str or list of int + Component name(s) + suffix : str, default "" + All components are named after name with this added suffix. + overwrite : bool, default False + If True, existing components with the same names as in `name` will be + overwritten. Otherwise only new components will be added and others will be + ignored. + return_names : bool | None, default=None + Whether to return the names of the components added. Defaults to module wide + option (default: False). See `https://go.pypsa.org/options-params` for more + information. + kwargs : Any + Component attributes, e.g. x=[0.1, 0.2], can be list, pandas.Series + or pandas.DataFrame for time-varying + + Returns + ------- + new_names : pandas.index or None + Names of new components (including suffix) if return_names is True, + otherwise None + + Examples + -------- + Add a single component: + + >>> n = pypsa.Network() + >>> n.add("Bus", "my_bus_0") + >>> n.add("Bus", "my_bus_1", v_nom=380) + >>> n.add("Line", "my_line_name", bus0="my_bus_0", bus1="my_bus_1", length=34, r=2, x=4) + + Add multiple components with static attributes: + + >>> n.add("Load", ["load 1", "load 2"], + ... bus=["1", "2"], + ... p_set=np.random.rand(len(n.snapshots), 2)) + + Add multiple components with time-varying attributes: + + >>> import pandas as pd, numpy as np + >>> buses = range(13) + >>> snapshots = range(7) + >>> n = pypsa.Network() + >>> n.set_snapshots(snapshots) + >>> n.add("Bus", buses) + >>> # add load as numpy array + >>> n.add("Load", + ... n.buses.index + " load", + ... bus=buses, + ... p_set=np.random.rand(len(snapshots), len(buses))) + >>> # add wind availability as pandas DataFrame + >>> wind = pd.DataFrame(np.random.rand(len(snapshots), len(buses)), + ... index=n.snapshots, + ... columns=buses) + >>> # use a suffix to avoid boilerplate to rename everything + >>> n.add("Generator", + ... buses, + ... suffix=' wind', + ... bus=buses, + ... p_nom_extendable=True, + ... capital_cost=1e5, + ... p_max_pu=wind) + + + """ + # Handle default parameters from options + if return_names is None: + return_names = options.params.add.return_names + + c = as_components(self, class_name) + # Process name/names to pandas.Index of strings and add suffix + single_component = np.isscalar(name) + + # Check if multi-index names are passed + if isinstance(name, pd.MultiIndex): + msg = "Component names must be a one-dimensional." + if self.has_scenarios: + msg += " For stochastic networks, they will be casted to all dimensions and data per scenario can be changed after adding them." + raise TypeError(msg) + + names = pd.Index([name]) if single_component else pd.Index(name) + names = names.astype(str) + suffix + + names_str = "name" if single_component else "names" + # Read kwargs into static and time-varying attributes + series = {} + static = {} + + # Check if names are unique + if not names.is_unique: + msg = f"Names for {c.name} must be unique." + raise ValueError(msg) + + # Check custom attributes + standard_attrs = set(c.defaults.index) + custom_attrs = set(kwargs.keys()) - standard_attrs + if custom_attrs: + # Raise warning if user adds a custom attribute which is a standard attribute + # for other components + unintended_attr_names = custom_attrs & all_standard_attrs_set + for unintended_attr_name in unintended_attr_names: + logger.warning( + "The attribute '%s' is a standard attribute for other components but not for %s. " + "This could cause confusion and it should be renamed. " + "See also: https://go.pypsa.org/warning-attr-misleading.", + unintended_attr_name, + c.list_name, + ) + # Check if levenshtein distance to standard attributes is small (<= 1) which + # indicates a typo in the attribute name + if options.warnings.attribute_typos: + potential_typos = _get_potential_typos(custom_attrs, standard_attrs) + for custom_attr, standard_attr in potential_typos: + logger.warning( + "The attribute '%s' is not a standard attribute for %s. " + "Did you mean '%s'? " + "See also: https://go.pypsa.org/warning-attr-typo.", + custom_attr, + c.list_name, + standard_attr, + ) + + for k, v in kwargs.items(): + # If index/ columnes are passed (pd.DataFrame or pd.Series) + # - cast names index to string and add suffix + # - check if passed index/ columns align + msg = "{} has an index which does not align with the passed {}." + if isinstance(v, pd.Series) and single_component: + if not v.index.equals(self.snapshots): + raise ValueError(msg.format(f"Series {k}", "network snapshots")) + elif isinstance(v, pd.Series): + # Cast names index to string + suffix + v = v.rename( + index=lambda s: str(s) + if str(s).endswith(suffix) + else str(s) + suffix + ) + if not v.index.equals(names): + raise ValueError(msg.format(f"Series {k}", names_str)) + if isinstance(v, pd.DataFrame): + # Cast names columns to string + suffix + v = v.rename( + columns=lambda s: str(s) + if str(s).endswith(suffix) + else str(s) + suffix + ) + if not v.index.equals(self.snapshots): + raise ValueError(msg.format(f"DataFrame {k}", "network snapshots")) + if not v.columns.equals(names): + raise ValueError(msg.format(f"DataFrame {k}", names_str)) + + # Convert list-like and 1-dim array to pandas.Series + if is_1d_list_like(v): + try: + if single_component: + v = pd.Series(v, index=self.snapshots) + else: + v = pd.Series(v) + if len(v) == 1: + v = v.iloc[0] + logger.debug( + "Single value sequence for %s is treated as a scalar " + "and broadcasted to all components. It is recommended " + "to explicitly pass a scalar instead.", + k, + ) + else: + v.index = names + except ValueError as e: + expec_str = ( + f"{len(self.snapshots)} for each snapshot." + if single_component + else f"{len(names)} for each component name." + ) + msg = f"Data for {k} has length {len(v)} but expected {expec_str}" + raise ValueError(msg) from e + # Convert 2-dim array to pandas.DataFrame + if isinstance(v, np.ndarray): + if v.shape == (len(self.snapshots), len(names)): + v = pd.DataFrame(v, index=self.snapshots, columns=names) + else: + msg = ( + f"Array {k} has shape {v.shape} but expected " + f"({len(self.snapshots)}, {len(names)})." + ) + raise ValueError(msg) + + if isinstance(v, dict): + msg = ( + "Dictionaries are not supported as attribute values. Please use " + "pandas.Series or pandas.DataFrame instead." + ) + raise NotImplementedError(msg) + + # Handle addition of single component + if single_component: + # Read 1-dim data as time-varying attribute + if isinstance(v, pd.Series): + series[k] = pd.DataFrame( + v.values, index=self.snapshots, columns=names + ) + # Read 0-dim data as static attribute + else: + static[k] = v + + # Handle addition of multiple components + elif not single_component: + # Read 2-dim data as time-varying attribute + if isinstance(v, pd.DataFrame): + series[k] = v + # Read 1-dim data as static attribute + elif isinstance(v, pd.Series): + static[k] = v.values + # Read scalar data as static attribute + else: + static[k] = v + + # Load static attributes as components + if static: + static_df = pd.DataFrame(static, index=names) + else: + static_df = pd.DataFrame(index=names) + self._import_components_from_df(static_df, c.name, overwrite=overwrite) + + # Load time-varying attributes as components + for k, v in series.items(): + self._import_series_from_df(v, c.name, k, overwrite=overwrite) + + if return_names: + return names + return None + + def remove( + self, + class_name: str, + name: str | int | Sequence[int | str], + suffix: str = "", + ) -> None: + """Remove a single component or a list of components from the network. + + Removes it from component DataFrames. + + Parameters + ---------- + class_name : str + Component class name + name : str, int, list-like or pandas.Index + Component name(s) + suffix : str, default='' + Suffix to be added to the component name(s) + + Examples + -------- + >>> n = pypsa.Network() + >>> n.snapshots = pd.date_range("2015-01-01", freq="h", periods=2) + >>> n.add("Bus", ["bus0", "bus1"]) + >>> n.add("Bus", "bus2", p_min_pu=[1, 1]) + >>> n.components.buses.static + v_nom type x y ... v_mag_pu_max control generator sub_network + name ... + bus0 1.0 0.0 0.0 ... inf PQ + bus1 1.0 0.0 0.0 ... inf PQ + bus2 1.0 0.0 0.0 ... inf PQ + + [3 rows x 13 columns] + + Remove a single component: + >>> n.remove("Bus", "bus2") + + + Any component data is dropped from the component DataFrames. + >>> n.components.buses.static + v_nom type x y ... v_mag_pu_max control generator sub_network + name ... + bus0 1.0 0.0 0.0 ... inf PQ + bus1 1.0 0.0 0.0 ... inf PQ + + [2 rows x 13 columns] + >>> n.components.buses.dynamic.p_min_pu + Empty DataFrame + Columns: [] + Index: [2015-01-01 00:00:00, 2015-01-01 01:00:00] + + Remove multiple components: + >>> n.remove("Bus", ["bus0", "bus1"]) + + >>> n.components.buses.static + Empty DataFrame + Columns: [v_nom, type, x, y, carrier, unit, location, v_mag_pu_set, v_mag_pu_min, v_mag_pu_max, control, generator, sub_network] + Index: [] + + """ + c = as_components(self, class_name) + + # Process name/names to pandas.Index of strings and add suffix + names = pd.Index([name]) if np.isscalar(name) else pd.Index(name) + names = names.astype(str) + suffix + + # Drop from static components + cls_static = c.static + cls_static.drop(names, inplace=True) + + # Drop from time-varying components + for df in c.dynamic.values(): + df.drop(df.columns.intersection(names), axis=1, inplace=True) + + def merge( + self, + other: Network, + components_to_skip: Collection[str] | None = None, + inplace: bool = False, + with_time: bool = True, + ) -> Any: + """Merge the components of two networks. + + Requires disjunct sets of component indices and, if time-dependent data is + merged, identical snapshots and snapshot weightings. + + If a component in `ther` does not have values for attributes present in + `n`, default values are set. + + If a component in `other` has attributes which are not present in + `n` these attributes are ignored. + + Parameters + ---------- + other : pypsa.Network + Network to add from. + components_to_skip : list-like, default None + List of names of components which are not to be merged e.g. "Bus" + inplace : bool, default False + If True, merge into `n` in-place, otherwise a copy is made. + with_time : bool, default True + If False, only static data is merged. + + Returns + ------- + receiving_n : pypsa.Network + Merged network, or None if inplace=True + + """ + to_skip = {"Network", "SubNetwork", "LineType", "TransformerType"} + if components_to_skip: + to_skip.update(components_to_skip) + to_iterate = other.all_components - to_skip + # ensure buses are merged first + to_iterate_list = ["Bus"] + sorted(to_iterate - {"Bus"}) + for c in other.components: + if c.name not in to_iterate_list: + continue + # for c in other.iterate_components(to_iterate_list): + if not c.static.index.intersection(self.c[c.name].static.index).empty: + msg = f"Component {c.name} has overlapping indices, cannot merge networks." + raise ValueError(msg) + if with_time: + snapshots_aligned = self.snapshots.equals(other.snapshots) + if not snapshots_aligned: + msg = "Snapshots do not agree, cannot merge networks." + raise ValueError(msg) + weightings_aligned = self.snapshot_weightings.equals( + other.snapshot_weightings + ) + if not weightings_aligned: + # Check if only index order is different + # TODO fix with #1128 + if self.snapshot_weightings.reindex( + sorted(self.snapshot_weightings.columns), axis=1 + ).equals( + other.snapshot_weightings.reindex( + sorted(other.snapshot_weightings.columns), axis=1 + ) + ): + weightings_aligned = True + else: + msg = "Snapshot weightings do not agree, cannot merge networks." + raise ValueError(msg) + new = self if inplace else self.copy() + if other.srid != new.srid: + logger.warning( + "Spatial Reference System Indentifier of networks do not agree: " + "%s, %s. Assuming %s.", + new.srid, + other.srid, + new.srid, + ) + for c in other.components: + if c.name not in to_iterate_list: + continue + new.add(c.name, c.static.index, **c.static) + if with_time: + for k, v in c.dynamic.items(): + new._import_series_from_df(v, c.name, k) + + return None if inplace else new + + def rename_component_names( + self, component: str | Components, **kwargs: str + ) -> None: + """Rename component names. + + Rename components of component type and also update all cross-references of + the component in network. + + Parameters + ---------- + component : str or pypsa.Components + Component type or instance of pypsa.Components. + **kwargs + Mapping of old names to new names. + + + Examples + -------- + Define some network + + >>> n = pypsa.Network() + >>> n.add("Bus", ["bus1"]) + >>> n.add("Generator", ["gen1"], bus="bus1") + + Now rename the bus component + + >>> n.rename_component_names("Bus", bus1="bus2") + + Which updates the bus components + + >>> n.buses.index + Index(['bus2'], dtype='object', name='name') + + and all references in the network + + >>> n.generators.bus + name + gen1 bus2 + Name: bus, dtype: object + + """ + c = as_components(self, component) + c.rename_component_names(**kwargs) diff --git a/PyPSA/source/pypsa/networks.py b/PyPSA/source/pypsa/networks.py new file mode 100644 index 0000000000000000000000000000000000000000..658876a3a04e52af5952f77d346c38895e6b9b3f --- /dev/null +++ b/PyPSA/source/pypsa/networks.py @@ -0,0 +1,1972 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Power system components.""" + +from __future__ import annotations + +import copy +import logging +import warnings +from typing import TYPE_CHECKING, Any +from weakref import ref + +from pypsa.common import deprecated_in_next_major, equals +from pypsa.components.components import Components +from pypsa.constants import DEFAULT_EPSG, DEFAULT_TIMESTAMP +from pypsa.statistics.abstract import AbstractStatisticsAccessor + +try: + from cloudpathlib import AnyPath as Path +except ImportError: + from pathlib import Path + +import functools + +import linopy +import numpy as np +import pandas as pd +import pyproj +import validators +from pyproj import CRS, Transformer +from scipy.sparse import csgraph + +from pypsa.clustering import ClusteringAccessor +from pypsa.common import ( + as_index, +) +from pypsa.components.components import SubNetworkComponents +from pypsa.components.store import ComponentsStore +from pypsa.consistency import NetworkConsistencyMixin +from pypsa.definitions.structures import Dict +from pypsa.network.components import NetworkComponentsMixin +from pypsa.network.descriptors import NetworkDescriptorsMixin +from pypsa.network.graph import NetworkGraphMixin +from pypsa.network.index import NetworkIndexMixin +from pypsa.network.io import NetworkIOMixin +from pypsa.network.power_flow import ( + NetworkPowerFlowMixin, + SubNetworkPowerFlowMixin, + find_cycles, +) +from pypsa.network.transform import NetworkTransformMixin +from pypsa.optimization.optimize import OptimizationAccessor +from pypsa.plot.accessor import PlotAccessor +from pypsa.plot.maps import explore +from pypsa.statistics.expressions import StatisticsAccessor +from pypsa.version import __version_base__ + +if TYPE_CHECKING: + from collections.abc import Collection, Iterator, Sequence + + from scipy.sparse import spmatrix + + from pypsa.components.legacy import Component + + +logger = logging.getLogger(__name__) + + +dir_name = Path(__file__).parent + +standard_types_dir_name = "data/standard_types" + + +inf = float("inf") + + +class Network( + NetworkComponentsMixin, + NetworkDescriptorsMixin, + NetworkTransformMixin, + NetworkIndexMixin, + NetworkConsistencyMixin, + NetworkGraphMixin, + NetworkPowerFlowMixin, + NetworkIOMixin, +): + """Network container for all [pypsa.Components][] and helds most of PyPSA's functionality. + + | + """ + + # Optimization + _multi_invest: int + _linearized_uc: int + iteration: int + + # ---------------- + # Dunder methods + # ---------------- + + def __init__( + self, + import_name: str | Path = "", + name: str = "Unnamed Network", + ignore_standard_types: bool = False, + **kwargs: Any, + ) -> None: + """Initialize a new PyPSA Network. + + Parameters + ---------- + import_name : string or Path + Path to netCDF file, HDF5 .h5 store or folder of CSV files from which to + import network data. The string could be a URL. If cloudpathlib is installed, + the string could be a object storage URI with an `s3`, `gs` or `az` URI scheme. + name : string + Network name. + ignore_standard_types : boolean + If True, do not read in PyPSA standard types into standard types + DataFrames. + kwargs : Any + Any remaining attributes to set + + Examples + -------- + >>> nw1 = pypsa.Network("network.nc") # doctest: +SKIP + >>> nw2 = pypsa.Network("/my/folder") # doctest: +SKIP + >>> nw3 = pypsa.Network("https://github.com/PyPSA/PyPSA/raw/master/examples/scigrid-de/scigrid-with-load-gen-trafos.nc") # doctest: +SKIP + >>> nw4 = pypsa.Network("s3://my-bucket/my-network.nc") # doctest: +SKIP + + """ + # Initialise root logger and set its level, if this has not been done before + logging.basicConfig(level=logging.INFO) + + # Store PyPSA version + self._pypsa_version: str = __version_base__ + + # Set attributes + self._name = name + self._meta: dict = {} + self._crs: CRS = CRS.from_epsg(DEFAULT_EPSG) + + # Dimensions + # Snapshots + cols = ["objective", "stores", "generators"] + index = pd.Index([DEFAULT_TIMESTAMP], name="snapshot") + self._snapshots_data = pd.DataFrame(1, index=index, columns=cols) + + # Investment periods coordinate + cols = ["objective", "years"] + self._investment_periods_data = pd.DataFrame(index=self.periods, columns=cols) + + # Scenarios + cols = ["weight"] + index = pd.Index([], name="scenario") + self._scenarios_data: pd.DataFrame = pd.DataFrame([], index=index, columns=cols) + + # Risk preference + self._risk_preference: dict[str, float] | None = None + + self._model: linopy.Model | None = None + self._objective: float | None = None + self._objective_constant: float | None = None + + # Initialize accessors + self.optimize: OptimizationAccessor = OptimizationAccessor(self) + """ + Network [optimization functionality][pypsa.optimization.OptimizationAccessor] accessor. + """ + self.cluster: ClusteringAccessor = ClusteringAccessor(self) + """ + Network [clustering functionality][pypsa.clustering.ClusteringAccessor] accessor. + """ + self.statistics: StatisticsAccessor = StatisticsAccessor(self) + """ + Network [statistics functionality][pypsa.statistics.StatisticsAccessor] accessor. + """ + self.plot: PlotAccessor = PlotAccessor(self) + """ + Network [plotting functionality][pypsa.plot.PlotAccessor] accessor. + """ + + NetworkComponentsMixin.__init__(self) + + if not ignore_standard_types: + self._read_in_default_standard_types() + + if import_name: + if not validators.url(str(import_name)): + import_name = Path(import_name) + + # Read specified file + if str(import_name).endswith(".h5"): + self.import_from_hdf5(import_name) + elif str(import_name).endswith(".nc"): + self.import_from_netcdf(import_name) + elif str(import_name).endswith((".xls", ".xlsx", ".xlsm", ".xlsb")): + self.import_from_excel(import_name) + elif isinstance(import_name, Path) and import_name.is_dir(): + self.import_from_csv_folder(import_name) + else: + msg = f"import_name '{import_name}' is not a valid .h5 file, .nc file or directory." + raise ValueError(msg) + + for key, value in kwargs.items(): + setattr(self, key, value) + + def __str__(self) -> str: + """Get a string representation of the network. + + + + Examples + -------- + >>> str(n) + "PyPSA Network 'AC-DC-Meshed'" + >>> str(n_stochastic) + "Stochastic PyPSA Network 'Stochastic-Network'" + + """ + prefix = "Stochastic PyPSA Network" if self.has_scenarios else "PyPSA Network" + return f"{prefix} '{self.name}'" + + def __repr__(self) -> str: + """Get representation of the network. + + + + Examples + -------- + >>> n # doctest: +ELLIPSIS + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 9 + - Carrier: ... + - Generator: ... + - GlobalConstraint: ... + - Line: ... + - Link: ... + - Load: ... + - SubNetwork: ... + Snapshots: 10 + + + """ + # TODO make this actually for the REPL + header = f"{self}\n" + "-" * len(str(self)) # + "\n" + comps = { + c.name: f" - {c.name}: {len(c.static)}" + for c in self.components + if "Type" not in c.name and len(c.static) + } + content = "\nComponents:" + if comps: + content += "\n" + "\n".join(comps[c] for c in sorted(comps)) + else: + header = "Empty " + header + content += " none" + content += "\n" + + content += f"Snapshots: {len(self.snapshots)}" + content += "\n" + + if self.has_scenarios: + content += f"Scenarios: {len(self.scenarios)}" + + return header + content + + def __add__(self, other: Network) -> None: + """Merge all components of two networks. + + + + Parameters + ---------- + other : Network + Network to merge into this one. + + See Also + -------- + [pypsa.Network.merge][] + + Examples + -------- + >>> n1 = pypsa.Network() + >>> n2 = pypsa.Network() + >>> n1.add("Bus", "bus1") + >>> n2.add("Bus", "bus2") + >>> new_network = n1 + n2 + >>> len(new_network.buses) + 2 + + """ + return self.__class__.merge(self, other) + + def __eq__(self, other: object) -> bool: + """Check for equality of two networks. + + Parameters + ---------- + other : Any + The other network to compare with. + + Returns + ------- + bool + True if the networks are equal, False otherwise. + + See Also + -------- + [pypsa.Network.equals][] + + """ + return self.equals(other) + + def __getitem__(self, key: str) -> Network: + """Return a shallow slice of the Network object. + + A network can be sliced in three different ways: + 1. If the key is a scenario name and the network has scenarios, the corresponding + scenario is returned as a new network object. + See [pypsa.Network.get_scenario][]. + 2. If the network is a collection and the key is a name of one of the networks + in the collection, that network is returned. + 3. If the key is a bus name or a boolean indexer for buses, a sliced copy of + the network is returned, containing only the selected buses and all + connected components. See [pypsa.Network.slice_network][]. + + A warning will be issued if the key matches multiple of the above + entities. In that case, the first match is returned. But it is recommended to + use the explicit methods (e.g. get_scenario(), get_network()) or use unique + scenario, collection and bus names to avoid ambiguity. + + Parameters + ---------- + key : str or boolean mask + The key or boolean mask to select a scenario, a network from a collection + or slice the network based on buses. + + Returns + ------- + n : pypsa.Network + + Examples + -------- + Select single scenario from a stochastic network: + + >>> n_stochastic + Stochastic PyPSA Network 'Stochastic-Network' + --------------------------------------------- + Components: + - Bus: 3 + - Generator: 12 + - Load: 3 + Snapshots: 2920 + Scenarios: 3 + >>> n_stochastic["high"] + PyPSA Network 'Stochastic-Network - Scenario 'high'' + ---------------------------------------------------- + Components: + - Bus: 1 + - Generator: 4 + - Load: 1 + Snapshots: 2920 + + Select single collection from a network collection: + + >>> nc + NetworkCollection + ----------------- + Networks: 2 + Index name: 'network' + Entries: ['AC-DC-Meshed', 'AC-DC-Meshed-Shuffled-Load'] + + >>> nc["AC-DC-Meshed"] + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 9 + - Carrier: 6 + - Generator: 6 + - GlobalConstraint: 1 + - Line: 7 + - Link: 4 + - Load: 6 + - SubNetwork: 3 + Snapshots: 10 + + + Select a network slice based on buses: + + >>> n + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 9 + - Carrier: 6 + - Generator: 6 + - GlobalConstraint: 1 + - Line: 7 + - Link: 4 + - Load: 6 + - SubNetwork: 3 + Snapshots: 10 + >>> n["London"] + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 1 + - Carrier: 6 + - GlobalConstraint: 1 + - Load: 1 + Snapshots: 10 + + or use the pandas `.loc` method to select multiple buses: + + >>> n[n.buses.carrier=='AC'] + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 6 + - Carrier: 6 + - Generator: 6 + - GlobalConstraint: 1 + - Line: 4 + - Link: 1 + - Load: 6 + Snapshots: 10 + + """ + # For a scalar key, selection is done either on scenarios, collection items or + # a network slice + if np.isscalar(key): + results = [] + + # Check scenarios + if key in self.scenarios: + results.append(("scenario", self.get_scenario(key))) + + # Check collection + if self.is_collection and key in self.networks.index: # type: ignore[attr-defined] + results.append(("collection", self.networks[key])) # type: ignore[attr-defined] + + # Check network slice + if key in self.c.buses.names: + results.append(("network", self.slice_network([key]))) + + # Handle results + if len(results) > 1: + types = [r[0] for r in results] + logger.warning( + "Key '%s' matches multiple entities: %s. Returning the " + "first match (%s). It is recommended to use explicit " + "methods, e.g. get_scenario() or get_network() to avoid " + "ambiguity or to use unique scenario, collection and bus names.", + key, + types, + types[0], + ) + + if results: + return results[0][1] + else: + msg = f"Key '{key}' not found in scenarios, collection, or buses." + raise KeyError(msg) + else: + # Check for deprecated tuple usage (buses, snapshots) + if isinstance(key, tuple) and len(key) == 2: + warnings.warn( + "Slicing by (buses, snapshots) tuples in __getitem__ is no longer supported. " + "Use the slice_network() method instead: " + "n.slice_network(buses=buses, snapshots=snapshots)", + DeprecationWarning, + stacklevel=2, + ) + msg = "Tuple slicing is deprecated. Use slice_network(buses=..., snapshots=...) instead." + raise NotImplementedError(msg) + + return self.slice_network(key) + + def equals(self, other: Any, log_mode: str = "silent") -> bool: + """Check for equality of two networks. + + Parameters + ---------- + other : Any + The other network to compare with. + log_mode: str, default="silent" + Controls how differences are reported: + - 'silent': No logging, just returns True/False + - 'verbose': Prints differences but doesn't raise errors + - 'strict': Raises ValueError on first difference + + Raises + ------ + ValueError + If log_mode is 'strict' and components are not equal. + + Returns + ------- + bool + True if the networks are equal, False otherwise. + + Examples + -------- + >>> n1 = pypsa.Network() + >>> n2 = pypsa.Network() + >>> n1.add("Bus", "bus1") + >>> n2.add("Bus", "bus2") + >>> n1.equals(n2) + False + + """ + ignore = [ + OptimizationAccessor, + ClusteringAccessor, + StatisticsAccessor, + PlotAccessor, + AbstractStatisticsAccessor, + linopy.Model, + ] + not_equal = False + if isinstance(other, self.__class__): + for key, value in self.__dict__.items(): + if not equals( + value, + other.__dict__[key], + ignored_classes=ignore, + log_mode=log_mode, + path="n." + key, + ): + logger.warning("Mismatch in attribute: %s", key) + not_equal = True + if not log_mode: + break + else: + logger.warning( + "Can only compare two pypsa.Network objects with each other. Got %s.", + type(other), + ) + + return False + + return not not_equal + + # ---------------- + # Meta data + # ---------------- + + @property + def name(self) -> str: + """Name of the network. + + + + The name is set when the network is created. It can also be changed by setting + the `name` attribute. It is only descriptive and not used for any + functionality. + + Examples + -------- + >>> n.name + 'AC-DC-Meshed' + + >>> n = pypsa.Network(name='Unnamed Network') + >>> n.name + 'Unnamed Network' + + >>> n.name = 'net' + >>> n.name + 'net' + + """ + return self._name + + @name.setter + def name(self, new: str) -> None: + """Set the name of the network.""" + self._name = new + + @property + def pypsa_version(self) -> str: + """PyPSA version of the network. + + + + The PyPSA version is set when the network is created and cannot be changed + manually. When a network of an older version is imported, the version is + automatically updated to the current version. + + Examples + -------- + >>> n = pypsa.Network() + >>> n.pypsa_version # doctest: +SKIP + '1.0.0' + + """ + return self._pypsa_version + + @property + def meta(self) -> dict: + """Dictionary of the network meta data. + + + + Any additional meta data can be added to the network by setting the `meta` + attribute. Meta data will be saved on export. + + Examples + -------- + >>> n = pypsa.Network() + >>> n.meta['description'] = 'This is a test network' + >>> n.meta['any_key'] = 'Any Key can be added' + >>> n.meta + {'description': 'This is a test network', 'any_key': 'Any Key can be added'} + + """ + return self._meta + + @meta.setter + def meta(self, new: dict) -> None: + """Set the network meta data.""" + if not isinstance(new, (dict | Dict)): + msg = f"Meta must be a dictionary, received a {type(new)}" + raise TypeError(msg) + self._meta = new + + @property + def model(self) -> linopy.Model: + """Access to linopy model object. + + + + After optimizing a network, the linopy model object is stored in the network + and can be accessed via this property. It cannot be set manually. + + Examples + -------- + >>> n.model + Linopy LP model + =============== + + Variables: + ---------- + * Generator-p_nom (name) + * Line-s_nom (name) + * Link-p_nom (name) + * Generator-p (snapshot, name) + * Line-s (snapshot, name) + * Link-p (snapshot, name) + * objective_constant + + Constraints: + ------------ + * Generator-ext-p_nom-lower (name) + * Line-ext-s_nom-lower (name) + * Link-ext-p_nom-lower (name) + * Generator-ext-p-lower (snapshot, name) + * Generator-ext-p-upper (snapshot, name) + * Line-ext-s-lower (snapshot, name) + * Line-ext-s-upper (snapshot, name) + * Link-ext-p-lower (snapshot, name) + * Link-ext-p-upper (snapshot, name) + * Bus-nodal_balance (name, snapshot) + * Kirchhoff-Voltage-Law (snapshot, cycle) + * GlobalConstraint-co2_limit + + Status: + ------- + ok + + """ + if self._model is None: + logger.warning( + "The network has not been optimized yet and no model is stored." + ) + return self._model + + @model.deleter + def model(self) -> None: + """Delete the model object.""" + self._model = None + + @property + def objective(self) -> float | None: + """Objective value of the solved network. + + + + The property yields the objective value of the solved network. It is set after + optimizing the network points to the linopy solution (e.g. is an alias for + `n.model.objective.value`). When loading a network from file and the model + object is not loaded, the objective value is still available, as it is stored + in the network object. + + When optimizing for system costs, the total system costs are the sum of the + [pypsa.Network.objective][] and the [pypsa.Network.objective_constant][]. + + Examples + -------- + >>> n.objective # doctest: +SKIP + -47274166... + + >>> n.objective + n.objective_constant # doctest: +SKIP + Size: 8B + array(18441021...) + + """ + if self._objective is None: + logger.warning( + "The network has not been optimized yet and no objective value is stored." + ) + return self._objective + + @property + def objective_constant(self) -> float | None: + """Objective constant of the network. + + + + The property yields the fixed part of the objective function. It is set after + optimizing the network. + + When optimizing for system costs, the total system costs are the sum of the + [pypsa.Network.objective][] and the [pypsa.Network.objective_constant][]. When + loading a network from file and the model object is not loaded, the objective + constant is still available, as it is stored in the network object. + + Examples + -------- + >>> n.objective_constant # doctest: +SKIP + Size: 8B + array(65715187...) + + >>> n.objective + n.objective_constant # doctest: +SKIP + Size: 8B + array(18441021...) + + """ + if self._objective_constant is None: + logger.warning( + "The network has not been optimized yet and no objective constant is stored." + ) + return self._objective_constant + + @property + def is_solved(self) -> bool: + """Check if the network has been solved. + + + + A solved network has an [objective][pypsa.Network.objective][] value assigned. A + [model][pypsa.Network.model][] does not necessarily need to be stored in the + network. + + Returns + ------- + bool + True if the network has been solved, False otherwise. + + Examples + -------- + >>> n.is_solved + True + + """ + return self._objective is not None + + @property + def crs(self) -> Any: + """Coordinate reference system of the network's geometries. + + + + Examples + -------- + >>> n.crs + + Name: WGS 84 + Axis Info [ellipsoidal]: + - Lat[north]: Geodetic latitude (degree) + - Lon[east]: Geodetic longitude (degree) + Area of Use: + - name: World. + - bounds: (-180.0, -90.0, 180.0, 90.0) + Datum: World Geodetic System 1984 ensemble + - Ellipsoid: WGS 84 + - Prime Meridian: Greenwich + + """ + return self._crs + + @crs.setter + def crs(self, new: Any) -> None: + """Set the coordinate reference system of the network's geometries. + + See Also + -------- + [pypsa.Network.srid][], [pypsa.Network.shapes][] + + """ + self.c.shapes.static.set_crs(new) + self._crs = self.c.shapes.static.crs + + def to_crs(self, new: int | str | pyproj.CRS) -> None: + """Convert the network's geometries and bus coordinates to a new crs. + + + + See Also + -------- + [pypsa.Network.crs][], [pypsa.Network.srid][], [pypsa.Network.shapes][] + + """ + current = self.crs + self.c.shapes.static.to_crs(new, inplace=True) + self._crs = self.c.shapes.static.crs + transformer = Transformer.from_crs(current, self.crs) + self.c.buses.static["x"], self.c.buses.static["y"] = transformer.transform( + self.c.buses.static["x"], self.c.buses.static["y"] + ) + + @property + def srid(self) -> int: + """Spatial reference system identifier of the network's geometries. + + + + Examples + -------- + >>> n.srid + 4326 + + See Also + -------- + [pypsa.Network.crs][], [pypsa.Network.shapes][] + + """ + return self.crs.to_epsg() + + @srid.setter + def srid(self, new: str | int) -> None: + """Set the spatial reference system identifier of the network's geometries. + + See Also + -------- + [pypsa.Network.crs][], [pypsa.Network.shapes][] + + """ + self.crs = pyproj.CRS.from_epsg(new) + + @functools.wraps(explore) + def explore(self, *args: Any, **kwargs: Any) -> Any: + """Alias for [pypsa.Network.plot.explore][pypsa.plot.PlotAccessor.explore].""" + return self.plot.explore(*args, **kwargs) + + def copy( + self, + snapshots: Sequence | None = None, + investment_periods: Sequence | None = None, + ignore_standard_types: bool = False, + ) -> Network: + """Return a deep copy of Network object. + + + + If only default arguments are passed, the copy will be created via + `copy.deepcopy` and will contain all components and time-varying data. + For most networks this is the fastest way. However, if the network is very + large, it might be better to filter snapshots and investment periods to reduce + the size of the copy. In this case `copy.deepcopy` is not used and only + the selected snapshots and investment periods are copied to a new object. + + + Parameters + ---------- + snapshots : list or tuple or pd.Index , default self.snapshots + A list of snapshots to copy, must be a subset of n.snapshots. Pass + an empty list ignore all snapshots. + investment_periods : list or tuple or pd.Index, default self.investment_period_weightings.index + A list of investment periods to copy, must be a subset of n.investment_periods. Pass + ignore_standard_types : boolean, default False + Ignore the PyPSA standard types. + + Returns + ------- + n : pypsa.Network + The copied network object. + + Examples + -------- + With a simple reference the network is not copied: + >>> n = pypsa.examples.ac_dc_meshed() + >>> network_copy = n + >>> id(network_copy) == id(n) + True + + Use the copy method to create a new network object: + >>> network_copy = n.copy() + >>> id(network_copy) == id(n) + False + + You can also filter on a subset of snapshots (or investment periods): + >>> n.snapshots + DatetimeIndex(['2015-01-01 00:00:00', '2015-01-01 01:00:00', + '2015-01-01 02:00:00', '2015-01-01 03:00:00', + '2015-01-01 04:00:00', '2015-01-01 05:00:00', + '2015-01-01 06:00:00', '2015-01-01 07:00:00', + '2015-01-01 08:00:00', '2015-01-01 09:00:00'], + dtype='datetime64[ns]', name='snapshot', freq=None) + >>> network_copy = n.copy(snapshots=n.snapshots[0]) + >>> network_copy.snapshots + DatetimeIndex(['2015-01-01'], dtype='datetime64[ns]', name='snapshot', freq=None) + + """ + if ( + self.is_solved + and hasattr(self._model, "solver_model") + and self._model is not None + and self._model.solver_model is not None + ): + msg = "Copying a solved network with an attached solver model is not supported." + msg += " Please delete the model first using `n.model.solver_model = None`." + raise ValueError(msg) + + # Use copy.deepcopy if no arguments are passed + args = [snapshots, investment_periods, ignore_standard_types] + if all(arg is None or arg is False for arg in args): + copied_network = copy.deepcopy(self) + return copied_network + + if self.has_scenarios: + msg = ( + "Copying a stochastic network with a selection is currently not " + "supported. Use `n.copy()` to copy the entire network." + ) + raise NotImplementedError(msg) + + # Convert to pandas.Index + snapshots_ = as_index(self, snapshots, "snapshots") + investment_periods_ = as_index(self, investment_periods, "investment_periods") + + # Setup new network + n = self.__class__(ignore_standard_types=ignore_standard_types) + + # Copy components + other_comps = sorted(self.all_components - {"Bus", "Carrier"}) + # Needs to copy buses and carriers first, since there are dependencies on them + for component in self.components[["Bus", "Carrier"] + other_comps]: + # Drop the standard types to avoid them being read in twice + if ( + not ignore_standard_types + and component.name in self.standard_type_components + ): + static = component.static.drop( + n.components[component.name]["standard_types"].index + ) + else: + static = component.static + n.add(component.name, static.index, **static) + + # Copy time-varying data, if given + + if len(snapshots_) > 0: + n.set_snapshots(snapshots_) + # Apply time-varying data + for component in self.components: + dynamic = getattr(n.c, component.list_name).dynamic + for k in component.dynamic: + # Check if all snapshots_ are in the index + if set(snapshots_).issubset(component.dynamic[k].index): + dynamic[k] = component.dynamic[k].loc[snapshots_].copy() + else: + dynamic[k] = component.dynamic[k].reindex(snapshots_).copy() + + # Apply investment periods + if not investment_periods_.empty: + n.set_investment_periods(investment_periods_) + + # Add weightings + n.snapshot_weightings = self.snapshot_weightings.loc[snapshots_].copy() + n.investment_period_weightings = self.investment_period_weightings.loc[ + investment_periods_ + ].copy() + + # Catch all remaining attributes of network + for attr in [ + "name", + "srid", + "_meta", + "_linearized_uc", + "_multi_invest", + "_objective", + "_objective_constant", + "now", + ]: + if hasattr(self, attr): + setattr(n, attr, getattr(self, attr)) + + return n + + # beware, this turns bools like s_nom_extendable into objects because of + # presence of links without s_nom_extendable + def _empty_components(self) -> list: + """Get a list of all components that are empty. + + Returns + ------- + list + List of empty components. + + """ + return [c.name for c in self.components if c.empty] + + def branches(self) -> pd.DataFrame: + """Get branches. + + + + Branches are Lines, Links and Transformers. + + !!! note + + This method will return a merged copy of all branches of the network. + Changes to the returned DataFrame will not be reflected in the network. + + Examples + -------- + >>> n.branches() # doctest: +ELLIPSIS + active b b_pu ... x x_pu x_pu_eff + component name ... + + Line 0 True 0.0 0.0 ... 0.796878 0.000006 0.000006 + 1 True 0.0 0.0 ... 0.391560 0.000003 0.000003 + 2 True 0.0 0.0 ... 0.000000 0.000000 0.000000 + 3 True 0.0 0.0 ... 0.000000 0.000000 0.000000 + 4 True 0.0 0.0 ... 0.000000 0.000000 0.000000 + 5 True 0.0 0.0 ... 0.238800 0.000002 0.000002 + 6 True 0.0 0.0 ... 0.400000 0.000003 0.000003 + Link Norwich Converter True NaN NaN ... NaN NaN NaN + Norway Converter True NaN NaN ... NaN NaN NaN + Bremen Converter True NaN NaN ... NaN NaN NaN + DC link True NaN NaN ... NaN NaN NaN + + [11 rows x 61 columns] + + See Also + -------- + [pypsa.Network.passive_branches][], + [pypsa.Network.controllable_branches][] + + """ + comps = list(set(self.branch_components) - set(self._empty_components())) + names = ( + ["component", "scenario", "name"] + if self.has_scenarios + else ["component", "name"] + ) + return pd.concat( + (self.c[c].static for c in comps), + keys=comps, + sort=True, + names=names, + ) + + def passive_branches(self) -> pd.DataFrame: + """Get passive branches. + + + + Passive branches are Lines and Transformers. + + !!! note + + This method will return a merged copy of all passive branches of the network. + Changes to the returned DataFrame will not be reflected in the network. + + Examples + -------- + >>> n.passive_branches() # doctest: +ELLIPSIS + active b b_pu ... x x_pu x_pu_eff + component ... + 0 True 0.0 0.0 ... 0.796878 0.000006 0.000006 + 1 True 0.0 0.0 ... 0.391560 0.000003 0.000003 + 2 True 0.0 0.0 ... 0.000000 0.000000 0.000000 + 3 True 0.0 0.0 ... 0.000000 0.000000 0.000000 + 4 True 0.0 0.0 ... 0.000000 0.000000 0.000000 + 5 True 0.0 0.0 ... 0.238800 0.000002 0.000002 + 6 True 0.0 0.0 ... 0.400000 0.000003 0.000003 + + [7 rows x 38 columns] + + """ + comps = sorted( + set(self.passive_branch_components) - set(self._empty_components()) + ) + names = ( + ["component", "scenario", "name"] + if self.has_scenarios + else ["component", "name"] + ) + return pd.concat( + (self.c[c].static for c in comps), + keys=comps, + sort=True, + names=names, + ) + + def controllable_branches(self) -> pd.DataFrame: + """Get controllable branches. + + + + Controllable branches are Links. + + !!! note + + This method will return a merged copy of all controllable branches of the network. + Changes to the returned DataFrame will not be reflected in the network. + + Examples + -------- + >>> n.controllable_branches() # doctest: +ELLIPSIS + active b b_pu ... x x_pu x_pu_eff + component name ... + Line 0 True 0.0 0.0 ... 0.796878 0.000006 0.000006 + 1 True 0.0 0.0 ... 0.391560 0.000003 0.000003 + 2 True 0.0 0.0 ... 0.000000 0.000000 0.000000 + 3 True 0.0 0.0 ... 0.000000 0.000000 0.000000 + 4 True 0.0 0.0 ... 0.000000 0.000000 0.000000 + 5 True 0.0 0.0 ... 0.238800 0.000002 0.000002 + 6 True 0.0 0.0 ... 0.400000 0.000003 0.000003 + + [7 rows x 38 columns] + + See Also + -------- + [pypsa.Network.branches][], + [pypsa.Network.passive_branches][] + + """ + comps = list( + set(self.passive_branch_components) - set(self._empty_components()) + ) + names = ( + ["component", "scenario", "name"] + if self.has_scenarios + else ["component", "name"] + ) + return pd.concat( + (self.c[c].static for c in comps), + keys=comps, + sort=True, + names=names, + ) + + def determine_network_topology( + self, + investment_period: int | str | None = None, + skip_isolated_buses: bool = False, + ) -> Network: + """Build sub_networks from topology. + + + + For the default case investment_period=None, it is not taken + into account whether the branch components are active (based on + build_year and lifetime). If the investment_period is specified, + the network topology is determined on the basis of the active + branches. + """ + adjacency_matrix = self.adjacency_matrix( + branch_components=self.passive_branch_components, + investment_period=investment_period, + return_dataframe=True, + ) + n_components, labels = csgraph.connected_components( + adjacency_matrix.values, directed=False + ) + labels = pd.Series(labels, adjacency_matrix.index, name="sub_network") + sub_network_map = labels.astype(str) + + # remove all old sub_networks + if not self.c.sub_networks.static.empty: + # Delete sub-network objects first + for sub_network in self.c.sub_networks.static.index: + obj = self.c.sub_networks.static.at[sub_network, "obj"] + del obj + + # Clear the sub_networks DataFrame completely + # This handles both regular and stochastic cases + self.c.sub_networks.static.drop( + self.c.sub_networks.static.index, inplace=True + ) + for dynamic in self.c.sub_networks.dynamic.values(): + dynamic.drop(dynamic.columns, inplace=True) + + if self.has_scenarios: + bus_carrier = self.c.buses.static.carrier.xs( + self.scenarios[0], level="scenario" + ) + else: + bus_carrier = self.c.buses.static.carrier + + for i in np.arange(n_components): + # index of first bus + buses = labels.index[labels == i] + + if skip_isolated_buses and (len(buses) == 1): + continue + + carrier = bus_carrier.at[buses[0]] + + if carrier not in ["AC", "DC"] and len(buses) > 1: + logger.warning( + "Warning, sub network %d is not electric but " + "contains multiple buses\nand branches. Passive " + "flows are not allowed for non-electric networks!", + i, + ) + + if (bus_carrier.loc[buses] != carrier).any(): + logger.warning( + "Warning, sub network %d contains buses with " + "mixed carriers! Value counts:" + "\n%s", + i, + bus_carrier.loc[buses].value_counts(), + ) + + self.add("SubNetwork", str(i), carrier=carrier) + + # add objects + self.c.sub_networks.static["obj"] = [ + SubNetwork(self, name) for name in self.c.sub_networks.static.index + ] + + self.c.buses.static = self.c.buses.static.drop(columns="sub_network").join( + sub_network_map, "name" + )[self.c.buses.static.columns] + + for c in self.components: + if c.name not in self.passive_branch_components: + continue + c.static["sub_network"] = c.static.bus0.map(sub_network_map) + + if investment_period is not None: + active = c.get_active_assets(investment_period) + # set non active assets to NaN + c.static.loc[~active, "sub_network"] = np.nan + + for sub in self.c.sub_networks.static.obj: + find_cycles(sub) + sub.find_bus_controls() + + return self + + def cycle_matrix( + self, investment_period: str | int | None = None, apply_weights: bool = False + ) -> pd.DataFrame: + """Get the cycles in the network and represent them as a DataFrame. + + + + This function identifies a cycle basis of the network topology and + returns a DataFrame representation of the cycle matrix. The cycles + matrix is a sparse matrix with branches as rows and independent + cycles as columns. An entry of +1 indicates the branch is traversed + in the direction from bus0 to bus1 in that cycle, -1 indicates + the opposite direction, and 0 indicates the branch is not part + of the cycle. + + Parameters + ---------- + investment_period : str or int, optional + Investment period to use when determining network topology. + If not given, all branches are considered regardless of + build_year and lifetime. + apply_weights : bool, default False + Whether to apply weights (e.g., reactance for AC lines, + resistance for DC lines) to the cycles. + + Returns + ------- + pandas.DataFrame + A DataFrame with branches as rows (MultiIndex of (component, name)) + and cycles as columns. Each column represents an independent cycle + in the network. + + """ + self.determine_network_topology( + investment_period=investment_period, skip_isolated_buses=True + ) + self.calculate_dependent_values() + + cycles = [] + + # Process each sub-network to find its cycles + for sub_network in self.c.sub_networks.static.obj: + branches = sub_network.branches() + + if self.has_scenarios: + branches = branches.xs(self.scenarios[0], level="scenario") + + branches_i = branches.index + branches_i.names = ["type", "component"] + if not hasattr(sub_network, "C") or not sub_network.C.size: + continue + + # Convert sparse matrix to DataFrame + C = pd.DataFrame(sub_network.C.todense(), index=branches_i) + cycles.append(C) + + if not cycles: + return pd.DataFrame() + + # Combine all cycles and fill missing values with 0 + cycles_df = pd.concat(cycles, axis=1, ignore_index=True).fillna(0) + + # Get all branch components + existing_branch_components = cycles_df.index.unique("type") + branches = self.branches() + + if self.has_scenarios: + branches = branches.xs(self.scenarios[0], level="scenario") + + branches.index.names = ["type", "name"] + branches_i = branches.loc[existing_branch_components].index + + if apply_weights: + is_ac = branches.sub_network.map(self.c.sub_networks.static.carrier) == "AC" + weights = branches.x_pu_eff.where(is_ac, branches.r_pu_eff) + weights = weights[cycles_df.index] + cycles_df = cycles_df.multiply(weights, axis=0) + + # Reindex to include all branches (even those not in cycles) + return cycles_df.reindex(branches_i, fill_value=0).rename_axis(columns="cycle") + + @deprecated_in_next_major( + details="Use `n.components.` instead.", + ) + def component(self, c_name: str) -> Component: + """Get a component from the network. + + !!! warning "Deprecated in " + + Use `n.components.` or `n.components[component_name]` instead. + + """ + return self.components[c_name] + + @deprecated_in_next_major(details="Use `for component in n.components` instead.") + def iterate_components( + self, components: Collection[str] | None = None, skip_empty: bool = True + ) -> Iterator[Component]: + """Iterate over components. + + !!! warning "Deprecated in " + + Use `for component in n.components` instead. + + Examples + -------- + >>> for component in n.iterate_components(): # doctest: +SKIP + ... print(component) + ... break + 'Bus' Components + + """ + if components is None: + components = self.all_components + + return ( + self.c[c_name] + for c_name in components + if not (skip_empty and self.c[c_name].static.empty) + ) + + +class SubNetwork(NetworkGraphMixin, SubNetworkPowerFlowMixin): + """SubNetwork for electric buses (AC or DC). + + + + SubNetworks are generated by [pypsa.Network.determine_network_topology][] for + electric buses with passive flows or isolated non-electric buses and stored in + the [`n.components.sub_networks`][pypsa.components.SubNetworks] component.` + """ + + # Type hints + # ---------------- + + buses_o: pd.Index + pvpqs: pd.Index + pqs: pd.Index + pvs: pd.Index + slack_bus: str + B: spmatrix + K: spmatrix + C: spmatrix + PTDF: spmatrix + BODF: spmatrix + + list_name = "sub_networks" + + def __init__(self, n: Network, name: str) -> None: + """Initialize a sub-network. + + Parameters + ---------- + n : pypsa.Network + The parent network of the sub-network. + name : str + The name of the sub-network. + + """ + self._n = ref(n) + self.name = name + + # TODO assign __str__ and __repr__ + @property + def n(self) -> Network: + """Get the parent network of the sub-network. + + Examples + -------- + >>> sub_network.n # doctest: +ELLIPSIS + PyPSA Network 'AC-DC-Meshed' + ---------------------------- + Components: + - Bus: 9 + ... + + """ + return self._n() # type: ignore + + @property + def components(self) -> ComponentsStore: + """Get the components for the sub-network. + + Sub network components behave like Components in a basic pypsa.Network, but are + a special class (SubNetworkComponents) to only return a view from the parent + network. + + Examples + -------- + Get single component: + >>> sub_network.components.generators + 'Generator' SubNetworkComponents + -------------------------------- + Attached to Sub-Network of PyPSA Network 'AC-DC-Meshed' + Components: 6 + + Getting a component is also possible via getitem: + >>> sub_network.components['generators'] # doctest: +ELLIPSIS + 'Generator' SubNetworkComponents + ... + + Or with the component name instead of list notation: + >>> sub_network.components['Generator'] # doctest: +ELLIPSIS + 'Generator' SubNetworkComponents + ... + + See Also + -------- + [pypsa.Network.components][] + + """ + + def filter_down(key: str, c: Components) -> Any: + value = c[key] + if key == "static": + if c.name in {"Bus"} | self.n.passive_branch_components: + return value[value.sub_network == self.name] + if c.name in self.n.one_port_components: + buses = self.c.buses.static.index.unique("name") + return value[value.bus.isin(buses)] + msg = f"Component {c.name} not supported for sub-networks" + raise ValueError(msg) + if key == "dynamic": + dynamic = Dict() + index = c.static.index + for k, v in c.dynamic.items(): + dynamic[k] = v[index.intersection(v.columns)] + return dynamic + return value + + return ComponentsStore( + { + key: SubNetworkComponents(value, filter_down) + for key, value in self.n.components.items() + } + ) + + @property + def c(self) -> ComponentsStore: + """Get the components for the sub-network. + + Alias for `sub_network.components`. + + See Also + -------- + [pypsa.SubNetwork.components][] + + """ + return self.components + + @property + def snapshots(self) -> pd.Index | pd.MultiIndex: + """Get the snapshots for the sub-network. + + See Also + -------- + [pypsa.Network.snapshots][] + + """ + return self.n.snapshots + + @property + def snapshot_weightings(self) -> pd.DataFrame: + """Get the snapshot weightings for the sub-network. + + See Also + -------- + [pypsa.Network.snapshot_weightings][] + + """ + return self.n.snapshot_weightings + + @property + def investment_periods(self) -> pd.Index: + """Get the investment periods for the sub-network. + + See Also + -------- + [pypsa.Network.investment_periods][] + + """ + return self.n.investment_periods + + @property + def investment_period_weightings(self) -> pd.DataFrame: + """Get the investment period weightings for the sub-network. + + See Also + -------- + [pypsa.Network.investment_period_weightings][] + + """ + return self.n.investment_period_weightings + + @property + def scenarios(self) -> pd.Series: + """Get the scenarios for the network. + + Returns + ------- + pd.Series + The scenarios for the network. + + """ + return self.n.scenarios + + @property + def has_scenarios(self) -> bool: + """Check if the network has scenarios. + + Returns + ------- + bool + True if the network has scenarios, False otherwise. + + """ + return self.n.has_scenarios + + def branches_i(self, active_only: bool = False) -> pd.MultiIndex: + """Get the index of the branches in the sub-network. + + Parameters + ---------- + active_only : bool, default False + If True, only return the index of the active branches. + + Returns + ------- + pd.MultiIndex + The index of the branches in the sub-network. + + Examples + -------- + >>> sub_network.branches_i() + MultiIndex([('Line', '0'), + ('Line', '1'), + ('Line', '5')], + names=['type', 'name']) + + """ + types = [] + names = [] + for c in self.components[sorted(self.n.passive_branch_components)]: + static = c.static + idx = static.query("active").index if active_only else static.index + types += len(idx) * [c.name] + names += list(idx) + return pd.MultiIndex.from_arrays([types, names], names=("type", "name")) + + def branches(self) -> pd.DataFrame: + """Get the branches in the sub-network. + + See Also + -------- + [pypsa.Network.branches][] + + """ + branches = self.n.passive_branches() + return branches[branches.sub_network == self.name] + + @deprecated_in_next_major( + details="Use `sub_network.components.` instead.", + ) + def component(self, c_name: str) -> SubNetworkComponents: + """Get a component from the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components.` instead. + + See Also + -------- + [pypsa.Network.components][] + + """ + return self.components[c_name] + + @deprecated_in_next_major( + details="Use `sub_network.components..static` instead.", + ) + def df(self, c_name: str) -> pd.DataFrame: + """Get a static component from the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components..static` instead. + + See Also + -------- + [pypsa.Network.static][] + + """ + return self.c[c_name].static + + @deprecated_in_next_major( + details="Use `sub_network.components..static` instead.", + ) + def static(self, c_name: str) -> pd.DataFrame: + """Get a static component from the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components..static` instead. + + See Also + -------- + [pypsa.Network.static][] + + """ + return self.components[c_name].static + + @deprecated_in_next_major( + details="Use `sub_network.components..dynamic` instead.", + ) + def pnl(self, c_name: str) -> Dict: + """Get a dynamic component from the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components..dynamic` instead. + + See Also + -------- + [pypsa.Network.dynamic][] + + """ + return self.c[c_name].dynamic + + @deprecated_in_next_major( + details="Use `sub_network.components..dynamic` instead.", + ) + def dynamic(self, c_name: str) -> Dict: + """Get a dynamic component from the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components..dynamic` instead. + + See Also + -------- + [pypsa.Network.dynamic][] + + """ + return self.components[c_name].dynamic + + @deprecated_in_next_major( + details="Use `sub_network.components.buses.static.index` instead.", + ) + def buses_i(self) -> pd.Index: + """Get the index of the buses in the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components.buses.static.index` instead. + + See Also + -------- + [pypsa.Network.buses][] + + """ + return self.c.buses.static.index + + @deprecated_in_next_major( + details="Use `sub_network.components.lines.static.index` instead.", + ) + def lines_i(self) -> pd.Index: + """Get the index of the lines in the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components.lines.static.index` instead. + + See Also + -------- + [pypsa.Network.lines][] + + """ + return self.c.lines.static.index + + @deprecated_in_next_major( + details="Use `sub_network.components.transformers.static.index` instead.", + ) + def transformers_i(self) -> pd.Index: + """Get the index of the transformers in the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components.transformers.static.index` instead. + + See Also + -------- + [pypsa.Network.transformers][] + + """ + return self.c.transformers.static.index + + @deprecated_in_next_major( + details="Use `sub_network.components.generators.static.index` instead.", + ) + def generators_i(self) -> pd.Index: + """Get the index of the generators in the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components.generators.static.index` instead. + + See Also + -------- + [pypsa.Network.generators][] + + """ + return self.c.generators.static.index + + @deprecated_in_next_major( + details="Use `sub_network.components.loads.static.index` instead.", + ) + def loads_i(self) -> pd.Index: + """Get the index of the loads in the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components.loads.static.index` instead. + + See Also + -------- + [pypsa.Network.loads][] + + """ + return self.c.loads.static.index + + @deprecated_in_next_major( + details="Use `sub_network.components.shunt_impedances.static.index` instead.", + ) + def shunt_impedances_i(self) -> pd.Index: + """Get the index of the shunt impedances in the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components.shunt_impedances.static.index` instead. + + See Also + -------- + [pypsa.Network.shunt_impedances][] + + """ + return self.c.shunt_impedances.static.index + + @deprecated_in_next_major( + details="Use `sub_network.components.storage_units.static.index` instead.", + ) + def storage_units_i(self) -> pd.Index: + """Get the index of the storage units in the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components.storage_units.static.index` instead. + + See Also + -------- + [pypsa.Network.storage_units][] + + """ + return self.c.storage_units.static.index + + @deprecated_in_next_major( + details="Use `sub_network.components.stores.index.static` instead.", + ) + def stores_i(self) -> pd.Index: + """Get the index of the stores in the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components.buses.static` instead. + + See Also + -------- + [pypsa.Network.buses][] + + """ + return self.c.buses.static + + @deprecated_in_next_major( + details="Use `sub_network.components.generators.static` instead.", + ) + def generators(self) -> pd.DataFrame: + """Get the generators in the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components.generators.static` instead. + + See Also + -------- + [pypsa.Network.generators][] + + """ + return self.c.generators.static + + @deprecated_in_next_major( + details="Use `sub_network.components.loads.static` instead.", + ) + def loads(self) -> pd.DataFrame: + """Get the loads in the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components.loads.static` instead. + + See Also + -------- + [pypsa.Network.loads][] + + """ + return self.c.loads.static + + @deprecated_in_next_major( + details="Use `sub_network.components.shunt_impedances.static` instead.", + ) + def shunt_impedances(self) -> pd.DataFrame: + """Get the shunt impedances in the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components.shunt_impedances.static` instead. + + See Also + -------- + [pypsa.Network.shunt_impedances][] + + """ + return self.c.shunt_impedances.static + + @deprecated_in_next_major( + details="Use `sub_network.components.storage_units.static` instead.", + ) + def storage_units(self) -> pd.DataFrame: + """Get the storage units in the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components.storage_units.static` instead. + + See Also + -------- + [pypsa.Network.storage_units][] + + """ + return self.c.storage_units.static + + @deprecated_in_next_major( + details="Use `!!! deprecated.components.stores.static` instead.", + ) + def stores(self) -> pd.DataFrame: + """Get the stores in the sub-network. + + !!! warning "Deprecated in " + + Use `sub_network.components.stores.static` instead. + """ + return self.c.stores.static + + @deprecated_in_next_major(details="Use `self.components` instead.") + # Deprecate: Use `self.iterate_components` instead + def iterate_components( + self, components: Collection[str] | None = None, skip_empty: bool = True + ) -> Iterator[SubNetworkComponents]: + """Iterate over components of the sub-network. + + Parameters + ---------- + components : list-like, optional + List of components ('Generator', 'Line', etc.) to iterate over, + by default None + skip_empty : bool, optional + Whether to skip a components with no assigned assets, + by default True + + See Also + -------- + [pypsa.Network.iterate_components][] + + Yields + ------ + Component + Container for component data. See Component class for details. + + """ + if components is None: + components = self.n.all_components + + return ( + self.components[c_name] + for c_name in components + if not (skip_empty and self.c[c_name].static.empty) + ) diff --git a/PyPSA/source/pypsa/optimization/__init__.py b/PyPSA/source/pypsa/optimization/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..acb77c726a8ece46c3d6117626223771a355dfdc --- /dev/null +++ b/PyPSA/source/pypsa/optimization/__init__.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Build optimisation problems from PyPSA networks with Linopy.""" + +from pypsa.optimization import abstract, constraints, optimize, variables +from pypsa.optimization.optimize import OptimizationAccessor + +__all__ = [ + "abstract", + "constraints", + "optimize", + "variables", + "OptimizationAccessor", +] diff --git a/PyPSA/source/pypsa/optimization/abstract.py b/PyPSA/source/pypsa/optimization/abstract.py new file mode 100644 index 0000000000000000000000000000000000000000..e53af9caec9564a12e3637dac41b2582253fc386 --- /dev/null +++ b/PyPSA/source/pypsa/optimization/abstract.py @@ -0,0 +1,636 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Build abstracted, extended optimisation problems from PyPSA networks with Linopy.""" + +from __future__ import annotations + +import gc +import logging +from itertools import product +from typing import TYPE_CHECKING, Any + +import numpy as np +import pandas as pd +import xarray as xr + +from pypsa._options import options +from pypsa.descriptors import nominal_attrs +from pypsa.optimization.mga import OptimizationAbstractMGAMixin + +if TYPE_CHECKING: + from collections.abc import Sequence + + from pypsa import Network +logger = logging.getLogger(__name__) + + +def discretized_capacity( + nom_opt: float, + nom_max: float, + unit_size: float, + threshold: float, + fractional_last_unit_size: bool, +) -> float: + """Discretize a optimal capacity to a capacity that is either a multiple of a unit size or the maximum capacity. + + Depending on the variable `fractional_last_unit_size`. This function checks if the optimal capacity is within the threshold of the unit + size. If so, it returns the next multiple of the unit size - if not it returns the + last multiple of the unit size. + In the special case that the maximum capacity is not a multiple of the unit size, + the variable `fractional_last_unit_size` determines if the returned capacity is the + maximum capacity (True) or the last multiple of the unit size (False). + In case the maximum capacity is lower than the unit size, the function returns the + maximum capacity. + + Parameters + ---------- + nom_opt : float + The optimal capacity as returned by the optimization. + nom_max : float + The maximum capacity as defined in the network. + unit_size : float + The unit size for the capacity. + threshold : float + The threshold relative to the unit size for discretizing the capacity. + fractional_last_unit_size : bool + Whether only multiples of the unit size or the maximum capacity. + + Returns + ------- + float + The discretized capacity. + + Examples + -------- + >>> discretized_capacity( + ... nom_opt = 7, + ... nom_max = 25, + ... unit_size = 5, + ... threshold = 0.1, + ... fractional_last_unit_size = False) + 10 + >>> discretized_capacity( + ... nom_opt = 7, + ... nom_max = 8, + ... unit_size = 5, + ... threshold = 0.1, + ... fractional_last_unit_size = False) + 5 + >>> discretized_capacity( + ... nom_opt = 7, + ... nom_max = 8, + ... unit_size = 5, + ... threshold = 0.1, + ... fractional_last_unit_size = True) + 8 + >>> discretized_capacity( + ... nom_opt = 3, + ... nom_max = 4, + ... unit_size = 5, + ... threshold = 0.1, + ... fractional_last_unit_size = False) + 4 + + """ + units = nom_opt // unit_size + (nom_opt % unit_size >= threshold * unit_size) + + block_capacity = units * unit_size + if nom_max % unit_size == 0: + return block_capacity + + if (nom_max - nom_opt) < unit_size: + if ( + fractional_last_unit_size + and ((nom_opt % unit_size) / (nom_max % unit_size)) >= threshold + ): + return nom_max + if nom_max < unit_size: + return nom_max + return (nom_opt // unit_size) * unit_size + return block_capacity + + +class OptimizationAbstractMixin(OptimizationAbstractMGAMixin): + """Mixin class for additional optimization methods. + + Class inherits to [pypsa.optimization.OptimizationAccessor][]. All attributes and + methods can be used within any Network instance via `n.optimize`. + + """ + + _n: Network + + def optimize_transmission_expansion_iteratively( + self, + snapshots: Sequence | None = None, + msq_threshold: float = 0.05, + min_iterations: int = 1, + max_iterations: int = 100, + track_iterations: bool = False, + line_unit_size: float | None = None, + link_unit_size: dict | None = None, + line_threshold: float | None = None, + link_threshold: dict | None = None, + fractional_last_unit_size: bool = False, + **kwargs: Any, + ) -> tuple[str, str]: + """Run iterative linear optimization. + + Updating the line parameters for passive AC + and DC lines. This is helpful when line expansion is enabled. After each + successful solving, line impedances and line resistance are recalculated + based on the optimization result. If warmstart is possible, it uses the + result from the previous iteration to fasten the optimization. + + Parameters + ---------- + snapshots : list or index slice + A list of snapshots to optimise, must be a subset of + n.snapshots, defaults to n.snapshots + msq_threshold: float, default 0.05 + Maximal mean square difference between optimized line capacity of + the current and the previous iteration. As soon as this threshold is + undercut, and the number of iterations is bigger than 'min_iterations' + the iterative optimization stops + min_iterations : integer, default 1 + Minimal number of iteration to run regardless whether the msq_threshold + is already undercut + max_iterations : integer, default 100 + Maximal number of iterations to run regardless whether msq_threshold + is already undercut + track_iterations: bool, default False + If True, the intermediate branch capacities and values of the + objective function are recorded for each iteration. The values of + iteration 0 represent the initial state. + line_unit_size: float, default None + The unit size for line components. + Use None if no discretization is desired. + link_unit_size: dict-like, default None + A dictionary containing the unit sizes for link components, + with carrier names as keys. Use None if no discretization is desired. + line_threshold: float, default 0.3 + The threshold relative to the unit size for discretizing line components. + link_threshold: dict-like, default 0.3 per carrier + The threshold relative to the unit size for discretizing link components. + fractional_last_unit_size: bool, default False + Whether only multiples of the unit size or in case of a maximum capacity fractions of unit size is allowed. + **kwargs + Keyword arguments of the `n.optimize` function which runs at each iteration + + """ + n = self._n + + n.c.lines.static["carrier"] = n.c.lines.static.bus0.map( + n.c.buses.static.carrier + ) + ext_i = n.c.lines.extendables.difference(n.c.lines.inactive_assets) + typed_i = n.c.lines.static.query('type != ""').index + ext_untyped_i = ext_i.difference(typed_i) + ext_typed_i = ext_i.intersection(typed_i) + base_s_nom = ( + np.sqrt(3) + * n.c.lines.static["type"].map(n.c.line_types.static.i_nom) + * n.c.lines.static.bus0.map(n.c.buses.static.v_nom) + ) + n.c.lines.static.loc[ext_typed_i, "num_parallel"] = ( + n.c.lines.static.s_nom / base_s_nom + )[ext_typed_i] + + def update_line_params(n: Network, s_nom_prev: float | pd.Series) -> None: + factor = n.c.lines.static.s_nom_opt / s_nom_prev + for attr, carrier in (("x", "AC"), ("r", "DC")): # noqa: B007 + ln_i = n.c.lines.static.query("carrier == @carrier").index.intersection( + ext_untyped_i + ) + n.c.lines.static.loc[ln_i, attr] /= factor[ln_i] + ln_i = ext_i.intersection(typed_i) + n.c.lines.static.loc[ln_i, "num_parallel"] = ( + n.c.lines.static.s_nom_opt / base_s_nom + )[ln_i] + + def msq_diff(n: Network, s_nom_prev: float | pd.Series) -> float: + lines_err = ( + np.sqrt((s_nom_prev - n.c.lines.static.s_nom_opt).pow(2).mean()) + / n.c.lines.static["s_nom_opt"].mean() + ) + logger.info( + "Mean square difference after iteration %s is %s", + iteration, + lines_err, + ) + return lines_err + + def save_optimal_capacities(n: Network, iteration: int, status: str) -> None: + for c, attr in pd.Series(nominal_attrs)[list(n.branch_components)].items(): + n.c[c].static[f"{attr}_opt_{iteration}"] = n.c[c].static[f"{attr}_opt"] + setattr(n, f"status_{iteration}", status) + setattr(n, f"objective_{iteration}", n.objective) + n.iteration = iteration + n.c.global_constraints.static = n.c.global_constraints.static.rename( + columns={"mu": f"mu_{iteration}"} + ) + + def discretize_branch_components( + n: Network, + line_unit_size: float | None, + link_unit_size: dict | None, + line_threshold: float | None, + link_threshold: dict | None, + fractional_last_unit_size: bool = False, + ) -> None: + """Discretizes the branch components of a network based on the specified unit sizes and thresholds.""" + # TODO: move default value definition to main function (unnest) + line_threshold = line_threshold or 0.3 + link_threshold = link_threshold or {} + + if line_unit_size: + n.c.lines.static["s_nom"] = n.c.lines.static.apply( + lambda row: discretized_capacity( + nom_opt=row["s_nom_opt"], + nom_max=row["s_nom_max"], + unit_size=line_unit_size, + threshold=line_threshold, + fractional_last_unit_size=fractional_last_unit_size, + ), + axis=1, + ) + + if link_unit_size: + for carrier in ( + link_unit_size.keys() & n.c.links.static.carrier.unique() + ): + idx = n.c.links.static.carrier == carrier + n.c.links.static.loc[idx, "p_nom"] = n.c.links.static.loc[ + idx + ].apply( + lambda row: discretized_capacity( + nom_opt=row["p_nom_opt"], + nom_max=row["p_nom_max"], + unit_size=link_unit_size[carrier], # noqa: B023 + threshold=link_threshold.get(carrier, 0.3), # noqa: B023 + fractional_last_unit_size=fractional_last_unit_size, + ), + axis=1, + ) + + if link_threshold is None: + link_threshold = {} + + if track_iterations: + for c, attr in pd.Series(nominal_attrs)[list(n.branch_components)].items(): + n.c[c].static[f"{attr}_opt_0"] = n.c[c].static[f"{attr}"] + + iteration = 1 + diff = msq_threshold + while diff >= msq_threshold or iteration < min_iterations: + if iteration > max_iterations: + logger.info( + "Iteration %s beyond max_iterations %s. Stopping ...", + iteration, + max_iterations, + ) + break + + s_nom_prev = ( + n.c.lines.static.s_nom_opt.copy() + if iteration + else n.c.lines.static.s_nom.copy() + ) + status, termination_condition = n.optimize(snapshots, **kwargs) + if status != "ok": + msg = ( + f"Optimization failed with status {status} and termination " + f"{termination_condition}" + ) + raise RuntimeError(msg) + if track_iterations: + save_optimal_capacities(n, iteration, status) + + update_line_params(n, s_nom_prev) + diff = msq_diff(n, s_nom_prev) + iteration += 1 + + logger.info( + "Deleting model instance `n.model` from previour run to reclaim memory." + ) + del n.model + gc.collect() + + logger.info( + "Preparing final iteration with fixed and potentially discretized branches (HVDC links and HVAC lines)." + ) + + link_carriers = {"DC"} if not link_unit_size else link_unit_size.keys() | {"DC"} + ext_links_to_fix_b = ( + n.c.links.static.p_nom_extendable + & n.c.links.static.carrier.isin(link_carriers) + ) + s_nom_orig = n.c.lines.static.s_nom.copy() + p_nom_orig = n.c.links.static.p_nom.copy() + + n.c.lines.static.loc[ext_i, "s_nom"] = n.c.lines.static.loc[ext_i, "s_nom_opt"] + n.c.lines.static.loc[ext_i, "s_nom_extendable"] = False + + n.c.links.static.loc[ext_links_to_fix_b, "p_nom"] = n.c.links.static.loc[ + ext_links_to_fix_b, "p_nom_opt" + ] + n.c.links.static.loc[ext_links_to_fix_b, "p_nom_extendable"] = False + + discretize_branch_components( + n, + line_unit_size, + link_unit_size, + line_threshold, + link_threshold, + fractional_last_unit_size, + ) + + n.calculate_dependent_values() + status, condition = n.optimize(snapshots, **kwargs) + + n.c.lines.static.loc[ext_i, "s_nom"] = s_nom_orig.loc[ext_i] + n.c.lines.static.loc[ext_i, "s_nom_extendable"] = True + + n.c.links.static.loc[ext_links_to_fix_b, "p_nom"] = p_nom_orig.loc[ + ext_links_to_fix_b + ] + n.c.links.static.loc[ext_links_to_fix_b, "p_nom_extendable"] = True + + ## add costs of additional infrastructure to objective value of last iteration + obj_links = ( + n.c.links.static[ext_links_to_fix_b] + .eval("capital_cost * (p_nom_opt - p_nom_min)") + .sum() + ) + obj_lines = n.c.lines.static.eval( + "capital_cost * (s_nom_opt - s_nom_min)" + ).sum() + n._objective += obj_links + obj_lines + n._objective_constant -= obj_links + obj_lines + + return status, condition + + def optimize_security_constrained( + self, + snapshots: Sequence | None = None, + branch_outages: Sequence | pd.Index | pd.MultiIndex | None = None, + multi_investment_periods: bool = False, + model_kwargs: dict | None = None, + **kwargs: Any, + ) -> tuple[str, str]: + """Compute Security-Constrained Linear Optimal Power Flow (SCLOPF). + + This ensures that no branch is overloaded even given the branch outages. + + Parameters + ---------- + snapshots : list-like, optional + Set of snapshots to consider in the optimization. The default is None. + branch_outages : list-like/pandas.Index/pandas.MultiIndex, optional + Subset of passive branches to consider as possible outages. If a list + or a pandas.Index is passed, it is assumed to identify lines. If a + multiindex is passed, its first level has to contain the component names, + the second the assets. The default None results in all passive branches + to be considered. + multi_investment_periods : bool, default False + Whether to optimise as a single investment period or to optimise in multiple + investment periods. Then, snapshots should be a `pd.MultiIndex`. + model_kwargs : dict, optional + Keyword arguments used by `linopy.Model`, such as `solver_dir` or `chunk`. + Defaults to module wide option (default: {}). See + `https://go.pypsa.org/options-params` for more information. + **kwargs: + Keyword argument used by `linopy.Model.solve`, such as `solver_name`, + `problem_fn` or solver options directly passed to the solver. + + """ + # Handle default parameters from options + if model_kwargs is None: + model_kwargs = options.params.optimize.model_kwargs.copy() + + n = self._n + + all_passive_branches = n.passive_branches().index + + if branch_outages is None: + branch_outages = all_passive_branches + elif isinstance(branch_outages, (list | pd.Index)): + branch_outages = pd.MultiIndex.from_product([("Line",), branch_outages]) + + if diff := set(branch_outages) - set(all_passive_branches): + msg = f"The following passive branches are not in the network: {diff}" + raise ValueError(msg) + + if not len(all_passive_branches): + return n.optimize( + snapshots, + multi_investment_periods=multi_investment_periods, + model_kwargs=model_kwargs, + **kwargs, + ) + + m = n.optimize.create_model( + snapshots=snapshots, + multi_investment_periods=multi_investment_periods, + **model_kwargs, + ) + + for sub_network in n.c.sub_networks.static.obj: + branches_i = sub_network.branches_i() + outages = branches_i.intersection(branch_outages) + + if outages.empty: + continue + + sub_network.calculate_BODF() + BODF = pd.DataFrame(sub_network.BODF, index=branches_i, columns=branches_i)[ + outages + ] + + for c_outage, c_affected in product( + outages.unique(0), branches_i.unique(0) + ): + c_outage_ = c_outage + "-outage" + c_outages = outages.get_loc_level(c_outage)[1] + flow_outage = m.variables[c_outage + "-s"].loc[:, c_outages] + flow_outage = flow_outage.rename({"name": c_outage_}) + + bodf = BODF.loc[c_affected, c_outage] + bodf = xr.DataArray(bodf, dims=[c_affected, c_outage_]) + added_flow = flow_outage * bodf + + for bound, kind in product(("lower", "upper"), ("fix", "ext")): + constraint = c_affected + "-" + kind + "-s-" + bound + if constraint not in m.constraints: + continue + + con = m.constraints[constraint] + + idx = con.lhs.indexes["name"].intersection( + added_flow.indexes[c_affected] + ) + + added_flow_aligned = added_flow.sel({c_affected: idx}).rename( + {c_affected: "name"} + ) + lhs = con.lhs.sel(name=idx) + added_flow_aligned + + name = ( + constraint + + f"-security-for-{c_outage_}-in-sub-network-{sub_network.name}" + ) + m.add_constraints( + lhs, con.sign.sel(name=idx), con.rhs.sel(name=idx), name=name + ) + + return n.optimize.solve_model(**kwargs) + + def optimize_with_rolling_horizon( + self, + snapshots: Sequence | None = None, + horizon: int = 100, + overlap: int = 0, + **kwargs: Any, + ) -> Network: + """Optimizes the network in a rolling horizon fashion. + + Parameters + ---------- + snapshots : list-like + Set of snapshots to consider in the optimization. The default is None. + horizon : int + Number of snapshots to consider in each iteration. Defaults to 100. + overlap : int + Number of snapshots to overlap between two iterations. Defaults to 0. + **kwargs: + Keyword argument used by `linopy.Model.solve`, such as `solver_name`, + + """ + n = self._n + if snapshots is None: + snapshots = n.snapshots + + if horizon <= overlap: + msg = "overlap must be smaller than horizon" + raise ValueError(msg) + + starting_points = range(0, len(snapshots), horizon - overlap) + for i, start in enumerate(starting_points): + end = min(len(snapshots), start + horizon) + sns = snapshots[start:end] + logger.info( + "Optimizing network for snapshot horizon [%s:%s] (%s/%s).", + sns[0], + sns[-1], + i + 1, + len(starting_points), + ) + + if i: + if not n.c.stores.static.empty: + n.c.stores.static.e_initial = n.c.stores.dynamic.e.loc[ + snapshots[start - 1] + ] + if not n.c.storage_units.static.empty: + n.c.storage_units.static.state_of_charge_initial = ( + n.c.storage_units.dynamic.state_of_charge.loc[ + snapshots[start - 1] + ] + ) + + status, condition = n.optimize(sns, **kwargs) + if status != "ok": + logger.warning( + "Optimization failed with status %s and condition %s", + status, + condition, + ) + return n + + def optimize_and_run_non_linear_powerflow( + self, + snapshots: Sequence | None = None, + skip_pre: bool = False, + x_tol: float = 1e-06, + use_seed: bool = False, + distribute_slack: bool = False, + slack_weights: str = "p_set", + **kwargs: Any, + ) -> dict: + """Optimizes the network and then performs a non-linear power flow for all snapshots. + + Parameters + ---------- + snapshots : Sequence | None, optional + Set of snapshots to consider in the optimization and power flow. + If None, uses all snapshots in the network. + skip_pre : bool, optional + Skip the preliminary steps of the power flow, by default False. + x_tol : float, optional + Power flow convergence tolerance, by default 1e-06. + use_seed : bool, optional + Use the last solution as initial guess, by default False. + distribute_slack : bool, optional + Distribute slack power across generators, by default False. + slack_weights : str, optional + How to distribute slack power, by default 'p_set'. + **kwargs : Any + Keyword arguments passed to the optimize function. + + Returns + ------- + Tuple[str, str, Dict] + A tuple containing: + - optimization status + - optimization condition + - dictionary of power flow results for all snapshots + + """ + n = self._n + if snapshots is None: + snapshots = n.snapshots + + n = self._n + + # Step 1: Optimize the network + status, condition = n.optimize(snapshots, **kwargs) + + if status != "ok": + logger.warning( + "Optimization failed with status %s and condition %s", + status, + condition, + ) + return {"status": status, "terminantion_condition": condition} + + for c in n.one_port_components: + n.c[c].dynamic["p_set"] = n.c[c].dynamic["p"] + for c in ("Link",): + n.c[c].dynamic["p_set"] = n.c[c].dynamic["p0"] + + n.c.generators.static.control = "PV" + for sub_network in n.c.sub_networks.static.obj: + n.c.generators.static.loc[sub_network.slack_generator, "control"] = "Slack" + # Need some PQ buses so that Jacobian doesn't break + for sub_network in n.c.sub_networks.static.obj: + generators = sub_network.c.generators.static.index + other_generators = generators.difference([sub_network.slack_generator]) + if not other_generators.empty: + n.c.generators.static.loc[other_generators[0], "control"] = "PQ" + + # Step 2: Perform non-linear power flow for all snapshots + logger.info("Running non-linear power flow iteratively...") + + # Run non-linear power flow + res = n.pf( + snapshots=snapshots, + skip_pre=skip_pre, + x_tol=x_tol, + use_seed=use_seed, + distribute_slack=distribute_slack, + slack_weights=slack_weights, + ) + + return dict(status=status, terminantion_condition=condition, **res) diff --git a/PyPSA/source/pypsa/optimization/common.py b/PyPSA/source/pypsa/optimization/common.py new file mode 100644 index 0000000000000000000000000000000000000000..9eae5e1cb48d5fb8ec2e7cce87ff6ada20aafaca --- /dev/null +++ b/PyPSA/source/pypsa/optimization/common.py @@ -0,0 +1,86 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Use common methods for optimization problem definition with Linopy.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pandas as pd +from numpy import hstack, ravel + +from pypsa.common import deprecated_in_next_major +from pypsa.constants import RE_PORTS + +if TYPE_CHECKING: + import xarray as xr + + from pypsa import Network + + +@deprecated_in_next_major( + details="Use xarray functionality instead (e.g. `ds.sel({dim: index}).rename({dim: index.name})`).", +) +def reindex(ds: xr.DataArray, dim: str, index: pd.Index) -> xr.DataArray: + """Index a xarray.DataArray by a pandas.Index while renaming according to the new index name. + + Parameters + ---------- + ds : xr.DataArray + The input DataArray to reindex. + dim : str + The dimension name to reindex. + index : pd.Index + The new index to use for reindexing. + + Returns + ------- + ds + Reindexed dataarray with renamed dimension. + + """ + return ds.sel({dim: index}).rename({dim: index.name}) + + +def _set_dynamic_data(n: Network, component: str, attr: str, df: pd.DataFrame) -> None: + """Update values in time-dependent attribute from new dataframe.""" + c = n.components[component] + if (attr not in c.dynamic) or (c.dynamic[attr].empty): + c.dynamic[attr] = df.reindex(n.snapshots) + + else: + c.dynamic[attr].loc[df.index, df.columns] = df + + c.dynamic[attr] = ( + c.dynamic[attr] + .reindex(n.snapshots, level="snapshot", axis=0) + .reindex(c.names, level="name", axis=1) + .fillna(0.0) + ) + + +def get_strongly_meshed_buses(n: Network, threshold: int = 45) -> pd.Series: + """Get the buses which are strongly meshed in the network. + + Parameters + ---------- + n : Network + The network to analyze. + threshold : int + Number of attached components to be counted as strongly meshed. + + Returns + ------- + pandas series of all meshed buses. + + """ + all_buses = pd.Series( + hstack([ravel(c.static.filter(regex=RE_PORTS.pattern)) for c in n.components]) + ) + all_buses = all_buses[all_buses != ""] + counts = all_buses.value_counts() + results = counts.index[counts > threshold].rename("Bus") + results = results.sort_values() + return results diff --git a/PyPSA/source/pypsa/optimization/constraints.py b/PyPSA/source/pypsa/optimization/constraints.py new file mode 100644 index 0000000000000000000000000000000000000000..64e5cb2d5137f91f195442d18da68953a999ed63 --- /dev/null +++ b/PyPSA/source/pypsa/optimization/constraints.py @@ -0,0 +1,1801 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Define optimisation constraints from PyPSA networks with Linopy.""" + +from __future__ import annotations + +import logging +from collections.abc import Sequence +from pathlib import Path +from typing import TYPE_CHECKING, Any + +import linopy +import pandas as pd +from linopy import merge +from numpy import inf, isfinite +from xarray import DataArray, concat + +from pypsa.common import as_index, expand_series +from pypsa.components.common import as_components +from pypsa.descriptors import nominal_attrs +from pypsa.optimization.common import reindex + +if TYPE_CHECKING: + from collections.abc import Sequence + + from xarray import DataArray # noqa: TC004 + + from pypsa import Network + + ArgItem = list[str | int | float | DataArray] + +logger = logging.getLogger(__name__) + +# TODO move to constants.py +lookup = pd.read_csv( + Path(__file__).parent / ".." / "data" / "variables.csv", + index_col=["component", "variable"], +) + + +def define_operational_constraints_for_non_extendables( + n: Network, sns: pd.Index, component: str, attr: str, transmission_losses: int +) -> None: + """Define operational constraints (lower-/upper bound). + + Sets operational constraints for a subset of non-extendable + and non-committable components based on their bounds. For each component, + the constraint enforces: + + lower_bound ≤ dispatch ≤ upper_bound + + where lower_bound and upper_bound are computed from the component's nominal + capacity and min/max per unit values. + + Applies to Components + --------------------- + Generator (p), Line (s), Transformer (s), Link (p), Store (e), StorageUnit (p_dispatch, p_store, state_of_charge) + + Parameters + ---------- + n : pypsa.Network + Network instance containing the model and component data + sns : pd.Index + Set of snapshots for which to define the constraints + component : str + Name of the network component (e.g. "Generator", "Link") + attr : str + Name of the attribute to constrain (e.g. "p" for active power) + transmission_losses : int + Number of segments for transmission loss linearization; if non-zero, + losses are considered in the constraints for passive branches + + Returns + ------- + None + + Notes + ----- + For passive branches with transmission losses, the constraint accounts for + the losses in both directions, see justification in [1]_. + + References + ---------- + [1] F. Neumann, T. Brown, "Transmission losses in power system + optimization models: A comparison of heuristic and exact solution methods," + Applied Energy, 2022, https://doi.org/10.1016/j.apenergy.2022.118859 + + """ + c = as_components(n, component) + fix_i = c.fixed.difference(c.committables).difference(c.inactive_assets) + + if fix_i.empty: + return + + nominal_fix = c.da[c._operational_attrs["nom"]].sel(name=fix_i) + min_pu, max_pu = c.get_bounds_pu(attr=attr) + max_pu = max_pu.sel(name=fix_i) + min_pu = min_pu.sel(name=fix_i) + if "snapshot" in min_pu.dims: + min_pu = min_pu.sel(snapshot=sns) + max_pu = max_pu.sel(snapshot=sns) + + lower = min_pu * nominal_fix + upper = max_pu * nominal_fix + + active = c.da.active.sel(name=fix_i, snapshot=sns) + + dispatch = n.model[f"{c.name}-{attr}"].sel(name=fix_i) + + if c.name in n.passive_branch_components and transmission_losses: + loss = n.model[f"{c.name}-loss"].sel(name=fix_i) + lhs_lower = dispatch - loss + lhs_upper = dispatch + loss + else: + lhs_lower = lhs_upper = dispatch + + n.model.add_constraints( + lhs_lower, ">=", lower, name=f"{c.name}-fix-{attr}-lower", mask=active + ) + n.model.add_constraints( + lhs_upper, "<=", upper, name=f"{c.name}-fix-{attr}-upper", mask=active + ) + + +def define_operational_constraints_for_extendables( + n: Network, sns: pd.Index, component: str, attr: str, transmission_losses: int +) -> None: + """Define operational constraints (lower-/upper bound) for extendable components. + + Sets operational constraints for extendable components based on their bounds. + For each component, the constraint enforces: + + lower_bound ≤ dispatch ≤ upper_bound + + where lower_bound and upper_bound are computed from the component's nominal + capacity and min/max per unit values. + + Applies to Components + --------------------- + Generator (p), Line (s), Transformer (s), Link (p), Store (e), StorageUnit (p_dispatch, p_store, state_of_charge) + + Parameters + ---------- + n : pypsa.Network + Network instance containing the model and component data + sns : pd.Index + Set of snapshots for which to define the constraints + component : str + Name of the network component (e.g. "Generator", "Link") + attr : str + Name of the attribute to constrain (e.g. "p" for active power) + transmission_losses : int + Number of segments for transmission loss linearization; if non-zero, + losses are considered in the constraints for passive branches + + Returns + ------- + None + + """ + c = as_components(n, component) + sns = as_index(n, sns, "snapshots") + + ext_i = c.extendables.difference(c.inactive_assets) + if ext_i.empty: + return + if isinstance(ext_i, pd.MultiIndex): + ext_i = ext_i.unique(level="name") + + min_pu, max_pu = c.get_bounds_pu(attr=attr) + min_pu = min_pu.sel(name=ext_i) + max_pu = max_pu.sel(name=ext_i) + if "snapshot" in min_pu.dims: + min_pu = min_pu.sel(snapshot=sns) + max_pu = max_pu.sel(snapshot=sns) + + dispatch = n.model[f"{c.name}-{attr}"].sel(name=ext_i) + capacity = n.model[f"{c.name}-{nominal_attrs[c.name]}"] + active = c.da.active.sel(name=ext_i, snapshot=sns) + + lhs_lower = dispatch - min_pu * capacity + lhs_upper = dispatch - max_pu * capacity + + if c.name in n.passive_branch_components and transmission_losses: + loss = n.model[f"{c.name}-loss"].sel(name=ext_i) + lhs_lower = lhs_lower - loss + lhs_upper = lhs_upper + loss + + n.model.add_constraints( + lhs_lower, ">=", 0, name=f"{c.name}-ext-{attr}-lower", mask=active + ) + n.model.add_constraints( + lhs_upper, "<=", 0, name=f"{c.name}-ext-{attr}-upper", mask=active + ) + + +def define_operational_constraints_for_committables( + n: Network, sns: pd.Index, component: str +) -> None: + """Define operational constraints (lower-/upper bound) for committable components. + + Sets operational constraints for components with unit commitment + decisions. The constraints include: + + 1. Power output limits based on commitment status + 2. State transition constraints (start-up/shut-down) + 3. Minimum up and down time constraints + 4. Ramp rate constraints for committed units + + Applies to Components + --------------------- + Generator, Link (when they have unit commitment capabilities) + + Parameters + ---------- + n : pypsa.Network + Network instance containing the model and component data + sns : pd.Index + Set of snapshots for which to define the constraints + component : str + Name of the network component ("Generator" or "Link") + + Returns + ------- + None + + Notes + ----- + The linearized approximation of the unit commitment problem + is possible with flag `n._linearized_uc`. Here linearization + implies that p_min_pu is fractional, ie component can start up + any fraction of its capacity. The linearization is based on + [2]_. + + For components with equal start-up and shut-down costs, additional + tightening constraints are applied to improve the linear relaxation. + + References + ---------- + [2] Y. Hua, C. Liu, J. Zhang, "Representing Operational + Flexibility in Generation Expansion Planning Through Convex Relaxation + of Unit Commitment," IEEE Transactions on Power Systems, vol. 32, + no. 5, pp. 3854-3865, 2017, https://doi.org/10.1109/TPWRS.2017.2735026 + + """ + c = as_components(n, component) + com_i = c.committables.difference(c.inactive_assets) + + if com_i.empty: + return + + # variables + status = n.model[f"{c.name}-status"] + start_up = n.model[f"{c.name}-start_up"] + shut_down = n.model[f"{c.name}-shut_down"] + status_diff = status - status.shift(snapshot=1) + p = n.model[f"{c.name}-p"].sel(name=com_i) + active = c.get_activity_mask(sns, com_i) + + # parameters + nominal = c.da[c._operational_attrs["nom"]].sel(name=com_i) + min_pu, max_pu = c.get_bounds_pu(attr="p") + min_pu = min_pu.sel(name=com_i, snapshot=sns) + max_pu = max_pu.sel(name=com_i, snapshot=sns) + + lower_p = min_pu * nominal + upper_p = max_pu * nominal + min_up_time_set = c.da.min_up_time.sel(name=com_i) + min_down_time_set = c.da.min_down_time.sel(name=com_i) + ramp_up_limit = nominal * c.da.ramp_limit_up.sel(name=com_i).fillna(1) + ramp_down_limit = nominal * c.da.ramp_limit_down.sel(name=com_i).fillna(1) + ramp_start_up = nominal * c.da.ramp_limit_start_up.sel(name=com_i) + ramp_shut_down = nominal * c.da.ramp_limit_shut_down.sel(name=com_i) + up_time_before_set = c.da.up_time_before.sel(name=com_i) + down_time_before_set = c.da.down_time_before.sel(name=com_i) + initially_up = up_time_before_set.astype(bool) + initially_down = down_time_before_set.astype(bool) + + # check if there are status calculated/fixed before given sns interval + if sns[0] != n.snapshots[0]: + start_i = n.snapshots.get_loc(sns[0]) + # get generators which are online until the first regarded snapshot + until_start_up = c._as_dynamic( + "status", n.snapshots[:start_i][::-1], inds=com_i + ) + ref = range(1, len(until_start_up) + 1) + up_time_before = until_start_up[until_start_up.cumsum().eq(ref, axis=0)].sum() + up_time_before_set = up_time_before.clip(upper=min_up_time_set) + initially_up = up_time_before_set.astype(bool) + # get number of snapshots for generators which are offline before the first regarded snapshot + until_start_down = ~until_start_up.astype(bool) + ref = range(1, len(until_start_down) + 1) + down_time_before = until_start_down[ + until_start_down.cumsum().eq(ref, axis=0) + ].sum() + down_time_before_set = down_time_before.clip(upper=min_down_time_set) + initially_down = down_time_before_set.astype(bool) + + # lower dispatch level limit + lhs_tuple = (1, p), (-lower_p, status) + n.model.add_constraints( + lhs_tuple, ">=", 0, name=f"{c.name}-com-p-lower", mask=active + ) + + # upper dispatch level limit + lhs_tuple = (1, p), (-upper_p, status) + n.model.add_constraints( + lhs_tuple, "<=", 0, name=f"{c.name}-com-p-upper", mask=active + ) + + # state-transition constraint + rhs = pd.DataFrame(0, sns, com_i) + # Convert xarray boolean to list of indices for DataFrame indexing + initially_up_indices = com_i[initially_up.values] + if not initially_up_indices.empty: + rhs.loc[sns[0], initially_up_indices] = -1 + + lhs = start_up - status_diff + n.model.add_constraints( + lhs, ">=", rhs, name=f"{c.name}-com-transition-start-up", mask=active + ) + + rhs = pd.DataFrame(0, sns, com_i) + if not initially_up_indices.empty: + rhs.loc[sns[0], initially_up_indices] = 1 + + lhs = shut_down + status_diff + n.model.add_constraints( + lhs, ">=", rhs, name=f"{c.name}-com-transition-shut-down", mask=active + ) + + # min up time + min_up_time_i = com_i[min_up_time_set.astype(bool)] + if not min_up_time_i.empty: + expr = [] + for g in min_up_time_i: + su = start_up.loc[:, g] + # Retrieve the minimum up time value for generator g and convert it to a scalar + up_time_value = min_up_time_set.sel({min_up_time_set.dims[0]: g}).item() + expr.append(su.rolling(snapshot=up_time_value).sum()) + lhs = -status.loc[:, min_up_time_i] + merge(expr, dim=com_i.name) + lhs = lhs.sel(snapshot=sns[1:]) + n.model.add_constraints( + lhs, + "<=", + 0, + name=f"{c.name}-com-up-time", + mask=DataArray(active[min_up_time_i]).sel(snapshot=sns[1:]), + ) + + # min down time + min_down_time_i = com_i[min_down_time_set.astype(bool)] + if not min_down_time_i.empty: + expr = [] + for g in min_down_time_i: + su = shut_down.loc[:, g] + down_time_value = min_down_time_set.sel( + {min_down_time_set.dims[0]: g} + ).item() + expr.append(su.rolling(snapshot=down_time_value).sum()) + lhs = status.loc[:, min_down_time_i] + merge(expr, dim=com_i.name) + lhs = lhs.sel(snapshot=sns[1:]) + n.model.add_constraints( + lhs, + "<=", + 1, + name=f"{c.name}-com-down-time", + mask=DataArray(active[min_down_time_i]).sel(snapshot=sns[1:]), + ) + # up time before + timesteps = pd.DataFrame([range(1, len(sns) + 1)] * len(com_i), com_i, sns).T + if initially_up.any(): + must_stay_up = (min_up_time_set - up_time_before_set).clip(min=0) + mask_values = (must_stay_up.values >= timesteps) & initially_up.values + mask = pd.DataFrame( + mask_values, index=timesteps.index, columns=timesteps.columns + ) + name = f"{c.name}-com-status-min_up_time_must_stay_up" + mask = mask & active if active is not None else mask + n.model.add_constraints(status, "=", 1, name=name, mask=mask) + + # down time before + if initially_down.any(): + must_stay_down = (min_down_time_set - down_time_before_set).clip(min=0) + mask_values = (must_stay_down.values >= timesteps) & initially_down.values + mask = pd.DataFrame( + mask_values, index=timesteps.index, columns=timesteps.columns + ) + name = f"{c.name}-com-status-min_down_time_must_stay_up" + mask = mask & active if active is not None else mask + n.model.add_constraints(status, "=", 0, name=name, mask=mask) + + # linearized approximation because committable can partly start up and shut down + start_up_cost = c.da.start_up_cost.sel(name=com_i) + shut_down_cost = c.da.shut_down_cost.sel(name=com_i) + cost_equal = (start_up_cost == shut_down_cost).values + + # only valid additional constraints if start up costs equal to shut down costs + if n._linearized_uc and not cost_equal.all(): + logger.warning( + "The linear relaxation of the unit commitment cannot be " + "tightened for all generators since the start up costs " + "are not equal to the shut down costs. Proceed with the " + "linear relaxation without the tightening by additional " + "constraints for these. This might result in a longer " + "solving time." + ) + if n._linearized_uc and cost_equal.any(): + # dispatch limit for partly start up/shut down for t-1 + p_ce = p.loc[:, cost_equal] + start_up_ce = start_up.loc[:, cost_equal] + status_ce = status.loc[:, cost_equal] + active_ce = DataArray(active.loc[:, cost_equal]).sel(snapshot=sns[1:]) + + # parameters + upper_p_ce = upper_p.loc[:, cost_equal] + lower_p_ce = lower_p.loc[:, cost_equal] + ramp_shut_down_ce = ramp_shut_down.loc[cost_equal] + ramp_start_up_ce = ramp_start_up.loc[cost_equal] + ramp_up_limit_ce = ramp_up_limit.loc[cost_equal] + ramp_down_limit_ce = ramp_down_limit.loc[cost_equal] + + lhs = ( + p_ce.shift(snapshot=1) + - ramp_shut_down_ce * status_ce.shift(snapshot=1) + - (upper_p_ce - ramp_shut_down_ce) * (status_ce - start_up_ce) + ) + lhs = lhs.sel(snapshot=sns[1:]) + n.model.add_constraints( + lhs, + "<=", + 0, + name=f"{c.name}-com-p-before", + mask=active_ce, + ) + + # dispatch limit for partly start up/shut down for t + lhs = ( + p_ce + - upper_p_ce * status_ce + + (upper_p_ce - ramp_start_up_ce) * start_up_ce + ) + lhs = lhs.sel(snapshot=sns[1:]) + n.model.add_constraints( + lhs, + "<=", + 0, + name=f"{c.name}-com-p-current", + mask=active_ce, + ) + + # ramp up if committable is only partly active and some capacity is starting up + lhs = ( + p_ce + - p_ce.shift(snapshot=1) + - (lower_p_ce + ramp_up_limit_ce) * status_ce + + lower_p_ce * status_ce.shift(snapshot=1) + + (lower_p_ce + ramp_up_limit_ce - ramp_start_up_ce) * start_up_ce + ) + lhs = lhs.sel(snapshot=sns[1:]) + n.model.add_constraints( + lhs, + "<=", + 0, + name=f"{c.name}-com-partly-start-up", + mask=active_ce, + ) + + # ramp down if committable is only partly active and some capacity is shutting up + lhs = ( + p_ce.shift(snapshot=1) + - p_ce + - ramp_shut_down_ce * status_ce.shift(snapshot=1) + + (ramp_shut_down_ce - ramp_down_limit_ce) * status_ce + - (lower_p_ce + ramp_down_limit_ce - ramp_shut_down_ce) * start_up_ce + ) + lhs = lhs.sel(snapshot=sns[1:]) + n.model.add_constraints( + lhs, + "<=", + 0, + name=f"{c.name}-com-partly-shut-down", + mask=active_ce, + ) + + +def define_nominal_constraints_for_extendables( + n: Network, component: str, attr: str +) -> None: + """Define capacity constraints for extendable components. + + Sets capacity expansion constraints for components with extendable + capacities. For each component, the constraint enforces: + + min_capacity ≤ capacity ≤ max_capacity + + where capacity is a decision variable representing the component's + optimal capacity. + + Applies to Components + --------------------- + Generator (p_nom), Line (s_nom), Transformer (s_nom), Link (p_nom), + Store (e_nom), StorageUnit (p_nom) + + Parameters + ---------- + n : pypsa.Network + Network instance containing the model and component data + component : str + Name of the network component (e.g. "Generator", "StorageUnit") + attr : str + Name of the capacity attribute (e.g. "p_nom" for nominal power) + + Returns + ------- + None + + Notes + ----- + Components with infinite max_capacity values are handled through masking + to avoid solver issues, particularly with GLPK which doesn't accept + infinite values in constraints. + + """ + c = as_components(n, component) + ext_i = c.extendables.difference(c.inactive_assets) + + if ext_i.empty: + return + + capacity = n.model[f"{c.name}-{attr}"] + lower = c.da[attr + "_min"].sel(name=ext_i) + upper = c.da[attr + "_max"].sel(name=ext_i) + + n.model.add_constraints(capacity, ">=", lower, name=f"{c.name}-ext-{attr}-lower") + + is_finite = upper != inf + if is_finite.any(): + n.model.add_constraints( + capacity, "<=", upper, name=f"{c.name}-ext-{attr}-upper", mask=is_finite + ) + + +def define_ramp_limit_constraints( + n: Network, sns: pd.Index, component: str, attr: str +) -> None: + """Define ramp rate limit constraints for components. + + Sets ramp rate constraints to limit the change in output between + consecutive time periods. The constraints are defined for fixed, + extendable, and committable components, with different formulations + for each case. + + Applies to Components + --------------------- + Generator (p), Link (p) + + Parameters + ---------- + n : pypsa.Network + Network instance containing the model and component data + sns : pd.Index + Set of snapshots for which to define the constraints + component : str + Name of the network component (e.g. "Generator") + attr : str + Name of the dispatch attribute (e.g. "p" for active power) + + Returns + ------- + None + + Notes + ----- + For rolling horizon optimization, the function handles linking between + optimization windows by including the previous snapshot's dispatch value. + + For committable components, ramp constraints incorporate the unit commitment + status and special ramp limits for start-up and shut-down periods. + + For extendable components, ramp constraints are defined relative to the + variable capacity, ensuring consistency in the optimization. + + """ + m = n.model + c = as_components(n, component) + + if {"ramp_limit_up", "ramp_limit_down"}.isdisjoint(c.static.columns): + return + + # Fix for as_dynamic function breaking with scenarios. TODO fix it OR leave this if clause + if c.static.size == 0: + return + + ramp_limit_up = c.da.ramp_limit_up.sel(snapshot=sns) + ramp_limit_down = c.da.ramp_limit_down.sel(snapshot=sns) + + # Skip if there are no ramp limits defined or if all are set to 1 (no limit) + if (ramp_limit_up.isnull() & ramp_limit_down.isnull()).all(): + return + if (ramp_limit_up == 1).all() and (ramp_limit_down == 1).all(): + return + + # ---------------- Check if ramping is at start of n.snapshots --------------- # + + # Both Generator and Link use "p" as their dispatch variable + var_attr = "p" + + # Check if we're in rolling horizon optimization (not starting from first snapshot) + # If so, retrieve historical data from the previous snapshot + p_start = pd.Series(dtype=float) + if sns[0] != n.snapshots[0]: + # Historical data: "p0" for Links, "p" for Generators + historical_attrs = {"p", "p0"}.intersection(c.dynamic.keys()) + if historical_attrs: + hist_attr = historical_attrs.pop() + start_i = n.snapshots.get_loc(sns[0]) - 1 + p_start = c.dynamic[hist_attr].iloc[start_i] + + is_rolling_horizon = not p_start.empty + + p = m[f"{c.name}-{var_attr}"] + + # Get different component groups for constraint application + com_i = c.committables.difference(c.inactive_assets) + fix_i = c.fixed.difference(c.inactive_assets) + fix_i = fix_i.difference(com_i).rename(fix_i.name) + ext_i = c.extendables.difference(c.inactive_assets) + + # Auxiliary variables for constraint application + ext_dim = ext_i.name if ext_i.name else c.name + original_ext_i = ext_i.copy() + original_com_i = com_i.copy() + + if is_rolling_horizon: + active = c.da.active.sel(name=fix_i, snapshot=sns) + rhs_start = pd.DataFrame(0.0, index=sns, columns=c.static.index) + rhs_start.loc[sns[0]] = p_start + + def p_actual(idx: pd.Index) -> DataArray: + return p.sel(name=idx) + + def p_previous(idx: pd.Index) -> DataArray: + return p.sel(name=idx).shift(snapshot=1) + + else: + active = c.da.active.sel(name=fix_i, snapshot=sns[1:]) + rhs_start = pd.DataFrame(0.0, index=sns[1:], columns=c.static.index) + rhs_start.index.name = "snapshot" + + def p_actual(idx: pd.Index) -> DataArray: + return p.sel(name=idx).sel(snapshot=sns[1:]) + + def p_previous(idx: pd.Index) -> DataArray: + return p.sel(name=idx).shift(snapshot=1).sel(snapshot=sns[1:]) + + rhs_start = DataArray(rhs_start) + + # ----------------------------- Fixed Components ----------------------------- # + if not fix_i.empty: + ramp_limit_up_fix = ramp_limit_up.sel(name=fix_i) + ramp_limit_down_fix = ramp_limit_down.sel(name=fix_i) + rhs_start_fix = rhs_start + p_nom = c.da[c._operational_attrs["nom"]].sel(name=fix_i) + + # Ramp up constraints for fixed components + non_null_up = ~ramp_limit_up_fix.isnull().all() + if non_null_up.any(): + lhs = p_actual(fix_i) - p_previous(fix_i) + rhs = (ramp_limit_up_fix * p_nom) + rhs_start_fix + mask = active & non_null_up + m.add_constraints( + lhs, "<=", rhs, name=f"{c.name}-fix-{attr}-ramp_limit_up", mask=mask + ) + + # Ramp down constraints for fixed components + non_null_down = ~ramp_limit_down_fix.isnull().all() + if non_null_down.any(): + lhs = p_actual(fix_i) - p_previous(fix_i) + rhs = (-ramp_limit_down_fix * p_nom) + rhs_start + mask = active & non_null_down + m.add_constraints( + lhs, ">=", rhs, name=f"{c.name}-fix-{attr}-ramp_limit_down", mask=mask + ) + + # ----------------------------- Extendable Components ----------------------------- # + if not ext_i.empty: + # Redefine active mask over ext_i + active_ext = ( + c.da.active.sel(name=ext_i, snapshot=sns) + if is_rolling_horizon + else c.da.active.sel(name=ext_i, snapshot=sns[1:]) + ) + + ramp_limit_up_ext = ramp_limit_up.reindex( + {"snapshot": active_ext.coords["snapshot"].values, "name": ext_i} + ).rename({"name": ext_dim}) + ramp_limit_down_ext = ramp_limit_down.reindex( + {"snapshot": active_ext.coords["snapshot"].values, "name": ext_i} + ).rename({"name": ext_dim}) + rhs_start_ext = rhs_start.sel({"name": ext_i}).rename({"name": ext_dim}) + + # For extendables, nominal capacity is a decision variable + p_nom_var = m[f"{c.name}-{c._operational_attrs['nom']}"] + + if not ramp_limit_up_ext.isnull().all(): + lhs = ( + p_actual(original_ext_i) + - p_previous(original_ext_i) + - (ramp_limit_up_ext * p_nom_var) + ) + mask = active_ext & (~ramp_limit_up_ext.isnull()) + m.add_constraints( + lhs, + "<=", + rhs_start_ext, + name=f"{c.name}-ext-{attr}-ramp_limit_up", + mask=mask, + ) + + if not ramp_limit_down_ext.isnull().all(): + lhs = ( + p_actual(original_ext_i) + - p_previous(original_ext_i) + + (ramp_limit_down_ext * p_nom_var) + ) + mask = active_ext & (~ramp_limit_down_ext.isnull()) + m.add_constraints( + lhs, + ">=", + rhs_start_ext, + name=f"{c.name}-ext-{attr}-ramp_limit_down", + mask=mask, + ) + # ----------------------------- Committable Components ----------------------------- # + if not com_i.empty: + # Redefine active mask over com_i and get parameters directly using component methods + active_com = ( + c.da.active.sel(name=com_i, snapshot=sns) + if is_rolling_horizon + else c.da.active.sel(name=com_i, snapshot=sns[1:]) + ) + + ramp_limit_up_com = ramp_limit_up.reindex( + {"snapshot": active_com.coords["snapshot"].values, "name": com_i} + ) + ramp_limit_down_com = ramp_limit_down.reindex( + {"snapshot": active_com.coords["snapshot"].values, "name": com_i} + ) + + ramp_limit_start_up_com = c.da.ramp_limit_start_up.sel(name=com_i) + ramp_limit_shut_down_com = c.da.ramp_limit_shut_down.sel(name=com_i) + p_nom_com = c.da[c._operational_attrs["nom"]].sel(name=original_com_i) + + # Transform rhs_start for committable components + rhs_start_com = rhs_start.sel(name=com_i) + + # com up + non_null_up = ~ramp_limit_up_com.isnull() + if non_null_up.any(): + limit_start = p_nom_com * ramp_limit_start_up_com + limit_up = p_nom_com * ramp_limit_up_com + + status = m[f"{c.name}-status"].sel( + snapshot=active_com.coords["snapshot"].values + ) + status_prev = ( + m[f"{c.name}-status"] + .shift(snapshot=1) + .sel(snapshot=active_com.coords["snapshot"].values) + ) + + lhs = ( + p_actual(original_com_i) + - p_previous(original_com_i) + + (limit_start - limit_up) * status_prev + - limit_start * status + ) + + rhs = rhs_start_com.copy() + if is_rolling_horizon: + status_start = c.dynamic["status"].iloc[start_i] + limit_diff = (limit_up - limit_start).isel(snapshot=0) + rhs.loc[{"snapshot": rhs.coords["snapshot"].item(0)}] += ( + limit_diff * status_start + ) + + mask = active_com & non_null_up + m.add_constraints( + lhs, "<=", rhs, name=f"{c.name}-com-{attr}-ramp_limit_up", mask=mask + ) + + # com down + non_null_down = ~ramp_limit_down_com.isnull() + if non_null_down.any(): + limit_shut = p_nom_com * ramp_limit_shut_down_com + limit_down = p_nom_com * ramp_limit_down_com + + status = m[f"{c.name}-status"].sel( + snapshot=active_com.coords["snapshot"].values + ) + status_prev = ( + m[f"{c.name}-status"] + .shift(snapshot=1) + .sel(snapshot=active_com.coords["snapshot"].values) + ) + + lhs = ( + p_actual(original_com_i) + - p_previous(original_com_i) + + (limit_down - limit_shut) * status + + limit_shut * status_prev + ) + + rhs = rhs_start_com.copy() + if is_rolling_horizon: + status_start = c.dynamic["status"].iloc[start_i] + rhs.loc[{"snapshot": rhs.coords["snapshot"].item(0)}] += ( + -limit_shut * status_start + ) + + mask = active_com & non_null_down + m.add_constraints( + lhs, ">=", rhs, name=f"{c.name}-com-{attr}-ramp_limit_down", mask=mask + ) + + +def define_nodal_balance_constraints( + n: Network, + sns: pd.Index, + transmission_losses: int = 0, + buses: Sequence | None = None, + suffix: str = "", +) -> None: + """Define energy balance constraints at each node. + + Creates constraints ensuring that the sum of power injections at each node + equals the demand at that node for each snapshot. This is the core constraint + implementing Kirchhoff's Current Law (KCL) in the power system model. However, + the logic is not limited to power networks and spans to other energy carriers. + + Using an example of power system, the general form of the constraint is: + + sum(power_injections) = sum(power_withdrawals) + + where power injections include generation, storage discharge, and incoming branch flows, + while power withdrawals include loads, storage charging, and outgoing branch flows. + + Applies to Components + --------------------- + Generator (p), Line (s), Transformer (s), Link (p), Store (p), Load (p), StorageUnit (p_dispatch, p_store)* + + Notes + ----- + * StorageUnit net power (p_dispatch - p_store) is calculated after optimization + * StorageUnit (spill) var is not in the nodal balance - it's handled internally within the storage unit energy balance + + Parameters + ---------- + n : pypsa.Network + Network instance containing the model and component data + sns : pd.Index + Set of snapshots for which to define the constraints + transmission_losses : int, default 0 + Number of segments for transmission loss linearization; if non-zero, + losses are included in the power balance + buses : Sequence | None, default None + Subset of buses for which to define constraints; if None, all buses are used + suffix : str, default "" + Optional suffix to append to constraint names and dimensions + + Returns + ------- + None + + Notes + ----- + Link components with multiple buses are handled with their respective + efficiency factors for conversion between energy carriers. + + The function raises an error if there's a bus with non-zero load but no + connected components to provide power. + + """ + m = n.model + if buses is None: + buses = n.c.buses.static.index.unique("name") + + links = as_components(n, "Link") + + args: list[Any] = [ + ["Generator", "p", "bus", 1], + ["Store", "p", "bus", 1], + ["StorageUnit", "p_dispatch", "bus", 1], + ["StorageUnit", "p_store", "bus", -1], + ["Line", "s", "bus0", -1], + ["Line", "s", "bus1", 1], + ["Transformer", "s", "bus0", -1], + ["Transformer", "s", "bus1", 1], + ["Link", "p", "bus0", -1], + ["Link", "p", "bus1", links.da.efficiency.sel(snapshot=sns)], + ] + + if not links.empty: + for i in n.c.links.additional_ports: + eff_attr = f"efficiency{i}" if i != "1" else "efficiency" + eff = links.da[eff_attr].sel(snapshot=sns) + args.append(["Link", "p", f"bus{i}", eff]) + + if transmission_losses: + args.extend( + [ + ["Line", "loss", "bus0", -0.5], + ["Line", "loss", "bus1", -0.5], + ["Transformer", "loss", "bus0", -0.5], + ["Transformer", "loss", "bus1", -0.5], + ] + ) + + exprs = [] + + for component, attr, column, sign in args: + c = as_components(n, component) + if c.static.empty: + continue + + if "sign" in c.static: + sign = sign * c.da.sign + + expr = sign * m[f"{c.name}-{attr}"] + + cbuses = c._as_xarray(column) + cbuses = cbuses.sel(name=c.active_assets) + # Only keep the first scenario if there are multiple + if n.has_scenarios: + cbuses = cbuses.isel(scenario=0, drop=True) + cbuses = cbuses[cbuses.isin(buses)].rename("Bus") + + if not cbuses.size: + continue + + # drop non-existent multiport buses which are '' + if column in ["bus" + i for i in n.c.links.additional_ports]: + cbuses = cbuses[cbuses != ""] + + expr = expr.sel(name=cbuses.coords["name"].values) + if expr.size: + exprs.append(expr.groupby(cbuses).sum().rename(Bus="name")) + + lhs = merge(exprs, join="outer").reindex(name=buses) + + # Prepare the RHS + loads = as_components(n, "Load") + + if loads.static.empty: + rhs = DataArray( + 0.0, + coords={"snapshot": sns, "name": buses}, + dims=["snapshot", "name"], + ) + else: + loads_values = loads.da.p_set.where( + loads.da.active.sel(name=loads.active_assets, snapshot=sns) + ) + loads_values = loads_values.reindex(name=loads.static.index.unique("name")) + load_buses = loads._as_xarray("bus").rename("Bus") + if n.has_scenarios: + load_buses = load_buses.isel(scenario=0, drop=True) + + # group by bus, then reindex over *all* buses (fill zeros where no loads) + rhs = ( + loads_values.groupby(load_buses) + .sum() + .rename(Bus="name") + .reindex(name=buses, fill_value=0) + ) + + empty_nodal_balance = (lhs.vars == -1).all("_term") + + if empty_nodal_balance.any(): + if (empty_nodal_balance & (rhs != 0)).any().item(): + msg = "Empty LHS with non-zero RHS in nodal balance constraint." + raise ValueError(msg) + + mask = ~empty_nodal_balance + else: + mask = None + + n.model.add_constraints(lhs, "=", rhs, name=f"Bus{suffix}-nodal_balance", mask=mask) + + +def define_kirchhoff_voltage_constraints(n: Network, sns: pd.Index) -> None: + """Define Kirchhoff's Voltage Law constraints for networks. + + Creates constraints ensuring that the sum of potential differences across + branches around all cycles in the network must sum to zero. For each cycle + in the network graph, the constraint enforces: + + sum_{l in cycle} x_l * s_l = 0 + + where + x_l : series reactance or resistance of branch l (depending on AC/DC) + s_l : branch flow variable for branch l in the cycle + + Applies to Components + --------------------- + Line, Transformer, Link (passive branch components) + + Parameters + ---------- + n : pypsa.Network + Network instance containing the model and component data + sns : pd.Index + Set of snapshots for which to define the constraints + + Returns + ------- + None + + Notes + ----- + While there are different formulations of KVL, the cycle-based + formulation was found to be much faster than other formulations + due to its sparsity, as shown in [3]_. + + The function first determines the network topology including cycles for each + network component (AC and DC sub-networks), then creates constraints for + each cycle. + + For multi-investment period models, the function creates separate constraints + for each investment period, reflecting the changing network topology over time. + + The impedances are scaled by 1e5 to improve numerical conditioning. + + References + ---------- + [3] J. Hörsch et al., "Linear optimal power flow using cycle flows," + Electric Power Systems Research, vol. 158, pp. 126-135, 2018, + https://doi.org/10.1016/j.epsr.2020.106908 + + """ + m = n.model + n.calculate_dependent_values() + + periods = sns.unique("period") if n._multi_invest else [None] + lhs = [] + for period in periods: + snapshots = sns if period is None else sns[sns.get_loc(period)] + C = n.cycle_matrix(investment_period=period, apply_weights=True) + if C.empty: + continue + + exprs = [] + for c in C.index.unique("type"): + C_branch = DataArray(C.loc[c]) + flow = m[f"{c}-s"].sel( + snapshot=snapshots, + name=C_branch.indexes["name"].difference(n.c[c].inactive_assets), + ) + exprs.append(flow @ C_branch * 1e5) + lhs.append(sum(exprs)) + + if lhs: + lhs = merge(lhs, dim="snapshot") + con = lhs == 0 + mask = con.rhs.notnull() + m.add_constraints(con, name="Kirchhoff-Voltage-Law", mask=mask) + + +def define_fixed_nominal_constraints(n: Network, component: str, attr: str) -> None: + """Define constraints for fixing component capacities to specified values. + + Sets constraints to fix nominal (capacity) variables of components to values + specified in the corresponding '_set' attribute. + + Applies to Components + --------------------- + Generator (p_nom), Line (s_nom), Transformer (s_nom), Link (p_nom), + Store (e_nom), StorageUnit (p_nom) + + Parameters + ---------- + n : pypsa.Network + Network instance containing the model and component data + component : str + Name of the network component (e.g. "Generator", "StorageUnit") + attr : str + Name of the capacity attribute (e.g. "p_nom" for nominal power) + + Returns + ------- + None + + Notes + ----- + The function only creates constraints for components that have non-NaN + values in their '{attr}_set' attribute. + + """ + c = as_components(n, component) + if attr + "_set" not in c.static: + return + + fix = c.static[attr + "_set"].dropna() + + if fix.empty: + return + + dim = f"{component}-{attr}_set_i" + fix = fix.rename_axis(dim) + + var = n.model[f"{component}-{attr}"] + var = reindex(var, var.dims[0], fix.index) + n.model.add_constraints(var, "=", fix, name=f"{component}-{attr}_set") + + +def define_modular_constraints(n: Network, component: str, attr: str) -> None: + """Define constraints for modular capacity expansion. + + Sets constraints ensuring that the optimal capacity of a component is + an integer multiple of a specified module size. This implements discrete + capacity expansion for components with modular units. + + For each modular component, the constraint enforces: + + capacity = n_modules * module_size + + where n_modules is an integer decision variable and module_size is the + specified size of each module. + + Applies to Components + --------------------- + Generator (p_nom), Line (s_nom), Transformer (s_nom), Link (p_nom), + Store (e_nom), StorageUnit (p_nom) + + Parameters + ---------- + n : pypsa.Network + Network instance containing the model and component data + component : str + Name of the network component (e.g. "Generator", "StorageUnit") + attr : str + Name of the capacity attribute (e.g. "p_nom" for nominal power) + + Returns + ------- + None + + Notes + ----- + This function is used for components where capacity expansion must occur + in discrete steps rather than continuous values, reflecting the reality + of many energy system technologies. + + The function only applies to components that are both extendable and have + a positive module size specified in the '{attr}_mod' attribute. + + """ + m = n.model + c = as_components(n, component) + + ext_attr = f"{attr}_extendable" + mod_attr = f"{attr}_mod" + + # Mask components that are both extendable and have a positive modular capacity + mask = c.static[ext_attr] & (c.static[mod_attr] > 0) + mod_i = c.static.index[mask] + + if (mod_i).empty: + return + + # Get modular capacity values + modular_capacity = c.da[mod_attr].sel(name=mod_i) + + # Get variables + modularity = m[f"{c.name}-n_mod"] + capacity = m.variables[f"{c.name}-{attr}"].loc[mod_i] + + con = capacity - modularity * modular_capacity.values == 0 + n.model.add_constraints(con, name=f"{c.name}-{attr}_modularity", mask=None) + + +def define_fixed_operation_constraints( + n: Network, sns: pd.Index, component: str, attr: str +) -> None: + """Define constraints for fixing operational variables to specified values. + + Sets constraints to fix dispatch variables of components to values specified + in the corresponding '_set' attribute. + + Applies to Components + --------------------- + Generator (p), Line (s), Transformer (s), Link (p), Store (e), StorageUnit (p_dispatch, p_store, state_of_charge) + + Parameters + ---------- + n : pypsa.Network + Network instance containing the model and component data + sns : pd.Index + Set of snapshots for which to define the constraints + component : str + Name of the network component (e.g. "Generator", "StorageUnit") + attr : str + Name of the dispatch attribute (e.g. "p" for active power) + + Returns + ------- + None + + Notes + ----- + This function is useful for modeling must-run generators, fixed imports/exports, + or pre-committed dispatch decisions. + + The function only creates constraints for snapshots and components where + the '{attr}_set' values are not NaN and the component is active. + + """ + c = as_components(n, component) + attr_set = f"{attr}_set" + + if attr_set not in c.dynamic.keys() or c.dynamic[attr_set].empty: + return + + fix = c.da[attr_set].sel(snapshot=sns, name=c.active_assets) + + if fix.isnull().all(): + return + + active = c.da.active.sel(snapshot=sns, name=fix.coords["name"].values) + mask = active & (~fix.isnull()) + + var = n.model[f"{c.name}-{attr}"] + + n.model.add_constraints(var, "=", fix, name=f"{c.name}-" + attr_set, mask=mask) + + +def define_storage_unit_constraints(n: Network, sns: pd.Index) -> None: + """Define energy balance constraints for storage units. + + Creates constraints ensuring energy conservation for storage units over time. + For each storage unit and snapshot, the constraint enforces: + + soc(t) = standing_eff * soc(t-1) + eff_store * p_store(t) + - (1/eff_dispatch) * p_dispatch(t) + - spill(t) + inflow(t) + + where soc is the state of charge, p_store and p_dispatch are the + charging and discharging power variables, and the efficiencies account + for energy losses. + + Applies to Components + --------------------- + StorageUnit (p_dispatch, p_store, state_of_charge, spill) + + Parameters + ---------- + n : pypsa.Network + Network instance containing the model and component data + sns : pd.Index + Set of snapshots for which to define the constraints + + Returns + ------- + None + + Notes + ----- + The function handles different storage operating modes: + - Cyclic storage (returning to initial state at the end of the period) + - Non-cyclic storage (with specified initial state of charge) + + For multi-investment period models, the function supports both cycling + within each period and carrying state of charge between periods. + + Three key flags control the behavior: + + - **C** (cyclic_state_of_charge): If True, globally cycle state of charge + from the last snapshot back to the first snapshot across all periods. + - **CP** (cyclic_state_of_charge_per_period): If True, cycle state of charge + within each investment period (last snapshot of period wraps to first). + - **IP** (state_of_charge_initial_per_period): If True, reset to initial + state_of_charge_initial value at the start of each period. + + When CP=True and IP=True simultaneously, CP takes precedence (wrapping behavior). + + Standing losses are applied based on the elapsed hours between snapshots. + + """ + m = n.model + component = "StorageUnit" + dim = "snapshot" + c = as_components(n, component) + active = c.da.active.sel(snapshot=sns, name=c.active_assets) + + if c.static.empty: + return + + # elapsed hours + elapsed_h = expand_series(n.snapshot_weightings.stores[sns], c.static.index) + eh = DataArray(elapsed_h) + try: + eh = eh.unstack("dim_1") + except ValueError: + pass + + # efficiencies as xarray DataArrays + eff_stand = (1 - c.da.standing_loss.sel(snapshot=sns, name=c.active_assets)) ** eh + eff_dispatch = c.da.efficiency_dispatch.sel(snapshot=sns, name=c.active_assets) + eff_store = c.da.efficiency_store.sel(snapshot=sns, name=c.active_assets) + + soc = m[f"{component}-state_of_charge"] + + lhs = [ + (-1, soc), + (-1 / eff_dispatch * eh, m[f"{component}-p_dispatch"]), + (eff_store * eh, m[f"{component}-p_store"]), + ] + + if f"{component}-spill" in m.variables: + lhs += [(-eh, m[f"{component}-spill"])] + + # We create a mask `include_previous_soc` which excludes the first snapshot + # for non-cyclic assets + noncyclic_b = ~c.da.cyclic_state_of_charge.sel(name=c.active_assets) + include_previous_soc = (active.cumsum(dim) != 1).where(noncyclic_b, True) + + previous_soc = ( + soc.where(active) + .ffill(dim) + .roll(snapshot=1) + .ffill(dim) + .where(include_previous_soc) + ) + + # We add inflow and initial soc for noncyclic assets to rhs + soc_init = c.da.state_of_charge_initial.sel(name=c.active_assets) + rhs = -c.da.inflow.sel(snapshot=sns, name=c.active_assets) * eh + + if n._multi_invest: + # If multi-horizon optimizing, we update the previous_soc and the rhs + # for all assets which are cyclic/non-cyclic per period + periods = soc.coords["period"] + # An asset is treated as per-period if: + # 1. It cycles per period (CP=cyclic_state_of_charge_per_period=True), OR + # 2. It uses initial state per period (IP=state_of_charge_initial_per_period=True) + per_period = c.da.cyclic_state_of_charge_per_period.sel( + name=c.active_assets + ) | c.da.state_of_charge_initial_per_period.sel(name=c.active_assets) + + # We calculate the previous soc per period while cycling within a period + # Normally, we should use groupby, but is broken for multi-index + # see https://github.com/pydata/xarray/issues/6836 + ps = sns.unique("period") + sl = slice(None) + previous_soc_pp_list = [ + soc.data.sel(snapshot=(p, sl)).roll(snapshot=1) for p in ps + ] + previous_soc_pp = concat(previous_soc_pp_list, dim="snapshot") + + # We create a mask `include_previous_soc_pp` which determines when to include + # previous state of charge from within the period: + # - Always include previous for snapshots within a period (periods == periods.shift()) + # - At period boundaries (first snapshot): + # * If CP=True AND IP=False: cycle to last snapshot of period (wrap) + # * If IP=True: use initial value instead (no wrap, handled via rhs) + # * If CP=True AND IP=True: CP takes precedence, wrap (IP ignored) + include_previous_soc_pp = active & ( + (periods == periods.shift(snapshot=1)) + | c.da.cyclic_state_of_charge_per_period.sel(name=c.active_assets) + ) + + # Ensure that dimension order is consistent for stochastic networks + if n.has_scenarios: + expected_dims = list(include_previous_soc_pp.dims) + if list(previous_soc_pp.dims) != expected_dims: + previous_soc_pp = previous_soc_pp.transpose(*expected_dims) + + # We take values still to handle internal xarray multi-index difficulties + previous_soc_pp = previous_soc_pp.where( + include_previous_soc_pp.values, linopy.variables.FILL_VALUE + ) + + # update the previous_soc variables and right hand side + previous_soc = previous_soc.where(~per_period, previous_soc_pp) + include_previous_soc = include_previous_soc_pp.where( + per_period, include_previous_soc + ) + + # Warn if cyclic overrides initial values (both global and per-period) + has_initial = c.da.state_of_charge_initial.sel(name=c.active_assets) != 0 + global_conflict = ( + c.da.cyclic_state_of_charge.sel(name=c.active_assets) & has_initial + ) + period_conflict = ( + ( + c.da.cyclic_state_of_charge_per_period.sel(name=c.active_assets) + & c.da.state_of_charge_initial_per_period.sel(name=c.active_assets) + & has_initial + ) + if n._multi_invest + else False + ) + + ignored = global_conflict | period_conflict + if ignored.any(): + affected = c.active_assets[ignored.values].tolist() + logger.warning( + "StorageUnits %s: Cyclic state of charge constraint overrules initial storage level setting. " + "User-defined state_of_charge_initial will be ignored.", + affected, + ) + + # Warn if per-period cyclic overrides global cyclic + if n._multi_invest: + cp_overrides_c = c.da.cyclic_state_of_charge.sel( + name=c.active_assets + ) & c.da.cyclic_state_of_charge_per_period.sel(name=c.active_assets) + if cp_overrides_c.any(): + affected = c.active_assets[cp_overrides_c.values].tolist() + logger.warning( + "StorageUnits %s: Per-period cyclic (cyclic_state_of_charge_per_period=True) " + "overrides global cyclic (cyclic_state_of_charge=True). " + "Storage will cycle within each investment period, not across the entire horizon.", + affected, + ) + + lhs += [(eff_stand, previous_soc)] + + rhs = rhs.where(include_previous_soc, rhs - soc_init) + + m.add_constraints(lhs, "=", rhs, name=f"{component}-energy_balance", mask=active) + + +def define_store_constraints(n: Network, sns: pd.Index) -> None: + """Define energy balance constraints for stores. + + Creates constraints ensuring energy conservation for store components over time. + For each store and snapshot, the constraint enforces: + + e(t) = eff_stand * e(t-1) + p(t) * elapsed_hours + + where + e(t) : energy level at time t + eff_stand : standing efficiency (1 - standing_loss)^elapsed_hours + e(t-1) : energy level at previous time step + p(t) : energy charging (positive), or discharging (negative) + elapsed_hours: duration of the time step + + Applies to Components + --------------------- + Store (e, p) + + Parameters + ---------- + n : pypsa.Network + Network instance containing the model and component data + sns : pd.Index + Set of snapshots for which to define the constraints + + Returns + ------- + None + + Notes + ----- + Stores differ from storage units in that they have a single power variable + that can be positive (charging) or negative (discharging) rather than + separate variables for charge and discharge. + + The function handles different store operating modes: + - Cyclic storage (returning to initial energy level at the end of the period) + - Non-cyclic storage (with specified initial energy level) + + For multi-investment period models, the function supports both cycling + within each period and carrying energy between periods. + + Three key flags control the behavior: + + - **C** (e_cyclic): If True, globally cycle energy level + from the last snapshot back to the first snapshot across all periods. + - **CP** (e_cyclic_per_period): If True, cycle energy level + within each investment period (last snapshot of period wraps to first). + - **IP** (e_initial_per_period): If True, reset to initial + e_initial value at the start of each period. + + When CP=True and IP=True simultaneously, CP takes precedence (wrapping behavior). + + Standing losses are applied based on the elapsed hours between snapshots. + + """ + m = n.model + component = "Store" + dim = "snapshot" + c = as_components(n, component) + active = c.da.active.sel(snapshot=sns, name=c.active_assets) + + if c.static.empty: + return + + # elapsed hours + elapsed_h = expand_series(n.snapshot_weightings.stores[sns], c.active_assets) + eh = DataArray(elapsed_h) + + # Unstack in stochastic networks with MultiIndex snapshots + if n.has_scenarios and "dim_1" in eh.dims: + eh = eh.unstack("dim_1") + + # standing efficiency + eff_stand = (1 - c.da.standing_loss.sel(snapshot=sns, name=c.active_assets)) ** eh + + e = m[f"{component}-e"] + p = m[f"{component}-p"] + + # Define LHS expression + lhs = [(-1, e), (-eh, p)] + + # We create a mask `include_previous_e` which excludes the first snapshot + # for non-cyclic assets + noncyclic_b = ~c.da.e_cyclic.sel(name=c.active_assets) + include_previous_e = (active.cumsum(dim) != 1).where(noncyclic_b, True) + + # Calculate previous energy state with proper handling of boundaries + previous_e = ( + e.where(active).ffill(dim).roll(snapshot=1).ffill(dim).where(include_previous_e) + ) + + # We add initial e for non-cyclic assets to rhs + e_init = c.da.e_initial.sel(name=c.active_assets) + rhs = DataArray(0) + + if n._multi_invest: + # If multi-horizon optimization, we update previous_e and the rhs + # for all assets which are cyclic/non-cyclic per period + periods = e.coords["period"] + # An asset is treated as per-period if: + # 1. It cycles per period (CP=e_cyclic_per_period=True), OR + # 2. It uses initial energy per period (IP=e_initial_per_period=True) + per_period = c.da.e_cyclic_per_period | c.da.e_initial_per_period + per_period = per_period.sel(name=c.active_assets) + + # We calculate the previous e per period while cycling within a period + # Normally, we should use groupby, but it's broken for multi-index + # see https://github.com/pydata/xarray/issues/6836 + ps = sns.unique("period") + sl = slice(None) + previous_e_pp_list = [e.data.sel(snapshot=(p, sl)).roll(snapshot=1) for p in ps] + previous_e_pp = concat(previous_e_pp_list, dim="snapshot") + + # We create a mask `include_previous_e_pp` which determines when to include + # previous energy from within the period: + # - Always include previous for snapshots within a period (periods == periods.shift()) + # - At period boundaries (first snapshot): + # * If CP=True AND IP=False: cycle to last snapshot of period (wrap) + # * If IP=True: use initial value instead (no wrap, handled via rhs) + # * If CP=True AND IP=True: CP takes precedence, wrap (IP ignored) + include_previous_e_pp = active & ( + (periods == periods.shift(snapshot=1)) + | c.da.e_cyclic_per_period.sel(name=c.active_assets) + ) + + # We take values still to handle internal xarray multi-index difficulties + previous_e_pp = previous_e_pp.where( + include_previous_e_pp.values, linopy.variables.FILL_VALUE + ) + + # update previous_e variables and rhs + previous_e = previous_e.where(~per_period, previous_e_pp) + include_previous_e = include_previous_e_pp.where(per_period, include_previous_e) + + # Warn if cyclic overrides initial values (both global and per-period) + has_initial = c.da.e_initial.sel(name=c.active_assets) != 0 + global_conflict = c.da.e_cyclic.sel(name=c.active_assets) & has_initial + period_conflict = ( + ( + c.da.e_cyclic_per_period.sel(name=c.active_assets) + & c.da.e_initial_per_period.sel(name=c.active_assets) + & has_initial + ) + if n._multi_invest + else False + ) + + ignored = global_conflict | period_conflict + if ignored.any(): + affected = c.active_assets[ignored.values].tolist() + logger.warning( + "Stores %s: Cyclic energy level constraint overrules initial value setting. " + "User-defined e_initial will be ignored.", + affected, + ) + + # Warn if per-period cyclic overrides global cyclic + if n._multi_invest: + cp_overrides_c = c.da.e_cyclic.sel( + name=c.active_assets + ) & c.da.e_cyclic_per_period.sel(name=c.active_assets) + if cp_overrides_c.any(): + affected = c.active_assets[cp_overrides_c.values].tolist() + logger.warning( + "Stores %s: Per-period cyclic (e_cyclic_per_period=True) " + "overrides global cyclic (e_cyclic=True). " + "Storage will cycle within each investment period, not across the entire horizon.", + affected, + ) + + # Add the previous energy term with standing efficiency factor + lhs += [(eff_stand, previous_e)] + + # For snapshots where we don't include previous_e, we need to account for initial values + rhs = -e_init.where(~include_previous_e, 0) + + m.add_constraints(lhs, "=", rhs, name=f"{component}-energy_balance", mask=active) + + +def define_loss_constraints( + n: Network, sns: pd.Index, component: str, transmission_losses: int +) -> None: + """Define power loss constraints for passive branches. + + This function approximates quadratic power losses using piecewise linear + constraints. It creates tangent segments to the quadratic loss curve + to model the relationship between power flow and losses. + + See equations (39)-(46) in [1]_ for further details on the formulation. + + Applies to Components + --------------------- + Line, Transformer (passive branch components when transmission_losses > 0) + + Parameters + ---------- + n : pypsa.Network + Network instance containing the model and branch data + sns : pd.Index + Set of snapshots for which to define the constraints + component : str + Name of the passive branch component (e.g. "Line", "Transformer") + transmission_losses : int + Number of tangent segments to use in the piecewise linearization + of the quadratic loss function; higher values increase accuracy + but also computational complexity + + Returns + ------- + None + + Notes + ----- + 3 segments offer a good trade-off between accuracy and solver performance. + + References + ---------- + [1] F. Neumann, T. Brown, "Transmission losses in power system + optimization models: A comparison of heuristic and exact solution methods," + Applied Energy, 2022, https://doi.org/10.1016/j.apenergy.2022.118859 + + """ + c = as_components(n, component) + + if c.static.empty or component not in n.passive_branch_components: + return + + tangents = transmission_losses + active = c.da.active.sel(snapshot=sns, name=c.active_assets) + + s_max_pu = c.da.s_max_pu.sel(snapshot=sns) + + # Define nominal capacity (depends on extendability of lines) + is_extendable = c.da.s_nom_extendable + s_nom_max = c.da.s_nom_max.where(is_extendable, c.da.s_nom) + + if not isfinite(s_nom_max).all(): + msg = ( + f"Loss approximation requires finite 's_nom_max' for extendable " + f"branches:\n {s_nom_max.sel(name=~isfinite(s_nom_max))}" + ) + raise ValueError(msg) + + r_pu_eff = c.da.r_pu_eff + + # Calculate upper bound on losses + upper_limit = r_pu_eff * (s_max_pu * s_nom_max) ** 2 + + # Get variables + loss = n.model[f"{c.name}-loss"] + flow = n.model[f"{c.name}-s"] + + # Add upper limit constraint + n.model.add_constraints( + loss <= upper_limit, name=f"{c.name}-loss_upper", mask=active + ) + + # Add linearization constraints for each tangent segment + for k in range(1, tangents + 1): + # Calculate linearization parameters for segment k + p_k = k / tangents * s_max_pu * s_nom_max + loss_k = r_pu_eff * p_k**2 + slope_k = 2 * r_pu_eff * p_k + offset_k = loss_k - slope_k * p_k + + # Add constraints for both positive and negative flow + for sign in [-1, 1]: + lhs = n.model.linexpr((1, loss), (sign * slope_k, flow)) + n.model.add_constraints( + lhs >= offset_k, name=f"{c.name}-loss_tangents-{k}-{sign}", mask=active + ) + + +def define_total_supply_constraints( + n: Network, sns: Sequence, component: str = "Generator" +) -> None: + """Define energy sum constraints for generators. + + Creates constraints limiting the total energy generated by each generator + over the specified snapshots. The constraints can enforce both minimum + and maximum energy production requirements. + + For generators with e_sum_min, the constraint enforces: + + sum(p(t) * weighting(t)) ≥ e_sum_min + + For generators with e_sum_max, the constraint enforces: + + sum(p(t) * weighting(t)) ≤ e_sum_max + + where the sum is taken over all snapshots and weighting accounts for the + duration of each snapshot. + + Applies to Components + --------------------- + Generator (by default, but component parameter can be changed) + + Parameters + ---------- + n : pypsa.Network + Network instance containing the model and component data + sns : Sequence + Set of snapshots for which to define the constraints + component : str, default "Generator" + Name of the network component to apply the constraints to + + Returns + ------- + None + + Notes + ----- + These constraints are useful for modeling: + - Minimum energy production requirements (e.g., contracted energy delivery) + - Maximum energy production limits (e.g., fuel availability, water reservoir limits) + - Must-run generators with flexibility in when to produce + + The constraints only apply to generators that have finite e_sum_min or + e_sum_max values specified. + + """ + sns_ = as_index(n, sns, "snapshots") + m = n.model + c = as_components(n, component) + + if c.static.empty: + return + + # elapsed hours + eh = DataArray( + expand_series(n.snapshot_weightings.generators[sns_], c.static.index) + ) + # Unstack in stochastic networks with MultiIndex snapshots + if n.has_scenarios: + eh = eh.unstack("dim_1") + + def _extract_names(index: pd.Index) -> pd.Index: + """Extract name level from MultiIndex or return as-is.""" + return ( + index.get_level_values("name") + if isinstance(index, pd.MultiIndex) + else index + ) + + # minimum energy production constraints + e_sum_min_i = c.static.index[c.static.e_sum_min > -inf] + if not e_sum_min_i.empty: + names = _extract_names(e_sum_min_i) + e_sum_min = c.da.e_sum_min.sel(name=names) + p = m[f"{c.name}-p"].sel(name=names, snapshot=sns_) + eh_selected = eh.sel(name=names) + energy = (p * eh_selected).sum(dim="snapshot") + m.add_constraints(energy, ">=", e_sum_min, name=f"{c.name}-e_sum_min") + + # maximum energy production constraints + e_sum_max_i = c.static.index[c.static.e_sum_max < inf] + if not e_sum_max_i.empty: + names = _extract_names(e_sum_max_i) + e_sum_max = c.da.e_sum_max.sel(name=names) + p = m[f"{c.name}-p"].sel(name=names, snapshot=sns_) + eh_selected = eh.sel(name=names) + energy = (p * eh_selected).sum(dim="snapshot") + m.add_constraints(energy, "<=", e_sum_max, name=f"{c.name}-e_sum_max") diff --git a/PyPSA/source/pypsa/optimization/expressions.py b/PyPSA/source/pypsa/optimization/expressions.py new file mode 100644 index 0000000000000000000000000000000000000000..a0532fe500048b15a493b54b8587170fb05ecc1a --- /dev/null +++ b/PyPSA/source/pypsa/optimization/expressions.py @@ -0,0 +1,704 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Statistics Expression Accessor.""" + +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, Any + +import linopy as ln +import numpy as np +import pandas as pd +from linopy import LinearExpression, Variable +from packaging import version +from xarray import DataArray + +from pypsa.common import deprecated_kwargs, pass_none_if_keyerror +from pypsa.descriptors import nominal_attrs +from pypsa.statistics import ( + get_transmission_branches, + port_efficiency, +) +from pypsa.statistics.abstract import AbstractStatisticsAccessor + +if TYPE_CHECKING: + from collections.abc import Callable, Collection, Sequence + + from pypsa import Network, NetworkCollection +logger = logging.getLogger(__name__) + + +USE_EMPTY_PROPERTY = version.parse(ln.__version__) >= version.parse("0.5.1") + + +def check_if_empty(expr: LinearExpression) -> bool: + """Check if the expression is empty. + + This is a workaround for the issue that linopy does not support + the empty property for older versions (`.empty` in >=0.5.1 vs `.empty()` in <0.5.1). + """ + if USE_EMPTY_PROPERTY: + return expr.empty + return expr.empty() + + +class StatisticExpressionsAccessor(AbstractStatisticsAccessor): + """Accessor to calculate different statistical expressions. + + This class is used to calculate different statistical expressions like + capital expenditure, capacity, energy balance, etc. + The results are aggregated by the given groupby function. + """ + + def _get_grouping( + self, + n: Network | NetworkCollection, + c: str, + groupby: Callable | Sequence[str] | str | bool, + port: str | None = None, + nice_names: bool = False, + ) -> pd.DataFrame: + result = super()._get_grouping(n, c, groupby, port, nice_names) + by = result["by"] + + if isinstance(by, list): + grouper = pd.concat(by, axis=1) + elif isinstance(by, pd.Series): + grouper = by.to_frame() + elif groupby is False: + grouper = pd.DataFrame(index=n.c[c].static.index) + else: + grouper = by + + grouper.insert(0, "component", c) # for tracking the component + return grouper + + def _get_component_index(self, obj: LinearExpression, c: str) -> pd.Index: + return obj.indexes["name"] + + def _concat_periods(self, exprs: dict[str, LinearExpression], c: str) -> Any: + return ln.merge(list(exprs.values()), dim=c) + + @staticmethod + def _aggregate_with_weights( + expr: LinearExpression, + weights: pd.Series, + agg: str | Callable, + ) -> LinearExpression: + """Apply weights to a time series.""" + if agg == "sum": + if isinstance(weights.index, pd.MultiIndex): + return expr.multiply(weights, axis=0).groupby(level=0).sum().T + return expr @ weights + msg = f"Aggregation method {agg} not supported." + raise ValueError(msg) + + def _aggregate_components_skip_iteration(self, vals: Any) -> bool: + return vals is None or (not np.prod(vals.shape) and (vals.const == 0).all()) + + def _aggregate_components_groupby( + self, + vals: LinearExpression, + grouping: pd.DataFrame, + agg: Callable | str, + c: str, + ) -> pd.DataFrame: + return vals.groupby(grouping).sum() + + def _aggregate_components_concat_values( + self, exprs: list[LinearExpression], agg: Callable | str + ) -> LinearExpression: + res = ln.merge(exprs) + if not (index := res.indexes[res.dims[0]]).is_unique: + if agg != "sum": + msg = f"Aggregation method {agg} not supported." + raise ValueError(msg) + non_unique_groups = pd.DataFrame(list(index), columns=index.names) + res = res.groupby(non_unique_groups).sum() + return res + + def _aggregate_components_concat_data( + self, res: dict[str, LinearExpression], is_one_component: bool + ) -> LinearExpression: + if res == {}: + return LinearExpression(None, self._n.model) + if is_one_component: + first_key = next(iter(res)) + return res[first_key].loc[first_key] + return ln.merge(list(res.values()), dim="group") + + def _apply_option_kwargs( + self, + expr: LinearExpression, + nice_names: bool | None, + drop_zero: bool | None, + round: int | None, + ) -> LinearExpression: + # Expressions only support nice_names right now which applied elsewhere + # TODO + return expr + + def _aggregate_across_components( + self, expr: LinearExpression, agg: Callable | str + ) -> LinearExpression: + if agg != "sum": + msg = f"Aggregation method {agg} not supported." + raise ValueError(msg) + if check_if_empty(expr): + return expr + group = expr.indexes["group"].to_frame().drop(columns="component").squeeze() + return expr.groupby(group).sum() + + def _get_operational_variable(self, c: str) -> Variable | LinearExpression: + # TODO: move function to better place to avoid circular imports + from pypsa.optimization.optimize import lookup # noqa: PLC0415 + + m = self._n.model + + if c == "Load": + return LinearExpression(self._n.get_switchable_as_dense(c, "p_set"), m) + attr = lookup.query("not nominal and not handle_separately").loc[c].index + if c == "StorageUnit": + return m.variables[f"{c}-p_dispatch"] - m.variables[f"{c}-p_store"] + attr = attr.item() + return m.variables[f"{c}-{attr}"] + + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def capex( + self, + components: str | Sequence[str] | None = None, + groupby_method: str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: bool | str | Sequence[str] = False, + bus_carrier: str | Sequence[str] | None = None, + carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + cost_attribute: str = "capital_cost", + include_non_extendable: bool = True, + ) -> LinearExpression: + """Calculate the capital expenditure of the network in given currency. + + If `bus_carrier` is given, only components which are connected to buses + with carrier `bus_carrier` are considered. + + For information on the list of arguments, see the docs in + `Network.statistics` or `pypsa.statistics.StatisticsAccessor`. + """ + + @pass_none_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series | None: + m = n.model + capacity = m.variables[f"{c}-{nominal_attrs[c]}"] + if include_non_extendable: + query = f"~{nominal_attrs[c]}_extendable" + capacity = capacity + n.c[c].static.query(query)["p_nom"] + costs = n.c[c].static[cost_attribute][capacity.indexes["name"]] + return capacity * costs + + return self._aggregate_components( + func, + components=components, + agg=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + bus_carrier=bus_carrier, + carrier=carrier, + nice_names=nice_names, + ) + + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def capacity( + self, + components: str | Sequence[str] | None = None, + groupby_method: str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: str | Sequence[str] | bool | None = None, + bus_carrier: str | Sequence[str] | None = None, + carrier: str | Sequence[str] | None = None, + storage: bool = False, + nice_names: bool | None = None, + include_non_extendable: bool = True, + ) -> LinearExpression: + """Calculate the optimal capacity of the network components in MW. + + If `bus_carrier` is given, the capacity is weighed by the output efficiency + of components at buses with carrier `bus_carrier`. + + If storage is set to True, only storage capacities of the component + `Store` and `StorageUnit` are taken into account. + + For information on the list of arguments, see the docs in + `Network.statistics` or `pypsa.statistics.StatisticsAccessor`. + """ + if storage: + components = ("Store", "StorageUnit") + if bus_carrier and at_port is None: + at_port = True + + @pass_none_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series | None: + m = n.model + attr = nominal_attrs[c] + capacity = m.variables[f"{c}-{nominal_attrs[c]}"] + if include_non_extendable: + query = f"~{attr}_extendable" + capacity = capacity + n.c[c].static.query(query)[attr] + efficiency = port_efficiency(n, c, port=port)[capacity.indexes["name"]] + if not at_port: + efficiency = abs(efficiency) + res = capacity * efficiency + if storage and (c == "StorageUnit"): + res = res * n.components[c].static.max_hours + return res + + return self._aggregate_components( + func, + components=components, + agg=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + bus_carrier=bus_carrier, + carrier=carrier, + nice_names=nice_names, + ) + + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def opex( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_time: str | bool = "sum", + groupby_method: str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: bool | str | Sequence[str] = False, + bus_carrier: str | Sequence[str] | None = None, + carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + ) -> LinearExpression: + """Calculate the operational expenditure in the network in given currency. + + If `bus_carrier` is given, only components which are connected to buses + with carrier `bus_carrier` are considered. + + For information on the list of arguments, see the docs in + `Network.statistics` or `pypsa.statistics.StatisticsAccessor`. + + Parameters + ---------- + groupby_time : str, bool, optional + Type of aggregation when aggregating time series. + Note that for {'mean', 'sum'} the time series are aggregated + using snapshot weightings. With False the time series is given in currency/hour. Defaults to 'sum'. + + """ + from pypsa.optimization.optimize import lookup # noqa: PLC0415 + + @pass_none_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series | None: + attr = lookup.query("not nominal and marginal_cost").loc[c].index.item() + if attr is None: + return None + var = n.model.variables[f"{c}-{attr}"] + sns = var.indexes["snapshot"] + opex = var * n.get_switchable_as_dense(c, "marginal_cost").loc[sns] + weights = n.snapshot_weightings.objective.loc[sns] + return self._aggregate_timeseries(opex, weights, agg=groupby_time) + + return self._aggregate_components( + func, + components=components, + agg=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + bus_carrier=bus_carrier, + carrier=carrier, + nice_names=nice_names, + ) + + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def transmission( # noqa: D417 + self, + components: Collection[str] | str | None = None, + groupby_time: str | bool = "sum", + groupby_method: str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: bool | str | Sequence[str] = False, + bus_carrier: str | Sequence[str] | None = None, + carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + ) -> LinearExpression: + """Calculate the transmission of branch components in the network. + + Units depend on the regarded bus carrier. + + If `bus_carrier` is given, only the flow between buses with + carrier `bus_carrier` is calculated. + + For information on the list of arguments, see the docs in + `Network.statistics` or `pypsa.statistics.StatisticsAccessor`. + + Parameters + ---------- + groupby_time : str, bool, optional + Type of aggregation when aggregating time series. + Note that for {'mean', 'sum'} the time series are aggregated to MWh + using snapshot weightings. With False the time series is given in MW. Defaults to 'sum'. + + """ + if components is None: + components = self._n.branch_components + + transmission_branches = get_transmission_branches(self._n, bus_carrier) + + @pass_none_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series: + var = self._get_operational_variable(c) + sns = var.indexes["snapshot"] + idx = transmission_branches.get_loc_level(c)[1].rename(c) + efficiency = port_efficiency(n, c, port=port, dynamic=True) + if isinstance(efficiency, pd.DataFrame): + efficiency = efficiency.loc[sns] + p = var.loc[:, idx] * efficiency[idx] + weights = n.snapshot_weightings.generators.loc[sns] + return self._aggregate_timeseries(p, weights, agg=groupby_time) + + return self._aggregate_components( + func, + components=components, + agg=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + bus_carrier=bus_carrier, + carrier=carrier, + nice_names=nice_names, + ) + + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def energy_balance( + self, + components: str | Sequence[str] | None = None, + groupby_time: str | bool = "sum", + groupby_method: str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable | None = None, + at_port: bool | str | Sequence[str] = True, + bus_carrier: str | Sequence[str] | None = None, + carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + kind: str | None = None, + ) -> LinearExpression: + """Calculate the energy balance of components in the network. + + Positive values represent a supply and negative a withdrawal. Units depend on + the regarded bus carrier. + + For information on the list of arguments, see the docs in + `Network.statistics` or `pypsa.statistics.StatisticsAccessor`. + + Additional parameter + -------------------- + aggregate_bus: bool, optional + Whether to obtain the nodal or carrier-wise energy balance. Default is True, corresponding to the carrier-wise balance. + groupby_time : str, bool, optional + Type of aggregation when aggregating time series. + Note that for {'mean', 'sum'} the time series are aggregated to MWh + using snapshot weightings. With False the time series is given in MW. Defaults to 'sum'. + """ + if groupby is None: + groupby = ["carrier", "bus_carrier"] + if ( + self._n.c.buses.static.carrier.unique().size > 1 + and groupby is None + and bus_carrier is None + ): + logger.warning( + "Network has multiple bus carriers which are aggregated together. " + "To separate bus carriers set `bus_carrier` or use `bus_carrier` in the groupby argument." + ) + + @pass_none_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series: + var = self._get_operational_variable(c) + sns = var.indexes["snapshot"] + # negative branch contributions are considered by the efficiency + efficiency = port_efficiency(n, c, port=port, dynamic=True) + if isinstance(efficiency, pd.DataFrame): + efficiency = efficiency.loc[sns] + sign = n.c[c].static.get("sign", 1.0) + weights = n.snapshot_weightings.generators.loc[sns] + coeffs = DataArray(efficiency * sign) + if kind == "supply": + coeffs = coeffs.clip(min=0) + elif kind == "withdrawal": + logger.warning( + "The sign convention for withdrawal has changed: withdrawal values are now reported as positive numbers instead of negative numbers." + ) + coeffs = -coeffs.clip(max=0) + elif kind is not None: + msg = f"Got unexpected argument kind={kind}. Must be 'supply', 'withdrawal' or None." + raise ValueError(msg) + p = var.where(coeffs != 0) * coeffs + return self._aggregate_timeseries(p, weights, agg=groupby_time) + + return self._aggregate_components( + func, + components=components, + agg=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + bus_carrier=bus_carrier, + carrier=carrier, + nice_names=nice_names, + ) + + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def supply( + self, + components: str | Sequence[str] | None = None, + groupby_time: str | bool = "sum", + groupby_method: str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable | None = None, + at_port: bool | str | Sequence[str] = True, + bus_carrier: str | Sequence[str] | None = None, + carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + ) -> LinearExpression: + """Calculate the supply of components in the network. + + Units depend on the regarded bus carrier. + + If `bus_carrier` is given, only the supply to buses with carrier + `bus_carrier` is calculated. + + For information on the list of arguments, see the docs in + `Network.statistics` or `pypsa.statitics.StatisticsAccessor`. + """ + if groupby is None: + groupby = ["carrier", "bus_carrier"] + return self.energy_balance( + components=components, + groupby_time=groupby_time, + groupby_method=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + bus_carrier=bus_carrier, + carrier=carrier, + nice_names=nice_names, + kind="supply", + ) + + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def withdrawal( + self, + components: str | Sequence[str] | None = None, + groupby_time: str | bool = "sum", + groupby_method: str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable | None = None, + at_port: bool | str | Sequence[str] = True, + bus_carrier: str | Sequence[str] | None = None, + carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + ) -> LinearExpression: + """Calculate the withdrawal of components in the network. + + Units depend on the regarded bus carrier. + + If `bus_carrier` is given, only the withdrawal from buses with + carrier `bus_carrier` is calculated. + + For information on the list of arguments, see the docs in + `Network.statistics` or `pypsa.statitics.StatisticsAccessor`. + """ + if groupby is None: + groupby = ["carrier", "bus_carrier"] + return self.energy_balance( + components=components, + groupby_time=groupby_time, + groupby_method=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + bus_carrier=bus_carrier, + carrier=carrier, + nice_names=nice_names, + kind="withdrawal", + ) + + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def curtailment( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_time: str | bool = "sum", + groupby_method: str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: bool | str | Sequence[str] = False, + bus_carrier: str | Sequence[str] | None = None, + carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + ) -> LinearExpression: + """Calculate the curtailment of components in the network in MWh. + + The calculation only considers assets with a `p_max_pu` time + series, which is used to quantify the available power potential. + + If `bus_carrier` is given, only the assets are considered which are + connected to buses with carrier `bus_carrier`. + + For information on the list of arguments, see the docs in + `Network.statistics` or `pypsa.statistics.StatisticsAccessor`. + + Parameters + ---------- + groupby_time : str, bool, optional + Type of aggregation when aggregating time series. + Note that for {'mean', 'sum'} the time series are aggregated to MWh + using snapshot weightings. With False the time series is given in MW. Defaults to 'sum'. + + """ + + @pass_none_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series: + attr = nominal_attrs[c] + capacity = ( + n.model.variables[f"{c}-{attr}"] + + n.c[c].static.query(f"~{attr}_extendable")[attr] + ) + idx = capacity.indexes["name"] + operation = self._get_operational_variable(c).loc[:, idx] + sns = operation.indexes["snapshot"] + p_max_pu = DataArray(n.get_switchable_as_dense(c, "p_max_pu")[idx]).loc[sns] + # the following needs to be fixed in linopy, right now constants cannot be used for broadcasting + # TODO curtailment = capacity * p_max_pu - operation + curtailment = (capacity - operation / p_max_pu) * p_max_pu + weights = n.snapshot_weightings.generators.loc[sns] + return self._aggregate_timeseries(curtailment, weights, agg=groupby_time) + + return self._aggregate_components( + func, + components=components, + agg=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + bus_carrier=bus_carrier, + carrier=carrier, + nice_names=nice_names, + ) + + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def operation( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_time: str | bool = "mean", + groupby_method: str = "sum", + aggregate_across_components: bool = False, + at_port: bool | str | Sequence[str] = False, + groupby: str | Sequence[str] | Callable = "carrier", + bus_carrier: str | Sequence[str] | None = None, + carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + ) -> LinearExpression: + """Calculate the operation of components in the network. + + If `bus_carrier` is given, only the assets are considered which are + connected to buses with carrier `bus_carrier`. + + For information on the list of arguments, see the docs in + `Network.statistics` or `pypsa.statistics.StatisticsAccessor`. + + Parameters + ---------- + groupby_time : str, bool, optional + Type of aggregation when aggregating time series. + Note that for {'mean', 'sum'} the time series are aggregated to + using snapshot weightings. With False the time series is given. Defaults to 'mean'. + + """ + + @pass_none_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series: + operation = self._get_operational_variable(c) + sns = operation.indexes["snapshot"] + weights = n.snapshot_weightings.generators.loc[sns] + return self._aggregate_timeseries(operation, weights, agg=groupby_time) + + return self._aggregate_components( + func, + agg=groupby_method, + components=components, + groupby=groupby, + aggregate_across_components=aggregate_across_components, + at_port=at_port, + bus_carrier=bus_carrier, + carrier=carrier, + nice_names=nice_names, + ) diff --git a/PyPSA/source/pypsa/optimization/global_constraints.py b/PyPSA/source/pypsa/optimization/global_constraints.py new file mode 100644 index 0000000000000000000000000000000000000000..0183c42649c559de361495783dc5eb6773fed323 --- /dev/null +++ b/PyPSA/source/pypsa/optimization/global_constraints.py @@ -0,0 +1,876 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Define global constraints for optimisation problems with Linopy.""" + +from __future__ import annotations + +import logging +import re +import warnings +from typing import TYPE_CHECKING + +import pandas as pd +from linopy.expressions import merge +from numpy import isnan +from xarray import DataArray + +from pypsa.descriptors import nominal_attrs + +if TYPE_CHECKING: + from collections.abc import Sequence + + from pypsa import Network +logger = logging.getLogger(__name__) + + +def define_tech_capacity_expansion_limit(n: Network, sns: Sequence) -> None: + """Define per-carrier and potentially per-bus capacity expansion limits. + + Parameters + ---------- + n : pypsa.Network + The network to apply constraints to. + sns : list-like + Set of snapshots to which the constraint should be applied. + + """ + m = n.model + glcs = n.c.global_constraints.static.loc[ + lambda df: df.type == "tech_capacity_expansion_limit" + ] + + if n.has_scenarios and not glcs.empty: + msg = "Technology expansion limits for stochastic networks are not implemented." + raise NotImplementedError(msg) + + for (carrier, sense, period), glcs_group in glcs.groupby( + ["carrier_attribute", "sense", "investment_period"] + ): + period = None if isnan(period) else int(period) + sign = "=" if sense == "==" else sense + busdim = f"Bus-{carrier}-{period}" + lhs_per_bus_list = [] + + for c, attr in nominal_attrs.items(): + var = f"{c}-{attr}" + static = n.c[c].static + + if "carrier" not in static: + continue + + ext_i = n.components[c].extendables.difference(n.c[c].inactive_assets) + ext_i = ext_i.intersection(static.index[static.carrier == carrier]) + if period is not None: + ext_i = ext_i[n.components[c].get_active_assets(period)[ext_i]] + + if ext_i.empty: + continue + + bus = "bus0" if c in n.branch_components else "bus" + busmap = static.loc[ext_i, bus].rename(busdim).to_xarray() + expr = m[var].loc[ext_i].groupby(busmap).sum() + lhs_per_bus_list.append(expr) + + if not lhs_per_bus_list: + continue + + lhs_per_bus = merge(lhs_per_bus_list) + + for name, glc in glcs_group.iterrows(): + bus_glc = glc.get("bus") or None + if bus_glc is None: + lhs = lhs_per_bus.sum(busdim) + else: + lhs = lhs_per_bus.sel(**{busdim: str(bus_glc)}, drop=True) + + n.model.add_constraints( + lhs, sign, glc.constant, name=f"GlobalConstraint-{name}" + ) + + +def define_nominal_constraints_per_bus_carrier(n: Network, sns: pd.Index) -> None: + """Set an capacity expansion limit for assets of the same carrier at the same bus. + + The function searches for columns in the `buses` dataframe matching the pattern + "nom_{min/max}_{carrier}". In case the constraint should only be defined for one + investment period, the column name can be constructed according to + "nom_{min/max}_{carrier}_{period}" where period must be in `n.investment_periods`. + + Parameters + ---------- + n : pypsa.Network + The network to apply constraints to. + sns : list-like + Set of snapshots to which the constraint should be applied. + + """ + m = n.model + cols = n.c.buses.static.columns[n.c.buses.static.columns.str.startswith("nom_")] + buses = n.c.buses.static.index[n.c.buses.static[cols].notnull().any(axis=1)] + + if not cols.empty: + warnings.warn( + "Nominal constraints per bus carrier are deprecated and will be removed in the future. " + "Use global constraint of type 'define_tech_capacity_expansion_limit' instead." + "Deprecated in PyPSA 1.0 and will be removed in PyPSA 2.0.", + DeprecationWarning, + stacklevel=2, + ) + if n.has_scenarios and not buses.empty: + msg = "Nominal constraints per bus carrier are not implemented for stochastic networks." + raise NotImplementedError(msg) + + for col in cols: + msg = ( + f"Bus column '{col}' has invalid specification and cannot be " + "interpreted as constraint, must match the pattern " + "`nom_{min/max}_{carrier}` or `nom_{min/max}_{carrier}_{period}`" + ) + if col.startswith("nom_min_"): + sign = ">=" + elif col.startswith("nom_max_"): + sign = "<=" + else: + logger.warning(msg) + continue + remainder = col[len("nom_max_") :] + if remainder in n.c.carriers.static.index: + carrier = remainder + period = None + elif isinstance(n.snapshots, pd.MultiIndex): + carrier, period = remainder.rsplit("_", 1) + period = int(period) + if carrier not in n.c.carriers.static.index or period not in sns.unique( + "period" + ): + logger.warning(msg) + continue + else: + logger.warning(msg) + continue + + lhs = [] + + for c, attr in nominal_attrs.items(): + var = f"{c}-{attr}" + static = n.c[c].static + + if c not in n.one_port_components or "carrier" not in static: + continue + + ext_i = n.c[c].extendables.difference(n.c[c].inactive_assets) + ext_i = ext_i.intersection(static.index[static.carrier == carrier]) + if period is not None: + ext_i = ext_i[n.components[c].get_active_assets(period)[ext_i]] + + if ext_i.empty: + continue + + busmap = static.loc[ext_i, "bus"].rename(buses.name).to_xarray() + expr = m[var].loc[ext_i].groupby(busmap).sum().reindex({buses.name: buses}) + lhs.append(expr) + + if not lhs: + continue + + lhs = merge(lhs) + rhs = n.c.buses.static.loc[buses, col] + mask = rhs.notnull() + n.model.add_constraints(lhs, sign, rhs, name=f"Bus-{col}", mask=mask) + + +def define_growth_limit(n: Network, sns: pd.Index) -> None: + """Constraint new installed capacity per investment period. + + Parameters + ---------- + n : pypsa.Network + The network to apply constraints to. + sns : list-like + Set of snapshots to which the constraint should be applied. + + """ + if not n._multi_invest: + return + + m = n.model + periods = sns.unique("period") + + # Handle stochastic optimization: find strictest (minimum) growth limit across scenarios + if n.has_scenarios: + max_growth = n.c.carriers.static.groupby(level="name")["max_growth"].min() + max_relative_growth = n.c.carriers.static.groupby(level="name")[ + "max_relative_growth" + ].min() + else: + max_growth = n.c.carriers.static["max_growth"] + max_relative_growth = n.c.carriers.static["max_relative_growth"] + + carrier_i = max_growth[max_growth != float("inf")].index.rename("Carrier") + max_absolute_growth = DataArray(max_growth.loc[carrier_i]) + max_relative_growth = DataArray(max_relative_growth.loc[carrier_i]).clip(min=0) + + if carrier_i.empty: + return + + lhs_list = [] + for c, attr in nominal_attrs.items(): + var = f"{c}-{attr}" + static = n.c[c].static + + if "carrier" not in static: + continue + + component_carriers = static.loc[:, "carrier"] + + if n.has_scenarios: + unique_component_names = n.components[c].names + carrier_map = component_carriers.groupby(level="name").first() + else: + unique_component_names = static.index + carrier_map = component_carriers + + carriers_match = unique_component_names[carrier_map.isin(carrier_i)] + limited_names = carriers_match.intersection( + n.c[c].extendables.difference(n.c[c].inactive_assets) + ) + + if limited_names.empty: + continue + + # Get active assets for the limited components + active = pd.concat( + {p: n.components[c].get_active_assets(p) for p in periods}, axis=1 + ) + + if n.has_scenarios: + active = active.groupby(level="name").first() + + active = active.loc[limited_names].rename_axis(columns="periods").T + first_active = DataArray(active.cumsum() == 1) + carriers = carrier_map.loc[limited_names].rename("Carrier") + + vars = m[var].sel(name=limited_names).where(first_active) + expr = vars.groupby(carriers.to_xarray()).sum() + + if (max_relative_growth.loc[carriers.unique()] > 0).any(): + expr = expr - expr.shift(periods=1) * max_relative_growth + + lhs_list.append(expr) + + if not lhs_list: + return + + lhs = merge(lhs_list) + rhs = max_absolute_growth.reindex_like(lhs.data) + + m.add_constraints(lhs, "<=", rhs, name="Carrier-growth_limit") + + +def define_primary_energy_limit(n: Network, sns: pd.Index) -> None: + """Define primary energy constraints. + + It limits the byproducts of primary energy sources (defined by carriers) such + as CO2. + + Parameters + ---------- + n : pypsa.Network + The network to apply constraints to. + sns : list-like + Set of snapshots to which the constraint should be applied. + + """ + m = n.model + weightings = n.snapshot_weightings.loc[sns] + glcs = n.c.global_constraints.static.query('type == "primary_energy"') + + if n._multi_invest: + period_weighting = n.investment_period_weightings.years[sns.unique("period")] + weightings = weightings.mul(period_weighting, level=0, axis=0) + period_last_sns = pd.MultiIndex.from_frame( + sns.to_frame(index=False).groupby("period").timestep.last().reset_index() + ) + storage_weightings = ( + pd.Series(1, n.snapshots).mul(period_weighting).loc[period_last_sns] + ) + + unique_names = glcs.index.unique("name") + + for name in unique_names: + if n.has_scenarios: + glc_group = glcs.xs(name, level="name") + scenarios = glc_group.index.get_level_values("scenario") + else: + glc_group = glcs.loc[name] + scenarios = [slice(None)] + + expressions = [] + for scenario in scenarios: + glc = glc_group.loc[scenario] + + if isnan(glc.investment_period): + sns_sel = slice(None) + elif glc.investment_period in sns.unique("period"): + sns_sel = sns.get_loc(glc.investment_period) + else: + continue + + lhs = [] + emissions = n.c.carriers.static[glc.carrier_attribute][ + lambda ds: ds != 0 + ].loc[scenario] + + if emissions.empty: + continue + + # generators + emission_carriers = emissions.index + gens = n.c.generators.static[ + n.c.generators.static.carrier.isin(emission_carriers) + ] + + if not gens.empty: + gens = gens.loc[scenario] + efficiency = ( + n.c.generators._as_dynamic("efficiency") + .loc[:, scenario] + .loc[sns[sns_sel], gens.index] + ) + em_pu = gens.carrier.map(emissions) / efficiency + em_pu = em_pu.multiply(weightings.generators[sns_sel], axis=0) + + p = m["Generator-p"].sel(name=gens.index, snapshot=sns[sns_sel]) + + if n.has_scenarios: + p = p.sel(scenario=scenario, drop=True) + + expr = (p * em_pu).sum() + lhs.append(expr) + + # storage units + cond = "carrier in @emissions.index and not cyclic_state_of_charge" + sus = n.c.storage_units.static.query(cond) + if not sus.empty: + sus = sus.loc[scenario] + em_pu = sus.carrier.map(emissions) + soc = m["StorageUnit-state_of_charge"].sel( + name=sus.index, snapshot=sns[sns_sel] + ) + + if n._multi_invest: + sus_continuous = sus.query("not state_of_charge_initial_per_period") + if not sus_continuous.empty and period_weighting.ne(1).any(): + msg = ( + "Found non-cyclic storage units with associated carrier emissions " + "and continuous depletion over multiple investment periods " + "combined with investment period year weightings != 1. " + "The primary energy constraint will be inconsistent. " + "Please consider setting `state_of_charge_initial_per_period` to True, " + "using equal period weightings or a cyclic storage unit instead." + ) + raise NotImplementedError(msg) + + if not sus_continuous.empty and period_weighting.eq(1).all(): + soc_final = ( + soc.sel(name=sus_continuous.index) + .ffill("snapshot") + .isel(snapshot=-1) + ) + if n.has_scenarios: + soc_final = soc_final.sel(scenario=scenario, drop=True) + lhs.append( + (soc_final * -em_pu).sum() + + em_pu @ sus_continuous.state_of_charge_initial + ) + + sus_per_period = sus.query("state_of_charge_initial_per_period") + if not sus_per_period.empty: + soc_final = soc.loc[period_last_sns, sus_per_period.index] + if n.has_scenarios: + soc_final = soc_final.sel(scenario=scenario, drop=True) + soc_delta = -soc_final + sus_per_period.state_of_charge_initial + lhs.append((soc_delta * storage_weightings * em_pu).sum()) + + else: + soc_final = soc.ffill("snapshot").isel(snapshot=-1) + if n.has_scenarios: + soc_final = soc_final.sel(scenario=scenario, drop=True) + lhs.append( + (soc_final * -em_pu).sum() + em_pu @ sus.state_of_charge_initial + ) + + # stores + stores = n.c.stores.static.query( + "carrier in @emissions.index and not e_cyclic" + ) + if not stores.empty: + stores = stores.loc[scenario] + em_pu = stores.carrier.map(emissions) + e = m["Store-e"].sel(name=stores.index, snapshot=sns[sns_sel]) + + if n._multi_invest: + stores_continuous = stores.query("not e_initial_per_period") + if not stores_continuous.empty and period_weighting.ne(1).any(): + msg = ( + "Found non-cyclic stores with associated carrier emissions " + "and continuous depletion over multiple investment periods " + "combined with investment period year weightings != 1. " + "The primary energy constraint will be inconsistent. " + "Please consider setting `e_initial_per_period` to True, " + "using equal period weightings or a cyclic store instead." + ) + raise NotImplementedError(msg) + + if not stores_continuous.empty and period_weighting.eq(1).all(): + e_final = ( + e.sel(name=stores_continuous.index) + .ffill("snapshot") + .isel(snapshot=-1) + ) + if n.has_scenarios: + e_final = e_final.sel(scenario=scenario, drop=True) + lhs.append( + (e_final * -em_pu).sum() + + em_pu @ stores_continuous.e_initial + ) + + stores_per_period = stores.query("e_initial_per_period") + if not stores_per_period.empty: + e_final = e.loc[period_last_sns, stores_per_period.index] + if n.has_scenarios: + e_final = e_final.sel(scenario=scenario, drop=True) + e_delta = -e_final + stores_per_period.e_initial + lhs.append((e_delta * storage_weightings * em_pu).sum()) + + else: + e_final = e.ffill("snapshot").isel(snapshot=-1) + if n.has_scenarios: + e_final = e_final.sel(scenario=scenario, drop=True) + lhs.append((e_final * -em_pu).sum() + em_pu @ stores.e_initial) + + if not lhs: + continue + + lhs = merge(lhs) + expressions.append(lhs) + + if not expressions: + continue + + if n.has_scenarios: + expression = merge(expressions, dim="scenario").assign_coords( + scenario=scenarios + ) + else: + expression = expressions[0] + + m.add_constraints( + expression, + glc_group.sense, + glc_group.constant, + name=f"GlobalConstraint-{name}", + ) + + +def define_operational_limit(n: Network, sns: pd.Index) -> None: + """Define operational limit constraints. + + It limits the net production of a carrier taking into account generator, storage + units and stores. + + Parameters + ---------- + n : pypsa.Network + The network to apply constraints to. + sns : list-like + Set of snapshots to which the constraint should be applied. + + """ + m = n.model + weightings = n.snapshot_weightings.loc[sns] + glcs = n.c.global_constraints.static.query('type == "operational_limit"') + + unique_names = glcs.index.unique("name") + + for name in unique_names: + if n.has_scenarios: + glc_group = glcs.xs(name, level="name") + scenarios = glc_group.index.get_level_values("scenario") + else: + glc_group = glcs.loc[name] + scenarios = [slice(None)] + + expressions = [] + for scenario in scenarios: + glc = glc_group.loc[scenario] + + if isnan(glc.investment_period): + sns_sel = slice(None) + elif glc.investment_period in sns.unique("period"): + sns_sel = sns.get_loc(glc.investment_period) + else: + continue + + # Filter weightings and calculate period-specific values + weightings_filtered = weightings.loc[sns[sns_sel]] + if n._multi_invest: + period_weighting = n.investment_period_weightings.years[ + sns[sns_sel].unique("period") + ] + weightings_filtered = weightings_filtered.mul( + period_weighting, level=0, axis=0 + ) + period_last_sns = pd.MultiIndex.from_frame( + sns[sns_sel] + .to_frame(index=False) + .groupby("period") + .timestep.last() + .reset_index() + ) + storage_weightings = ( + pd.Series(1, n.snapshots).mul(period_weighting).loc[period_last_sns] + ) + + lhs = [] + + # generators + gens = n.c.generators.static.query( + "carrier == @glc.carrier_attribute and active" + ) + if not gens.empty: + gens = gens.loc[scenario] + p = m["Generator-p"].sel(name=gens.index, snapshot=sns[sns_sel]) + if n.has_scenarios: + p = p.sel(scenario=scenario, drop=True) + + w = DataArray( + weightings_filtered.generators.values, + coords={"snapshot": weightings_filtered.index}, + dims=["snapshot"], + ) + expr = (p * w).sum() + lhs.append(expr) + + # storage units (non-cyclic): subtract end SoC and add initial SoC as constant + cond = "carrier == @glc.carrier_attribute and not cyclic_state_of_charge and active" + sus = n.c.storage_units.static.query(cond) + + if not sus.empty: + sus = sus.loc[scenario] + soc = m["StorageUnit-state_of_charge"].sel( + name=sus.index, snapshot=sns[sns_sel] + ) + + if n._multi_invest: + sus_continuous = sus.query("not state_of_charge_initial_per_period") + if not sus_continuous.empty and period_weighting.ne(1).any(): + msg = ( + "Found non-cyclic storage units with " + "continuous depletion over multiple investment periods " + "combined with investment period year weightings != 1. " + "The operational constraint will be inconsistent. " + "Please consider setting `state_of_charge_initial_per_period` to True, " + "using equal period weightings or a cyclic storage unit instead." + ) + raise NotImplementedError(msg) + + if not sus_continuous.empty and period_weighting.eq(1).all(): + soc_final = ( + soc.sel(name=sus_continuous.index) + .ffill("snapshot") + .isel(snapshot=-1) + ) + if n.has_scenarios: + soc_final = soc_final.sel(scenario=scenario, drop=True) + lhs.append( + (-soc_final).sum() + + sus_continuous.state_of_charge_initial.sum() + ) + + sus_per_period = sus.query("state_of_charge_initial_per_period") + if not sus_per_period.empty: + soc_final = soc.loc[period_last_sns, sus_per_period.index] + if n.has_scenarios: + soc_final = soc_final.sel(scenario=scenario, drop=True) + soc_delta = -soc_final + sus_per_period.state_of_charge_initial + lhs.append((soc_delta * storage_weightings).sum()) + + else: + soc_final = soc.ffill("snapshot").isel(snapshot=-1) + if n.has_scenarios: + soc_final = soc_final.sel(scenario=scenario, drop=True) + lhs.append((-soc_final).sum() + sus.state_of_charge_initial.sum()) + + # stores (non-cyclic): subtract end e and add initial e as constant + stores = n.c.stores.static.query( + "carrier == @glc.carrier_attribute and not e_cyclic and active" + ) + if not stores.empty: + stores = stores.loc[scenario] + e = m["Store-e"].sel(name=stores.index, snapshot=sns[sns_sel]) + + if n._multi_invest: + stores_continuous = stores.query("not e_initial_per_period") + if not stores_continuous.empty and period_weighting.ne(1).any(): + msg = ( + "Found non-cyclic stores with " + "continuous depletion over multiple investment periods " + "combined with investment period year weightings != 1. " + "The primary energy constraint will be inconsistent. " + "Please consider setting `e_initial_per_period` to True, " + "using equal period weightings or a cyclic store instead." + ) + raise NotImplementedError(msg) + + if not stores_continuous.empty and period_weighting.eq(1).all(): + e_final = ( + e.sel(name=stores_continuous.index) + .ffill("snapshot") + .isel(snapshot=-1) + ) + if n.has_scenarios: + e_final = e_final.sel(scenario=scenario, drop=True) + lhs.append((-e_final).sum() + stores_continuous.e_initial.sum()) + + stores_per_period = stores.query("e_initial_per_period") + if not stores_per_period.empty: + e_final = e.loc[period_last_sns, stores_per_period.index] + if n.has_scenarios: + e_final = e_final.sel(scenario=scenario, drop=True) + e_delta = -e_final + stores_per_period.e_initial + lhs.append((e_delta * storage_weightings).sum()) + + else: + e_final = e.ffill("snapshot").isel(snapshot=-1) + if n.has_scenarios: + e_final = e_final.sel(scenario=scenario, drop=True) + lhs.append((-e_final).sum() + stores.e_initial.sum()) + + if not lhs: + continue + + lhs = merge(lhs) + expressions.append(lhs) + + if not expressions: + continue + + if n.has_scenarios: + expression = merge(expressions, dim="scenario").assign_coords( + scenario=scenarios + ) + else: + expression = expressions[0] + + m.add_constraints( + expression, + glc_group.sense, + glc_group.constant, + name=f"GlobalConstraint-{name}", + ) + + +def define_transmission_volume_expansion_limit(n: Network, sns: Sequence) -> None: + """Set a limit for line volume expansion. + + For the capacity expansion only the carriers 'AC' and 'DC' are considered. + + Parameters + ---------- + n : pypsa.Network + The network to apply constraints to. + sns : list-like + Set of snapshots to which the constraint should be applied. + + """ + m = n.model + glcs = n.c.global_constraints.static.query( + "type == 'transmission_volume_expansion_limit'" + ) + + def substr(s: str) -> str: + return re.sub("[\\[\\]\\(\\)]", "", s) + + # Create one constraint per name, optionally with a scenario dimension + if glcs.empty: + return + + unique_names = ( + glcs.index.unique("name") + if isinstance(glcs.index, pd.MultiIndex) + else glcs.index.unique() + ) + + for name in unique_names: + if n.has_scenarios: + glc_group = glcs.xs(name, level="name") + scenarios = glc_group.index.get_level_values("scenario") + else: + glc_group = glcs.loc[name] + scenarios = [slice(None)] + + expressions = [] + for scenario in scenarios: + glc = glc_group.loc[scenario] + + lhs = [] + # fmt: off + car = [substr(c.strip()) for c in # noqa: F841 + glc.carrier_attribute.split(",")] + # fmt: on + period = glc.investment_period + + for c in n.components[["Line", "Link"]]: + attr = nominal_attrs[c.name] + + # Start from extendable components by name + ext_all = c.extendables.difference(c.inactive_assets) + if ext_all.empty: + continue + + # Filter by carrier, handling scenarios (MultiIndex) if present + if n.has_scenarios and isinstance(c.static.index, pd.MultiIndex): + eligible_by_carrier = ( + c.static.query("carrier in @car") + .groupby(level="name") + .first() + .index + ) + else: + eligible_by_carrier = c.static.query("carrier in @car").index + + ext_i = ext_all.intersection(eligible_by_carrier).rename(ext_all.name) + if ext_i.empty: + continue + + # Filter by investment period activity + if not isnan(period): + active = c.get_active_assets(investment_period=int(period)) + ext_i = ext_i[active.loc[ext_i]].rename(ext_i.name) + elif isinstance(sns, pd.MultiIndex): + # Active in any of the periods present in sns + periods = sns.unique("period") + active_df = pd.concat( + { + p: c.get_active_assets(investment_period=int(p)) + for p in periods + }, + axis=1, + ) + active_any = active_df.any(axis=1) + ext_i = ext_i[active_any.loc[ext_i]].rename(ext_i.name) + + if ext_i.empty: + continue + + # Length per name (collapse scenario level if present) + if n.has_scenarios and isinstance(c.static.index, pd.MultiIndex): + length = ( + c.static.length.groupby(level="name").first().reindex(ext_i) + ) + else: + length = c.static.length.reindex(ext_i) + + vars = m[f"{c.name}-{attr}"].loc[ext_i] + lhs.append(m.linexpr((length, vars)).sum()) + + if not lhs: + continue + + expr = merge(lhs) + expressions.append(expr) + + if not expressions: + continue + + if n.has_scenarios: + expression = merge(expressions, dim="scenario").assign_coords( + scenario=scenarios + ) + else: + expression = expressions[0] + + sign = glc_group.sense + rhs = glc_group.constant + + m.add_constraints(expression, sign, rhs, name=f"GlobalConstraint-{name}") + + +def define_transmission_expansion_cost_limit(n: Network, sns: pd.Index) -> None: + """Set a limit for line expansion costs. + + For the capacity expansion only the carriers 'AC' and 'DC' are considered. + + Parameters + ---------- + n : pypsa.Network + The network to apply constraints to. + sns : list-like + Set of snapshots to which the constraint should be applied. + + """ + m = n.model + glcs = n.c.global_constraints.static.query( + "type == 'transmission_expansion_cost_limit'" + ) + + if n._multi_invest: + periods = sns.unique("period") + period_weighting = n.investment_period_weightings.objective[periods] + + def substr(s: str) -> str: + return re.sub("[\\[\\]\\(\\)]", "", s) + + for name, glc in glcs.iterrows(): + lhs = [] + # fmt: off + car = [substr(c.strip()) for c in # noqa: F841 + glc.carrier_attribute.split(",")] + # fmt: on + period = glc.investment_period + + for c in n.components[["Line", "Link"]]: + attr = nominal_attrs[c.name] + + ext_i = c.extendables.difference(c.inactive_assets) + if ext_i.empty: + continue + + ext_i = ext_i.intersection(c.static.query("carrier in @car").index).rename( + ext_i.name + ) + + if not isnan(period): + ext_i = ext_i[ + c.get_active_assets(investment_period=period)[ext_i] + ].rename(ext_i.name) + weights = 1 + + elif isinstance(sns, pd.MultiIndex): + ext_i = ext_i[ + c.get_active_assets(investment_period=sns.unique("period"))[ext_i] + ].rename(ext_i.name) + active = pd.concat( + { + period: c.get_active_assets(investment_period=period)[ext_i] + for period in sns.unique("period") + }, + axis=1, + ) + weights = active @ period_weighting + else: + weights = 1 + + cost = c.static.capital_cost.reindex(ext_i) * weights + vars = m[f"{c.name}-{attr}"].loc[ext_i] + lhs.append(m.linexpr((cost, vars)).sum()) + + if not lhs: + continue + + lhs = merge(lhs) + sign = "=" if glc.sense == "==" else glc.sense + m.add_constraints(lhs, sign, glc.constant, name=f"GlobalConstraint-{name}") diff --git a/PyPSA/source/pypsa/optimization/mga.py b/PyPSA/source/pypsa/optimization/mga.py new file mode 100644 index 0000000000000000000000000000000000000000..b0e59f244704a3aaaf78331a71bc5e6b200a1b5d --- /dev/null +++ b/PyPSA/source/pypsa/optimization/mga.py @@ -0,0 +1,736 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Run modelling-to-generate-alternatives (MGA) optimizations.""" + +from __future__ import annotations + +import logging +import signal +import tempfile +from multiprocessing import get_context +from pathlib import Path +from typing import TYPE_CHECKING, Any + +import linopy +import numpy as np +import pandas as pd +from linopy import LinearExpression, QuadraticExpression, merge +from scipy.stats.qmc import Halton + +from pypsa._options import options +from pypsa.descriptors import nominal_attrs + +if TYPE_CHECKING: + from collections.abc import Sequence + + from pypsa import Network +logger = logging.getLogger(__name__) + + +def generate_directions_random( + keys: Sequence[str], + n_directions: int, + seed: int | None = None, +) -> pd.DataFrame: + """Generate random directions for MGA in a given low-dimensional space. + + The directions are normalized to unit vectors. + + Parameters + ---------- + keys : Sequence[str] + A sequence of strings representing the keys (dimensions) for which to + generate random directions. + n_directions : int + The number of random directions to generate. + seed : int | None, optional + A seed for the random number generator to ensure reproducibility. + If None, a random seed will be used. Defaults to None. + + Returns + ------- + pd.DataFrame + A DataFrame containing the generated random directions, where each row + represents a direction and each column corresponds to a key from `keys`. + + """ + # Use numpy with random seed 0 to generate a random array with + # `len(keys)` columns and `n_directions` rows. + directions = ( + np.random.RandomState(seed).uniform(size=(n_directions, len(keys))) - 0.5 + ) + # Normalize lengths of row vectors to 1 + directions = directions / np.linalg.norm(directions, axis=1, keepdims=True) + # Return as dataframe with keys as columns + return pd.DataFrame(directions, columns=keys) + + +def generate_directions_evenly_spaced( + keys: Sequence[str], + n_directions: int, +) -> pd.DataFrame: + """Generate evenly spaced directions in a 2D space. + + This function generates directions that are uniformly distributed on a unit circle. + It only supports exactly two keys (dimensions). + + Parameters + ---------- + keys : Sequence[str] + A sequence of exactly two strings representing the keys (dimensions) for which to + generate evenly spaced directions. + n_directions : int + The number of evenly spaced directions to generate. + + Returns + ------- + pd.DataFrame + A DataFrame containing the generated evenly spaced directions, where each row + represents a direction and each column corresponds to a key from `keys`. + + See Also + -------- + [pypsa.optimization.mga.generate_directions_random][] + + """ + # Check that there are exactly two keys + if len(keys) != 2: + msg = "This function only supports two keys for 2D space." + raise ValueError(msg) + # Generate evenly spaced directions in 2D space + angles = np.linspace(0, 2 * np.pi, n_directions, endpoint=False) + directions = np.column_stack((np.cos(angles), np.sin(angles))) + return pd.DataFrame(directions, columns=keys) + + +def generate_directions_halton( + keys: Sequence[str], + n_directions: int, + seed: int | None = None, +) -> pd.DataFrame: + """Generate directions using a Halton sequence for MGA in a given low-dimensional space. + + The directions are normalized to unit vectors, providing a quasi-random distribution + that tends to fill the space more uniformly than a purely random sequence. + + Parameters + ---------- + keys : Sequence[str] + A sequence of strings representing the keys (dimensions) for which to + generate directions. + n_directions : int + The number of directions to generate. + seed : int | None, optional + A seed for the random number generator to ensure reproducibility when + initializing the Halton sampler. If None, a random seed will be used. + Defaults to None. + + Returns + ------- + pd.DataFrame + A DataFrame containing the generated directions, where each row + represents a direction and each column corresponds to a key from `keys`. + + See Also + -------- + [pypsa.optimization.mga.generate_directions_random][] + + """ + n = len(keys) + halton_sampler = Halton(n, rng=np.random.default_rng(seed)) + directions: list[np.ndarray] = [] + while len(directions) < n_directions: + # Sample a point, then transform from unit cube to cube around origin. + d = 2 * halton_sampler.random(1) - 1 + # Only take points within the unit hypersphere in order to + # get a uniform distribution. + if np.linalg.norm(d) <= 1: + # Scale to lie on the unit hypersphere. + directions.append((d / np.linalg.norm(d)).flatten()) + # Return as dataframe with keys as columns + return pd.DataFrame(directions, columns=keys) + + +def _convert_to_dict(obj: Any) -> Any: + if isinstance(obj, pd.DataFrame): + return obj.to_dict(orient="list") + if isinstance(obj, pd.Series): + return obj.to_dict() + if isinstance(obj, dict): + return {k: _convert_to_dict(v) for k, v in obj.items()} + return obj + + +def _worker_init() -> None: + """Initialize worker processes with proper signal handling.""" + # Ignore SIGINT in worker processes (let parent handle it) + signal.signal(signal.SIGINT, signal.SIG_IGN) + + logging.basicConfig( + level=logging.WARNING, # Reduce noise from workers + format="[Worker %(process)d] %(levelname)s: %(message)s", + ) + + +class OptimizationAbstractMGAMixin: + """Mixin class for MGA (Models to Generate Alternatives) optimization. + + Class inherits to [pypsa.optimization.OptimizationAccessor][]. All attributes and + methods can be used within any Network instance via `n.optimize`. + + """ + + _n: Network + + def build_linexpr_from_weights( + self, + weights: dict, + model: linopy.Model | None = None, + ) -> LinearExpression: + """Build a linopy LinearExpression from the given weights. + + Parameters + ---------- + weights : dict + A dictionary specifying the weights for different components and + their attributes. The structure should be + `{'component_name': {'attribute_name': coefficients}}`. + Coefficients can be a float, `pd.Series`, or `pd.DataFrame`. + model : linopy.Model | None, optional + The linopy model to use. If None, `self._n.model` is used. + Defaults to None. + + Returns + ------- + linopy.LinearExpression + A linear expression built according to the specified weights. + + """ + m = model or self._n.model + expr = [] + for c, attrs in weights.items(): + for attr, coeffs in attrs.items(): + if isinstance(coeffs, dict): + coeffs = pd.Series(coeffs) + if attr == nominal_attrs[c] and isinstance(coeffs, pd.Series): + coeffs = coeffs.reindex(self._n.c[c].extendables, fill_value=0) + coeffs.index.name = "" + elif isinstance(coeffs, pd.Series): + coeffs = coeffs.reindex(columns=self._n.c[c].static.index) + elif isinstance(coeffs, pd.DataFrame): + coeffs = coeffs.reindex( + columns=self._n.c[c].static.index, index=self._n.snapshots + ) + expr.append(m[f"{c}-{attr}"] * coeffs) + return merge(expr) + + def _add_near_opt_constraint( + self, + multi_investment_periods: bool = False, + slack: float = 0.05, + ) -> None: + """Add a near-optimal cost constraint to the linopy model. + + This constraint ensures that the total cost of the solution remains within + a certain slack of the previously calculated optimal cost. + + Parameters + ---------- + multi_investment_periods : bool, default False + Whether the optimization considers multiple investment periods. + If True, investment period weightings are applied to cost calculations. + slack : float, default 0.05 + The percentage by which the total cost is allowed to exceed the + optimal cost. For example, a slack of 0.05 means the total cost + must be <= 1.05 * optimal_cost. + + """ + # Check that the network has a model and that it has been solved + n = self._n + if not n.is_solved: + msg = "Network needs to be solved with `n.optimize()` before adding near-optimal constraint." + raise ValueError(msg) + # Find optimal costs and fixed costs + if not multi_investment_periods: + optimal_cost = n.statistics.capex().sum() + n.statistics.opex().sum() + fixed_cost = n.statistics.installed_capex().sum() + else: + w = n.investment_period_weightings.objective + optimal_cost = ( + n.statistics.capex().sum() * w + n.statistics.opex().sum() * w + ).sum() + fixed_cost = (n.statistics.installed_capex().sum() * w).sum() + + # Add constraint + objective = n.model.objective + if not isinstance(objective, (LinearExpression | QuadraticExpression)): + objective = objective.expression + + n.model.add_constraints( + objective + fixed_cost <= (1 + slack) * optimal_cost, name="budget" + ) + + def optimize_mga( + self, + snapshots: Sequence | None = None, + multi_investment_periods: bool = False, + weights: dict | None = None, + sense: str | int = "min", + slack: float = 0.05, + model_kwargs: dict | None = None, + **kwargs: Any, + ) -> tuple[str, str]: + """Run modelling-to-generate-alternatives (MGA) on network to find near-optimal solutions. + + Parameters + ---------- + snapshots : list-like + Set of snapshots to consider in the optimization. The default is None. + multi_investment_periods : bool, default False + Whether to optimise as a single investment period or to optimize in + multiple investment periods. Then, snapshots should be a + `pd.MultiIndex`. + weights : dict-like + Weights for alternate objective function. The default is None, which + minimizes generation capacity. The weights dictionary should be keyed + with the component and variable (see `pypsa/data/variables.csv`), followed + by a float, dict, pd.Series or pd.DataFrame for the coefficients of the + objective function. + sense : str|int + Optimization sense of alternate objective function. Defaults to 'min'. + Can also be 'max'. + slack : float + Cost slack for budget constraint. Defaults to 0.05. + model_kwargs : dict, optional + Keyword arguments used by `linopy.Model`, such as `solver_dir` or `chunk`. + Defaults to module wide option (default: {}). See + `https://`go.pypsa.org/options-params` for more information. + **kwargs: + Keyword argument used by `linopy.Model.solve`, such as `solver_name`, + + Returns + ------- + status : str + The status of the optimization, either "ok" or one of the codes listed + in [linopy.constants.SolverStatus](https://linopy.readthedocs.io/en/latest/generated/linopy.constants.SolverStatus.html). + condition : str + The termination condition of the optimization, either + "optimal" or one of the codes listed in + [linopy.constants.TerminationCondition](https://linopy.readthedocs.io/en/latest/generated/linopy.constants.TerminationCondition.html) + + """ + # Handle default parameters from options + if model_kwargs is None: + model_kwargs = options.params.optimize.model_kwargs.copy() + + n = self._n + + if snapshots is None: + snapshots = n.snapshots + + if weights is None: + weights = { + "Generator": {"p_nom": pd.Series(1, index=n.c.generators.static.index)} + } + + # check that network has been solved + if not self._n.is_solved: + msg = "Network needs to be solved with `n.optimize()` before running MGA." + raise ValueError(msg) + + # create basic model + m = n.optimize.create_model( + snapshots=snapshots, + multi_investment_periods=multi_investment_periods, + **model_kwargs, + ) + + # add budget constraint + self._n.optimize._add_near_opt_constraint(multi_investment_periods, slack) + + # parse optimization sense + if ( + isinstance(sense, str) + and sense.startswith("min") + or isinstance(sense, int) + and sense > 0 + ): + sense = 1 + elif ( + isinstance(sense, str) + and sense.startswith("max") + or isinstance(sense, int) + and sense < 0 + ): + sense = -1 + else: + msg = f"Could not parse optimization sense {sense}" + raise ValueError(msg) + + # build alternate objective + m.objective = self.build_linexpr_from_weights(weights, model=m) * sense + + status, condition = self._n.optimize.solve_model(**kwargs) + + # write MGA coefficients into metadata + n.meta["slack"] = slack + n.meta["sense"] = sense + n.meta["weights"] = _convert_to_dict(weights) + + return status, condition + + def project_solved( + self, + dimensions: dict, + ) -> pd.Series: + """Project solved model onto low-dimensional space. + + Parameters + ---------- + dimensions : dict + A dictionary representing the dimensions of the + low-dimensional space. The keys are user-defined names for + the dimensions (matching those in the `direction` + argument), and the values are dictionaries with the same + structure as the `weights` argument in `optimize_mga`. + + Returns + ------- + pd.Series + A pd.Series representing the coordinates of a solved + network in the dimensions given by the user. The index + consists of the keys in the `dimensions` argument; values + are floats. + + """ + # Check that the network has a solved linopy model + if not self._n.is_solved: + msg = "Network needs to be solved with `n.optimize()` before projecting result." + raise ValueError(msg) + # Build linear expressions and evaluate them + return pd.Series( + { + key: float( + self.build_linexpr_from_weights( + dim, model=self._n.model + ).solution.sum() + ) + for key, dim in dimensions.items() + } + ) + + def optimize_mga_in_direction( + self, + direction: dict | pd.Series, + dimensions: dict, + snapshots: Sequence | None = None, + multi_investment_periods: bool = False, + slack: float = 0.05, + model_kwargs: dict | None = None, + **kwargs: Any, + ) -> tuple[str, str, pd.Series | None]: + """Run MGA in a given direction in a low-dimension projection. + + Parameters + ---------- + direction : dict | pd.Series + A dictionary or pd.Series representing the direction in the low-dimensional space. + The keys or index are user-defined names for the dimensions, and the values are vector coordinates. + dimensions : dict + A dictionary representing the dimensions of the + low-dimensional space. The keys are user-defined names for + the dimensions (matching those in the `direction` + argument), and the values are dictionaries with the same + structure as the `weights` argument in `optimize_mga`. + snapshots : Sequence | None, optional + Set of snapshots to consider in the optimization. If None, uses all + snapshots from the network. Defaults to None. + multi_investment_periods : bool, default False + Whether to optimise as a single investment period or to optimize in + multiple investment periods. Then, snapshots should be a + `pd.MultiIndex`. + slack : float + Cost slack for budget constraint. Defaults to 0.05. + model_kwargs : dict, optional + Keyword arguments used by `linopy.Model`, such as `solver_dir` or `chunk`. + Defaults to module wide option (default: {}). See + `https://`go.pypsa.org/options-params` for more information. + **kwargs: + Keyword argument used by `linopy.Model.solve`, such as `solver_name`, + + Returns + ------- + status : str + The status of the optimization, either "ok" or one of the codes listed + in [linopy.constants.SolverStatus](https://linopy.readthedocs.io/en/latest/generated/linopy.constants.SolverStatus.html). + condition : str + The termination condition of the optimization, either + "optimal" or one of the codes listed in + [linopy.constants.TerminationCondition](https://linopy.readthedocs.io/en/latest/generated/linopy.constants.TerminationCondition.html) + coordinates : pd.Series | None + If the optimization status is "ok", then the final return + value is a pd.Series representing the coordinates of the + solved network in dimensions given by the user. The index + consists of the keys in the `dimensions` argument; values + are floats. If the optimization status is not "ok", then + this value is None. + + """ + # Handle default parameters from options + if model_kwargs is None: + model_kwargs = options.params.optimize.model_kwargs.copy() + + # Check consistency of `direction` and `dimensions` arguments: keys have to be the same + if set(direction.keys()) != set(dimensions.keys()): + msg = ( + "Keys of `direction` and `dimensions` arguments must match. " + f"Got {set(direction.keys())} and {set(dimensions.keys())}." + ) + raise ValueError(msg) + + if snapshots is None: + snapshots = self._n.snapshots + + # check that network has been solved + if not self._n.is_solved: + msg = "Network needs to be solved with `n.optimize()` before running MGA." + raise ValueError(msg) + + # create basic model + m = self._n.optimize.create_model( + snapshots=snapshots, + multi_investment_periods=multi_investment_periods, + **model_kwargs, + ) + + # build budget constraint + self._n.optimize._add_near_opt_constraint(multi_investment_periods, slack) + + # Build objective as linear combination of direction and + # dimensions. Flip the sign in order to maximize in the given + # direction. + m.objective = -sum( + direction[key] * self.build_linexpr_from_weights(dimensions[key], model=m) + for key in direction.keys() + ) + + status, condition = self._n.optimize.solve_model(**kwargs) + coordinates = self.project_solved(dimensions) if status == "ok" else None + + # write MGA coefficients into metadata + self._n.meta["slack"] = slack + self._n.meta["dimensions"] = _convert_to_dict(dimensions) + self._n.meta["direction"] = direction + + return status, condition, coordinates + + @staticmethod + def _solve_single_direction( + fn: str, + direction: dict, + dimensions: dict, + snapshots: Sequence, + multi_investment_periods: bool, + slack: float, + model_kwargs: dict, + kwargs: dict, + ) -> tuple[dict, pd.Series | None]: + """Solve a single direction for parallel execution (helper method). + + This wrapper is necessary since the network is read from a file in + this case; also simplifies the return argument management. + + """ + from pypsa.networks import Network # noqa: PLC0415 + + try: + n = Network(fn) + _, _, coordinates = n.optimize.optimize_mga_in_direction( + direction=direction, + dimensions=dimensions, + snapshots=snapshots, + multi_investment_periods=multi_investment_periods, + slack=slack, + model_kwargs=model_kwargs, + **kwargs, + ) + except KeyboardInterrupt: + # Handle interruption gracefully + logger.info("Worker process interrupted") + return (direction, None) + except Exception as e: + # Log error but don't crash the worker + logger.warning( + "Error solving in direction", + extra={"direction": direction, "error": str(e)}, + ) + return (direction, None) + else: + return (direction, coordinates) + + def optimize_mga_in_multiple_directions( + self, + directions: list[dict] | pd.DataFrame, + dimensions: dict, + snapshots: Sequence | None = None, + multi_investment_periods: bool = False, + slack: float = 0.05, + model_kwargs: dict | None = None, + max_parallel: int = 4, + **kwargs: Any, + ) -> tuple[pd.DataFrame, pd.DataFrame]: + """Run MGA optimization in multiple directions in parallel. + + This method performs modelling-to-generate-alternatives (MGA) optimization + across multiple directions simultaneously using parallel processing. Each + direction represents a different objective in the low-dimensional projection + space defined by the dimensions parameter. + + Note that, in order to achieve parallelism, this method exports the network + to a temporary NetCDF file which is then re-imported in each parallel process. + This leads to a slight overhead in IO and disk space. The temporary file is + always cleaned up after the optimization is complete, regardless of whether + any errors occurred during the optimization. + + Parameters + ---------- + directions : list[dict] | pd.DataFrame + Multiple directions in the low-dimensional space. If a list, each element + should be a dictionary with keys matching those in `dimensions` and values + representing vector coordinates. If a DataFrame, rows represent directions + and columns represent dimension names. + dimensions : dict + A dictionary representing the dimensions of the low-dimensional space. + The keys are user-defined names for the dimensions (matching those in the + `directions` argument), and the values are dictionaries with the same + structure as the `weights` argument in `optimize_mga`. + snapshots : Sequence | None, optional + Set of snapshots to consider in the optimization. If None, uses all + snapshots from the network. Defaults to None. + multi_investment_periods : bool, default False + Whether to optimize as a single investment period or to optimize in + multiple investment periods. Then, snapshots should be a `pd.MultiIndex`. + slack : float + Cost slack for budget constraint. Defaults to 0.05. + model_kwargs : dict, optional + Keyword arguments used by `linopy.Model`, such as `solver_dir` or `chunk`. + Defaults to module wide option (default: {}). See + `https://go.pypsa.org/options-params` for more information. + max_parallel : int + Maximum number of parallel processes to use for solving multiple directions. + Defaults to 4. + **kwargs: + Keyword argument used by `linopy.Model.solve`, such as `solver_name`, + + Returns + ------- + directions_df : pd.DataFrame + DataFrame containing the successfully solved directions, where each row + represents a direction and columns correspond to dimension names. + coordinates_df : pd.DataFrame + DataFrame containing the coordinates of each successfully solved network + in the user-defined dimensions. Rows correspond to solved directions + and columns to dimension names. + + Examples + -------- + >>> dimensions = { + ... "wind": {"Generator": {"p_nom": {"wind": 1}}}, + ... "solar": {"Generator": {"p_nom": {"solar": 1}}} + ... } + >>> directions = pypsa.optimization.mga.generate_directions_random(["wind", "solar"], 10) + >>> dirs_df, coords_df = n.optimize.optimize_mga_in_multiple_directions( + ... directions, dimensions, max_parallel=2 + ... ) + >>> dirs_df # doctest: +SKIP + wind solar + 0 0.958766 0.284198 + 1 -0.937432 -0.348170 + 2 -0.805652 0.592389 + ... + >>> coords_df # doctest: +ELLIPSIS + wind solar + 0 0.0 0.0 + 1 0.0 0.0 + 2 0.0 0.0 + ... + + """ + # Handle default parameters from options + if model_kwargs is None: + model_kwargs = options.params.optimize.model_kwargs.copy() + + # Iterate over rows of `directions` if a DataFrame + if isinstance(directions, pd.DataFrame): + directions = list(directions.T.to_dict().values()) + + # Create temporary file to export the network. Note: cannot pass + # the network as an argument directly since it is not picklable. + with tempfile.NamedTemporaryFile(suffix=".nc", delete=False) as f: + fn = f.name + + # Wrap in try-finally to ensure the temporary file is deleted + # even if an error occurs + try: + self._n.export_to_netcdf(fn) + + # Use a process pool to solve in parallel + with ( + get_context("spawn").Pool( + processes=max_parallel, + initializer=_worker_init, + maxtasksperchild=1, # Kill workers after each task to prevent memory leaks + ) as pool + ): + try: + results = pool.starmap( + OptimizationAbstractMGAMixin._solve_single_direction, + [ + ( + fn, + direction, + dimensions, + snapshots, + multi_investment_periods, + slack, + model_kwargs, + kwargs, + ) + for direction in directions + ], + ) + except Exception: + # Terminate all workers if something goes wrong + pool.terminate() + pool.join() + raise + + # Separate successful and failed results + successful = [ + (direction, coords) + for direction, coords in results + if coords is not None + ] + failed_count = len(results) - len(successful) + + if failed_count > 0: + logger.warning( + "%s out of %s optimizations failed", failed_count, len(results) + ) + + if not successful: + return pd.DataFrame(), pd.DataFrame() + + successful_directions, successful_coordinates = zip( + *successful, strict=True + ) + return ( + pd.DataFrame(successful_directions), + pd.DataFrame(successful_coordinates), + ) + finally: + # Clean up temporary file + if Path(fn).exists(): + Path(fn).unlink() diff --git a/PyPSA/source/pypsa/optimization/optimize.py b/PyPSA/source/pypsa/optimization/optimize.py new file mode 100644 index 0000000000000000000000000000000000000000..34d75eb7efce39a11f6600ca43e33e7311d02b94 --- /dev/null +++ b/PyPSA/source/pypsa/optimization/optimize.py @@ -0,0 +1,1032 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Build optimisation problems from PyPSA networks with Linopy.""" + +from __future__ import annotations + +import logging +from pathlib import Path +from typing import TYPE_CHECKING, Any + +import numpy as np +import pandas as pd +import xarray as xr +from linopy import Model, merge +from linopy.solvers import available_solvers + +from pypsa._options import options +from pypsa.common import UnexpectedError, as_index +from pypsa.components.array import _from_xarray +from pypsa.components.common import as_components +from pypsa.descriptors import nominal_attrs +from pypsa.guards import _assert_data_integrity +from pypsa.optimization.abstract import OptimizationAbstractMixin +from pypsa.optimization.common import _set_dynamic_data, get_strongly_meshed_buses +from pypsa.optimization.constraints import ( + define_fixed_nominal_constraints, + define_fixed_operation_constraints, + define_kirchhoff_voltage_constraints, + define_loss_constraints, + define_modular_constraints, + define_nodal_balance_constraints, + define_nominal_constraints_for_extendables, + define_operational_constraints_for_committables, + define_operational_constraints_for_extendables, + define_operational_constraints_for_non_extendables, + define_ramp_limit_constraints, + define_storage_unit_constraints, + define_store_constraints, + define_total_supply_constraints, +) +from pypsa.optimization.expressions import StatisticExpressionsAccessor +from pypsa.optimization.global_constraints import ( + define_growth_limit, + define_nominal_constraints_per_bus_carrier, + define_operational_limit, + define_primary_energy_limit, + define_tech_capacity_expansion_limit, + define_transmission_expansion_cost_limit, + define_transmission_volume_expansion_limit, +) +from pypsa.optimization.variables import ( + define_cvar_variables, + define_loss_variables, + define_modular_variables, + define_nominal_variables, + define_operational_variables, + define_shut_down_variables, + define_spillage_variables, + define_start_up_variables, + define_status_variables, +) + +if TYPE_CHECKING: + from collections.abc import Callable, Sequence + + from pypsa import Network, SubNetwork +logger = logging.getLogger(__name__) + + +lookup = pd.read_csv( + Path(__file__).parent / ".." / "data" / "variables.csv", + index_col=["component", "variable"], +) + + +def define_objective(n: Network, sns: pd.Index) -> None: + """Define and write the optimization objective function. + + Builds the (linear or quadratic) objective by assembling the following terms: + + 1. **Constant term** for already-built capacity + Calculates capex of existing assets and stores it in `n.objective_constant`. + 2. **Operating costs** + Marginal generation costs, storage operation costs, and spill costs weighted by snapshot durations. + 3. **Quadratic costs** + If present, adds second-order marginal cost terms to convex quadratic objective. + 4. **Stand-by costs** + Fixed costs for committed assets (e.g. generators and links) when online. + 5. **Investment costs** + Capex for new capacity, weighted by investment periods if `n._multi_invest` is True. + 6. **Unit-commitment costs** + Start-up and shut-down costs for committable components. + 7. **Conditional CVaR terms** + Define auxiliary CVaR constraints for stochastic risk-averse optimization. + + Parameters + ---------- + n : pypsa.Network + Network instance containing the Linopy model and component data. + sns : pandas.Index + Snapshots (and, for multi-investment, periods) over which to build the objective. + + Returns + ------- + None + + Notes + ----- + - The final objective expression is assigned to `n.model.objective`. + - Applies snapshot and investment-period weightings to operational and capex terms. + - For a stochastic problem, scenario probabilities are applied as weightings to all cost (includes *both* investment terms). + + """ + weighted_cost: xr.DataArray | int + m = n.model + # Separate lists to distinguish CAPEX and OPEX terms + capex_terms = [] + opex_terms = [] + is_quadratic = False + + if n._multi_invest: + periods = sns.unique("period") + period_weighting = n.investment_period_weightings.objective[periods] + + # constant for already done investment + nom_attr = nominal_attrs.items() + constant: xr.DataArray | float = 0 + terms = [] + + for c_name, attr in nom_attr: + c = as_components(n, c_name) + ext_i = c.extendables.difference(c.inactive_assets) + + if ext_i.empty: + continue + + capital_cost = c.da.capital_cost.sel(name=ext_i) + if capital_cost.size == 0: + continue + + nominal = c.da[attr].sel(name=ext_i) + + # only charge capex for already-existing assets + if n._multi_invest: + weighted_cost = 0 + for period in periods: + # collapse time axis via any() so capex value isn't broadcasted + active = c.da.active.sel(period=period, name=ext_i).any(dim="timestep") + weighted_cost += capital_cost * active * period_weighting.loc[period] + else: + # collapse time axis via any() so capex value isn’t broadcasted + active = c.da.active.sel(name=ext_i).any(dim="snapshot") + weighted_cost = capital_cost * active + + terms.append((weighted_cost * nominal).sum(dim=["name"])) + + constant += sum(terms) + + # Handle constant for stochastic vs deterministic networks + if n.has_scenarios and isinstance(constant, xr.DataArray): + # For stochastic networks, weight constant by scenario probabilities + weighted_constant = sum( + constant.sel(scenario=s) * n.scenario_weightings.loc[s, "weight"] + for s in n.scenarios + ) + n._objective_constant = float(weighted_constant) + has_const = (constant != 0).any().item() + else: + n._objective_constant = float(constant) + has_const = constant != 0 + if has_const: + object_const = m.add_variables(constant, constant, name="objective_constant") + # Treat constant as part of CAPEX block + capex_terms.append(-1 * object_const) + + # Weightings + weighting = n.snapshot_weightings.objective + if n._multi_invest: + weighting = weighting.mul(period_weighting, level=0).loc[sns] + else: + weighting = weighting.loc[sns] + weight = xr.DataArray(weighting.values, coords={"snapshot": sns}, dims=["snapshot"]) + + # marginal costs, marginal storage cost, and spill cost + for cost_type in ["marginal_cost", "marginal_cost_storage", "spill_cost"]: + for c_name, attr in lookup.query(cost_type).index: + c = as_components(n, c_name) + + if c.static.empty: + continue + + var_name = f"{c.name}-{attr}" + if var_name not in m.variables and cost_type == "spill_cost": + continue + + cost = c.da[cost_type].sel(snapshot=sns, name=c.active_assets) + if cost.size == 0 or (cost == 0).all(): + continue + + cost = cost * weight + + operation = m[var_name].sel(snapshot=sns, name=cost.coords["name"].values) + opex_terms.append((operation * cost).sum(dim=["name", "snapshot"])) + + # marginal cost quadratic + for c_name, attr in lookup.query("marginal_cost_quadratic").index: + c = as_components(n, c_name) + + if c.static.empty or "marginal_cost_quadratic" not in c.static.columns: + continue + + cost = c.da.marginal_cost_quadratic.sel(snapshot=sns) + if cost.size == 0 or (cost == 0).all(): + continue + + cost = cost * weight + + operation = m[f"{c.name}-{attr}"].sel( + snapshot=sns, name=cost.coords["name"].values + ) + opex_terms.append((operation * operation * cost).sum(dim=["name", "snapshot"])) + is_quadratic = True + + # stand-by cost + for c_name in ["Generator", "Link"]: + c = as_components(n, c_name) + com_i = c.committables.difference(c.inactive_assets) + + if com_i.empty: + continue + + stand_by_cost = c.da.stand_by_cost.sel(name=com_i, snapshot=sns) + if stand_by_cost.size == 0 or (stand_by_cost == 0).all(): + continue + + stand_by_cost = stand_by_cost * weight + + status = m[f"{c.name}-status"].sel( + snapshot=sns, name=stand_by_cost.coords["name"].values + ) + opex_terms.append((status * stand_by_cost).sum(dim=["name", "snapshot"])) + + # investment + for c_name, attr in nominal_attrs.items(): + c = as_components(n, c_name) + ext_i = c.extendables.difference(c.inactive_assets) + + if ext_i.empty: + continue + + capital_cost = c.da.capital_cost.sel(name=ext_i) + if capital_cost.size == 0 or (capital_cost == 0).all(): + continue + + # only charge capex for already-existing assets + if n._multi_invest: + weighted_cost = 0 + for period in periods: + # collapse time axis via any() so capex value isn't broadcasted + active = c.da.active.sel(period=period, name=ext_i).any(dim="timestep") + weighted_cost += capital_cost * active * period_weighting.loc[period] + else: + # collapse time axis via any() so capex value isn't broadcasted + active = c.da.active.sel(name=ext_i).any(dim="snapshot") + weighted_cost = capital_cost * active + + caps = m[f"{c.name}-{attr}"].sel(name=ext_i) + capex_terms.append((caps * weighted_cost).sum(dim=["name"])) + + # unit commitment + keys = ["start_up", "shut_down"] # noqa: F841 + for c_name, attr in lookup.query("variable in @keys").index: + c = as_components(n, c_name) + com_i = c.committables.difference(c.inactive_assets) + + if com_i.empty: + continue + + cost = c.da[attr + "_cost"].sel(name=com_i) + + if cost.size == 0 or cost.sum().item() == 0: + continue + + var = m[f"{c.name}-{attr}"].sel(name=com_i) + opex_terms.append((var * cost).sum(dim=["name", "snapshot"])) + + if not (capex_terms or opex_terms): + msg = ( + "Objective function could not be created. " + "Please make sure the components have assigned costs." + ) + raise ValueError(msg) + + # Build expected CAPEX and expected OPEX (scenario-weighted if stochastic) + def _expected(exprs: list) -> Any: + if not exprs: + return 0 + if n.has_scenarios: + terms = [] + for s, p in n.scenario_weightings["weight"].items(): + selected = [e.sel(scenario=s) for e in exprs] + # If quadratic terms exist, avoid merge (which is linear-only) and sum instead + merged = sum(selected) if is_quadratic else merge(selected) + terms.append(merged * p) + return sum(terms) if is_quadratic else merge(terms) + return sum(exprs) if is_quadratic else merge(exprs) + + expected_capex = _expected(capex_terms) + expected_opex = _expected(opex_terms) + + # CVaR augmentation if enabled + if n.has_risk_preference: + rp = n.risk_preference + if rp is None: # mypy type guard + msg = "risk_preference is None when has_risk_preference is True" + raise UnexpectedError(msg) + alpha = rp["alpha"] + omega = rp["omega"] + + # Guard: quadratic OPEX would make CVaR constraints quadratic + if is_quadratic: + msg_q = ( + "CVaR with quadratic operational costs yields quadratic constraints. " + "So a(s) >= OPEX(s) - theta becomes a quadratic inequality. " + "Remove/approximate quadratic costs (e.g. set 'marginal_cost_quadratic=0' " + "or use a piecewise-linear approximation)." + ) + raise ValueError(msg_q) + + # Create per-scenario OPEX expressions to use in constraints + scen_opex_exprs: dict[Any, Any] = {} + for s in n.scenarios: + scen_selected = [e.sel(scenario=s) for e in opex_terms] + scen_opex_exprs[s] = ( + sum(scen_selected) if is_quadratic else merge(scen_selected) + ) + + # Retrieve CVaR auxiliary variables + a = m["CVaR-a"] + theta = m["CVaR-theta"] + cvar = m["CVaR"] + + for s in n.scenarios: + lhs = a.sel(scenario=s) - scen_opex_exprs[s] + theta + m.add_constraints(lhs, ">=", 0, name=f"CVaR-excess-{s}") + + inv_tail = 1.0 / (1.0 - alpha) + weighted_a = None + for s, p in n.scenario_weightings["weight"].items(): + term = a.sel(scenario=s) * float(p) + weighted_a = term if weighted_a is None else weighted_a + term + if weighted_a is None: # mypy type guard + msg = "No scenarios found in scenario_weightings" + raise UnexpectedError(msg) + m.add_constraints(theta + inv_tail * weighted_a, "<=", cvar, name="CVaR-def") + + # Final objective: CAPEX + (1-omega) * E[OPEX] + omega * CVaR + obj_expr = expected_capex + (1 - omega) * expected_opex + omega * cvar + else: + # Deterministic or no risk: CAPEX + OPEX + obj_expr = expected_capex + expected_opex + + # Set objective + m.objective = obj_expr + + +class OptimizationAccessor(OptimizationAbstractMixin): + """Optimization accessor for building and solving models using linopy. + + + """ + + def __init__(self, n: Network) -> None: + """Initialize the optimization accessor.""" + self._n = n + self.expressions = StatisticExpressionsAccessor(self._n) + + def __call__( + self, + snapshots: Sequence | None = None, + multi_investment_periods: bool = False, + transmission_losses: int = 0, + linearized_unit_commitment: bool = False, + model_kwargs: dict | None = None, + extra_functionality: Callable | None = None, + assign_all_duals: bool = False, + solver_name: str | None = None, + solver_options: dict | None = None, + compute_infeasibilities: bool = False, + **kwargs: Any, + ) -> tuple[str, str]: + """Optimize the pypsa network using linopy. + + Parameters + ---------- + snapshots : list or index slice + A list of snapshots to optimise, must be a subset of + n.snapshots, defaults to n.snapshots + multi_investment_periods : bool, default False + Whether to optimise as a single investment period or to optimise in multiple + investment periods. Then, snapshots should be a `pd.MultiIndex`. + transmission_losses : int, default 0 + Whether an approximation of transmission losses should be included + in the linearised power flow formulation. A passed number will denote + the number of tangents used for the piecewise linear approximation. + Defaults to 0, which ignores losses. + linearized_unit_commitment : bool, default False + Whether to optimise using the linearised unit commitment formulation or not. + model_kwargs : dict, optional + Keyword arguments used by `linopy.Model`, such as `solver_dir` or `chunk`. + Defaults to module wide option (default: {}). See + `https://go.pypsa.org/options-params` for more information. + extra_functionality : callable + This function must take two arguments + `extra_functionality(n, snapshots)` and is called after + the model building is complete, but before it is sent to the + solver. It allows the user to + add/change constraints and add/change the objective function. + assign_all_duals : bool, default False + Whether to assign all dual values or only those that already + have a designated place in the network. + solver_name : str, optional + Name of the solver to use. Defaults to module wide option + (default: 'highs'). See `https://go.pypsa.org/options-params` for more + information. + solver_options : dict, optional + Keyword arguments used by the solver. Can also be passed via `**kwargs`. + Defaults to module wide option (default: {}). See + `https://go.pypsa.org/options-params` for more information. + compute_infeasibilities : bool, default False + Whether to compute and print Irreducible Inconsistent Subsystem (IIS) in case + of an infeasible solution. Requires Gurobi. + **kwargs: + Keyword argument used by `linopy.Model.solve`, such as `solver_name`, + `problem_fn` or solver options directly passed to the solver. + + Returns + ------- + status : str + The status of the optimization, either "ok" or one of the codes listed + in [linopy.constants.SolverStatus](https://linopy.readthedocs.io/en/latest/generated/linopy.constants.SolverStatus.html) + condition : str + The termination condition of the optimization, either + "optimal" or one of the codes listed in + [linopy.constants.TerminationCondition](https://linopy.readthedocs.io/en/latest/generated/linopy.constants.TerminationCondition.html) + + """ + # Handle default parameters from options + if model_kwargs is None: + model_kwargs = options.params.optimize.model_kwargs.copy() + if solver_name is None: + solver_name = options.params.optimize.solver_name + if solver_options is None: + solver_options = options.params.optimize.solver_options.copy() + + n = self._n + sns = as_index(n, snapshots, "snapshots") + n._multi_invest = int(multi_investment_periods) + n._linearized_uc = linearized_unit_commitment + + n.consistency_check(strict=["unknown_buses"]) + m = n.optimize.create_model( + sns, + multi_investment_periods, + transmission_losses, + linearized_unit_commitment, + consistency_check=False, + **model_kwargs, + ) + if extra_functionality: + extra_functionality(n, sns) + status, condition = m.solve(solver_name=solver_name, **solver_options, **kwargs) + + if status == "ok": + n.optimize.assign_solution() + n.optimize.assign_duals(assign_all_duals) + n.optimize.post_processing() + + if ( + condition == "infeasible" + and compute_infeasibilities + and "gurobi" in available_solvers + ): + n.model.print_infeasibilities() + + return status, condition + + def create_model( + self, + snapshots: Sequence | None = None, + multi_investment_periods: bool = False, + transmission_losses: int = 0, + linearized_unit_commitment: bool = False, + consistency_check: bool = True, + **kwargs: Any, + ) -> Model: + """Create a linopy.Model instance from a pypsa network. + + The model is stored at `n.model`. + + Parameters + ---------- + snapshots : list or index slice + A list of snapshots to optimise, must be a subset of + n.snapshots, defaults to n.snapshots + multi_investment_periods : bool, default: False + Whether to optimise as a single investment period or to optimize in multiple + investment periods. Then, snapshots should be a `pd.MultiIndex`. + transmission_losses : int, default: 0 + Whether an approximation of transmission losses should be included + in the linearised power flow formulation. + linearized_unit_commitment : bool, default: False + Whether to optimise using the linearised unit commitment formulation or not. + consistency_check : bool, default: True + Whether to run the consistency check before building the model. + **kwargs: + Keyword arguments used by `linopy.Model()`, such as `solver_dir` or `chunk`. + + Returns + ------- + linopy.model + + """ + n = self._n + sns = as_index(n, snapshots, "snapshots") + n._linearized_uc = int(linearized_unit_commitment) + n._multi_invest = int(multi_investment_periods) + if consistency_check: + n.consistency_check() + + kwargs.setdefault("force_dim_names", True) + n._model = Model(**kwargs) + n.model.parameters = n.model.parameters.assign(snapshots=sns) + + # Define variables + for c, attr in lookup.query("nominal").index: + define_nominal_variables(n, c, attr) + define_modular_variables(n, c, attr) + + for c, attr in lookup.query("not nominal and not handle_separately").index: + define_operational_variables(n, sns, c, attr) + define_status_variables(n, sns, c, linearized_unit_commitment) + define_start_up_variables(n, sns, c, linearized_unit_commitment) + define_shut_down_variables(n, sns, c, linearized_unit_commitment) + + define_spillage_variables(n, sns) + define_operational_variables(n, sns, "Store", "p") + + # CVaR auxiliary variables (only when stochastic + risk preference is set) + define_cvar_variables(n) + + if transmission_losses: + for c in n.passive_branch_components: + define_loss_variables(n, sns, c) + + # Define constraints + for c, attr in lookup.query("nominal").index: + define_nominal_constraints_for_extendables(n, c, attr) + define_fixed_nominal_constraints(n, c, attr) + define_modular_constraints(n, c, attr) + + for c, attr in lookup.query("not nominal and not handle_separately").index: + define_operational_constraints_for_non_extendables( + n, sns, c, attr, transmission_losses + ) + define_operational_constraints_for_extendables( + n, sns, c, attr, transmission_losses + ) + define_operational_constraints_for_committables(n, sns, c) + define_ramp_limit_constraints(n, sns, c, attr) + define_fixed_operation_constraints(n, sns, c, attr) + + meshed_threshold = kwargs.get("meshed_threshold", 45) + meshed_buses = get_strongly_meshed_buses(n, threshold=meshed_threshold) + + if isinstance(n.c.buses.static.index, pd.MultiIndex): + bus_names = n.c.buses.static.index.get_level_values(1) + weakly_meshed_buses = pd.Index( + [b for b in bus_names if b not in meshed_buses], name="Bus" + ) + else: + weakly_meshed_buses = n.c.buses.static.index.difference(meshed_buses) + + if not meshed_buses.empty and not weakly_meshed_buses.empty: + # Write constraint for buses many terms and for buses with a few terms + # separately. This reduces memory usage for large networks. + define_nodal_balance_constraints( + n, + sns, + transmission_losses=transmission_losses, + buses=weakly_meshed_buses, + ) + define_nodal_balance_constraints( + n, + sns, + transmission_losses=transmission_losses, + buses=meshed_buses, + suffix="-meshed", + ) + else: + define_nodal_balance_constraints( + n, sns, transmission_losses=transmission_losses + ) + + define_kirchhoff_voltage_constraints(n, sns) + define_storage_unit_constraints(n, sns) + define_store_constraints(n, sns) + define_total_supply_constraints(n, sns) + + if transmission_losses: + for c in n.passive_branch_components: + define_loss_constraints(n, sns, c, transmission_losses) + + # Define global constraints + define_primary_energy_limit(n, sns) + define_transmission_expansion_cost_limit(n, sns) + define_transmission_volume_expansion_limit(n, sns) + define_tech_capacity_expansion_limit(n, sns) + define_operational_limit(n, sns) + define_nominal_constraints_per_bus_carrier(n, sns) + define_growth_limit(n, sns) + + define_objective(n, sns) + + return n.model + + def solve_model( + self, + extra_functionality: Callable | None = None, + solver_name: str | None = None, + solver_options: dict | None = None, + assign_all_duals: bool = False, + **kwargs: Any, + ) -> tuple[str, str]: + """Solve an already created model and assign its solution to the network. + + Parameters + ---------- + extra_functionality : callable + This function must take two arguments + `extra_functionality(n, snapshots)` and is called after + the model building is complete, but before it is sent to the + solver. It allows the user to + add/change constraints and add/change the objective function. + solver_name : str | None, default=None + Name of the solver to use. Defaults to module wide option + (default: 'highs'). See `https://go.pypsa.org/options-params` for more + information. + solver_options : dict | None, default=None + Keyword arguments used by the solver. Defaults to module wide option + (default: {}). Can also be passed via `**kwargs`. See + `https://go.pypsa.org/options-params` for more information. + assign_all_duals : bool, default False + Whether to assign all dual values or only those that already + have a designated place in the network. + **kwargs: + Keyword argument used by `linopy.Model.solve`, such as `solver_name`, + `problem_fn` or solver options directly passed to the solver. + + Returns + ------- + status : str + The status of the optimization, either "ok" or one of the + codes listed in + [linopy.constants.SolverStatus](https://linopy.readthedocs.io/en/latest/generated/linopy.constants.SolverStatus.html) + condition : str + The termination condition of the optimization, either + "optimal" or one of the codes listed in + [linopy.constants.TerminationCondition](https://linopy.readthedocs.io/en/latest/generated/linopy.constants.TerminationCondition.html) + + """ + # Handle default parameters from options + if solver_options is None: + solver_options = options.params.optimize.solver_options.copy() + if solver_name is None: + solver_name = options.params.optimize.solver_name + + n = self._n + if extra_functionality: + extra_functionality(n, n.snapshots) + m = n.model + status, condition = m.solve(solver_name=solver_name, **solver_options, **kwargs) + + if status == "ok": + self._n.optimize.assign_solution() + self._n.optimize.assign_duals(assign_all_duals) + self._n.optimize.post_processing() + + # Optional runtime verification + if options.debug.runtime_verification: + _assert_data_integrity(self._n) + + return status, condition + + def assign_solution(self) -> None: + """Map solution to network components.""" + n = self._n + m = n.model + sns = n.model.parameters.snapshots.to_index() + + for name, variable in m.variables.items(): + sol = variable.solution + if name == "objective_constant": + continue + + # Skip auxiliary CVaR variables + if name.startswith("CVaR"): + continue + + # Log variables without component-attribute naming + if "-" not in name: + # Custom variables might not contain a dash + logger.info( + "The variable '%s' could not be mapped to the network component because it does not include the symbol '-'.", + name, + ) + continue + + _c_name, attr = name.split("-", 1) + if not hasattr(n.c, _c_name): + # Custom variables might correspond to a designated component + logger.info( + "The variable '%s' could not be mapped to the network component because the component '%s' does not exist.", + name, + _c_name, + ) + continue + c = n.c[_c_name] + df = _from_xarray(sol, c) + + if "snapshot" in sol.dims: + if c.name in n.passive_branch_components and attr == "s": + _set_dynamic_data(n, c.name, "p0", df) + _set_dynamic_data(n, c.name, "p1", -df) + + elif c.name == "Link" and attr == "p": + _set_dynamic_data(n, c.name, "p0", df) + + for i in ["1"] + n.c.links.additional_ports: + i_eff = "" if i == "1" else i + eff = n.get_switchable_as_dense( + "Link", f"efficiency{i_eff}", sns + ) + _set_dynamic_data(n, c.name, f"p{i}", -df * eff) + c.dynamic[f"p{i}"].loc[ + sns, c.static.index[c.static[f"bus{i}"] == ""] + ] = float(c.defaults.loc[f"p{i}", "default"]) + + else: + _set_dynamic_data(n, c.name, attr, df) + # Ignore `n_mod` + elif attr == "n_mod": + pass + else: + c.static.update(df.rename(attr + "_opt"), overwrite=True) + + # If nominal capacity was no variable set optimal value to nominal + for c_name, attr in lookup.query("nominal").index: + c = n.components[c_name] + fix_i = c.fixed + if n.has_scenarios: + fix_i = pd.MultiIndex.from_product([n.scenarios, fix_i]) + if not fix_i.empty: + c.static.loc[fix_i, f"{attr}_opt"] = c.static.loc[fix_i, attr] + + # Recalculate storageunit net dispatch + storage_units = n.c.storage_units + if not storage_units.empty: + storage_units.dynamic["p"] = ( + storage_units.dynamic["p_dispatch"] - storage_units.dynamic["p_store"] + ) + + n._objective = m.objective.value + + def assign_duals(self, assign_all_duals: bool = False) -> None: + """Map dual values i.e. shadow prices to network components. + + Parameters + ---------- + assign_all_duals : bool, default False + Whether to assign all dual values or only those that already + have a designated place in the network. + + """ + m = self._n.model + unassigned_constraints = [] + + # Early return if no dual values are available + if all("dual" not in constraint for _, constraint in m.constraints.items()): + logger.info("No shadow prices were assigned to the network.") + return + + # Process each constraint and its dual values + for constraint_name, constraint in m.constraints.items(): + # Parse constraint name into component and attribute + # Dual variable doesn't have a designated component are ignored + + # Split constraint name into component and attribute + # GlobalConstraints refer instead to the component name, e.g. "GlobalConstraint-X" + try: + prefix, suffix = constraint_name.split("-", 1) + c = self._n.components[prefix] + except (ValueError, KeyError): + unassigned_constraints.append(constraint_name) + continue + + # Add placeholder for custom constraints, marked as GlobalConstraint + # TODO This should go to an actual custom constraint + if ( + c.name == "GlobalConstraint" + and suffix not in c.static.index + and assign_all_duals + ): + if c.has_scenarios: + msg = ( + "Dual values for custom constraints with scenarios are not " + "yet supported for stochastic optimization." + ) + raise NotImplementedError(msg) + else: + c.static.loc[suffix] = None + + # Dynamic duals (constraints with snapshot dimension) + if "snapshot" in constraint.dual.dims: + # Get dual from constraint as formatted pandas DataFrame + dual_df = _from_xarray(constraint.dual, c) + + # Standard components: extract last part after final dash + # e.g., "Line-s-upper" -> "upper", "Generator-p-lower" -> "lower" + try: + dual_spec = suffix.rsplit("-", 1)[-1] + except ValueError: + dual_spec = suffix + # Don't try to split GlobalConstraint names, since they refer to + # component name instead of attribute name + if c.name == "GlobalConstraint": + dual_spec = suffix + + # Handle special cases for dual attribute name + + # 1. Nodal balance duals become marginal prices + if suffix.endswith("nodal_balance"): + dual_attr_name = "marginal_price" + + # Standard case: assign as "mu_" + # (e.g., "mu_upper", "mu_generation_limit_dynamic") + elif assign_all_duals or f"mu_{dual_spec}" in c.static: + dual_attr_name = f"mu_{dual_spec}" + + # Duals that don't have a placeholder are ignored + else: + unassigned_constraints.append(constraint_name) + continue + + # Assign dynamic duals to component + _set_dynamic_data(self._n, c.name, dual_attr_name, dual_df) + + # SCALAR DUALS (constraints without snapshot dimension) + # else: + elif c.name == "GlobalConstraint" and suffix in c.static.index: + if c.has_scenarios: + raise NotImplementedError() + + c.static.loc[suffix, "mu"] = constraint.dual + + if unassigned_constraints: + logger.info( + "The shadow-prices of the constraints %s were not assigned to the network.", + ", ".join(unassigned_constraints), + ) + + def post_processing(self) -> None: + """Post-process the optimized network. + + This calculates quantities derived from the optimized values such as + power injection per bus and snapshot, voltage angle. + """ + n = self._n + sns = n.model.parameters.snapshots.to_index() + + # correct prices with objective weightings + if n._multi_invest: + period_weighting = n.investment_period_weightings.objective + weightings = n.snapshot_weightings.objective.mul( + period_weighting, level=0, axis=0 + ).loc[sns] + else: + weightings = n.snapshot_weightings.objective.loc[sns] + + n.c.buses.dynamic.marginal_price.loc[sns] = ( + n.c.buses.dynamic.marginal_price.loc[sns].divide(weightings, axis=0) + ) + + # load + if len(n.loads): + _set_dynamic_data( + n, "Load", "p", n.get_switchable_as_dense("Load", "p_set", sns) + ) + + # line losses + if "Line-loss" in n.model.variables: + losses = n.model["Line-loss"].solution.to_pandas() + n.c.lines.dynamic.p0 += losses / 2 + n.c.lines.dynamic.p1 += losses / 2 + + # recalculate injection + ca = [ + ("Generator", "p", "bus"), + ("Store", "p", "bus"), + ("Load", "p", "bus"), + ("StorageUnit", "p", "bus"), + ("Link", "p0", "bus0"), + ("Link", "p1", "bus1"), + ] + ca.extend([("Link", f"p{i}", f"bus{i}") for i in n.c.links.additional_ports]) + + def sign(c: str) -> int: + return n.c[c].static.get("sign", -1) # -1 is the sign for 'Link' + + n.c.buses.dynamic.p = ( + pd.concat( + [ + n.c[c] + .dynamic[attr] + .mul(sign(c)) + .rename(columns=n.c[c].static[group], level="name") + for c, attr, group in ca + ], + axis=1, + ) + .T.groupby(level=0) + .sum() + .T.reindex(columns=n.c.buses.static.index, fill_value=0.0) + ) + + if not n.has_scenarios: + + def v_ang_for_(sub: SubNetwork) -> pd.DataFrame: + buses_i = sub.buses_o + if len(buses_i) == 1: + return pd.DataFrame(0, index=sns, columns=buses_i) + sub.calculate_B_H(skip_pre=True) + Z = pd.DataFrame(np.linalg.pinv((sub.B).todense()), buses_i, buses_i) + Z -= Z[sub.slack_bus] + return n.c.buses.dynamic.p.reindex(columns=buses_i) @ Z + + # TODO: if multi investment optimization, the network topology is not the necessarily the same, + # i.e. one has to iterate over the periods in order to get the correct angles. + + # Determine_network_topology is not necessarily called (only if KVL was assigned) + if n.c.sub_networks.static.empty: + n.determine_network_topology() + + # Calculate voltage angles (only needed for power flow) + if "obj" in n.c.sub_networks.static: + n.c.buses.dynamic.v_ang = pd.concat( + [v_ang_for_(sub) for sub in n.c.sub_networks.static.obj], axis=1 + ).reindex(columns=n.c.buses.static.index, fill_value=0.0) + + def fix_optimal_capacities(self) -> None: + """Fix capacities of extendable assets to optimized capacities. + + Use this function when a capacity expansion optimization was + already performed and a operational optimization should be done + afterwards. + """ + n = self._n + for c, attr in nominal_attrs.items(): + c = n.components[c] + ext_i = c.extendables.difference(c.inactive_assets) + c.static.loc[ext_i, attr] = c.static.loc[ext_i, attr + "_opt"] + c.static[attr + "_extendable"] = False + + def fix_optimal_dispatch(self) -> None: + """Fix dispatch of all assets to optimized values. + + Use this function when the optimal dispatch should be used as an + starting point for power flow calculation (`Network.pf`). + """ + for c in self._n.one_port_components: + self._n.components[c].dynamic.p_set = self._n.components[c].dynamic.p + for c in self._n.controllable_branch_components: + self._n.components[c].dynamic.p_set = self._n.components[c].dynamic.p0 + + def add_load_shedding( + self, + suffix: str = " load shedding", + buses: pd.Index | None = None, + sign: float | pd.Series = 1e-3, + marginal_cost: float | pd.Series = 1e2, + p_nom: float | pd.Series = 1e9, + ) -> pd.Index: + """Add load shedding in form of generators to all or a subset of buses. + + For more information on load shedding see + http://journal.frontiersin.org/article/10.3389/fenrg.2015.00055/full + + Parameters + ---------- + suffix : str, default: " load shedding" + Suffix of the load shedding generators. See suffix parameter of + [pypsa.Network.add]. + buses : pandas.Index, optional + Subset of buses where load shedding should be available. + Defaults to all buses. + sign : float/Series, optional + Scaling of the load shedding. This is used to scale the price of the + load shedding. The default is 1e-3 which translates to a measure in kW instead + of MW. + marginal_cost : float/Series, optional + Price of the load shedding. The default is 1e2. + p_nom : float/Series, optional + Maximal load shedding. The default is 1e9 (kW). + + """ + if "Load" not in self._n.c.carriers.static.index: + self._n.add("Carrier", "Load") + if buses is None: + buses = self._n.c.buses.static.index + + return self._n.add( + "Generator", + buses, + suffix, + bus=buses, + carrier="load", + sign=sign, + marginal_cost=marginal_cost, + p_nom=p_nom, + ) diff --git a/PyPSA/source/pypsa/optimization/variables.py b/PyPSA/source/pypsa/optimization/variables.py new file mode 100644 index 0000000000000000000000000000000000000000..adbcc66e8c49c780fbf6bd6efdf44a88858c4069 --- /dev/null +++ b/PyPSA/source/pypsa/optimization/variables.py @@ -0,0 +1,275 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Define optimisation variables from PyPSA networks with Linopy.""" + +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +import pandas as pd +import xarray as xr + +if TYPE_CHECKING: + from collections.abc import Sequence + + from pypsa import Network + +logger = logging.getLogger(__name__) + + +def define_operational_variables( + n: Network, sns: Sequence, c_name: str, attr: str +) -> None: + """Initialize variables for power dispatch for a given component and a given attribute. + + Parameters + ---------- + n : pypsa.Network + Network instance + sns : Sequence + Snapshots + c_name : str + name of the network component + attr : str + name of the attribute, e.g. 'p' + + """ + c = n.components[c_name] + if c.empty: + return + + active = c.da.active.sel(name=c.active_assets, snapshot=sns) + coords = active.coords + n.model.add_variables(coords=coords, name=f"{c.name}-{attr}", mask=active) + + +def define_status_variables( + n: Network, sns: Sequence, c_name: str, is_linearized: bool = False +) -> None: + """Initialize variables for unit status decisions. + + Parameters + ---------- + n : pypsa.Network + Network instance + sns : Sequence + Snapshots + c_name : str + name of the network component + is_linearized : bool, default False + Whether the unit commitment should be linearized + + """ + c = n.components[c_name] + com_i = c.committables.difference(c.inactive_assets) + + if com_i.empty: + return + + active = c.da.active.sel(name=com_i, snapshot=sns) + coords = active.coords + is_binary = not is_linearized + kwargs = {"upper": 1, "lower": 0} if not is_binary else {} + n.model.add_variables( + coords=coords, name=f"{c.name}-status", mask=active, binary=is_binary, **kwargs + ) + + +def define_start_up_variables( + n: Network, sns: Sequence, c_name: str, is_linearized: bool = False +) -> None: + """Initialize variables for unit start-up decisions. + + Parameters + ---------- + n : pypsa.Network + Network instance + sns : Sequence + Snapshots + c_name : str + name of the network component + is_linearized : bool, default False + Whether the unit commitment should be linearized + + """ + c = n.components[c_name] + com_i = c.committables.difference(c.inactive_assets) + + if com_i.empty: + return + + active = c.da.active.sel(name=com_i, snapshot=sns) + coords = active.coords + is_binary = not is_linearized + kwargs = {"upper": 1, "lower": 0} if not is_binary else {} + n.model.add_variables( + coords=coords, + name=f"{c.name}-start_up", + mask=active, + binary=is_binary, + **kwargs, + ) + + +def define_shut_down_variables( + n: Network, sns: Sequence, c_name: str, is_linearized: bool = False +) -> None: + """Initialize variables for unit shut-down decisions. + + Parameters + ---------- + n : pypsa.Network + Network instance + sns : Sequence + Snapshots + c_name : str + name of the network component + is_linearized : bool, default False + Whether the unit commitment should be linearized + + """ + c = n.components[c_name] + com_i = c.committables.difference(c.inactive_assets) + + if com_i.empty: + return + + active = c.da.active.sel(name=com_i, snapshot=sns) + coords = active.coords + is_binary = not is_linearized + kwargs = {"upper": 1, "lower": 0} if not is_binary else {} + n.model.add_variables( + coords=coords, + name=f"{c.name}-shut_down", + binary=is_binary, + **kwargs, + mask=active, + ) + + +def define_nominal_variables(n: Network, c_name: str, attr: str) -> None: + """Initialize variables for nominal capacities. + + Parameters + ---------- + n : pypsa.Network + Network instance + c_name : str + name of network component of which the nominal capacity should be defined + attr : str + name of the variable, e.g. 'p_nom' + + """ + c = n.components[c_name] + ext_i = c.extendables.difference(c.inactive_assets) + if ext_i.empty: + return + if isinstance(ext_i, pd.MultiIndex): + ext_i = ext_i.unique(level="name") + + n.model.add_variables(coords=[ext_i], name=f"{c.name}-{attr}") + + +def define_modular_variables(n: Network, c_name: str, attr: str) -> None: + """Initialize variables 'attr' for a given component c to allow a modular expansion of the attribute 'attr_nom'. + + It allows to define 'n_opt', the optimal number of installed modules. + + Parameters + ---------- + n : pypsa.Network + Network instance + c_name : str + name of network component of which the nominal capacity should be defined + attr : str + name of the variable to be handled attached to modular constraints, e.g. 'p_nom' + + """ + c = n.components[c_name] + mod_i = c.static.query(f"{attr}_extendable and ({attr}_mod>0)").index + mod_i = mod_i.difference(c.inactive_assets) + + if mod_i.empty: + return + + n.model.add_variables(lower=0, coords=[mod_i], name=f"{c.name}-n_mod", integer=True) + + +def define_spillage_variables(n: Network, sns: Sequence) -> None: + """Define the spillage variables for storage units.""" + c_name = "StorageUnit" + c = n.components[c_name] + + if c.empty: + return + + upper = c.da.inflow.sel(name=c.active_assets, snapshot=sns) + if upper.size == 0 or (upper.max() <= 0).all(): + return + + active = c.da.active.sel(snapshot=sns, name=c.active_assets) + + active_aligned, upper_aligned = xr.align(active, upper, join="inner") + active = active_aligned.where(upper_aligned > 0, False) + + n.model.add_variables(0, upper_aligned, name=f"{c.name}-spill", mask=active) + + +def define_loss_variables(n: Network, sns: Sequence, c_name: str) -> None: + """Initialize variables for transmission losses. + + Parameters + ---------- + n : pypsa.Network + Network instance + sns : Sequence + Snapshots + c_name : str + name of the network component + + """ + c = n.components[c_name] + if c.empty or c.name not in n.passive_branch_components: + return + + active = c.da.active.sel(name=c.active_assets, snapshot=sns) + coords = active.coords + n.model.add_variables(0, coords=coords, name=f"{c.name}-loss", mask=active) + + +def define_cvar_variables(n: Network) -> None: + """Define auxiliary variables used in the CVaR (Conditional Value-at-Risk) formulation. + + This helper adds three auxiliary variables to the model when + stochastic optimisation with risk preference is enabled. + + * `CVaR-a` (per-scenario, non-negative): auxiliary excess loss variables `a_s`. + They linearise the tail expectation: `a_s >= OPEX_s - theta`. + * `CVaR-theta` (scalar): the Value-at-Risk (VaR) level `theta` at confidence `alpha`. + * `CVaR` (scalar): the Conditional Value-at-Risk (Expected Shortfall) objective term. + + These variables are linked by constraints (added in the objective construction) + to implement the linear CVaR formulation. + + Parameters + ---------- + n : pypsa.Network + Network instance + + """ + if n.has_scenarios and n.has_risk_preference is False: + return + + # Per-scenario auxiliary variables a[s] + scenarios = n.scenarios + if scenarios is None or len(scenarios) == 0: + return + + # Non-negative excess loss variables per scenario + n.model.add_variables(lower=0, coords=[scenarios], name="CVaR-a") + # Scalar theta (VaR) and CVaR + n.model.add_variables(name="CVaR-theta") + n.model.add_variables(name="CVaR") diff --git a/PyPSA/source/pypsa/plot/__init__.py b/PyPSA/source/pypsa/plot/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f64d708d2b6cdc8e7694595204116731821029aa --- /dev/null +++ b/PyPSA/source/pypsa/plot/__init__.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Plotting package for PyPSA networks.""" + +from pypsa.plot.accessor import PlotAccessor +from pypsa.plot.maps.static import ( + add_legend_arrows, + add_legend_circles, + add_legend_lines, + add_legend_patches, + add_legend_semicircles, +) +from pypsa.plot.statistics.plotter import StatisticInteractivePlotter, StatisticPlotter + +__all__ = [ + "PlotAccessor", + "StatisticInteractivePlotter", + "StatisticPlotter", + "add_legend_arrows", + "add_legend_circles", + "add_legend_lines", + "add_legend_patches", + "add_legend_semicircles", +] diff --git a/PyPSA/source/pypsa/plot/accessor.py b/PyPSA/source/pypsa/plot/accessor.py new file mode 100644 index 0000000000000000000000000000000000000000..ea6aab6b01d9c09de9adb23c75e3aaca5a24f142 --- /dev/null +++ b/PyPSA/source/pypsa/plot/accessor.py @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Plotting accessor for PyPSA.""" + +import functools +from typing import TYPE_CHECKING, Any + +from pypsa.plot.maps import explore, iplot, plot + +if TYPE_CHECKING: + from pypsa import Network + + +class PlotAccessor: + """Accessor for plotting statistics. + + The class inherits from StatisticsAccessor and provides the same statistic + functions, but returns a StatisticPlotter object instead of a DataFrame. + """ + + """Abstract accessor to calculate different statistical values.""" + + def __init__(self, n: "Network") -> None: + """Initialize the statistics accessor.""" + self._n = n + + @functools.wraps(plot) + def __call__(self, *args: Any, **kwargs: Any) -> Any: + """Alias for [pypsa.Network.plot.map][pypsa.plot.PlotAccessor.map].""" + return plot(self._n, *args, **kwargs) + + @functools.wraps(plot) + def map(self, *args: Any, **kwargs: Any) -> Any: + """Plot method.""" + return plot(self._n, *args, **kwargs) + + @functools.wraps(iplot) + def iplot(self, *args: Any, **kwargs: Any) -> Any: + """Interactive plot method.""" + return iplot(self._n, *args, **kwargs) + + @functools.wraps(explore) + def explore(self, *args: Any, **kwargs: Any) -> Any: + """Interactive map plot method.""" + return explore(self._n, *args, **kwargs) diff --git a/PyPSA/source/pypsa/plot/maps/__init__.py b/PyPSA/source/pypsa/plot/maps/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..555614b6c33e30fdfd10e4431deacb7975893b6d --- /dev/null +++ b/PyPSA/source/pypsa/plot/maps/__init__.py @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Map plots for PyPSA. + +This module contains all plotting functions which can be used on top of the +functions of [`pypsa.Network.plot`][], [`pypsa.Network.explore`][], [`pypsa.Network.iplot`][]. +""" + +from pypsa.plot.maps.interactive import explore, iplot +from pypsa.plot.maps.static import MapPlotter, plot + +__all__ = [ + "MapPlotter", + "plot", + "iplot", + "explore", +] diff --git a/PyPSA/source/pypsa/plot/maps/common.py b/PyPSA/source/pypsa/plot/maps/common.py new file mode 100644 index 0000000000000000000000000000000000000000..8dff50d352ff72ebf646e6e63985e2135e6ad7af --- /dev/null +++ b/PyPSA/source/pypsa/plot/maps/common.py @@ -0,0 +1,996 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Define common functions for plotting maps in PyPSA.""" + +import importlib +import logging +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Literal, cast, overload + +import matplotlib.colors as mcolors +import matplotlib.pyplot as plt +import networkx as nx +import numpy as np +import pandas as pd +from shapely import wkt +from shapely.geometry import LineString, MultiPolygon, Polygon, mapping + +from pypsa.common import _convert_to_series +from pypsa.constants import EARTH_RADIUS + +if TYPE_CHECKING: + import cartopy.feature + + from pypsa import Network + +logger = logging.getLogger(__name__) + + +def _is_cartopy_available() -> bool: # noqa + """Check if cartopy is available at runtime.""" + return importlib.util.find_spec("cartopy") is not None + + +def apply_cmap( # noqa + colors: pd.Series, + cmap: str | mcolors.Colormap | None, + cmap_norm: mcolors.Normalize | None = None, +) -> pd.Series: + if np.issubdtype(colors.dtype, np.number): + if not isinstance(cmap, mcolors.Colormap): + cmap = plt.get_cmap(cmap) + if not cmap_norm: + cmap_norm = plt.Normalize(vmin=colors.min(), vmax=colors.max()) + colors = colors.apply(lambda cval: cmap(cmap_norm(cval))) + return colors + + +def as_branch_series( # noqa + ser: pd.Series | dict | list, arg: str, c_name: str, n: "Network" +) -> pd.Series: + ser = pd.Series(ser, index=n.components[c_name].static.index) + if ser.isnull().any(): + msg = f"{c_name}_{arg}s does not specify all " + f"entries. Missing values for {c_name}: {list(ser[ser.isnull()].index)}" + raise ValueError(msg) + return ser + + +@overload +def apply_layouter( + n: "Network", + layouter: Callable[..., Any] | None = None, + inplace: Literal[True] = True, +) -> None: ... + + +@overload +def apply_layouter( + n: "Network", + layouter: Callable[..., Any] | None = None, + inplace: Literal[False] = False, +) -> tuple[pd.Series, pd.Series]: ... + + +def apply_layouter( + n: "Network", + layouter: Callable[..., Any] | None = None, + inplace: Literal[True, False] = False, +) -> Any: + """Automatically generate bus coordinates for the network graph. + + Layouting function from [networkx](https://networkx.github.io/) is used to + determine the coordinates of the buses in the network. + + Parameters + ---------- + n : pypsa.Network + Network to generate coordinates for. + layouter : networkx.drawing.layout function, default None + Layouting function from [networkx](https://networkx.github.io/). See + [list](https://networkx.github.io/documentation/stable/reference/drawing.html#module-networkx.drawing.layout) + of available options. By default, coordinates are determined for a + [planar layout](https://networkx.github.io/documentation/stable/reference/generated/networkx.drawing.layout.planar_layout.html#networkx.drawing.layout.planar_layout) + if the network graph is planar, otherwise for a + [Kamada-Kawai layout](https://networkx.github.io/documentation/stable/reference/generated/networkx.drawing.layout.kamada_kawai_layout.html#networkx.drawing.layout.kamada_kawai_layout). + inplace : bool, default False + Assign generated coordinates to the network bus coordinates + at `n.buses[['x', 'y']]` if True, otherwise return them. + + Returns + ------- + coordinates : pd.DataFrame or None + DataFrame with x and y coordinates for each bus. Only returned if + inplace is False. + + Examples + -------- + >>> import pypsa + >>> n = pypsa.examples.ac_dc_meshed() + >>> x, y = apply_layouter(n, layouter=nx.circular_layout) + >>> x + London 1.000000 + Norwich 0.766044 + Norwich DC 0.173648 + Manchester -0.500000 + Bremen -0.939693 + Bremen DC -0.939693 + Frankfurt -0.500000 + Norway 0.173648 + Norway DC 0.766044 + Name: x, dtype: float64 + >>> y + London 1.986821e-08 + Norwich 6.427876e-01 + Norwich DC 9.848077e-01 + Manchester 8.660254e-01 + Bremen 3.420202e-01 + Bremen DC -3.420201e-01 + Frankfurt -8.660254e-01 + Norway -9.848077e-01 + Norway DC -6.427877e-01 + Name: y, dtype: float64 + + """ + G = n.graph() + + if layouter is None: + if nx.check_planarity(G)[0]: + layouter = nx.planar_layout + else: + layouter = nx.kamada_kawai_layout + + coordinates = pd.DataFrame(layouter(G)).T.rename({0: "x", 1: "y"}, axis=1) + + if inplace: + n.c.buses.static[["x", "y"]] = coordinates + return None + return coordinates.x, coordinates.y + + +def add_jitter( + x: pd.Series, y: pd.Series, jitter: float +) -> tuple[pd.Series, pd.Series]: + """Add random jitter to Series data, preserving index and name. + + Parameters + ---------- + x : pd.Series + X data. + y : pd.Series + Y data. + jitter : float + The amount of jitter to add. Function adds a random number between -jitter and + jitter to each element in the data arrays. + + Returns + ------- + x_jittered : pd.Series + X data with added jitter. + y_jittered : pd.Series + Y data with added jitter. + + """ + rng = np.random.default_rng() # Create a random number generator + x_jittered = x + rng.uniform(low=-jitter, high=jitter, size=len(x)) + y_jittered = y + rng.uniform(low=-jitter, high=jitter, size=len(y)) + + return x_jittered, y_jittered + + +def to_rgba255( + color: str, + alpha: float = 1.0, +) -> list[int]: + """Convert a Matplotlib color name/hex to an RGBA list with 0-255 integer values. + + Parameters + ---------- + color : str + Matplotlib color name or hex string. + + alpha : float, default 1.0 + Alpha transparency value between 0 (transparent) and 1 (opaque). + + Returns + ------- + list of int + List of RGBA values as integers in the range 0-255. + + """ + rgb = [round(c * 255) for c in mcolors.to_rgb(color)] + a = round(alpha * 255) + return rgb + [a] + + +def to_rgba255_css(color: str, alpha: float = 1.0) -> str: + """Convert Matplotlib color to CSS rgba() string. + + Parameters + ---------- + color : str + Matplotlib color name or hex string. + alpha : float, default 1.0 + Alpha transparency value between 0 (transparent) and 1 (opaque). + + Returns + ------- + str + CSS rgba() string. + + """ + rgb = [round(c * 255) for c in mcolors.to_rgb(color)] + return f"rgba({rgb[0]}, {rgb[1]}, {rgb[2]}, {alpha:.2f})" + + +def set_tooltip_style( + background_alpha: float = 0.7, + background_color: str = "black", + font_color: str = "white", + font_family: str = "Arial", + font_size: int = 12, + max_width: int = 300, + padding: int = 10, +) -> dict[str, str]: + """Set CSS style for pydeck tooltips. + + Parameters + ---------- + background_alpha : float + Alpha transparency for background color (0 to 1). + background_color : str + Matplotlib color name or hex string for background. + font_color : str + Font color name or hex string. + font_family : str + Font family name. + font_size : int + Font size in pixels. + max_width : int + Maximum width of tooltip in pixels. + padding : int + Padding inside tooltip in pixels. + + Returns + ------- + dict + Dictionary of CSS styles for pydeck tooltip. + + """ + return { + "backgroundColor": to_rgba255_css(background_color, background_alpha), + "color": font_color, + "fontFamily": font_family, + "fontSize": f"{font_size}px", + "borderCollapse": "collapse", + "border": "1px solid white", + "padding": f"{padding}px", + "maxWidth": f"{max_width}px", + "overflowWrap": "break-word", + "overflow": "hidden", + } + + +def scale_to_max_abs(series: pd.Series, max_value: float = 1.0) -> pd.Series: + """Scale a pandas Series by a single factor so that its maximum absolute value equals max_value. + + Parameters + ---------- + series : pd.Series + Input data to be scaled. + max_value : float, optional + Desired maximum of scaled series. Default is 1.0. + + Returns + ------- + pd.Series + Scaled Series. + + """ + s_max = series.abs().max() + if s_max == 0: + # avoid division by zero + return pd.Series([0.0] * len(series), index=series.index) + + factor = max_value / s_max + return series * factor + + +def get_global_stat( + elements: list[float | int | dict[Any, float | int] | pd.Series], + stat: str | Callable = "max", + absolute: bool = True, +) -> float | None: + """Compute a global statistic (min, max, median, mean, etc.) across a list of floats, dicts, or pandas Series. + + Parameters + ---------- + elements : list of (float | int | dict | pd.Series) + Elements to evaluate. + stat : str or Callable, default "max" + Aggregation to apply ("max", "min", "median", "mean", ...). + Can also pass a custom function like np.percentile. + absolute : bool, default True + Whether to apply abs() before aggregation. + + Returns + ------- + float or None + The global statistic across all elements. Returns None if no valid values are found. + + """ + # Resolve stat into a function + if isinstance(stat, str): + stat_map = { + "max": np.max, + "min": np.min, + "median": np.median, + "mean": np.mean, + "sum": np.sum, + } + if stat not in stat_map: + msg = f"Unsupported stat '{stat}'. Supported: {list(stat_map)}." + raise ValueError(msg) + func = stat_map[stat] + elif callable(stat): + func = stat + else: + msg = "stat must be a string or callable." + raise TypeError(msg) + + # Collect all values into one flat array + values: list[float] = [] + for el in elements: + if el is None: + continue + if isinstance(el, (int | float)): + if np.isnan(el): + continue + values.append(el) + elif isinstance(el, dict): + values.extend(v for v in el.values() if v is not None and not pd.isna(v)) + elif isinstance(el, pd.Series): + if not el.empty: + values.extend(el.dropna().to_numpy()) + else: + msg = f"Unsupported type: {type(el)}." + raise TypeError(msg) + + if not values: + return None + + arr = np.array(values, dtype=float) + if absolute: + arr = np.abs(arr) + + cast_func = Callable[[np.ndarray], Any] + return float(cast("cast_func", func)(arr)) + + +def create_rgba_colors( + df: pd.DataFrame, + color: str | float | dict | pd.Series | None, + cmap: str | mcolors.Colormap, + cmap_norm: mcolors.Normalize | None, + alpha: float | dict | pd.Series = 0.9, + target_col: str | None = None, + fallback_col: str | None = None, +) -> None: + """Create RGBA colors in 0-255 integer list format and store them in a DataFrame column. + + Parameters + ---------- + df : pd.DataFrame + DataFrame to store colors in. + color : str, float, dict, pandas.Series, or None + Colors to apply. If numeric, a colormap is applied. + If None, colors are copied from `fallback_col`. + cmap : str or matplotlib.colors.Colormap + Colormap to use if `color` is numeric. + cmap_norm : matplotlib.colors.Normalize, optional + Normalization to use if `color` is numeric. + alpha : float, dict, or pandas.Series, default 0.9 + Alpha transparency for the colors. + target_col : str, required + Column in df to store the resulting RGBA colors as 0-255 integer lists. + fallback_col : str, optional + If `color` is None, copy colors from this column to `target_col`. + + """ + if target_col is None: + msg = "target_col must be specified." + raise ValueError(msg) + + if color is not None: + colors = _convert_to_series(color, df.index) + alphas = _convert_to_series(alpha, df.index) + df["color"] = colors + colors = apply_cmap(colors, cmap, cmap_norm) + df[target_col] = [ + to_rgba255(c, a) for c, a in zip(colors, alphas, strict=False) + ] + elif fallback_col is not None: + if fallback_col not in df: + msg = f"fallback_col '{fallback_col}' not in DataFrame." + raise KeyError(msg) + df[target_col] = df[fallback_col] + + +def wkt_to_linestring(wkt_str: str) -> LineString: + """Convert a WKT string to a Shapely LineString. + + Raises + ------ + TypeError + If the WKT does not represent a LineString. + ValueError + If the string cannot be parsed. + + Parameters + ---------- + wkt_str : str + WKT representation of a LineString. + + Returns + ------- + LineString + Shapely LineString object. + + """ + geom = wkt.loads(wkt_str) + if not isinstance(geom, LineString): + msg = f"Expected LineString, got {type(geom)}" + raise TypeError(msg) + return geom + + +def linestring_to_pdk_path(line: LineString) -> list[list[float]]: + """Convert a single LineString to a pydeck list of [lon, lat] coordinates for PathLayer. + + Parameters + ---------- + line : LineString + Shapely LineString object. + + Returns + ------- + list of list of float + List of [lon, lat] coordinates. + + """ + if not isinstance(line, LineString): + msg = f"Expected LineString, got {type(line)}." + raise TypeError(msg) + return [[x, y] for x, y in line.coords] + + +def series_to_pdk_path(geoms: pd.Series) -> list[list[list[float]]]: + """Convert a pandas Series of LineStrings or WKT strings to Pydeck-ready paths. + + Parameters + ---------- + geoms : pd.Series + Each element must be either a LineString or a WKT string representing a LineString. + + Returns + ------- + list of list of list of float + List of Pydeck paths. + + """ + pydeck_paths = [] + for g in geoms: + if isinstance(g, str): + g = wkt_to_linestring(g) + pydeck_paths.append(linestring_to_pdk_path(g)) + return pydeck_paths + + +# Geometric functions +def rotate_polygon( + poly: np.ndarray, + angle_rad: float, +) -> np.ndarray: + """Rotate polygon around origin by angle in radians. + + Parameters + ---------- + poly : np.ndarray + Nx2 array of polygon vertices. + angle_rad : float + Rotation angle in radians. + + Returns + ------- + np.ndarray + Rotated polygon as Nx2 array. + + """ + c, s = np.cos(angle_rad), np.sin(angle_rad) + R = np.array([[c, -s], [s, c]]) + return poly @ R.T + + +def flip_polygon( + poly: np.ndarray, + axis: str = "x", +) -> np.ndarray: + """Flip polygon around specified axis ('x' or 'y'). + + Parameters + ---------- + poly : np.ndarray + Nx2 array of polygon vertices. + axis : str, default 'x' + Axis to flip around, either 'x' or 'y'. + + Returns + ------- + np.ndarray + Flipped polygon as Nx2 array. + + """ + if axis == "x": + return poly * np.array([1, -1]) + elif axis == "y": + return poly * np.array([-1, 1]) + else: + msg = "Axis must be 'x' or 'y'." + raise ValueError(msg) + + +def scale_polygon_by_width( + poly: np.ndarray, + target_width: float, +) -> np.ndarray: + """Scale a polygon so that its base width = base_width_m. Proportions are preserved. + + Parameters + ---------- + poly : np.ndarray + Nx2 array of polygon vertices. + target_width : float + Desired width of the polygon base. + + Returns + ------- + np.ndarray + Scaled polygon as Nx2 array. + + """ + width = poly[:, 1].max() - poly[:, 1].min() + + if width == 0: + msg = "Cannot scale polygon with zero width." + raise ValueError(msg) + + return poly * (target_width / width) + + +def translate_polygon( + poly: np.ndarray, + offset: tuple[float, float], +) -> np.ndarray: + """Translate polygon by offset (dx, dy). + + Parameters + ---------- + poly : np.ndarray + Nx2 array of polygon vertices. + offset : tuple of float + (dx, dy) translation offsets. + + Returns + ------- + np.ndarray + Translated polygon as Nx2 array. + + """ + return poly + np.array(offset) + + +def calculate_midpoint( + p0: tuple[float, float], + p1: tuple[float, float], +) -> tuple[float, float]: + """Calculate the midpoint between two points p0 and p1. + + Parameters + ---------- + p0 : tuple of float + (x0, y0) coordinates of the first point. + p1 : tuple of float + (x1, y1) coordinates of the second point. + + Returns + ------- + tuple of float + (x, y) coordinates of the midpoint. + + """ + return ((p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2) + + +def calculate_angle( + p0: tuple[float, float], + p1: tuple[float, float], +) -> float: + """Calculate the angle in radians between two points p0 and p1. + + Parameters + ---------- + p0 : tuple of float + (x0, y0) coordinates of the first point. + p1 : tuple of float + (x1, y1) coordinates of the second point. + + Returns + ------- + float + Angle in radians from p0 to p1. + + """ + dx = p1[0] - p0[0] + dy = p1[1] - p0[1] + return np.arctan2(dy, dx) + + +def meters_to_lonlat( + poly: np.ndarray, p0_m: tuple[float, float], r: float = EARTH_RADIUS +) -> np.ndarray: + """Convert polygon vertices from local meters to lon/lat relative to a reference point p0. + + Parameters + ---------- + poly : np.ndarray + Nx2 array of polygon vertices in meters. + p0_m : tuple of float + (lon0, lat0) reference point in degrees. + r : float, default EARTH_RADIUS + Earth radius in meters. + + Returns + ------- + np.ndarray + Nx2 array of polygon vertices in (lon, lat) degrees. + + """ + lon0, lat0 = p0_m + x, y = poly[:, 0], poly[:, 1] + dlon = (x / (r * np.cos(np.radians(lat0)))) * (180.0 / np.pi) + dlat = (y / r) * (180.0 / np.pi) + return np.column_stack((lon0 + dlon, lat0 + dlat)) + + +# GeoJSON functions +def clip_lat( + coords: list[tuple[float, float]], + pole_buffer: float = 1e-6, +) -> list[tuple[float, float]]: + """Clip latitudes in coordinates to be within [-90 + pole_buffer, 90 - pole_buffer]. + + Parameters + ---------- + coords : list of tuple of float + List of (lon, lat) coordinates. + pole_buffer : float, default 1e-6 + Small buffer to avoid exact pole values. + + Returns + ------- + list of tuple of float + List of (lon, lat) coordinates with latitudes clipped. + + """ + clipped_coords = [ + (lon, max(min(lat, 90 - pole_buffer), -90 + pole_buffer)) for lon, lat in coords + ] + + return clipped_coords + + +def poly_to_geojson( + poly: Polygon, + pole_buffer: float = 1e-6, +) -> dict[str, Any]: + """Convert a shapely Polygon to a GeoJSON Feature, clipping latitudes to avoid poles. + + Parameters + ---------- + poly : shapely.geometry.Polygon + Shapely Polygon object. + pole_buffer : float, default 1e-6 + Small buffer to avoid exact pole values. + + Returns + ------- + dict + GeoJSON Feature dictionary. + + """ + exterior = clip_lat(list(poly.exterior.coords), pole_buffer) + interiors = [clip_lat(list(ring.coords), pole_buffer) for ring in poly.interiors] + new_poly = Polygon(exterior, interiors) + + # Ensure closed polygon + if new_poly.exterior.coords[0] != new_poly.exterior.coords[-1]: + coords = list(new_poly.exterior.coords) + coords.append(coords[0]) + new_poly = Polygon(coords, interiors) + + return {"type": "Feature", "geometry": mapping(new_poly)} + + +def feature_to_geojson( + feature: "cartopy.feature.NaturalEarthFeature", pole_buffer: float = 1e-6 +) -> list[dict[str, Any]]: + """Convert a Cartopy NaturalEarthFeature to a list of GeoJSON features. + + Parameters + ---------- + feature : cartopy.feature.NaturalEarthFeature + Cartopy NaturalEarthFeature object. + pole_buffer : float, default 1e-6 + Small buffer to avoid exact pole values. + + Returns + ------- + list of dict + List of GeoJSON Feature dictionaries. + + """ + geojson_features: list[dict[str, Any]] = [] + + for geom in feature.geometries(): + if isinstance(geom, Polygon): + geojson_features.append(poly_to_geojson(geom, pole_buffer)) + elif isinstance(geom, MultiPolygon): + geojson_features.extend( + poly_to_geojson(poly, pole_buffer) for poly in geom.geoms + ) + return geojson_features + + +def shapefile_to_geojson( + resolution: str = "110m", + category: str = "cultural", + name: str = "admin_0_countries", + pole_buffer: float = 1e-6, +) -> list[dict[str, Any]]: + """Convert a Natural Earth shapefile (e.g. countries) to GeoJSON Features. + + Parameters + ---------- + resolution : str, default "110m" + Natural Earth resolution ("10m", "50m", "110m"). + category : str, default "cultural" + Natural Earth category ("cultural" or "physical"). + name : str, default "admin_0_countries" + Natural Earth dataset name, e.g. "admin_0_countries". + pole_buffer : float, default 1e-6 + Small buffer to avoid exact pole values. + + Returns + ------- + list of dict + List of GeoJSON features (each with geometry + properties). + + """ + if not _is_cartopy_available(): + logger.warning( + "Cartopy is not available. Falling back to non-geographic plotting." + ) + + from cartopy.io import shapereader # noqa: PLC0415 + + shpfilename = shapereader.natural_earth( + resolution=resolution, + category=category, + name=name, + ) + reader = shapereader.Reader(shpfilename) + + features: list[dict[str, Any]] = [] + for record in reader.records(): + geom = record.geometry + props = record.attributes + + if isinstance(geom, Polygon): + features.append(poly_to_geojson(geom, pole_buffer) | {"properties": props}) + elif isinstance(geom, MultiPolygon): + features.extend( + poly_to_geojson(poly, pole_buffer) | {"properties": props} + for poly in geom.geoms + ) + + return features + + +def shorten_string(s: Any, max_length: int | None = None) -> str: + """Convert any object to a string and shorten it with an ellipsis ("...") if it exceeds the specified maximum length. + + Parameters + ---------- + s : Any + The object to convert to a string. + max_length : int, optional + Maximum allowed string length. If None, no shortening is applied. + + Returns + ------- + str + The string representation of the input, shortened if necessary. + + """ + s_str = str(s) + if max_length is not None and len(s_str) > max_length: + return s_str[:max_length] + "..." + return s_str + + +def round_value( + v: Any, rounding: int | dict[str, int] | None = None, key: str | None = None +) -> Any: + """Round a numeric value based on the rounding specification. + + Parameters + ---------- + v : Any + The value to round. + rounding : int or dict of str to int, optional + - If int, rounds all numbers to this precision. + - If dict, looks up precision using `key`. + - If None, no rounding is applied. + key : str, optional + Identifier used for dict-based rounding. + + Returns + ------- + Any + Rounded numeric value if applicable, otherwise the original value. + + """ + if isinstance(v, (int | float)): + if isinstance(rounding, int): + if isinstance(v, int) or (isinstance(v, float) and v.is_integer()): + return int(v) + return round(v, rounding) + elif isinstance(rounding, dict) and key in rounding: + r = rounding[key] + rounded = round(v, r) + return int(rounded) if rounded.is_integer() else rounded + return v + + +def series_to_html_str( + df_row: pd.Series, + columns: list[str] | None = None, + bold_header: bool = True, + headline: str | None = None, + rounding: int | dict | None = None, + value_align: str = "left", + max_header_length: int | None = None, + max_value_length: int | None = None, +) -> str: + """Convert a pd.Series to html string representation of a vertical table (columns become rows) with optional headline, bold left column, right-aligned values, and rounding. + + Parameters + ---------- + df_row : pd.Series + A Series representing a single row of a DataFrame. + columns : list of str, optional + Columns to include. Defaults to empty (no rows). + bold_header : bool + Whether to make the left column (headers) bold. + headline : str, optional + Optional headline to display above the table. + rounding : int or dict, optional + Number of decimals to round numeric values. If dict, keys are column names. + value_align : str + Alignment for value column: "right", "left", or "center". + max_header_length : int, optional + Maximum length of headline. Longer headlines are truncated with "...". + max_value_length : int, optional + Maximum length of each value string. Longer values are truncated with "...". + + Returns + ------- + str + HTML string representation of the table. + + """ + if not columns and not headline: + return "" + + # Headline + table_html = "" + if headline: + table_html += f"{shorten_string(headline, max_header_length)}\n" + + if not columns: + return table_html + + # Extract and process values + values = df_row[columns].to_numpy(dtype=object) + if rounding is not None: + values = np.array( + [ + round_value(v, rounding, col) + for v, col in zip(values, columns, strict=False) + ], + dtype=object, + ) + + values = np.array( + [shorten_string(v, max_value_length) for v in values], + dtype=object, + ) + + # Header column + left_style = "font-weight:bold" if bold_header else "" + left_arr = [ + f"{col}:" if left_style else f"{col}:" + for col in columns + ] + + # Value column + right_arr = [f"{v}" for v in values] + + # Combine rows + row_html_arr = [ + f"{l}{r}" for l, r in zip(left_arr, right_arr, strict=False) + ] + table_html += "\n" + "\n".join(row_html_arr) + "\n
" + + return table_html + + +def df_to_html_table( + df: pd.DataFrame, + columns: list[str] | None = None, + bold_header: bool = True, + max_header_length: int | None = None, + max_value_length: int | None = None, + rounding: int | dict | None = None, + value_align: str = "left", +) -> pd.Series: + """Convert a DataFrame row to a vertical HTML table (columns become rows) with optional headline, bold left column, right-aligned values, and rounding. + + Parameters + ---------- + df : pd.DataFrame + A DataFrame with one or more rows. + columns : list of str, optional + Columns to include. Defaults to empty (no rows). + bold_header : bool + Whether to make the left column (headers) bold. + max_header_length : int, optional + Maximum length of headline. Longer headlines are truncated with "...". + max_value_length : int, optional + Maximum length of each value string. Longer values are truncated with "...". + rounding : int or dict, optional + Number of decimals to round numeric values. If dict, keys are column names. + value_align : str + Alignment for value column: "right", "left", or "center". + + Returns + ------- + pd.Series + Series of HTML strings for each row in the DataFrame. + + """ + return df.apply( + lambda row: series_to_html_str( + row, + columns=columns, + bold_header=bold_header, + headline=row.name, + rounding=rounding, + value_align=value_align, + max_header_length=max_header_length, + max_value_length=max_value_length, + ), + axis=1, + ) diff --git a/PyPSA/source/pypsa/plot/maps/interactive.py b/PyPSA/source/pypsa/plot/maps/interactive.py new file mode 100644 index 0000000000000000000000000000000000000000..d84b42c543f8dd0cd796b3a22ab64e9217dc66bc --- /dev/null +++ b/PyPSA/source/pypsa/plot/maps/interactive.py @@ -0,0 +1,2101 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Plot the network interactively using plotly or pydeck.""" + +import logging +from collections.abc import Callable, Sequence +from functools import wraps +from typing import TYPE_CHECKING, Any, Literal + +import matplotlib.colors as mcolors +import numpy as np +import pandas as pd +import plotly.graph_objects as go +import plotly.offline as pltly +import pydeck as pdk +import pyproj + +from pypsa.common import _convert_to_series, deprecated_kwargs +from pypsa.components.common import as_components +from pypsa.plot.maps.common import ( + _is_cartopy_available, + add_jitter, + apply_cmap, + apply_layouter, + as_branch_series, + calculate_angle, + calculate_midpoint, + create_rgba_colors, + df_to_html_table, + feature_to_geojson, + flip_polygon, + get_global_stat, + meters_to_lonlat, + rotate_polygon, + scale_polygon_by_width, + scale_to_max_abs, + series_to_pdk_path, + set_tooltip_style, + shapefile_to_geojson, + to_rgba255, +) + +if TYPE_CHECKING: + from pypsa.networks import Network + + +logger = logging.getLogger(__name__) + + +_token_required_mb_styles = [ + "basic", + "streets", + "outdoors", + "light", + "dark", + "satellite", + "satellite-streets", +] + +_open__mb_styles = [ + "open-street-map", + "white-bg", + "carto-positron", + "carto-darkmatter", + "stamen-terrain", + "stamen-toner", + "stamen-watercolor", +] + + +@deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + bus_sizes="bus_size", + bus_colors="bus_color", + bus_split_circles="bus_split_circle", + branch_colors="branch_color", + branch_widths="branch_width", + arrow_colors="arrow_color", + geomap_colors="geomap_color", + line_colors="line_color", + line_widths="line_width", + link_colors="link_color", + link_widths="link_width", + transformer_colors="transformer_color", + transformer_widths="transformer_width", +) +def iplot( + n: "Network", + fig: dict | None = None, + bus_color: str | dict | pd.Series = "cadetblue", + bus_alpha: float = 1, + bus_size: float | pd.Series = 10, + bus_cmap: str | mcolors.Colormap | None = None, + bus_colorbar: dict | None = None, + bus_text: pd.Series | None = None, + line_color: str | pd.Series = "rosybrown", + link_color: str | pd.Series = "darkseagreen", + transformer_color: str | pd.Series = "orange", + line_width: float | pd.Series = 3, + link_width: float | pd.Series = 3, + transformer_width: float | pd.Series = 3, + line_text: pd.Series | None = None, + link_text: pd.Series | None = None, + transformer_text: pd.Series | None = None, + layouter: Callable | None = None, + title: str = "", + size: tuple[int, int] | None = None, + branch_components: Sequence[str] | set[str] | None = None, + iplot: bool = True, + jitter: float | None = None, + mapbox: bool = False, + mapbox_style: str = "open-street-map", + mapbox_token: str = "", + mapbox_parameters: dict | None = None, +) -> dict: + """Plot the network buses and lines interactively using plotly. + + Parameters + ---------- + n : pypsa.Network + The network to plot. + fig : dict, default None + If not None, figure is built upon this fig. + bus_color : dict/pandas.Series + Colors for the buses, defaults to "cadetblue". If bus_size is a + pandas.Series with a Multiindex, bus_color defaults to the + n.c.carriers.static['color'] column. + bus_alpha : float + Add alpha channel to buses, defaults to 1. + bus_size : float/pandas.Series + Sizes of bus points, defaults to 10. + bus_cmap : mcolors.Colormap/str + If bus_color are floats, this color map will assign the colors + bus_colorbar : dict + Plotly colorbar, e.g. {'title' : 'my colorbar'} + bus_text : pandas.Series + Text for each bus, defaults to bus names + line_color : str/pandas.Series + Colors for the lines, defaults to 'rosybrown'. + link_color : str/pandas.Series + Colors for the links, defaults to 'darkseagreen'. + transformer_color : str/pandas.Series + Colors for the transfomer, defaults to 'orange'. + line_width : dict/pandas.Series + Widths of lines, defaults to 1.5 + link_width : dict/pandas.Series + Widths of links, defaults to 1.5 + transformer_width : dict/pandas.Series + Widths of transformer, defaults to 1.5 + line_text : pandas.Series + Text for lines, defaults to line names. + link_text : pandas.Series + Text for links, defaults to link names. + transformer_text : pandas.Series + Text for transformers, defaults to transformer names. + layouter : networkx.drawing.layout function, default None + Layouting function from [networkx](https://networkx.github.io/) which + overrules coordinates given in `n.buses[['x', 'y']]`. See + [list](https://networkx.github.io/documentation/stable/reference/drawing.html#module-networkx.drawing.layout) + of available options. + title : string + Graph title + size : None|tuple + Tuple specifying width and height of figure; e.g. (width, heigh). + branch_components : list of str + Branch components to be plotted, defaults to Line and Link. + iplot : bool, default True + Automatically do an interactive plot of the figure. + jitter : None|float + Amount of random noise to add to bus positions to distinguish + overlapping buses + mapbox : bool, default False + Switch to use Mapbox. + mapbox_style : str, default 'open-street-map' + Define the mapbox layout style of the interactive plot. If this is set + to a mapbox layout, the argument `mapbox_token` must be a valid Mapbox + API access token. + + Valid open layouts are: + open-street-map, white-bg, carto-positron, carto-darkmatter, + stamen-terrain, stamen-toner, stamen-watercolor + + Valid mapbox layouts are: + basic, streets, outdoors, light, dark, satellite, satellite-streets + + mapbox_token : string + Mapbox API access token. Obtain from `https://www.mapbox.com`. + Can also be included in mapbox_parameters as `accesstoken=mapbox_token`. + mapbox_parameters : dict + Configuration parameters of the Mapbox layout. + E.g. {"bearing": 5, "pitch": 10, "zoom": 1, "style": 'dark'}. + + + Returns + ------- + fig: dictionary for plotly figure + + """ + if fig is None: + fig = {"data": [], "layout": {}} + + if bus_text is None: + bus_text = "Bus " + n.c.buses.static.index + if mapbox_parameters is None: + mapbox_parameters = {} + x, y = apply_layouter(n, layouter=layouter, inplace=False) + + rng = np.random.default_rng() # Create a random number generator + if jitter is not None: + x = x + rng.uniform(low=-jitter, high=jitter, size=len(x)) + y = y + rng.uniform(low=-jitter, high=jitter, size=len(y)) + + bus_trace = { + "x": x, + "y": y, + "text": bus_text, + "type": "scatter", + "mode": "markers", + "hoverinfo": "text", + "opacity": bus_alpha, + "marker": {"color": bus_color, "size": bus_size}, + } + + if bus_cmap is not None: + bus_trace["marker"]["colorscale"] = bus_cmap + + if bus_colorbar is not None: + bus_trace["marker"]["colorbar"] = bus_colorbar + + if branch_components is None: + branch_components = n.branch_components + + branch_color = { + "Line": line_color, + "Link": link_color, + "Transformer": transformer_color, + } + branch_width = { + "Line": line_width, + "Link": link_width, + "Transformer": transformer_width, + } + branch_text = { + "Line": line_text, + "Link": link_text, + "Transformer": transformer_text, + } + + shapes = [] + shape_traces = [] + + for c in n.components: + if c.name not in branch_components: + continue + b_widths = as_branch_series(branch_width[c.name], "width", c.name, n) + b_colors = as_branch_series(branch_color[c.name], "color", c.name, n) + b_text = branch_text[c.name] + + if b_text is None: + b_text = c.name + " " + c.static.index + + x0 = c.static.bus0.map(x) + x1 = c.static.bus1.map(x) + y0 = c.static.bus0.map(y) + y1 = c.static.bus1.map(y) + + shapes.extend( + [ + { + "type": "line", + "opacity": 0.8, + "x0": x0[b], + "y0": y0[b], + "x1": x1[b], + "y1": y1[b], + "line": {"color": b_colors[b], "width": b_widths[b]}, + } + for b in c.static.index + ] + ) + + shape_traces.append( + { + "x": 0.5 * (x0 + x1), + "y": 0.5 * (y0 + y1), + "text": b_text, + "type": "scatter", + "mode": "markers", + "hoverinfo": "text", + "marker": {"opacity": 0.0}, + } + ) + + if mapbox: + shape_traces_latlon = [] + for st in shape_traces: + st["lon"] = st.pop("x") + st["lat"] = st.pop("y") + shape_traces_latlon.append(go.Scattermapbox(st)) + shape_traces = shape_traces_latlon + + shapes_mapbox = [] + for s in shapes: + s["lon"] = [s.pop("x0"), s.pop("x1")] + s["lat"] = [s.pop("y0"), s.pop("y1")] + shapes_mapbox.append(go.Scattermapbox(s, mode="lines")) + shapes = shapes_mapbox + + bus_trace["lon"] = bus_trace.pop("x") + bus_trace["lat"] = bus_trace.pop("y") + bus_trace = go.Scattermapbox(bus_trace) + + fig["data"].extend(shapes + shape_traces + [bus_trace]) + else: + fig["data"].extend([bus_trace] + shape_traces) + + fig["layout"].update({"title": title, "hovermode": "closest", "showlegend": False}) + + if size is not None: + if len(size) != 2: + msg = "Parameter size must specify a tuple (width, height)." + raise ValueError(msg) + fig["layout"].update({"width": size[0], "height": size[1]}) + + if mapbox: + if mapbox_token != "": + mapbox_parameters["accesstoken"] = mapbox_token + + mapbox_parameters.setdefault("style", mapbox_style) + + if ( + mapbox_parameters["style"] in _token_required_mb_styles + and "accesstoken" not in mapbox_parameters + ): + msg = ( + "Using Mapbox layout styles requires a valid access token from " + "[https://www.mapbox.com/](https://www.mapbox.com/), style which do not require a token " + "are:\n{', '.join(_open__mb_styles)}." + ) + raise ValueError(msg) + + if "center" not in mapbox_parameters: + lon = (n.c.buses.static.x.min() + n.c.buses.static.x.max()) / 2 + lat = (n.c.buses.static.y.min() + n.c.buses.static.y.max()) / 2 + mapbox_parameters["center"] = {"lat": lat, "lon": lon} + + if "zoom" not in mapbox_parameters: + mapbox_parameters["zoom"] = 2 + + fig["layout"]["mapbox"] = mapbox_parameters + else: + fig["layout"]["shapes"] = shapes + + if iplot: + pltly.iplot(fig) + + return fig + + +class PydeckPlotter: + """Class to create and manage an interactive pydeck map for a PyPSA network.""" + + # Class-level constants + VALID_MAP_STYLES = { + "light": pdk.map_styles.LIGHT, + "dark": pdk.map_styles.DARK, + "road": pdk.map_styles.ROAD, + "dark_no_labels": pdk.map_styles.DARK_NO_LABELS, + "light_no_labels": pdk.map_styles.LIGHT_NO_LABELS, + "none": "", + } + ARROW = np.array( + [ + [1, 0], # reference triangle as arrow head + [0, 0.5], # base right + [0, -0.5], # base left + ] + ) + ARROW = ARROW - ARROW.mean(axis=0) # center at geometric center + + PROJ = pyproj.Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True) + PROJ_INV = pyproj.Transformer.from_crs("EPSG:3857", "EPSG:4326", always_xy=True) + + def __init__( + self, + n: "Network", + map_style: str, + view_state: dict | pdk.ViewState | None = None, + layouter: Callable | None = None, + jitter: float | None = None, + ) -> None: + """Initialize the PydeckPlotter. + + Parameters + ---------- + n : Network + The PyPSA network to plot. + map_style : str + Map style to use for the plot. One of 'light', 'dark', 'road', 'dark_no_labels', 'light_no_labels', and 'none'. + view_state : dict/pdk.ViewState/None, optional + Initial view state for the map. If None, a default view state is created. + If a dict is provided, it should contain keys like 'longitude', 'latitude', 'zoom', 'pitch', and 'bearing'. + layouter : Callable | None, optional + Layouting function from [networkx](https://networkx.github.io/) which + overrules coordinates given in `n.buses[['x', 'y']]`. See + [list](https://networkx.github.io/documentation/stable/reference/drawing.html#module-networkx.drawing.layout) + of available options. + jitter : float, optional + Amount of random noise to add to node positions + + """ + self._n: Network = n + self._x: pd.Series + self._y: pd.Series + self._init_xy(layouter=layouter) + if jitter: + self._x, self._y = add_jitter(x=self._x, y=self._y, jitter=jitter) + + self._map_style: str = self._init_map_style(map_style) + self._view_state: pdk.ViewState = self._init_view_state( + view_state=view_state, + ) + self._layers: dict[str, pdk.Layer] = {} + self._tooltip_style: dict[str, str] = set_tooltip_style() + self._component_data: dict[str, pd.DataFrame] = {} + + @property + def map_style(self) -> str: + """Get the current map style.""" + return self._map_style + + @property + def tooltip_style(self) -> dict: + """Get the current tooltip CSS styles.""" + return self._tooltip_style + + @property + def layers(self) -> dict[str, pdk.Layer]: + """Get the layers of the interactive map.""" + return self._layers + + def _init_xy( + self, + layouter: Callable | None = None, + ) -> None: + """Initialize x and y coordinates from the network buses.""" + buses = self._n.c.buses.static + + # Check if all x and y are missing/zero → then fallback to layouter + is_empty = (buses[["x", "y"]].isnull() | (buses[["x", "y"]] == 0)).all().all() + + if layouter or is_empty: + self._x, self._y = apply_layouter(self._n, layouter, inplace=False) + else: + self._x, self._y = buses["x"], buses["y"] + + # Validation mask for WGS84 coordinates + valid = ( + self._x.notnull() + & self._y.notnull() + & (self._x >= -180) + & (self._x <= 180) # longitude + & (self._y >= -90) + & (self._y <= 90) # latitude + ) + + # Keep only valid buses + dropped = (~valid).sum() + if dropped: + logger.warning("Dropping %d buses with invalid WGS84 coordinates", dropped) + self._x, self._y = self._x[valid], self._y[valid] + + def _init_map_style(self, map_style: str) -> str: + """Set the initial map style for the interactive map.""" + if map_style not in self.VALID_MAP_STYLES: + msg = ( + f"Invalid map style '{map_style}'.\n" + f"Must be one of: {', '.join(self.VALID_MAP_STYLES)}." + ) + raise ValueError(msg) + return map_style + + def _init_view_state( + self, + view_state: dict | pdk.ViewState | None = None, + ) -> pdk.ViewState: + """Compute the initial view state based on network bus coordinates. + + Parameters + ---------- + view_state : dict/pdk.ViewState/None, optional + Initial view state for the map. If None, a default view state is created. + If a dict is provided, it should contain keys like 'longitude', 'latitude', 'zoom', 'pitch', and 'bearing'. + + Returns + ------- + pdk.ViewState + The initialized view state for the map. + + """ + if isinstance(view_state, pdk.ViewState): + return view_state + + vs = { + "longitude": self._x.mean(), + "latitude": self._y.mean(), + "zoom": 4, + "min_zoom": None, + "max_zoom": None, + "pitch": 0, + "bearing": 0, + } + + if isinstance(view_state, dict): + vs.update(view_state) + + return pdk.ViewState(**vs) + + @property + def view_state(self) -> pdk.ViewState: + """Get the current view state of the map. + + Returns + ------- + pdk.ViewState + The current view state of the map. + + """ + return self._view_state + + @staticmethod + def _make_arrows( + flow: float, + p0_geo: tuple[float, float], + p1_geo: tuple[float, float], + arrow_size_factor: float, + ) -> list[tuple[float, float]]: + """Create arrows scaled and projected for a given flow and p0, p1 geographical coordinates. Additional scaling by arrow_size_factor. + + Parameters + ---------- + flow : float + Flow value to determine arrow direction and size. + p0_geo : tuple + Geographical coordinates (lon, lat) of the start point. + p1_geo : tuple + Geographical coordinates (lon, lat) of the end point. + arrow_size_factor : float + Factor to scale the arrow size. + + Returns + ------- + list of tuple + List of (lon, lat) tuples representing the arrow polygon coordinates. + + """ + # project end points from lon/lat into meters + p0_m = PydeckPlotter.PROJ.transform(*p0_geo) + p1_m = PydeckPlotter.PROJ.transform(*p1_geo) + + # return empty list if any bus coordinates are nan + if np.any(np.isnan(p0_m)) or np.any(np.isnan(p1_m)): + return [] + + # center point (tuple) between p0 and p1 and angle + p_center = calculate_midpoint(p0_m, p1_m) + angle = calculate_angle(p0_m, p1_m) + + # geometric operations on arrow + base_width_m = abs(flow) * arrow_size_factor + arrow = scale_polygon_by_width(PydeckPlotter.ARROW, base_width_m) + if flow < 0: + arrow = flip_polygon(arrow, "y") + arrow = rotate_polygon(arrow, angle) + + # transform back to lon/lat relative to center point + coords_center = PydeckPlotter.PROJ_INV.transform(*p_center) + arrow = meters_to_lonlat(arrow, coords_center) + + return arrow.tolist() + + # Data wrangling + def prepare_component_data( + self, + component: str, + default_columns: list[str] | None = None, + extra_columns: list[str] | None = None, + ) -> pd.DataFrame: + """Prepare data for a specific component type. + + Parameters + ---------- + component : str + Name of the component type, e.g. "Bus", "Line", "Link", "Transformer". + default_columns : list of str, optional + List of default columns to include for the component. + extra_columns : list of str, optional + Additional columns to include for the component. + + Returns + ------- + pandas.DataFrame + DataFrame containing the prepared data for the component. + + """ + df = as_components(self._n, component).static + if default_columns is None: + default_columns = [] + + layer_columns = default_columns + + if extra_columns: + extra_columns = [ + col for col in extra_columns if col not in default_columns + ] # Drop default columns from extra_columns + missing_columns = [col for col in extra_columns if col not in df.columns] + valid_columns = [col for col in extra_columns if col in df.columns] + + if missing_columns: + msg = ( + f"Columns {missing_columns} not found in {component}. " + f"Using only valid columns: {valid_columns}." + ) + logger.warning(msg) + + layer_columns.extend(valid_columns) + + df = df[layer_columns].copy() + df.index.name = "name" + + return df + + # Layer functions + def add_bus_layer( + self, + bus_size: float | dict | pd.Series = 25, # km² + bus_size_factor: float = 1.0, + bus_size_max: float = 10000, # km² + bus_color: str | dict | pd.Series = "cadetblue", + bus_cmap: str | mcolors.Colormap = "Reds", + bus_cmap_norm: mcolors.Normalize | None = None, + bus_alpha: float | dict | pd.Series = 0.9, + bus_columns: list | None = None, + auto_scale: bool = False, + tooltip: bool = True, + ) -> None: + """Add a bus layer of Pydeck type ScatterplotLayer to the interactive map. + + Parameters + ---------- + bus_size : float/dict/pandas.Series + Sizes of bus points in km² (corresponds to circle area), defaults to 25 km². + bus_size_factor : float, default 1.0 + Bus sizes are scaled by this factor. + bus_size_max : float, default 10000 + Maximum size of bus points in km² when auto-scaling. + bus_color : str/dict/pandas.Series + Colors for the buses, defaults to "cadetblue". If bus_size is a + pandas.Series with a Multiindex, bus_color defaults to the + n.c.carriers.static['color'] column. + bus_cmap : mcolors.Colormap/str, default 'Reds' + If bus_color are floats, this color map will assign the colors. + bus_cmap_norm : mcolors.Normalize/None + Normalization for bus_cmap, defaults to None. + bus_alpha : float/dict/pandas.Series + Add alpha channel to buses, defaults to 0.9. + bus_columns : list, default None + List of bus columns to include. + auto_scale : bool, default False + Whether to auto-scale bus sizes to fit within bus_size_max. + tooltip : bool, default True + Whether to show a tooltip on hover. + + Returns + ------- + None + + """ + msg = "bus_size_factor must be non-negative" + if bus_size_factor < 0: + raise ValueError(msg) + + bus_data = self.prepare_component_data("Bus", extra_columns=bus_columns) + + valid_idx = self._x.index.intersection(bus_data.index) + bus_data = bus_data.loc[valid_idx].copy() + self._component_data["Bus"] = bus_data + bus_data["x"] = self._x.loc[bus_data.index] + bus_data["y"] = self._y.loc[bus_data.index] + + # Handle bus sizes + bus_size_series = _convert_to_series(bus_size, bus_data.index).clip(lower=0) + if bus_size_series.eq(0).all(): + return + bus_data["size"] = bus_size_series + + if auto_scale: + bus_size_series = scale_to_max_abs(bus_size_series, bus_size_max) + else: + bus_size_series = bus_size_series * bus_size_factor + + bus_data["size_pdk"] = ( + bus_size_series * 1e6 / np.pi + ) ** 0.5 # convert to meters for Pydeck + + # 6. Handle colors and alpha + color_series = _convert_to_series(bus_color, bus_data.index) + alpha_series = _convert_to_series(bus_alpha, bus_data.index) + color_series = apply_cmap(color_series, bus_cmap, bus_cmap_norm) + bus_data["rgba"] = [ + to_rgba255(c, a) for c, a in zip(color_series, alpha_series, strict=False) + ] + + # 7. Create tooltips if requested + if tooltip: + self.create_tooltips("Bus") + + # 8. Create Pydeck layer + layer = pdk.Layer( + "ScatterplotLayer", + data=bus_data, + get_position=["x", "y"], + get_color="rgba", + get_radius="size_pdk", + pickable=True, + auto_highlight=True, + parameters={"depthTest": False}, # prevent z-fighting + ) + + # 9. Store the layer + self._layers["Bus"] = layer + + @staticmethod + def _pie_slice_vertices( + p_center: tuple[float, float], + radius: float, + start_angle: float, + end_angle: float, + points_per_radian: int = 5, + ) -> list[list[float]]: + """Generate vertices of a pie slice as a closed polygon using numpy. + + Parameters + ---------- + p_center : tuple of float + Center point of the pie chart as (lon, lat). + radius : float + Radius of the pie chart in meters. + start_angle : float + Starting angle of the pie slice in radians. + end_angle : float + Ending angle of the pie slice in radians. + points_per_radian : int, default 5 + Number of points per radian for pie chart resolution. + + Returns + ------- + list of list of float + List of [lon, lat] coordinates representing the pie slice polygon. + + """ + angle_span = end_angle - start_angle + steps = max(1, int(np.ceil(points_per_radian * abs(angle_span)))) + angles = np.linspace(start_angle, end_angle, steps + 1) + + # arc vertices in meters + x = radius * np.cos(angles) + y = radius * np.sin(angles) + arc = np.column_stack((x, y)) # create 2D arc polygon + arc = meters_to_lonlat( + arc, + p_center, + ) # convert arc vertices into lon/lat relative to center point + + # full slice polygon (closed polygon center -> arc -> back to center) + coords = np.vstack( + ( + p_center, + arc, + p_center, + ) + ) + return ( + coords.tolist() + ) # object needs to be list for pydeck's json serialisation to work + + @staticmethod + def _make_pie( + bus: str, + p_center: tuple[float, float], + radius_m: float, + values: np.ndarray, + colors: list[list[int]], + labels: list[str], + points_per_radian: int = 5, + flip_y: bool = False, + bus_split_circle: bool = False, + ) -> list[dict]: + """Create pie chart polygons with metadata using numpy. + + Parameters + ---------- + bus : str + Name of the bus for which the pie chart is created. + p_center : tuple of float + Center point of the pie chart as (lon, lat). + radius_m : float + Radius of the pie chart in meters. + values : np.ndarray + Values for each pie slice. + colors : list of list of int + List of RGBA colors for each pie slice. + labels : list of str + Labels for each pie slice. + points_per_radian : int, default 5 + Number of points per radian for pie chart resolution. + flip_y : bool, default False + Flip the pie chart vertically. Useful for negative values in bus_split_circle mode. + bus_split_circle : bool, default False + Draw half circles if True. The upper half circle includes all positive values, + the lower half circle all negative values. + + Returns + ------- + list of dict + List of dictionaries containing pie slice polygons and metadata. + + """ + EPS = 1e-6 + if len(values) == 0 or ( + np.sum(values[values > 0]) < EPS and np.sum(values[values < 0]) > -EPS + ): + return [] + + circ = np.pi if bus_split_circle else 2 * np.pi + flip_y_factor = -1 if flip_y else 1 + rotate_by_quarter = 0 if bus_split_circle else np.pi / 2 + + angles = flip_y_factor * np.array(values) / np.sum(values) * circ + start_angles = np.concatenate(([0], np.cumsum(angles)[:-1])) + rotate_by_quarter + + polygons = [ + { + "polygon": PydeckPlotter._pie_slice_vertices( + p_center, + radius_m, + start, + start + delta, + points_per_radian, + ), + "color": color, + "bus": bus, + "label": label, + "size": val, + } + for val, color, label, start, delta in zip( + values, colors, labels, start_angles, angles, strict=False + ) + ] + return polygons + + def add_pie_chart_layer( + self, + bus_size: pd.Series, + bus_size_factor: float | None = None, + bus_size_max: float = 10000, # km² + bus_split_circle: bool = False, + bus_alpha: float | dict | pd.Series = 0.9, + bus_columns: list | None = None, + points_per_radian: int = 5, + auto_scale: bool = False, + tooltip: bool = True, + ) -> None: + """Add a bus layer of Pydeck type ScatterplotLayer to the interactive map. + + Parameters + ---------- + bus_size : float/dict/pandas.Series + Sizes of bus points in radius² (km²), defaults to 25. + bus_size_factor : float, default 1.0 + Bus sizes are scaled by this factor. + bus_size_max : float, default 10000 + Maximum size of bus points in km² when auto-scaling. + bus_split_circle : bool, default False + Draw half circles if bus_size is a pandas.Series with a Multiindex. + If set to true, the upper half circle per bus then includes all positive values + of the series, the lower half circle all negative values. Defaults to False. + bus_alpha : float/dict/pandas.Series + Add alpha channel to buses, defaults to 0.9. + bus_columns : list, default None + List of bus columns to include. + points_per_radian : int, default 5 + Number of points per radian for pie chart resolution. + auto_scale : bool, default False + Whether to auto-scale bus sizes to fit within bus_size_max. + tooltip : bool, default True + Whether to show a tooltip on hover. + + Returns + ------- + None + + """ + EPS = 1e-6 # Small epsilon to avoid numerical issues + bus_data = self.prepare_component_data( + "Bus", + extra_columns=bus_columns, + ) + + # Only keep buses with valid coordinates, same index order as self._x and self._y + bus_data = bus_data.loc[self._x.index[self._x.index.isin(bus_data.index)]] + + # Drop tiny values to avoid numerical issues + bus_size = bus_size.drop(bus_size[abs(bus_size) < EPS].index) + + # Reindex first level of MultiIndex to only valid buses + bus_size = bus_size.reindex( + bus_data.index.intersection(bus_size.index.get_level_values(0)), + level=0, + ) + bus_size = bus_size.unstack(level=1, fill_value=0) + carrier_order = bus_size.columns.to_numpy() + + valid_buses = bus_data.index.intersection(bus_size.index) + bus_size = bus_size.loc[valid_buses] + + # --- Split positive and negative contributions --- + bus_area_pos = bus_size.clip(lower=0).sum(axis=1) # positive only + bus_area_neg = (-bus_size.clip(upper=0)).sum(axis=1) # negative magnitudes + + # --- Global scaling factor based on largest magnitude --- + max_val = max(bus_area_pos.max(), bus_area_neg.max()) + + if auto_scale: + scale_factor = bus_size_max / max_val if max_val > 0 else 1 + else: + scale_factor = bus_size_factor + + # Apply scaling + bus_area_pos = bus_area_pos * scale_factor + bus_area_neg = bus_area_neg * scale_factor + + # Convert to m² + bus_area_pos = bus_area_pos * 1e6 + bus_area_neg = bus_area_neg * 1e6 + + # Radii in meters + bus_radius_pos = (bus_area_pos / np.pi) ** 0.5 + bus_radius_neg = (bus_area_neg / np.pi) ** 0.5 + + # Convert to NumPy arrays for speed-up + bus_coords = np.column_stack( + [self._x.loc[valid_buses], self._y.loc[valid_buses]] + ) # assumes that bus_data is aligned with self._x and self._y, done above + bus_indices = valid_buses.to_numpy() + bus_values = bus_size.to_numpy() + + alphas = _convert_to_series(bus_alpha, bus_size.index) + carrier_colors = self._n.c.carriers.static["color"] + carrier_rgba = { + bus: {c: to_rgba255(col, alphas[bus]) for c, col in carrier_colors.items()} + for bus in bus_size.index + } + + polygons = [] + for i, bus in enumerate(bus_indices): + values = bus_values[i] + x, y = bus_coords[i] + + pos_mask = values > 0 + neg_mask = values < 0 + + if bus_split_circle and np.any(values < 0): + if np.any(pos_mask) and bus_radius_pos[bus] > 0: + colors = [carrier_rgba[bus][c] for c in carrier_order[pos_mask]] + labels = list(carrier_order[pos_mask]) + vals = values[pos_mask].round(3) + + poly_pos = PydeckPlotter._make_pie( + bus=bus, + p_center=(x, y), + radius_m=bus_radius_pos[bus], + values=vals, + colors=colors, + labels=labels, + points_per_radian=points_per_radian, + flip_y=False, + bus_split_circle=True, + ) + polygons.extend(poly_pos) + + if np.any(neg_mask) and bus_radius_neg[bus] > 0: + colors = [carrier_rgba[bus][c] for c in carrier_order[neg_mask]] + labels = list(carrier_order[neg_mask]) + vals = (-values[neg_mask]).round(3) + poly_neg = PydeckPlotter._make_pie( + bus=bus, + p_center=(x, y), + radius_m=bus_radius_neg[bus], + values=vals, + colors=colors, + labels=labels, + points_per_radian=points_per_radian, + flip_y=True, + bus_split_circle=True, + ) + polygons.extend(poly_neg) + elif np.any(pos_mask) and bus_radius_pos[bus] > 0: + colors = [carrier_rgba[bus][c] for c in carrier_order[pos_mask]] + labels = list(carrier_order[pos_mask]) + vals = values[pos_mask].round(3) + poly_pos = PydeckPlotter._make_pie( + bus=bus, + p_center=(x, y), + radius_m=bus_radius_pos[bus], + values=vals, + colors=colors, + labels=labels, + points_per_radian=points_per_radian, + flip_y=False, + bus_split_circle=False, + ) + polygons.extend(poly_pos) + + p_data = pd.DataFrame(polygons) + p_data.set_index("bus", inplace=True) + + # Tooltip + if tooltip: + columns = ["label", "size"] + bus_data.columns.tolist() + p_data = bus_data.reindex(p_data.index).join(p_data) + + p_data["tooltip_html"] = df_to_html_table( + p_data, + columns=columns, + rounding=2, + value_align="left", + max_header_length=30, + ) + + layer = pdk.Layer( + "PolygonLayer", + data=p_data, + get_polygon="polygon", + get_fill_color="color", + pickable=True, + auto_highlight=True, + parameters={ + "depthTest": False # To prevent z-fighting issues/flickering in 3D space + }, + ) + + self._layers["PieChart"] = layer + + def init_branch_component_data( + self, + c_name: str, + branch_columns: list | None = None, + ) -> None: + """Initialize data for a specific branch component type. + + Parameters + ---------- + c_name : str + Name of the branch component type, e.g. "Line", "Link", "Transformer + branch_columns : list, default None + List of branch columns to include. Specify additional columns to include in the tooltip. + + Returns + ------- + None + + """ + if as_components(self._n, c_name).empty: + msg = f"No data found for component '{c_name}'. Skipping layer creation." + logger.warning(msg) + return + + # Prepare data for lines + c_data = self.prepare_component_data( + c_name, + default_columns=["bus0", "bus1"], + extra_columns=branch_columns, + ) + + # Only keep rows where both bus0 and bus1 are present + valid = c_data["bus0"].isin(self._x.index) & c_data["bus1"].isin(self._x.index) + if not valid.all(): + dropped = (~valid).sum() + logger.warning( + "Dropping %d row(s) in '%s' with missing buses", dropped, c_name + ) + c_data = c_data[valid] + + if c_data.empty: + return + + self._component_data[c_name] = c_data + + def create_branch_paths( + self, + c_name: str, + geometry: bool = False, + ) -> None: + """Create path geometries for a specific branch component type. + + Parameters + ---------- + c_name : str + Name of the branch component type, e.g. "Line", "Link", "Transformer". + geometry : bool, default False + Whether to use the geometry column of the branch components. + + Returns + ------- + None + + """ + c_data = self._component_data[c_name] + static_data = as_components(self._n, c_name).static + + # Build path column as list of [lon, lat] pairs for each line + # Assuming x, y are aligned + if geometry and "geometry" in static_data.columns: + geoms = static_data["geometry"].reindex(c_data.index) + branch_paths = series_to_pdk_path(geoms) + else: + branch_paths = [ + [[x0, y0], [x1, y1]] + for x0, y0, x1, y1 in zip( + self._x.loc[c_data["bus0"]], + self._y.loc[c_data["bus0"]], + self._x.loc[c_data["bus1"]], + self._y.loc[c_data["bus1"]], + strict=False, + ) + ] + c_data["path"] = branch_paths + + def create_branch_color( + self, + c_name: str, + branch_color: str | dict | pd.Series = "rosybrown", + branch_cmap: str | mcolors.Colormap = "viridis", + branch_cmap_norm: mcolors.Normalize | None = None, + branch_alpha: float | dict | pd.Series = 0.9, + ) -> None: + """Create colors for a specific branch component type. + + Parameters + ---------- + c_name : str + Name of the branch component type, e.g. "Line", "Link", "Transformer + branch_color : str/dict/pandas.Series + Colors for the branch component, defaults to 'rosybrown'. + branch_cmap : str/matplotlib.colors.Colormap, default 'viridis' + Colormap to use if branch_color is a numeric pandas.Series. + branch_cmap_norm : matplotlib.colors.Normalize, optional + Normalization to use if branch_color is a numeric pandas.Series. + branch_alpha : float/dict/pandas.Series + Add alpha channel to branch components, defaults to 0.9. + + Returns + ------- + None + + """ + c_data = self._component_data[c_name] + create_rgba_colors( + df=c_data, + color=branch_color, + cmap=branch_cmap, + cmap_norm=branch_cmap_norm, + alpha=branch_alpha, + target_col="rgba", + ) + + # Add branch layer + def create_branch_layer( + self, + c_name: str, + ) -> None: + """Add a line layer of Pydeck type PathLayer to the interactive map. + + Parameters + ---------- + c_name : str + Name of the branch component type, e.g. "Line", "Link", "Transformer". + + Returns + ------- + None + + """ + c_data = self._component_data[c_name] + + # Create PathLayer, use "path" column for get_path + layer = pdk.Layer( + "PathLayer", + data=c_data.reset_index(), + get_path="path", + get_width="width_pdk", + get_color="rgba", + pickable=True, + auto_highlight=True, + parameters={ + "depthTest": False + }, # To prevent z-fighting issues/flickering in 3D space + ) + + self._layers[c_name] = layer + + def init_arrow_data( + self, + c_name: str, + branch_flow: float | dict | pd.Series = 0, + arrow_size_factor: float = 1.5, + ) -> None: + """Initialize arrow data for a specific branch component type. + + Parameters + ---------- + c_name : str + Name of the branch component type, e.g. "Line", "Link", "Transformer + branch_flow : float/dict/pandas.Series + Flow values for the branch component, defaults to 0. + If not 0, arrows will be drawn on the lines. + arrow_size_factor : float, default 1.5 + Factor to scale the arrow size. If 0, no arrows will be drawn. + + Returns + ------- + None + + """ + c_data = self._component_data[c_name] + + # Arrow layer + branch_flow = _convert_to_series(branch_flow, c_data.index) + branch_flow = branch_flow * 1e3 # Convert flow from km to m + flows_are_zero = branch_flow.eq(0).all() + + if ( + not as_components(self._n, c_name).empty + and not flows_are_zero + and arrow_size_factor != 0 + ): + c_data["flow"] = c_data.index.map(branch_flow) + + def create_arrows( + self, + c_name: str, + arrow_size_factor: float = 1.5, + ) -> None: + """Create and scale arrows for a specific branch component type. + + Parameters + ---------- + c_name : str + Name of the branch component type, e.g. "Line", "Link", "Transformer". + arrow_size_factor : float, default 1.5 + Factor to scale the arrow size. If 0, no arrows will be drawn. + + """ + c_data = self._component_data[c_name] + + def center_segment(path: list[list[float]]) -> tuple[list[float], list[float]]: + """Return the two “center” points of a path for arrow placement.""" + n = len(path) + mid_idx = n // 2 + return path[mid_idx - 1], path[mid_idx] + + # Precompute start/end points for arrows + arrow_points = c_data["path"].apply( + lambda path: center_segment(path) if len(path) > 2 else (path[0], path[-1]) + ) + + # Apply _make_arrows using the precomputed points + c_data["arrow"] = c_data.apply( + lambda row: PydeckPlotter._make_arrows( + row["flow_pdk"], + arrow_points.loc[row.name][0], # p0_geo + arrow_points.loc[row.name][1], # p1_geo + arrow_size_factor, + ), + axis=1, + ) + + def create_arrow_color( + self, + c_name: str, + arrow_color: str | dict | pd.Series | None = None, + arrow_cmap: str | mcolors.Colormap = "viridis", + arrow_cmap_norm: mcolors.Normalize | None = None, + arrow_alpha: float | dict | pd.Series = 0.9, + ) -> None: + """Create arrow colors for a specific branch component type. + + Parameters + ---------- + c_name : str + Name of the branch component type, e.g. "Line", "Link", "Transformer + arrow_color : str/dict/pandas.Series/None + Colors for the arrows, defaults to None. + If None, the branch color is used for the arrows. + arrow_cmap : str/matplotlib.colors.Colormap, default 'viridis' + Colormap to use if arrow_color is a numeric pandas.Series. + arrow_cmap_norm : matplotlib.colors.Normalize, optional + Normalization to use if arrow_color is a numeric pandas.Series. + arrow_alpha : float/dict/pandas.Series + Add alpha channel to arrows, defaults to 0.9. + + Returns + ------- + None + + """ + c_data = self._component_data[c_name] + create_rgba_colors( + df=c_data, + color=arrow_color, + cmap=arrow_cmap, + cmap_norm=arrow_cmap_norm, + alpha=arrow_alpha, + target_col="rgba_arrow", + fallback_col="rgba", # reuse branch colors if arrow_color is None + ) + + def create_arrow_layer( + self, + c_name: str, + ) -> None: + """Create arrow PolygonLayer for a specific branch component type. + + Parameters + ---------- + c_name : str + Name of the branch component type, e.g. "Line", "Link", "Transformer + + Returns + ------- + None + + """ + c_data = self._component_data[c_name] + + layer = pdk.Layer( + "PolygonLayer", + data=c_data.reset_index(), + get_polygon="arrow", + get_fill_color="rgba_arrow", + pickable=True, + auto_highlight=True, + parameters={ + "depthTest": False + }, # To prevent z-fighting issues/flickering in 3D space + ) + self._layers[f"{c_name}_arrows"] = layer + + def scale_branch_param( + self, + c_name: str, + branch_param_name: str, + branch_param: float | dict | pd.Series = 2, + branch_param_factor: float = 1, + branch_param_max: float = 10, # km + global_param_max: float | None = 1, # km + keep_algebraic_sign: bool = True, + auto_scale: bool = False, + ) -> None: + """Scale branch params (width, flow) for a specific branch component type. + + Parameters + ---------- + c_name : str + Name of the branch component type, e.g. "Line", "Link", "Transformer + branch_param_name : str + Name of the branch parameter to be scaled, e.g. "width", "flow". + branch_param : float/dict/pandas.Series/None + Parameter of branch component in km. If None, width falls back to 1.5 km. + branch_param_factor : float/None + If None, branch params are auto-scaled to branch_param_max. + If a float is provided, branch params are scaled by this factor. + branch_param_max : float, default 10 + Maximum param of branch component in km when auto-scaling. + global_param_max : float/None, default 1 + If multiple branch components are plotted, this ensures that the maximum params are scaled proportionally. + keep_algebraic_sign : bool, default True + If True, the algebraic sign of branch_param is readded to the scaled parameter. + auto_scale : bool, default False + Whether to auto-scale branch params to fit within branch_param_max. + + Returns + ------- + None + + """ + c_data = self._component_data[c_name] + + branch_param_series: pd.Series = _convert_to_series(branch_param, c_data.index) + + if branch_param_series.eq(0).all(): + c_data[f"{branch_param_name}_pdk"] = 0 + return + + c_data[branch_param_name] = branch_param_series + + # Use absolute values + sign = ( + branch_param_series.apply(np.sign) + if keep_algebraic_sign + else pd.Series(1, index=branch_param_series.index) + ) + branch_param_deck = branch_param_series.abs() + local_param_max = branch_param_deck.max() + + if auto_scale: + # if global_param_max is None or global_param_max == 0: + # global_param_max = local_param_max + + scaling_factor = local_param_max / global_param_max * 1e3 + branch_param_deck = scaling_factor * scale_to_max_abs( + branch_param_deck, branch_param_max + ) + else: + branch_param_deck = branch_param_deck * branch_param_factor * 1e3 + + c_data[f"{branch_param_name}_pdk"] = branch_param_deck * sign + + def create_tooltips( + self, + c_name: str, + columns: list | None = None, + ) -> None: + """Create tooltip HTML for a specific branch component type. + + Parameters + ---------- + c_name : str + Name of the branch component type, e.g. "Line", "Link", "Transformer". + columns : list, default None + List of branch columns to include. If None, all columns are used. + + Returns + ------- + None + + """ + c_data = self._component_data[c_name] + if columns is None: + columns = list(c_data.columns) + + exclude_cols = [ + "path", + "width_pdk", + "arrow", + "rgba", + "rgba_arrow", + "geometry", + "size_pdk", + ] + columns = [ + col for col in columns if col not in exclude_cols and col in c_data.columns + ] + + c_data["tooltip_html"] = df_to_html_table( + c_data, + columns=columns, + rounding=2, + value_align="left", + max_header_length=30, + ) + + def add_branch_and_arrow_layer( + self, + branch_components: list | set | None = None, + branch_width_factor: float = 1, + branch_width_max: float = 10, # km + line_flow: float | dict | pd.Series = 0, + line_color: str | dict | pd.Series = "rosybrown", + line_cmap: str | mcolors.Colormap = "viridis", + line_cmap_norm: mcolors.Normalize | None = None, + line_alpha: float | dict | pd.Series = 0.9, + line_width: float | dict | pd.Series = 2, + line_columns: list | None = None, + link_flow: float | dict | pd.Series = 0, + link_color: str | dict | pd.Series = "darkorange", + link_cmap: str | mcolors.Colormap = "viridis", + link_cmap_norm: mcolors.Normalize | None = None, + link_alpha: float | dict | pd.Series = 0.9, + link_width: float | dict | pd.Series = 2, + link_columns: list | None = None, + transformer_flow: float | dict | pd.Series = 0, + transformer_color: str | dict | pd.Series = "purple", + transformer_cmap: str | mcolors.Colormap = "viridis", + transformer_cmap_norm: mcolors.Normalize | None = None, + transformer_alpha: float | dict | pd.Series = 0.9, + transformer_width: float | dict | pd.Series = 2, + transformer_columns: list | None = None, + arrow_color: str | dict | pd.Series | None = None, + arrow_cmap: str | mcolors.Colormap = "viridis", + arrow_cmap_norm: mcolors.Normalize | None = None, + arrow_alpha: float | dict | pd.Series = 0.9, + arrow_size_factor: float = 1.5, + geometry: bool = False, + auto_scale: bool = False, + tooltip: bool = True, + ) -> None: + """Add branch and arrow layers of Pydeck type PathLayer and PolygonLayer to the interactive map. + + Parameters + ---------- + branch_components : list, set, optional, default ['Line', 'Link', 'Transformer'] + Branch components to be plotted. + branch_width_factor : float, default 1.0 + Branch widths are scaled by this factor. + branch_width_max : float, default 10 + Maximum width of branch component in km when auto-scaling. + line_flow : float/dict/pandas.Series, default 0 + Series of line flows indexed by line names, defaults to 0. If 0, no arrows will be created. + If a float is provided, it will be used as a constant flow for all lines. + line_color : str/dict/pandas.Series + Colors for the lines, defaults to 'rosybrown'. + line_cmap : matplotlib.colors.Colormap/str + If line_color are floats, this color map will assign the colors. + line_cmap_norm : matplotlib.colors.Normalize + The norm applied to the line_cmap. + line_alpha : float/dict/pandas.Series + Add alpha channel to lines, defaults to 0.9. + line_width : float/dict/pandas.Series, default 2 + Widths of line component in km. + link_flow : float/dict/pandas.Series, default 0 + Series of link flows indexed by link names, defaults to 0. If 0, no arrows will be created. + If a float is provided, it will be used as a constant flow for all links. + link_color : str/dict/pandas.Series + Colors for the links, defaults to 'darkseagreen'. + link_cmap : matplotlib.colors.Colormap/str, default 'viridis' + If link_color are floats, this color map will assign the colors. + link_cmap_norm : matplotlib.colors.Normalize|matplotlib.colors.*Norm + The norm applied to the link_cmap. + link_alpha : float/dict/pandas.Series + Add alpha channel to links, defaults to 0.9. + link_width : float/dict/pandas.Series, default 2 + Widths of link component in km. + transformer_flow : float/dict/pandas.Series, default 0 + Series of transformer flows indexed by transformer names, defaults to 0. If 0, no arrows will be created. + If a float is provided, it will be used as a constant flow for all transformers. + transformer_color : str/dict/pandas.Series + Colors for the transformers, defaults to 'orange'. + transformer_cmap : matplotlib.colors.Colormap/str + If transformer_color are floats, this color map will assign the colors. + transformer_cmap_norm : matplotlib.colors.Normalize|matplotlib.colors.*Norm + The norm applied to the transformer_cmap. + transformer_alpha : float/dict/pandas.Series + Add alpha channel to transformers, defaults to 0.9. + transformer_width : float/dict/pandas.Series, default 2 + Widths of transformer in km. + arrow_color : str/dict/pandas.Series/None + Colors for the arrows, defaults to None. + If None, the branch color is used for the arrows. + arrow_cmap : str/matplotlib.colors.Colormap, default 'viridis' + Colormap to use if arrow_color is a numeric pandas.Series. + arrow_cmap_norm : matplotlib.colors.Normalize, optional + Normalization to use if arrow_color is a numeric pandas.Series. + arrow_alpha : float/dict/pandas.Series + Add alpha channel to arrows, defaults to 0.9. + arrow_size_factor : float, default 1.5 + Factor to scale the arrow size. If 0, no arrows will be drawn. + geometry : bool, default False + Whether to use the geometry column of the branch components. + auto_scale : bool, default False + Whether to auto-scale branch widths to fit within branch_width_max. + line_columns : list, default None + List of line columns to include. Specify additional columns to include in the tooltip. + link_columns : list, default None + List of link columns to include. Specify additional columns to include in the tooltip. + transformer_columns : list, default None + List of transformer columns to include. Specify additional columns to include in the tooltip. + tooltip : bool, default True + Whether to show a tooltip on hover. + + Returns + ------- + None + + """ + n = self._n + + global_width_max = get_global_stat( + elements=[line_width, link_width, transformer_width], + stat="max", + absolute=True, + ) # If elements empty, global_width_max is None + + global_flow_max = get_global_stat( + elements=[line_flow, link_flow, transformer_flow], + stat="max", + absolute=True, + ) # If elements empty, global_flow_max is None + + for c in branch_components or n.branch_components: + if c == "Line": + branch_flow = line_flow + branch_color = line_color + branch_cmap = line_cmap + branch_cmap_norm = line_cmap_norm + branch_alpha = line_alpha + branch_width = line_width + branch_columns = line_columns + elif c == "Link": + branch_flow = link_flow + branch_color = link_color + branch_cmap = link_cmap + branch_cmap_norm = link_cmap_norm + branch_alpha = link_alpha + branch_width = link_width + branch_columns = link_columns + elif c == "Transformer": + branch_flow = transformer_flow + branch_color = transformer_color + branch_cmap = transformer_cmap + branch_cmap_norm = transformer_cmap_norm + branch_alpha = transformer_alpha + branch_width = transformer_width + branch_columns = transformer_columns + + if as_components(n, c).empty: + continue + + # Branch lines + self.init_branch_component_data( + c_name=c, + branch_columns=branch_columns, + ) + self.create_branch_paths( + c, + geometry=geometry, + ) + self.create_branch_color( + c_name=c, + branch_color=branch_color, + branch_cmap=branch_cmap, + branch_cmap_norm=branch_cmap_norm, + branch_alpha=branch_alpha, + ) + self.scale_branch_param( + c_name=c, + branch_param_name="width", + branch_param=branch_width, + branch_param_factor=branch_width_factor, + branch_param_max=branch_width_max, # km + global_param_max=global_width_max, + keep_algebraic_sign=False, + auto_scale=auto_scale, + ) + if tooltip: + self.create_tooltips( + c_name=c, + ) + self.create_branch_layer( + c_name=c, + ) + + # Branch arrows + self.init_arrow_data( + c_name=c, + branch_flow=branch_flow, + arrow_size_factor=arrow_size_factor, + ) + self.scale_branch_param( + c_name=c, + branch_param_name="flow", + branch_param=branch_flow, + branch_param_factor=branch_width_factor, + branch_param_max=branch_width_max, # km + global_param_max=global_flow_max, + keep_algebraic_sign=True, + auto_scale=auto_scale, + ) + self.create_arrows( + c_name=c, + arrow_size_factor=arrow_size_factor, + ) + self.create_arrow_color( + c_name=c, + arrow_color=arrow_color, + arrow_cmap=arrow_cmap, + arrow_cmap_norm=arrow_cmap_norm, + arrow_alpha=arrow_alpha, + ) + if tooltip: + self.create_tooltips( + c_name=c, + columns=["flow"], + ) + self.create_arrow_layer( + c_name=c, + ) + + def add_geomap_layer( + self, + geomap_alpha: float = 0.9, + geomap_color: dict | None = None, + geomap_resolution: Literal["110m", "50m", "10m"] = "50m", + ) -> None: + """Add a geomap layer of Pydeck type GeoJsonLayer to the interactive map. + + Parameters + ---------- + geomap_alpha : float, default 0.9 + Alpha transparency for the geomap features. + geomap_color : dict | None, default None + Dictionary specifying colors for different geomap features. If None, default colors will be used: `{'land': 'whitesmoke', 'ocean': 'lightblue'} + geomap_resolution : {'110m', '50m', '10m'}, default '50m' + Resolution of the geomap features. One of '110m', '50m', or '10m'. + + Returns + ------- + None + + """ + if not _is_cartopy_available(): + logger.warning( + "Cartopy is not available. Falling back to non-geographic plotting." + ) + return + + import cartopy.feature # noqa: PLC0415 + + if geomap_resolution not in ["110m", "50m", "10m"]: + msg = "Resolution has to be one of '110m', '50m', or '10m'." + raise ValueError(msg) + + if geomap_color is None: + geomap_color = { + "ocean": "lightblue", + "land": "whitesmoke", + } + + line_color = [100, 100, 100, 255] + + # Always render ocean first + if "ocean" in geomap_color: + features = feature_to_geojson( + cartopy.feature.OCEAN.with_scale(geomap_resolution) + ) + + fill_color = to_rgba255(geomap_color["ocean"], geomap_alpha) + layer = pdk.Layer( + "PolygonLayer", + data=features, + get_polygon="geometry.coordinates", + filled=True, + stroked=True, + get_fill_color=fill_color, + get_line_color=line_color, + line_width_min_pixels=1, + auto_highlight=False, + pickable=False, + ) + self._layers["Geomap_ocean"] = layer + + # Then render land + if "land" in geomap_color: + features = shapefile_to_geojson( + resolution=geomap_resolution, + category="cultural", + name="admin_0_countries", + pole_buffer=1e-6, + ) + fill_color = to_rgba255(geomap_color["land"], geomap_alpha) + layer = pdk.Layer( + "PolygonLayer", + data=features, + get_polygon="geometry.coordinates", + filled=True, + stroked=True, + get_fill_color=fill_color, + get_line_color=line_color, + line_width_min_pixels=1, + auto_highlight=False, + pickable=False, + ) + self._layers["Geomap_land"] = layer + + def deck( + self, + tooltip: bool = True, + ) -> pdk.Deck: + """Display the interactive map. + + Parameters + ---------- + tooltip : bool, default True + Whether to show a tooltip on hover. + + Returns + ------- + pdk.Deck + The Pydeck Deck object representing the interactive map. + + """ + layers = list(self._layers.values()) + + tooltip_content: bool | dict[str, str | dict[str, str]] + if not tooltip: + tooltip_content = False + else: + tooltip_content = {"html": "{tooltip_html}", "style": self._tooltip_style} + + deck = pdk.Deck( + layers=layers, + map_style=self._map_style, + tooltip=tooltip_content, + initial_view_state=self.view_state, + # set 3d view + ) + return deck + + def build_layers( # noqa: D417 + self, + branch_components: list | set | None = None, + branch_width_factor: float = 1, + bus_size: float | dict | pd.Series = 25, + bus_size_factor: float = 1.0, + bus_split_circle: bool = False, + bus_color: str | dict | pd.Series = "cadetblue", + bus_cmap: str | mcolors.Colormap = "Reds", + bus_cmap_norm: mcolors.Normalize | None = None, + bus_alpha: float | dict | pd.Series = 0.9, + line_flow: float | dict | pd.Series = 0, + line_color: str | dict | pd.Series = "rosybrown", + line_cmap: str | mcolors.Colormap = "viridis", + line_cmap_norm: mcolors.Normalize | None = None, + line_alpha: float | dict | pd.Series = 0.9, + line_width: float | dict | pd.Series = 2, + link_flow: float | dict | pd.Series = 0, + link_color: str | dict | pd.Series = "darkseagreen", + link_cmap: str | mcolors.Colormap = "viridis", + link_cmap_norm: mcolors.Normalize | None = None, + link_alpha: float | dict | pd.Series = 0.9, + link_width: float | dict | pd.Series = 2, + transformer_flow: float | dict | pd.Series = 0, + transformer_color: str | dict | pd.Series = "orange", + transformer_cmap: str | mcolors.Colormap = "viridis", + transformer_cmap_norm: mcolors.Normalize | None = None, + transformer_alpha: float | dict | pd.Series = 0.9, + transformer_width: float | dict | pd.Series = 2, + arrow_size_factor: float = 1.5, + arrow_color: str | dict | pd.Series | None = None, + arrow_cmap: str | mcolors.Colormap = "viridis", + arrow_cmap_norm: mcolors.Normalize | None = None, + arrow_alpha: float | dict | pd.Series = 0.9, + tooltip: bool = True, + auto_scale: bool = False, + branch_width_max: float = 10, # km + bus_size_max: float = 10000, # km² + bus_columns: list | None = None, + line_columns: list | None = None, + link_columns: list | None = None, + transformer_columns: list | None = None, + geomap: bool = False, + geomap_alpha: float = 0.9, + geomap_color: dict | None = None, + geomap_resolution: Literal["110m", "50m", "10m"] = "50m", + geometry: bool = False, + ) -> "PydeckPlotter": + """Create an interactive map of the PyPSA network using Pydeck. + + Parameters + ---------- + branch_width_factor : float, default 1.0 + Branch widths are scaled by this factor. + bus_size : float/dict/pandas.Series + Sizes of bus points in km² (corresponds to circle area), defaults to 25 km². + bus_size_factor : float, default 1.0 + Bus sizes are scaled by this factor. + bus_split_circle : bool, default False + Draw half circles if bus_size is a pandas.Series with a Multiindex. + If set to true, the upper half circle per bus then includes all positive values + of the series, the lower half circle all negative values. Defaults to False. + bus_color : str/dict/pandas.Series/None + Colors for the buses, defaults to "cadetblue". If bus_size is a + pandas.Series with a Multiindex, bus_color defaults to the + n.c.carriers.static['color'] column. + bus_cmap : mcolors.Colormap/str, default 'Reds' + If bus_color are floats, this color map will assign the colors. + bus_cmap_norm : mcolors.Normalize/None + Normalization for bus_cmap, defaults to None. + bus_alpha : float/dict/pandas.Series + Add alpha channel to buses, defaults to 0.9. + line_flow : float/dict/pandas.Series, default 0 + Series of line flows indexed by line names, defaults to 0. If 0, no arrows will be created. + If a float is provided, it will be used as a constant flow for all lines. + line_color : str/dict/pandas.Series + Colors for the lines, defaults to 'rosybrown'. + line_alpha : float/dict/pandas.Series + Add alpha channel to lines, defaults to 0.9. + line_width : float/dict/pandas.Series, default 2 + Widths of line component in km. + link_flow : float/dict/pandas.Series, default 0 + Series of link flows indexed by link names, defaults to 0. If 0, no arrows will be created. + If a float is provided, it will be used as a constant flow for all links. + link_color : str/dict/pandas.Series + Colors for the links, defaults to 'darkseagreen'. + link_alpha : float/dict/pandas.Series + Add alpha channel to links, defaults to 0.9. + link_width : float/dict/pandas.Series, default 2 + Widths of link component in km. + tooltip : bool, default True + Whether to add a tooltip to the bus layer. + + Other Parameters + ---------------- + branch_components : list, set, optional, default ['Line', 'Link', 'Transformer'] + Branch components to be plotted. + branch_width_max : float, default 10 + Maximum width of branch component in km when `auto_scale` is True. + bus_size_max : float, default 10000 + Maximum area size of bus component in km² when `auto_scale` is True. + line_cmap : mcolors.Colormap/str, default 'viridis' + If line_color are floats, this color map will assign the colors. + line_cmap_norm : mcolors.Normalize + The norm applied to the line_cmap. + link_cmap : mcolors.Colormap/str, default 'viridis' + If link_color are floats, this color map will assign the colors. + link_cmap_norm : mcolors.Normalize|matplotlib.colors.*Norm + The norm applied to the link_cmap. + transformer_flow : float/dict/pandas.Series, default 0 + Series of transformer flows indexed by transformer names, defaults to 0. If 0, no arrows will be created. + If a float is provided, it will be used as a constant flow for all transformers. + transformer_color : str/dict/pandas.Series + Colors for the transformers, defaults to 'orange'. + transformer_cmap : mcolors.Colormap/str, default 'viridis' + If transformer_color are floats, this color map will assign the colors. + transformer_cmap_norm : matplotlib.colors.Normalize|matplotlib.colors.*Norm + The norm applied to the transformer_cmap. + transformer_alpha : float/dict/pandas.Series + Add alpha channel to transformers, defaults to 0.9. + transformer_width : float/dict/pandas.Series, default 2 + Widths of transformer in km. + arrow_size_factor : float, default 1.5 + Multiplier on branch flows to scale the arrow size. + arrow_color : str/dict/pandas.Series | None, default None + Colors for the arrows. If not specified, defaults to the same colors as the respective branch component. + arrow_cmap : str/matplotlib.colors.Colormap, default 'viridis' + Colormap to use if arrow_color is a numeric pandas.Series. + arrow_cmap_norm : matplotlib.colors.Normalize, optional + Normalization to use if arrow_color is a numeric pandas.Series. + arrow_alpha : float/dict/pandas.Series, default 0.9 + Add alpha channel to arrows, defaults to 0.9. + bus_columns : list, default None + List of bus columns to include. + Specify additional columns to include in the tooltip. + line_columns : list, default None + List of line columns to include. If None, only the bus0 and bus1 columns are used. + Specify additional columns to include in the tooltip. + link_columns : list, default None + List of link columns to include. If None, only the bus0 and bus1 columns are used. + Specify additional columns to include in the tooltip. + transformer_columns : list, default None + List of transformer columns to include. If None, only the bus0 and bus1 columns are used. + Specify additional columns to include in the tooltip. + geomap : bool, default False + Whether to add a geomap layer to the plot. + geomap_alpha : float, default 0.9 + Alpha transparency for the geomap features. + geomap_color : dict | None, default None + Dictionary specifying colors for different geomap features. If None, default colors will be used: `{'land': 'whitesmoke', 'ocean': 'lightblue'} + geomap_resolution : {'110m', '50m', '10m'}, default '50m' + Resolution of the geomap features. One of '110m', '50m', or '10m'. + geometry : bool, default False + Whether to use the geometry column of the branch components. + + Returns + ------- + PydeckPlotter + The PydeckPlotter instance with the created layers. + + """ + n = self._n + + if geomap: + self.add_geomap_layer( + geomap_alpha=geomap_alpha, + geomap_color=geomap_color, + geomap_resolution=geomap_resolution, + ) + + # Branch layers + if branch_components is None: + branch_components = n.branch_components + + self.add_branch_and_arrow_layer( + branch_components=branch_components, + branch_width_factor=branch_width_factor, + branch_width_max=branch_width_max, + line_flow=line_flow, + line_color=line_color, + line_cmap=line_cmap, + line_cmap_norm=line_cmap_norm, + line_alpha=line_alpha, + line_width=line_width, + line_columns=line_columns, + link_flow=link_flow, + link_color=link_color, + link_cmap=link_cmap, + link_cmap_norm=link_cmap_norm, + link_alpha=link_alpha, + link_width=link_width, + link_columns=link_columns, + transformer_flow=transformer_flow, + transformer_color=transformer_color, + transformer_cmap=transformer_cmap, + transformer_cmap_norm=transformer_cmap_norm, + transformer_alpha=transformer_alpha, + transformer_width=transformer_width, + transformer_columns=transformer_columns, + arrow_color=arrow_color, + arrow_cmap=arrow_cmap, + arrow_cmap_norm=arrow_cmap_norm, + arrow_alpha=arrow_alpha, + arrow_size_factor=arrow_size_factor, + geometry=geometry, + auto_scale=auto_scale, + tooltip=tooltip, + ) + + # Bus layer + if hasattr(bus_size, "index") and isinstance(bus_size.index, pd.MultiIndex): + self.add_pie_chart_layer( + bus_size=bus_size, + bus_size_factor=bus_size_factor, + bus_size_max=bus_size_max, + bus_split_circle=bus_split_circle, + bus_alpha=bus_alpha, + bus_columns=bus_columns, + points_per_radian=5, + auto_scale=auto_scale, + tooltip=tooltip, + ) + else: + self.add_bus_layer( + bus_size=bus_size, + bus_size_factor=bus_size_factor, + bus_size_max=bus_size_max, + bus_color=bus_color, + bus_cmap=bus_cmap, + bus_cmap_norm=bus_cmap_norm, + bus_alpha=bus_alpha, + bus_columns=bus_columns, + auto_scale=auto_scale, + tooltip=tooltip, + ) + + return self + + +# TODO: fix typing differences between PydeckPlotter.build_layers and explore function +@wraps( + PydeckPlotter.build_layers, + assigned=("__doc__", "__annotations__", "__type_params__"), +) +def explore( # noqa: D103 + n: "Network", + map_style: str = "road", + view_state: dict | pdk.ViewState | None = None, + layouter: Callable | None = None, + jitter: float | None = None, + **kwargs: Any, +) -> pdk.Deck: + """Create an interactive map of the PyPSA network using Pydeck. + + + + Returns + ------- + pdk.Deck + The Pydeck object representing the interactive map. + + """ + plotter = PydeckPlotter( + n, + map_style=map_style, + view_state=view_state, + layouter=layouter, + jitter=jitter, + ) + + # Optional tooltip_kwargs + tooltip_kwargs = kwargs.pop("tooltip_kwargs", {}) + plotter._tooltip_style = set_tooltip_style(**tooltip_kwargs) + + plotter.build_layers(**kwargs) + + tooltip = kwargs.get("tooltip", True) + + return plotter.deck(tooltip=tooltip) diff --git a/PyPSA/source/pypsa/plot/maps/static.py b/PyPSA/source/pypsa/plot/maps/static.py new file mode 100644 index 0000000000000000000000000000000000000000..8ce0e39ffd7c87b15267c3a1f570e4cc2525557d --- /dev/null +++ b/PyPSA/source/pypsa/plot/maps/static.py @@ -0,0 +1,1898 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Map plots for network objects.""" + +from __future__ import annotations + +import logging +import warnings +from functools import wraps +from typing import TYPE_CHECKING, Any, Literal + +import matplotlib.colors as mcolors +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from matplotlib.axes import Axes +from matplotlib.collections import LineCollection, PatchCollection +from matplotlib.legend_handler import HandlerPatch +from matplotlib.patches import Circle, FancyArrow, Patch, Polygon, Wedge +from shapely.geometry import LineString +from shapely.wkt import loads + +from pypsa.common import _convert_to_series, deprecated_kwargs +from pypsa.constants import DEFAULT_EPSG +from pypsa.geo import ( + compute_bbox, + get_projected_area_factor, +) +from pypsa.plot.maps.common import ( + _is_cartopy_available, + add_jitter, + apply_cmap, + apply_layouter, +) + +if TYPE_CHECKING: + from collections.abc import Callable + + import networkx as nx + from cartopy.mpl.geoaxes import GeoAxesSubplot + from matplotlib.legend import Legend + + from pypsa.components.components import Components + from pypsa.networks import Network + + +logger = logging.getLogger(__name__) + + +class MapPlotter: + """Class to plot a PyPSA network on a map.""" + + def __init__( + self, + n: Network, + layout: Callable[[nx.Graph], dict[Any, tuple[float, float]]] | None = None, + boundaries: tuple[float, float, float, float] | None = None, + margin: float = 0.05, + jitter: float | None = None, + buses: pd.Index | None = None, + ) -> None: + """Initialize MapPlotGenerator instance. + + Parameters + ---------- + n : Network + The PyPSA network to plot + layout : networkx.drawing.layout, optional + Layout function to use for node positioning + boundaries : tuple[float, float, float, float], optional + Plot boundaries as [xmin, xmax, ymin, ymax] + margin : float, default 0.05 + Margin around plot boundaries + jitter : float, optional + Amount of random noise to add to node positions + buses : pd.Index, optional + Subset of buses to plot + + """ + """Initialize MapPlotGenerator instance. + + Parameters + ---------- + n : Network + The PyPSA network to plot + layout : networkx.drawing.layout, optional + Layout function to use for node positioning + boundaries : tuple[float, float, float, float], optional + Plot boundaries as [xmin, xmax, ymin, ymax] + margin : float, default 0.05 + Margin around plot boundaries + jitter : float, optional + Amount of random noise to add to node positions + buses : pd.Index, optional + Subset of buses to plot + """ + self._n = n + self._x = None + self._y = None + self._layout = layout + self._boundaries = boundaries + self._margin = margin + self._ax: Axes | GeoAxesSubplot | None = None + self._area_factor = 1.0 + + if jitter: + self.x, self.y = add_jitter(x=self.x, y=self.y, jitter=jitter) + + @property + def n(self) -> Network: + """Get the network object.""" + return self._n + + @property + def x(self) -> pd.Series: + """Get the x-coordinates of the buses.""" + if self._x is None: + self.set_layout() + return self._x + + @x.setter + def x(self, value: pd.Series) -> None: + """Set the x-coordinates of the buses.""" + self._x = value + + @property + def y(self) -> pd.Series: + """Get the y-coordinates of the buses.""" + if self._y is None: + self.set_layout() + return self._y + + @y.setter + def y(self, value: pd.Series) -> None: + """Set the y-coordinates of the buses.""" + self._y = value + + @property + def margin(self) -> float: + """Get the margin around the plot boundaries.""" + return self._margin + + @margin.setter + def margin(self, value: float) -> None: + """Set the margin around the plot boundaries.""" + self._margin = value + + @property + def boundaries(self) -> tuple[float, float, float, float] | None: + """Get the plot boundaries.""" + if self._boundaries is None: + self.set_boundaries( + self._boundaries, self.margin, self._n.c.buses.static.index + ) + return self._boundaries + + @boundaries.setter + def boundaries( + self, + value: tuple[float, float, float, float] | None, + ) -> None: + """Set the plot boundaries.""" + if value is not None and len(value) != 4: + msg = "Boundaries must be a sequence of 4 values (xmin, xmax, ymin, ymax)" + raise ValueError(msg) + self._boundaries = value + + @property + def ax( + self, + ) -> Axes | GeoAxesSubplot | None: + """Get the axis for plotting.""" + return self._ax + + @ax.setter + def ax( + self, + value: Axes | GeoAxesSubplot | None, + ) -> None: + """Set the axis for plotting.""" + if value is not None: + if _is_cartopy_available(): + from cartopy.mpl.geoaxes import GeoAxesSubplot # noqa: PLC0415 + + axis_type: tuple[type, ...] = (Axes, GeoAxesSubplot) + else: + axis_type = (Axes,) + if not isinstance(value, axis_type): + msg = "ax must be either matplotlib Axes or GeoAxesSubplot" + raise ValueError(msg) + self._ax = value + + @property + def area_factor(self) -> float: + """Get the area factor for scaling.""" + return self._area_factor + + @area_factor.setter + def area_factor(self, value: float | None) -> None: + """Set the area factor for scaling.""" + if value is not None and not isinstance(value, int | float): + msg = "area_factor must be a number" + raise ValueError(msg) + self._area_factor = float(value) if value is not None else 1.0 + + def set_layout(self, layouter: Callable | None = None) -> None: + """Set the layout for node positions. + + Parameters + ---------- + layouter : networkx.drawing.layout, optional + Layout function to use. If None, uses planar layout if possible, + otherwise Kamada-Kawai layout. + + """ + """Set the layout for node positions. + + Parameters + ---------- + layouter : networkx.drawing.layout, optional + Layout function to use. If None, uses planar layout if possible, + otherwise Kamada-Kawai layout. + """ + # Check if networkx layouter is given or needed to get bus positions + is_empty = ( + ( + self.n.c.buses.static[["x", "y"]].isnull() + | (self.n.c.buses.static[["x", "y"]] == 0) + ) + .all() + .all() + ) + if layouter or self._layout or is_empty: + self.x, self.y = apply_layouter(self.n, layouter, inplace=False) + else: + self.x, self.y = self.n.c.buses.static["x"], self.n.c.buses.static["y"] + self.crs = self.n.crs + + def set_boundaries( + self, + boundaries: tuple[float, float, float, float] | None = None, + margin: float = 0.05, + buses: pd.Index | None = None, + ) -> None: + """Set the plot boundaries. + + Parameters + ---------- + boundaries : tuple[float, float, float, float], optional + Plot boundaries as [xmin, xmax, ymin, ymax]. If None, computed from bus positions. + margin : float, default 0.05 + Margin around plot boundaries + buses : pd.Index, optional + Subset of buses to use for computing boundaries + + """ + """Set the plot boundaries. + + Parameters + ---------- + boundaries : tuple[float, float, float, float], optional + Plot boundaries as [xmin, xmax, ymin, ymax]. If None, computed from bus positions. + margin : float, default 0.05 + Margin around plot boundaries + buses : pd.Index, optional + Subset of buses to use for computing boundaries + """ + # Set boundaries, if not given + + if buses is None: + buses = self.n.c.buses.static.index + + if boundaries is None: + (x1, y1), (x2, y2) = compute_bbox(self.x[buses], self.y[buses], margin) + self.boundaries = (x1, x2, y1, y2) + else: + self.boundaries = boundaries + + def init_axis( + self, + ax: Axes | None = None, + projection: Any = None, + geomap: bool = True, + geomap_resolution: Literal["10m", "50m", "110m"] = "50m", + geomap_color: dict | bool | None = None, + boundaries: tuple[float, float, float, float] | None = None, + title: str = "", + ) -> None: + """Initialize the plot axis with geographic features if requested. + + Parameters + ---------- + ax : matplotlib Axes, optional + Axis to plot on. If None, creates new axis. + projection : cartopy.crs.Projection, optional + Map projection to use + geomap : bool, default True + Whether to add geographic features + geomap_resolution : {'10m', '50m', '110m'}, default '50m' + Resolution of geographic features + geomap_color : dict or bool, optional + Colors for geographic features + boundaries : tuple[float, float, float, float], optional + Plot boundaries as [xmin, xmax, ymin, ymax] + title : str, default "" + Plot title + + """ + # Ensure that boundaries are set + if boundaries is None: + boundaries = self.boundaries + + # Check if geomap is requested but cartopy not available + if geomap and not _is_cartopy_available(): + logger.warning( + "Cartopy is not available. Falling back to non-geographic plotting." + ) + geomap = False + + # Set up plot (either cartopy or matplotlib) + if geomap: + import cartopy.crs # noqa: PLC0415 + from cartopy.mpl.geoaxes import GeoAxesSubplot # noqa: PLC0415 + + network_projection = cartopy.crs.Projection(self.n.crs) + if projection is None: + projection = network_projection + elif not isinstance(projection, cartopy.crs.Projection): + msg = "The passed projection is not a cartopy.crs.Projection" + raise ValueError(msg) + + if ax is None: + self.ax = plt.axes(projection=projection) + elif not isinstance(ax, GeoAxesSubplot): + msg = "The passed axis is not a GeoAxesSubplot. You can " + "create one with: \nimport cartopy.crs as ccrs \n" + "fig, ax = plt.subplots(" + 'subplot_kw={"projection":ccrs.PlateCarree()})' + raise ValueError(msg) + else: + self.ax = ax + + # Transform bus positions to projection, track the new crs + x, y, _ = self.ax.projection.transform_points( # type: ignore + network_projection, self.x.values, self.y.values + ).T + self.x_trans, self.y_trans = ( + pd.Series(x, self.x.index), + pd.Series(y, self.y.index), + ) + + if geomap_color is not False: + if geomap_color is None: + geomap_color = {} + if isinstance(geomap_color, dict): + self.add_geomap_features(geomap_resolution, geomap_color) + else: + self.add_geomap_features(geomap_resolution) + + self.ax.set_extent(boundaries, crs=network_projection) # type: ignore + + self.area_factor = get_projected_area_factor(self.ax, self.n.srid) + else: + if ax is None: + self.ax = plt.gca() + else: + self.ax = ax + # Only set axis boundaries if they are not degenerate + if ( + boundaries is not None + and boundaries[0] != boundaries[1] + and boundaries[2] != boundaries[3] + ): + self.ax.axis(boundaries) + self.x_trans, self.y_trans = self.x, self.y + self.ax.set_aspect("equal") + self.ax.axis("off") + self.ax.set_title(title) + + def add_geomap_features( + self, + resolution: Literal["10m", "50m", "110m"] = "50m", + geomap_color: dict | None = None, + ) -> None: + """Add geographic features to the map using cartopy. + + Parameters + ---------- + resolution : {'10m', '50m', '110m'}, default '50m' + Resolution of geographic features + geomap_color : dict, optional + Colors for geographic features. Keys can include: + - 'ocean': color for ocean areas + - 'land': color for land areas + - 'border': color for country borders + - 'coastline': color for coastlines + + """ + """Add geographic features to the map using cartopy.""" + if not _is_cartopy_available(): + logger.warning("Cartopy is not available. Cannot add geographic features.") + return + + import cartopy.feature # noqa: PLC0415 + from cartopy.mpl.geoaxes import GeoAxesSubplot # noqa: PLC0415 + + if not isinstance(self.ax, GeoAxesSubplot): + msg = "The axis must be a GeoAxesSubplot to add geographic features." + raise TypeError(msg) + + if resolution not in ["10m", "50m", "110m"]: + msg = "Resolution has to be one of '10m', '50m', '110m'" + raise ValueError(msg) + + if geomap_color is None: + geomap_color = { + "ocean": "lightblue", + "land": "whitesmoke", + "border": "darkgray", + "coastline": "black", + } + + if "land" in geomap_color: + self.ax.add_feature( + cartopy.feature.LAND.with_scale(resolution), + facecolor=geomap_color["land"], + ) + + if "ocean" in geomap_color: + self.ax.add_feature( + cartopy.feature.OCEAN.with_scale(resolution), + facecolor=geomap_color["ocean"], + ) + + self.ax.add_feature( + cartopy.feature.BORDERS.with_scale(resolution), + linewidth=0.3, + edgecolor=geomap_color.get("border", "black"), + ) + + self.ax.add_feature( + cartopy.feature.COASTLINE.with_scale(resolution), + linewidth=0.3, + edgecolor=geomap_color.get("coastline", "black"), + ) + + def get_multiindex_buses( + self, + sizes: pd.Series, + colors: pd.Series, + alpha: float | pd.Series, + split_circles: bool, + ) -> list[Wedge]: + """Create patches for buses with multi-indexed size data. + + Parameters + ---------- + sizes : pd.Series + Series with multi-index where first level represents buses + and second level represents components/carriers. + colors : pd.Series + Series with colors for each component/carrier. + alpha : float | pd.Series + Transparency value(s) for the patches. + split_circles : bool + Whether to split the circles into halves for positive/negative values. + + Returns + ------- + list[Wedge] + List of Wedge patches representing the buses. + + """ + # We are drawing pies to show all the different shares + patches = [] + for b_i in sizes.index.unique(level=0): + s_base = sizes.loc[b_i] + + if split_circles: + # As only half of the circle is drawn, increase area by factor 2 + s_base = s_base * 2 + s_base = ( + s_base[s_base > 0], + s_base[s_base < 0], + ) + starts: tuple[float, ...] = 0.0, 1.0 + scope = 180 + else: + s_base = (s_base[s_base > 0],) + starts = (0.25,) + scope = 360 + + for s, start in zip(s_base, starts, strict=False): + radius = abs(s.sum()) ** 0.5 + ratios = abs(s) if radius == 0.0 else s / s.sum() + for i, ratio in ratios.items(): + patches.append( + Wedge( + (self.x_trans.at[b_i], self.y_trans.at[b_i]), + radius, + scope * start, + scope * (start + ratio), + facecolor=colors[i], + alpha=alpha, + ) + ) + start = start + ratio + return patches + + def get_singleindex_buses( + self, + sizes: pd.Series, + colors: pd.Series, + alpha: float | pd.Series, + ) -> list[Circle]: + """Create patches for buses with single-indexed size data. + + Parameters + ---------- + sizes : pd.Series + Series with bus indices representing sizes of buses. + colors : pd.Series + Series with colors for each bus. + alpha : float | pd.Series + Transparency value(s) for the patches. + + Returns + ------- + list[Circle] + List of Circle patches representing the buses. + + """ + patches = [] + for b_i in sizes.index[(sizes != 0) & ~sizes.isna()]: + radius = sizes.at[b_i] ** 0.5 + patches.append( + Circle( + (self.x_trans.at[b_i], self.y_trans.at[b_i]), + radius, + facecolor=colors.at[b_i], + alpha=alpha, + ) + ) + return patches + + @staticmethod + def _dataframe_from_arguments(index: pd.Index, **kwargs: Any) -> pd.DataFrame: + if any(isinstance(v, pd.Series) for v in kwargs.values()): + return pd.DataFrame(kwargs) + return pd.DataFrame(kwargs, index=index) + + def _flow_ds_from_arg( + self, + flow: pd.Series | str | float | Callable | None, + c_name: str, + ) -> pd.Series | None: + """Convert flow argument to pandas Series. + + Parameters + ---------- + flow : Series|str|int|float|callable|None + Flow data to convert + c_name : str + Name of the network component + + Returns + ------- + pd.Series | None + Converted flow data as Series, or None if flow was None + + """ + if isinstance(flow, pd.Series): + return flow + + if flow in self.n.snapshots: + return self.n.c[c_name].dynamic.p0.loc[flow] + + if isinstance(flow, str) or callable(flow): + return self.n.c[c_name].dynamic.p0.agg(flow, axis=0) + + if isinstance(flow, int | float): + return pd.Series(flow, index=self.n.components[c_name].static.index) + + if flow is not None: + msg = f"The 'flow' argument must be a pandas.Series, a string, a float or a callable, got {type(flow)}." + raise ValueError(msg) + + return None + + def get_branch_collection( + self, + c: Components, + widths: float | pd.Series, + colors: str | pd.Series, + alpha: float | pd.Series, + geometry: pd.Series, + auto_scale_branches: bool = True, + ) -> LineCollection | PatchCollection: + """Create a collection of branches for a single component. + + Parameters + ---------- + c : Component + Network component being plotted + widths : float/Series + Line widths for the component + colors : str/Series + Colors for the component + alpha : float/Series + Alpha values for the component + geometry : bool + Whether to use geometry data for plotting + auto_scale_branches : bool + Whether to create a LineCollection with widths in data units + or a PatchCollection with widths in display units + + """ + if auto_scale_branches: + return self._get_branch_collection_lines(c, widths, colors, alpha, geometry) + if geometry: + msg = "The 'geometry' argument cannot be used with 'auto_scale_branches=True'." + raise ValueError(msg) + return self._get_branch_collection_patches(c, widths, colors, alpha) + + def _get_branch_collection_lines( + self, + c: Components, + widths: pd.Series, + colors: pd.Series, + alpha: pd.Series, + geometry: pd.Series, + ) -> LineCollection: + """Create a LineCollection for a single branch component.""" + idx = widths.index + if not geometry: + segments: np.ndarray = np.asarray( + ( + ( + c.static.bus0[idx].map(self.x_trans), + c.static.bus0[idx].map(self.y_trans), + ), + ( + c.static.bus1[idx].map(self.x_trans), + c.static.bus1[idx].map(self.y_trans), + ), + ) + ).transpose(2, 0, 1) + else: + linestrings = geometry[lambda ds: ds != ""].map(loads) + if not all(isinstance(ls, LineString) for ls in linestrings): + msg = "The WKT-encoded geometry in the 'geometry' column must be " + "composed of LineStrings" + raise ValueError(msg) + segments = np.asarray(list(linestrings.map(np.asarray))) + + return LineCollection( + list(segments), + linewidths=widths, + antialiaseds=(1,), + colors=colors, + alpha=alpha, + ) + + def _get_branch_collection_patches( + self, + c: Components, + widths: pd.Series, + colors: pd.Series, + alpha: pd.Series, + ) -> PatchCollection: + """Create a PatchCollection of polygons representing lines with widths.""" + idx = widths.index + patches = [] + facecolors = [] + alphas = [] + x0s, y0s = c.static.bus0.map(self.x_trans), c.static.bus0.map(self.y_trans) + x1s, y1s = c.static.bus1.map(self.x_trans), c.static.bus1.map(self.y_trans) + + for i in idx: + x0, y0 = x0s[i], y0s[i] + x1, y1 = x1s[i], y1s[i] + width = widths[i] * self.area_factor + + # Calculate the direction vector + dx = x1 - x0 + dy = y1 - y0 + length = np.hypot(dx, dy) + if length == 0: + continue # Skip zero-length lines + udx = dx / length + udy = dy / length + + # Perpendicular vector scaled by half the width + px = -udy * width / 2.0 + py = udx * width / 2.0 + + # Define the corners of the rectangle + corners = [ + (x0 + px, y0 + py), + (x1 + px, y1 + py), + (x1 - px, y1 - py), + (x0 - px, y0 - py), + ] + + polygon = Polygon(corners, closed=True) + patches.append(polygon) + facecolors.append(colors[i]) + alphas.append(alpha[i]) + + patch_collection = PatchCollection( + patches, + facecolors=facecolors, + edgecolors="none", + alpha=alphas, + ) + + return patch_collection + + @staticmethod + def _directed_flow( + coords: pd.DataFrame, + flow: pd.Series, + color: pd.Series, + area_factor: float, + alpha: float = 1, + ) -> PatchCollection: + """Generate arrows from flow data.""" + # this function is used for diplaying arrows representing the network flow + data = pd.DataFrame( + { + "arrowsize": flow.abs().pipe(np.sqrt).clip(lower=1e-8), + "direction": np.sign(flow), + "linelength": ( + np.sqrt( + (coords.x1 - coords.x2) ** 2.0 + (coords.y1 - coords.y2) ** 2 + ) + ), + } + ) + data = data.join(coords) + data["arrowsize"] = data["arrowsize"].mul(area_factor) + data["arrowtolarge"] = 1.5 * data.arrowsize > data.linelength + # swap coords for negativ directions + data.loc[data.direction == -1.0, ["x1", "x2", "y1", "y2"]] = data.loc[ + data.direction == -1.0, ["x2", "x1", "y2", "y1"] + ].values + if ((data.linelength > 0.0) & (~data.arrowtolarge)).any(): + data["arrows"] = data[(data.linelength > 0.0) & (~data.arrowtolarge)].apply( + lambda ds: FancyArrow( + ds.x1, + ds.y1, + 0.6 * (ds.x2 - ds.x1) + - ds.arrowsize * 0.75 * (ds.x2 - ds.x1) / ds.linelength, + 0.6 * (ds.y2 - ds.y1) + - ds.arrowsize * 0.75 * (ds.y2 - ds.y1) / ds.linelength, + head_width=ds.arrowsize, + ), + axis=1, + ) + data.loc[(data.linelength > 0.0) & (data.arrowtolarge), "arrows"] = data[ + (data.linelength > 0.0) & (data.arrowtolarge) + ].apply( + lambda ds: FancyArrow( + ds.x1, + ds.y1, + 0.001 * (ds.x2 - ds.x1), + 0.001 * (ds.y2 - ds.y1), + head_width=ds.arrowsize, + ), + axis=1, + ) + data = data.dropna(subset=["arrows"]) + return PatchCollection( + data.arrows, + color=color, + alpha=alpha, + edgecolors="black", + linewidths=0, + zorder=4, + ) + + def get_flow_collection( + self, + c: Components, + flow: pd.Series, + widths: pd.Series, + colors: pd.Series, + alpha: pd.Series, + ) -> PatchCollection: + """Create a flow arrow collection for a single branch component. + + Parameters + ---------- + c : Component + Network component being plotted + flow : pd.Series + Flow values for the component + widths : float/Series + Line widths to scale the arrows + colors : str/Series + Colors for the arrows + alpha : float/Series + Alpha values for the arrows + + """ + idx = widths.index + coords = pd.DataFrame( + { + "x1": c.static.bus0[idx].map(self.x_trans), + "y1": c.static.bus0[idx].map(self.y_trans), + "x2": c.static.bus1[idx].map(self.x_trans), + "y2": c.static.bus1[idx].map(self.y_trans), + } + ) + + return self._directed_flow(coords, flow, colors, self.area_factor, alpha) + + @staticmethod + def scaling_factor_from_area_contribution( + area_contributions: float, + x_min: float, + x_max: float, + y_min: float, + y_max: float, + target_area_fraction: float = 0.1, + ) -> float: + """Scale series for plotting. + + Makes sure that the total area of all area contributions + takes up approximately the specified fraction of the plot area. + + Parameters + ---------- + area_contributions : pd.Series + Series containing the balance values for each bus + x_min, x_max : float + The x-axis extent of the plot + y_min, y_max : float + The y-axis extent of the plot + target_area_fraction : float, optional + Desired fraction of plot area to be covered by all circles (default: 0.3) + + Returns + ------- + pd.Series + Scaled values + + """ + plot_area = (x_max - x_min) * (y_max - y_min) + target_total_circle_area = plot_area * target_area_fraction + current_total_area = np.sum(np.abs(area_contributions)) + if current_total_area == 0: + return 1 + return target_total_circle_area / current_total_area + + @staticmethod + def aggregate_flow_by_connection( + flow: pd.Series, branches: pd.DataFrame + ) -> pd.Series: + """Aggregate flow values by bus connections irrespective of direction. + + This method aggregates flow values from different branch components between + the same pair of buses, ensuring consistent directional representation. + + Parameters + ---------- + flow : pd.Series + Series containing flow values indexed by branch components + branches : pd.DataFrame + DataFrame with bus0 and bus1 columns defining connections + + Returns + ------- + pd.Series + Aggregated flow values with consistent direction + + """ + if flow.empty: + return flow + connected_buses = branches.loc[flow.index, ["bus0", "bus1"]] + sign_correction = np.where(connected_buses.bus0 < connected_buses.bus1, 1, -1) + + flow_sorted = flow * sign_correction + buses_sorted = connected_buses.apply(sorted, axis=1).str.join(" - ") + flow_grouped = ( + flow_sorted.groupby(buses_sorted).transform("sum") * sign_correction + ) + flow_grouped = flow_grouped[buses_sorted.drop_duplicates().index] + + return flow_grouped + + @staticmethod + def flow_to_width(flow: pd.Series, width_factor: float = 0.2) -> pd.Series: + """Calculate the width of a line based on the flow value. + + Parameters + ---------- + flow : float or pd.Series + Flow values + width_factor : float + Ratio between the flow width and and line width (default: 0.2) + + """ + return abs(flow) ** 0.5 * width_factor + + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + bus_sizes="bus_size", + bus_colors="bus_color", + bus_split_circles="bus_split_circle", + branch_colors="branch_color", + branch_widths="branch_width", + arrow_colors="arrow_color", + geomap_colors="geomap_color", + line_colors="line_color", + line_widths="line_width", + link_colors="link_color", + link_widths="link_width", + transformer_colors="transformer_color", + transformer_widths="transformer_width", + ) + def draw_map( # noqa: D417 + self, + ax: Axes | None = None, + projection: Any = None, + geomap: bool = True, + geomap_resolution: Literal["10m", "50m", "110m"] = "50m", + geomap_color: dict | bool | None = None, + title: str = "", + boundaries: tuple[float, float, float, float] | None = None, + branch_components: list | set | None = None, + bus_size: float | dict | pd.Series = 2e-2, + bus_split_circle: bool = False, + bus_color: str | dict | pd.Series = None, + bus_cmap: str | mcolors.Colormap | None = None, + bus_cmap_norm: mcolors.Normalize | None = None, + bus_alpha: float | dict | pd.Series = 1, + geometry: bool = False, + line_flow: float | str | Callable | dict | pd.Series = None, + line_color: str | dict | pd.Series = "rosybrown", + line_cmap: str | mcolors.Colormap = "viridis", + line_cmap_norm: mcolors.Normalize | None = None, + line_alpha: float | dict | pd.Series = 1, + line_width: float | dict | pd.Series = 1.5, + link_flow: float | str | Callable | dict | pd.Series = None, + link_color: str | dict | pd.Series = "darkseagreen", + link_cmap: str | mcolors.Colormap = "viridis", + link_cmap_norm: mcolors.Normalize | None = None, + link_alpha: float | dict | pd.Series = 1, + link_width: float | dict | pd.Series = 1.5, + transformer_flow: float | str | Callable | dict | pd.Series = None, + transformer_color: str | dict | pd.Series = "orange", + transformer_cmap: str | mcolors.Colormap = "viridis", + transformer_cmap_norm: mcolors.Normalize | None = None, + transformer_alpha: float | dict | pd.Series = 1, + transformer_width: float | dict | pd.Series = 1.5, + auto_scale_branches: bool = True, + ) -> dict: + """Plot the network buses and lines using matplotlib and cartopy. + + Parameters + ---------- + boundaries : list/tuple, default None + Boundaries of the plot in format [x1, x2, y1, y2] + ax : matplotlib.pyplot.Axes, defaults to None + Axis to plot on. Defaults to plt.gca() if geomap is False, otherwise + to plt.axes(projection=projection). + geomap: bool/str, default True + Switch to use Cartopy and draw geographical features. + If string is passed, it will be used as a resolution argument, + valid options are '10m', '50m' and '110m'. + geomap_resolution : str, default '50m' + Resolution of the geomap, only valid if cartopy is installed. + Valid options are '10m', '50m' and '110m'. + projection: cartopy.crs.Projection, defaults to None + Define the projection of your geomap, only valid if cartopy is + installed. If None (default) is passed the projection for cartopy + is set to cartopy.crs.PlateCarree + geomap_color : dict/bool, default None + Specify colors to paint land and sea areas in. + If True, it defaults to `{'ocean': 'lightblue', 'land': 'whitesmoke'}`. + If no dictionary is provided, colors are white. + If False, no geographical features are plotted. + title : string, default "" + Graph title + branch_components : list, default n.branch_components + Branch components to be plotted + bus_size : float/dict/pandas.Series + Sizes of bus points, defaults to 1e-2. If a multiindexed Series is passed, + the function will draw pies for each bus (first index level) with + segments of different color (second index level). Such a Series is ob- + tained by e.g. n.generators.groupby(['bus', 'carrier']).p_nom.sum() + bus_split_circle : bool, default False + Draw half circles if bus_size is a pandas.Series with a Multiindex. + If set to true, the upper half circle per bus then includes all positive values + of the series, the lower half circle all negative values. Defaults to False. + bus_color : str/dict/pandas.Series + Colors for the buses, defaults to "cadetblue". If bus_size is a + pandas.Series with a Multiindex, bus_color defaults to the + n.carriers['color'] column. + bus_cmap : mcolors.Colormap/str + If bus_color are floats, this color map will assign the colors + bus_cmap_norm : mcolors.Normalize + The norm applied to the bus_cmap + bus_alpha : float/dict/pandas.Series + Adds alpha channel to buses, defaults to 1 + geometry : bool, default False + Whether to use the geometry column of the branch components + flow : Callable + Function to calculate the flow for each branch component. + auto_scale_branches : bool + Whether to auto scale the flow and branch sizes. If true, the function + uses a rough auto-scaling when plotting flows as well as it creates + a LineCollection with widths in data units. If false, the function + does not scale the flow and branch sizes and creates a PatchCollection + with widths in display units. The latter is useful for plotting + consistent branch widths and flows in different zoom levels. + + Other Parameters + ---------------- + line_flow : str/callable/dict/pandas.Series/Network.snapshots, default None + Flow to be for each line branch. If an element of + n.snapshots is given, the flow at this timestamp will be + displayed. If an aggregation function is given, is will be applied + to the total network flow via pandas.DataFrame.agg (accepts also + function names). Otherwise flows can be specified by passing a pandas + Series. Use the corresponding width argument to adjust size of the + flow arrows. + line_color : str/pandas.Series + Colors for the lines, defaults to 'rosybrown'. + line_cmap : mcolors.Colormap/str|dict + If line_color are floats, this color map will assign the colors. + line_cmap_norm : mcolors.Normalize + The norm applied to the line_cmap. + line_alpha : str/pandas.Series + Alpha for the lines, defaults to 1. + line_width : dict/pandas.Series + Widths of lines, defaults to 1.5 + link_flow : str/callable/dict/pandas.Series/Network.snapshots, default None + Flow to be for each link branch. See line_flow for more information. + link_color : str/pandas.Series + Colors for the links, defaults to 'darkseagreen'. + link_cmap : mcolors.Colormap/str|dict + If link_color are floats, this color map will assign the colors. + link_cmap_norm : mcolors.Normalize|matplotlib.colors.*Norm + The norm applied to the link_cmap. + link_alpha : str/pandas.Series + Alpha for the links, defaults to 1. + link_width : dict/pandas.Series + Widths of links, defaults to 1.5 + transformer_flow : str/callable/dict/pandas.Series/Network.snapshots, default None + Flow to be for each transformer branch. See line_flow for more information. + transformer_color : str/pandas.Series + Colors for the transfomer, defaults to 'orange'. + transformer_cmap : mcolors.Colormap/str|dict + If transformer_color are floats, this color map will assign the colors. + transformer_cmap_norm : matplotlib.colors.Normalize|matplotlib.colors.*Norm + The norm applied to the transformer_cmap. + transformer_alpha : str/pandas.Series + Alpha for the transfomer, defaults to 1. + transformer_width : dict/pandas.Series + Widths of transformer, defaults to 1.5 + + !!! warning "Deprecated in " + + - `flow` will be deprecated, use `line_flow`, `link_flow` and `transformer_flow` instead. The argument will be passed to all branches. + - `bus_norm`, `line_norm`, `link_norm` and `transformer_norm` are deprecated, use `bus_cmap_norm`, `line_cmap_norm`, `link_cmap_norm` and `transformer_cmap_norm` instead. + - `color_geomap` is deprecated, use `geomap_color` instead. + + Returns + ------- + dict: collections of matplotlib objects + 2D dictinary with the following keys: + - 'nodes' + - 'Bus': Collection of bus points + - 'branches' (for each branch component) + - 'lines': Collection of line branches + - 'links': Collection of link branches + - 'transformers': Collection of transformer branches + - 'flows' (for each branch component) + - 'lines': Collection of line flows + - 'links': Collection of link flows + - 'transformers': Collection of transformer flows + + """ + n = self.n + + self.init_axis( + ax=ax, + projection=projection, + geomap=geomap, + geomap_resolution=geomap_resolution, + geomap_color=geomap_color, + title=title, + boundaries=boundaries, + ) + + if self.ax is None: + msg = "No axis passed or created." + raise ValueError(msg) + + # Check for ValueErrors + if geomap and not _is_cartopy_available(): + logger.warning("Cartopy needs to be installed to use `geomap=True`.") + geomap = False + + # Check if bus_size is a MultiIndex + multindex_buses = isinstance(bus_size, pd.Series) and isinstance( + bus_size.index, pd.MultiIndex + ) + + # Apply default values + if bus_color is None: + if multindex_buses: + bus_color = n.c.carriers.static.color + else: + bus_color = "cadetblue" + + # Format different input types + bus_color = _convert_to_series(bus_color, n.c.buses.static.index) + bus_size = _convert_to_series(bus_size, n.c.buses.static.index) + + # Add missing colors + # TODO: This is not consistent, since for multiindex a ValueError is raised + if not multindex_buses: + bus_color = bus_color.reindex(n.c.buses.static.index) + + # Raise additional ValueErrors after formatting + if multindex_buses: + if ( + len(bus_size.index.unique(level=0).difference(n.c.buses.static.index)) + != 0 + ): + msg = "The first MultiIndex level of sizes must contain buses" + raise ValueError(msg) + if not bus_size.index.unique(level=1).isin(bus_color.index).all(): + msg = "Colors not defined for all elements in the second MultiIndex " + "level of sizes, please make sure that all the elements are " + "included in colors or in n.c.carriers.static.color" + raise ValueError(msg) + + # Apply all cmaps + bus_color = apply_cmap(bus_color, bus_cmap, bus_cmap_norm) + + # Plot buses + bus_size = bus_size.sort_index(level=0, sort_remaining=False) + if geomap: + bus_size = bus_size * self.area_factor**2 + if isinstance(bus_size.index, pd.MultiIndex): + patches: list[Circle] | list[Wedge] + patches = self.get_multiindex_buses( + bus_size, bus_color, bus_alpha, bus_split_circle + ) + else: + patches = self.get_singleindex_buses(bus_size, bus_color, bus_alpha) + bus_collection = PatchCollection(patches, match_original=True, zorder=5) + self.ax.add_collection(bus_collection) + + # Plot branches and flows + if branch_components is None: + branch_components = n.branch_components + + branch_collections = {} + flow_collections = {} + + for c in n.components: + if c.name not in branch_components: + continue + # Get branch collection + if c.name == "Line": + widths = line_width + colors = line_color + alpha = line_alpha + flow = self._flow_ds_from_arg(line_flow, c.name) + cmap = line_cmap + cmap_norm = line_cmap_norm + elif c.name == "Link": + widths = link_width + colors = link_color + alpha = link_alpha + flow = self._flow_ds_from_arg(link_flow, c.name) + cmap = link_cmap + cmap_norm = link_cmap_norm + elif c.name == "Transformer": + widths = transformer_width + colors = transformer_color + alpha = transformer_alpha + flow = self._flow_ds_from_arg(transformer_flow, c.name) + cmap = transformer_cmap + cmap_norm = transformer_cmap_norm + + data = self._dataframe_from_arguments( + c.static.index, widths=widths, colors=colors, alpha=alpha, flow=flow + ) + if data.empty: + continue + + data["colors"] = apply_cmap(data.colors, cmap, cmap_norm) + + branch_coll = self.get_branch_collection( + c, data.widths, data.colors, data.alpha, geometry, auto_scale_branches + ) + if branch_coll is not None: + self.ax.add_collection(branch_coll) + branch_collections[c.name] = branch_coll + + # Get flow collection if flow data exists + if flow is not None: + if auto_scale_branches: + rough_scale = ( + sum([len(n.c[c].static) for c in branch_components]) + 100 + ) + data["flow"] = ( + data.flow.mul(abs(data.widths), fill_value=0) / rough_scale + ) + flow_coll = self.get_flow_collection( + c, + flow=data.flow, + widths=data.widths, + colors=data.colors, + alpha=data.alpha, + ) + if flow_coll is not None: + self.ax.add_collection(flow_coll) + flow_collections[c.name] = flow_coll + + return { + "nodes": {"Bus": bus_collection}, + "branches": branch_collections, + "flows": flow_collections, + } + + +@wraps( + MapPlotter.draw_map, + assigned=("__doc__", "__annotations__", "__type_params__"), +) +@deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + bus_sizes="bus_size", + bus_colors="bus_color", + bus_split_circles="bus_split_circle", + branch_colors="branch_color", + branch_widths="branch_width", + arrow_colors="arrow_color", + geomap_colors="geomap_color", + line_colors="line_color", + line_widths="line_width", + link_colors="link_color", + link_widths="link_width", + transformer_colors="transformer_color", + transformer_widths="transformer_width", +) +def plot( # noqa: D103 + n: Network, + ax: Axes | None = None, + layouter: Callable | None = None, + boundaries: tuple[float, float, float, float] | None = None, + margin: float | None = 0.05, + projection: Any = None, + geomap: bool | str = True, + geomap_resolution: Literal["10m", "50m", "110m"] = "50m", + geomap_color: dict | bool | None = None, + title: str = "", + jitter: float | None = None, + **kwargs: Any, +) -> dict: + if margin is None: + logger.warning( + "The `margin` argument does support None value anymore. " + "Falling back to the default value 0.05. This will raise " + "an error in the future." + ) + margin = 0.05 + + bus_size = kwargs.get("bus_size") + multindex_buses = isinstance(bus_size, pd.Series) and isinstance( + bus_size.index, pd.MultiIndex + ) + if isinstance(bus_size, pd.Series): + buses = ( + bus_size.index if not multindex_buses else bus_size.index.unique(level=0) + ) + else: + buses = n.c.buses.static.index + + if isinstance(geomap, str): + logger.warning( + "The `geomap` argument now only accepts a boolean value. " + "If you want to set the resolution, use the `geomap_resolution` " + "argument instead." + ) + geomap = True + geomap_resolution = geomap # type: ignore + + # setup plotter + plotter = MapPlotter( + n, + layouter, + boundaries=boundaries, + margin=margin, + buses=buses, + jitter=jitter, + ) + + return plotter.draw_map( + ax, + projection=projection, + geomap=geomap, + geomap_resolution=geomap_resolution, + geomap_color=geomap_color, + title=title, + **kwargs, + ) + + +class HandlerCircle(HandlerPatch): + """Legend Handler used to create circles for legend entries. + + This handler resizes the circles in order to match the same + dimensional scaling as in the applied axis. + """ + + LEGEND_SCALE_FACTOR = 72 + + def __init__(self, scale_factor: float | None = None) -> None: + """Initialize the HandlerCircle.""" + super().__init__() + self.scale_factor = scale_factor or self.LEGEND_SCALE_FACTOR + + def create_artists( + self, + legend: Legend, + orig_handle: Circle, # type: ignore + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + trans: Any, + ) -> list[Circle]: + """Create the artists for the legend.""" + fig = legend.get_figure() + if fig is None: + msg = "Legend must be placed on a figure. No figure found." + raise ValueError(msg) + + ax = legend.axes + + # take minimum to protect against too uneven x- and y-axis extents + unit = min(np.diff(ax.transData.transform([(0, 0), (1, 1)]), axis=0)[0]) + norm = (self.scale_factor / fig.dpi) * unit + radius = orig_handle.get_radius() * norm + center = 5 - xdescent, 3 - ydescent + p = plt.Circle(center, radius) + self.update_prop(p, orig_handle, legend) + p.set_transform(trans) + return [p] + + +class WedgeHandler(HandlerPatch): + """Legend Handler used to create sermi-circles for legend entries. + + This handler resizes the semi-circles in order to match the same + dimensional scaling as in the applied axis. + """ + + LEGEND_SCALE_FACTOR = 72 + + def __init__(self, scale_factor: float | None = None) -> None: + """Initialize the WedgeHandler.""" + super().__init__() + self.scale_factor = scale_factor or self.LEGEND_SCALE_FACTOR + + def create_artists( + self, + legend: Legend, + orig_handle: Wedge, # type: ignore + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + trans: Any, + ) -> list[Wedge]: + """Create the artists for the legend.""" + fig = legend.get_figure() + if fig is None: + msg = "Legend must be placed on a figure. No figure found." + raise ValueError(msg) + ax = legend.axes + center = 5 - xdescent, 3 - ydescent + unit = min(np.diff(ax.transData.transform([(0, 0), (1, 1)]), axis=0)[0]) + norm = (self.scale_factor / fig.dpi) * unit + r = orig_handle.r * norm + p = Wedge( + center=center, + r=r, + theta1=orig_handle.theta1, + theta2=orig_handle.theta2, + ) + self.update_prop(p, orig_handle, legend) + p.set_transform(trans) + return [p] + + +class HandlerArrow(HandlerPatch): + """Handler for FancyArrow patches in legends.""" + + # Empirically determined scale factor for legend arrow sizes + LEGEND_SCALE_FACTOR = 72 + + def __init__( + self, width_ratio: float = 0.2, scale_factor: float | None = None + ) -> None: + """Initialize the HandlerArrow. + + Parameters + ---------- + width_ratio : float, optional + Ratio of arrow width to head width, by default 0.2 + scale_factor : float, optional + Custom scaling factor for arrow size, by default None + + """ + super().__init__() + self.width_ratio = width_ratio + self.scale_factor = scale_factor or self.LEGEND_SCALE_FACTOR + + def create_artists( + self, + legend: Legend, + orig_handle: FancyArrow, # type: ignore + xdescent: float, + ydescent: float, + width: float, + height: float, + fontsize: float, + trans: Any, + ) -> list[FancyArrow]: + """Create the artists for the legend.""" + fig = legend.get_figure() + if fig is None: + msg = "Legend must be placed on a figure. No figure found." + raise ValueError(msg) + ax = legend.axes + unit = min(np.diff(ax.transData.transform([(0, 0), (1, 1)]), axis=0)[0]) + norm = (self.scale_factor / fig.dpi) * unit + arrow = FancyArrow( + 0, + ydescent + height / 2, + width / 4, + 0, + head_width=orig_handle._head_width * norm, # type: ignore + head_length=orig_handle._head_length * norm, # type: ignore + length_includes_head=False, + width=orig_handle._head_width * self.width_ratio * norm, # type: ignore + facecolor=orig_handle.get_facecolor(), + edgecolor=orig_handle.get_facecolor(), + **{k: getattr(orig_handle, f"get_{k}")() for k in ["linewidth", "alpha"]}, + ) + return [arrow] + + +def add_legend_lines( + ax: Axes, + sizes: list[float] | np.ndarray, + labels: list[str] | np.ndarray, + colors: list[str] | np.ndarray | None = None, + patch_kw: dict[str, Any] | None = None, + legend_kw: dict[str, Any] | None = None, +) -> Legend: + """Add a legend for lines and links. + + Parameters + ---------- + ax : matplotlib ax + Matplotlib axis to add the legend to. + sizes : list-like, float + Size of the line reference; for example [3, 2, 1] + labels : list-like, str + Label of the line reference; for example ["30 GW", "20 GW", "10 GW"] + colors: list-like, str + Color of the line reference; for example ["red, "green", "blue"] + patch_kw : defaults to {} + Keyword arguments passed to plt.Line2D + legend_kw : defaults to {} + Keyword arguments passed to ax.legend + + """ + sizes = np.atleast_1d(sizes) + labels = np.atleast_1d(labels) + colors = [] if colors is None else np.atleast_1d(colors) + if patch_kw is None: + patch_kw = {} + if legend_kw is None: + legend_kw = {} + + if len(sizes) != len(labels): + msg = "Sizes and labels must have the same length." + raise ValueError(msg) + if len(colors) > 0 and len(sizes) != len(colors): + msg = "Sizes, labels, and colors must have the same length." + raise ValueError(msg) + + if len(colors) == 0: + handles = [plt.Line2D([0], [0], linewidth=s, **patch_kw) for s in sizes] + else: + handles = [ + plt.Line2D([0], [0], linewidth=s, color=c, **patch_kw) + for s, c in zip(sizes, colors, strict=False) + ] + + legend = ax.legend(handles, labels, **legend_kw) + + fig = ax.get_figure() + if fig is not None: + fig.add_artist(legend) + + return legend + + +def add_legend_patches( + ax: Axes, + colors: list[str] | np.ndarray, + labels: list[str] | np.ndarray, + patch_kw: dict[str, Any] | None = None, + legend_kw: dict[str, Any] | None = None, +) -> Legend: + """Add patches for color references. + + Parameters + ---------- + ax : matplotlib ax + Matplotlib axis to add the legend to. + colors : list-like, float + Color of the patch; for example ["r", "g", "b"] + labels : list-like, str + Label of the patch; for example ["wind", "solar", "gas"] + patch_kw : defaults to {} + Keyword arguments passed to matplotlib.patches.Patch + legend_kw : defaults to {} + Keyword arguments passed to ax.legend + + """ + colors = np.atleast_1d(colors) + labels = np.atleast_1d(labels) + if patch_kw is None: + patch_kw = {} + if legend_kw is None: + legend_kw = {} + + if len(colors) != len(labels): + msg = "Colors and labels must have the same length." + raise ValueError(msg) + + handles = [Patch(facecolor=c, **patch_kw) for c in colors] + + legend = ax.legend(handles, labels, **legend_kw) + + fig = ax.get_figure() + if fig is not None: + fig.add_artist(legend) + + return legend + + +def add_legend_circles( + ax: Axes, + sizes: list[float] | np.ndarray, + labels: list[str] | np.ndarray, + srid: int = DEFAULT_EPSG, + patch_kw: dict[str, Any] | None = None, + legend_kw: dict[str, Any] | None = None, +) -> Legend: + """Add a legend for reference circles. + + !!! warning + When combining `n.plot()` with other plots on a geographical axis, + ensure `n.plot()` is called first or the final axis extent is set initially + (`ax.set_extent(boundaries, crs=crs)`) for consistent legend circle sizes. + + Parameters + ---------- + ax : matplotlib ax + Matplotlib axis to add the legend to. + sizes : list-like, float + Size of the reference circle; for example [3, 2, 1] + labels : list-like, str + Label of the reference circle; for example ["30 GW", "20 GW", "10 GW"] + srid : int, defaults to DEFAULT_EPSG + Spatial reference ID for area correction + patch_kw : defaults to {} + Keyword arguments passed to matplotlib.patches.Circle + legend_kw : defaults to {} + Keyword arguments passed to ax.legend + + """ + sizes = np.atleast_1d(sizes) + labels = np.atleast_1d(labels) + if patch_kw is None: + patch_kw = {} + if legend_kw is None: + legend_kw = {} + + if len(sizes) != len(labels): + msg = "Sizes and labels must have the same length." + raise ValueError(msg) + + if hasattr(ax, "projection"): + warnings.warn( + "When combining n.plot() with other plots on a geographical axis, " + "ensure n.plot() is called first or the final axis extent is set initially " + "(ax.set_extent(boundaries, crs=crs)) for consistent legend circle sizes.", + UserWarning, + stacklevel=2, + ) + area_correction = get_projected_area_factor(ax, srid) ** 2 + sizes = [s * area_correction for s in sizes] + + handles = [Circle((0, 0), radius=s**0.5, **patch_kw) for s in sizes] + + legend = ax.legend( + handles, labels, handler_map={Circle: HandlerCircle()}, **legend_kw + ) + + fig = ax.get_figure() + if fig is not None: + fig.add_artist(legend) + + return legend + + +def add_legend_semicircles( + ax: Axes, + sizes: list[float] | np.ndarray, + labels: list[str] | np.ndarray, + srid: int = DEFAULT_EPSG, + patch_kw: dict[str, Any] | None = None, + legend_kw: dict[str, Any] | None = None, +) -> Legend: + """Add a legend for reference semi-circles. + + !!! warning + When combining `n.plot()` with other plots on a geographical axis, + ensure `n.plot()` is called first or the final axis extent is set initially + (`ax.set_extent(boundaries, crs=crs)`) for consistent legend semicircle sizes. + + Parameters + ---------- + ax : matplotlib ax + Matplotlib axis to add the legend to. + sizes : list-like, float + Size of the reference circle; for example [3, 2, 1] + labels : list-like, str + Label of the reference circle; for example ["30 GW", "20 GW", "10 GW"] + srid : int, default 4326 + Spatial reference ID for area correction + patch_kw : defaults to {} + Keyword arguments passed to matplotlib.patches.Wedges + legend_kw : defaults to {} + Keyword arguments passed to ax.legend + + """ + sizes = np.atleast_1d(sizes) + labels = np.atleast_1d(labels) + + if len(sizes) != len(labels): + msg = "Sizes and labels must have the same length." + raise ValueError(msg) + + if hasattr(ax, "projection"): + warnings.warn( + "When combining n.plot() with other plots on a geographical axis, " + "ensure n.plot() is called first or the final axis extent is set initially " + "(ax.set_extent(boundaries, crs=crs)) for consistent legend semicircle sizes.", + UserWarning, + stacklevel=2, + ) + area_correction = get_projected_area_factor(ax, srid) ** 2 + sizes = [s * area_correction for s in sizes] + + if patch_kw is None: + patch_kw = {} + if legend_kw is None: + legend_kw = {} + + radius = [np.sign(s) * np.abs(s * 2) ** 0.5 for s in sizes] + handles = [ + Wedge((0, -r / 2), r=r, theta1=0, theta2=180, **patch_kw) for r in radius + ] + + legend = ax.legend( + handles, labels, handler_map={Wedge: WedgeHandler()}, **legend_kw + ) + + fig = ax.get_figure() + if fig is not None: + fig.add_artist(legend) + + return legend + + +def add_legend_arrows( + ax: Axes, + sizes: list[float] | np.ndarray, + labels: list[str] | np.ndarray, + srid: int = 4326, + colors: list[str] | np.ndarray | None = None, + arrow_to_tail_width: float = 0.15, + patch_kw: dict[str, Any] | None = None, + legend_kw: dict[str, Any] | None = None, +) -> Legend: + """Add a legend for flow arrows. + + Parameters + ---------- + ax : matplotlib ax + Matplotlib axis to add the legend to. + sizes : list-like, float + Size of the reference arrows; for example [3, 2, 1] + labels : list-like, str + Label of the reference arrows; for example ["30 GW", "20 GW", "10 GW"] + srid : int, default 4326 + Spatial reference ID for area correction + colors : str/list-like, default 'b' + Color(s) of the arrows + arrow_to_tail_width : float, default 0.15 + Ratio of arrow width to tail width + patch_kw : dict, optional + Keyword arguments passed to FancyArrow + legend_kw : dict, optional + Keyword arguments passed to ax.legend + + """ + sizes = np.atleast_1d(sizes) ** 0.5 + labels = np.atleast_1d(labels) + colors = np.atleast_1d(colors) # type: ignore + + if patch_kw is None: + patch_kw = {"linewidth": 1, "zorder": 4} + if legend_kw is None: + legend_kw = {} + + if len(sizes) != len(labels): + msg = "Sizes and labels must have the same length." + raise ValueError(msg) + + if hasattr(ax, "projection"): + area_correction = get_projected_area_factor(ax, srid) + sizes = [s * area_correction for s in sizes] + + if len(colors) == 1: + colors = np.repeat(colors, len(sizes)) + elif len(colors) != len(sizes): + msg = "Colors must be a single value or match length of sizes" + raise ValueError(msg) + + # Scale sizes to be more visible in legend + handles = [ + FancyArrow( + 0, + 0, + 1, + 0, # Shorter arrow length + head_width=s, + head_length=s / 0.6, + length_includes_head=False, + color=c, + **patch_kw, + ) + for s, c in zip(sizes, colors, strict=False) + ] + + legend = ax.legend( + handles, + labels, + handler_map={FancyArrow: HandlerArrow(width_ratio=arrow_to_tail_width)}, + **legend_kw, + ) + fig = ax.get_figure() + if fig is not None: + fig.add_artist(legend) + + return legend + + +def round_to_significant_digits(x: float, n: int = 2) -> int | float: + """Round a number to n significant figures.""" + if x == 0: + return 0 + magnitude = int(np.floor(np.log10(abs(x)))) + rounded = round(x, -magnitude + (n - 1)) + return int(rounded) if rounded >= 1 else rounded + + +def scaled_legend_label(value: float, base_unit: str = "MWh") -> str: + """Scale a value to an appropriate unit for legend labels. + + This function scales the value to a more human-readable format. Ensures scaled + values >= 1 are integers. + """ + unit_scales = { + "": 1, # base + "k": 1e3, # kilo + "M": 1e6, # mega + "G": 1e9, # giga + "T": 1e12, # tera + "P": 1e15, # peta + } + + # Extract base unit without prefix + base_prefix = "" + unit_name = base_unit + for prefix in sorted(unit_scales.keys(), key=len, reverse=True): + if base_unit.startswith(prefix): + base_prefix = prefix + unit_name = base_unit[len(prefix) :] + break + + # Calculate absolute value in base units + base_value = value * unit_scales[base_prefix] + + # Find appropriate prefix + magnitude = np.floor(np.log10(abs(base_value))) if base_value != 0 else 0 + + # Get closest unit scale that keeps value between 1 and 1000 + scales = np.array(list(unit_scales.values())) + prefixes = list(unit_scales.keys()) + target_scale_idx = np.searchsorted(scales, 10 ** (magnitude - 2)) + if target_scale_idx >= len(scales): + target_scale_idx = len(scales) - 1 + + target_scale = scales[target_scale_idx] + target_prefix = prefixes[target_scale_idx] + + # If base_unit already has a prefix, adjust the scale accordingly + if base_prefix: + # Calculate the relative scale between target and base prefix + scale_difference = target_scale / unit_scales[base_prefix] + scaled_value = value / scale_difference + else: + scaled_value = base_value / target_scale + + # Convert to integer if >= 1 + if abs(scaled_value) >= 1: + scaled_value = int(round(scaled_value)) + + return f"{scaled_value} {target_prefix}{unit_name}" + + +def get_legend_representatives( + series: pd.Series, + quantiles: list[float] | None = None, + n_significant: int = 1, + base_unit: str = "MWh", + group_on_first_level: bool = False, +) -> list[tuple[int | float, str]]: + """Get representative values from a numeric series for legend visualization. + + Automatic unit scaling is applied. Values >= 1 are returned as integers. + + Parameters + ---------- + series : pd.Series + Series containing the values + quantiles : list + List of quantile to use assuming a uniform distribution from + 0 to the maximum value (default: [0.6, 0.2]) + n_significant : int + Number of significant figures to round to + base_unit : str + Base unit of the values (default: "MWh") + group_on_first_level : bool + If True, group the series by the first level of the index + before calculating the maximum value + + Returns + ------- + list + List of tuples (scaled_value, unit) for each quantile + + """ + if quantiles is None: + quantiles = [0.6, 0.2] + if series.empty: + return [] + if group_on_first_level: + series = series.abs().groupby(level=0).sum() + max_value = series.abs().max() + values = [max_value * q for q in quantiles] + rounded_values = [round_to_significant_digits(v, n_significant) for v in values] + + return [(v, scaled_legend_label(v, base_unit)) for v in rounded_values] diff --git a/PyPSA/source/pypsa/plot/statistics/__init__.py b/PyPSA/source/pypsa/plot/statistics/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2a8fae910163ba209858f7041c077427ac1181fb --- /dev/null +++ b/PyPSA/source/pypsa/plot/statistics/__init__.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Statistic plots for PyPSA. + +This module contains all plotting functions which can be used on top of the +statistics functions of [`pypsa.Network.statistics`][]. +""" + +from pypsa.plot.statistics.charts import ChartGenerator +from pypsa.plot.statistics.maps import MapPlotGenerator + +__all__ = [ + "ChartGenerator", + "MapPlotGenerator", +] diff --git a/PyPSA/source/pypsa/plot/statistics/base.py b/PyPSA/source/pypsa/plot/statistics/base.py new file mode 100644 index 0000000000000000000000000000000000000000..0f204d8c8a9dabceadf3fdb3f8ec089d3ca83ef9 --- /dev/null +++ b/PyPSA/source/pypsa/plot/statistics/base.py @@ -0,0 +1,86 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Abstract base class to generate any plots based on statistics functions.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any + +import pandas as pd + +if TYPE_CHECKING: + from collections.abc import Sequence + + from pypsa import Network + + +class PlotsGenerator(ABC): + """Base plot generator class for statistics plots. + + This class provides a common interface for all plot generators which build up + on statistics functions of [`pypsa.Network.statistics`][]. Defined methods need + to be implemented by subclasses. + """ + + _n: Network + + def __init__(self, n: Network) -> None: + """Initialize plot generator. + + Parameters + ---------- + n : pypsa.Network + Network object. + + """ + self._n = n + + @abstractmethod + def derive_statistic_parameters( + self, + *args: str | None, + method_name: str = "", # make required + ) -> dict[str, Any]: + """Handle default statistics kwargs based on provided plot kwargs.""" + + def get_unique_carriers(self) -> pd.DataFrame: + """Get unique carriers from the network.""" + carriers = self._n.c.carriers.static + if isinstance(carriers.index, pd.MultiIndex): + for level in carriers.index.names: + if level != "name": + carriers = carriers.droplevel(level) + unique_carriers = carriers[~carriers.index.duplicated(keep="first")] + return unique_carriers.sort_index() + else: + return carriers.sort_index() + + def get_carrier_colors( + self, carriers: Sequence | None = None, nice_names: bool = True + ) -> dict: + """Get colors for carrier data with default gray colors.""" + carriers_df = self.get_unique_carriers() + if carriers is None: + carriers = carriers_df.index + colors = carriers_df.color[carriers] + if nice_names: + labels = self.get_carrier_labels(carriers=carriers, nice_names=nice_names) + colors = colors.rename(labels) + colors = colors[~colors.index.duplicated(keep="first")] + default_colors = {"-": "gray", None: "gray", "": "gray"} + return {**default_colors, **colors} + + def get_carrier_labels( + self, carriers: Sequence | None = None, nice_names: bool = True + ) -> pd.Series: + """Get mapping of carrier names to nice names if requested.""" + carriers_df = self.get_unique_carriers() + if carriers is None: + carriers = carriers_df.index + if nice_names: + names = carriers_df.nice_name[carriers] + return names.where(names != "", carriers) + return pd.Series(carriers, index=carriers) diff --git a/PyPSA/source/pypsa/plot/statistics/charts.py b/PyPSA/source/pypsa/plot/statistics/charts.py new file mode 100644 index 0000000000000000000000000000000000000000..cb93abb1f17f0f6237232e2c7c152924cd59d09d --- /dev/null +++ b/PyPSA/source/pypsa/plot/statistics/charts.py @@ -0,0 +1,643 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Chart plots based on statistics functions (like bar, line, area).""" + +from __future__ import annotations + +from abc import ABC +from typing import TYPE_CHECKING, Any + +import numpy as np +import pandas as pd +import plotly.express as px +import plotly.graph_objects as go +import seaborn as sns + +from pypsa.plot.statistics.base import PlotsGenerator + +if TYPE_CHECKING: + from collections.abc import Iterator, Sequence + + from matplotlib.axes import Axes + from matplotlib.figure import Figure + +CHART_TYPES = [ + "area", + "bar", + "scatter", + "line", + "box", + "violin", + "histogram", +] + + +def facet_iter( + g: sns.FacetGrid, + df: pd.DataFrame, + facet_row: str | None, + facet_col: str | None, + split_by_sign: bool = False, +) -> Iterator[tuple[Axes, pd.DataFrame]]: + """Generate (axis, filtered_data) for each facet in a FacetGrid. + + Parameters + ---------- + g : seaborn.FacetGrid + The FacetGrid instance to use + df : pandas.DataFrame + The DataFrame to filter for each facet + facet_row : str, optional + Column name used for creating row facets + facet_col : str, optional + Column name used for creating column facets + split_by_sign : bool, optional + Whether to split the data by sign (positive/negative) for the y-axis + Default is False. + + Yields + ------ + tuple : (axis, filtered_data) + - axis: The matplotlib Axes object for the current facet + - filtered_data: DataFrame filtered for the current facet + + """ + # Get the facet values + row_vals = g.row_names if g.row_names else [None] + col_vals = g.col_names if g.col_names else [None] + + for i, row_val in enumerate(row_vals): + for j, col_val in enumerate(col_vals): + # Get the axis for this facet + if g.axes.ndim == 1: + ax = g.axes[i if len(g.axes) > 1 else j] + else: + ax = g.axes[i, j] + + # Filter the data for this specific facet + facet_data = df.copy() + if facet_row is not None and row_val is not None: + mask = facet_data[facet_row] == row_val + facet_data = facet_data[mask] + if facet_col is not None and col_val is not None: + mask = facet_data[facet_col] == col_val + facet_data = facet_data[mask] + + # Skip if no data + if not facet_data.values.size: + continue + + if split_by_sign: + for clip in [{"lower": 0}, {"upper": 0}]: + yield ax, facet_data.assign(value=facet_data["value"].clip(**clip)) + else: + # Yield the axis and the filtered data + yield ax, facet_data + + +def map_dataframe_pandas_plot( + g: sns.FacetGrid, + df: pd.DataFrame, + x: str, + y: str, + color: str | None, + facet_row: str | None, + facet_col: str | None, + stacked: bool, + palette: dict | None = None, + kind: str = "area", + ylim: tuple[float, float] | None = None, + **kwargs: Any, +) -> sns.FacetGrid: + """Handle the creation of area or bar plots for FacetGrid. + + Parameters + ---------- + g : seaborn.FacetGrid + The FacetGrid instance to use + df : pandas.DataFrame + The DataFrame in long format to plot + x : str + Column name to use for x-axis + y : str + Column name to use for y-axis + color : str, optional + Column name to use for color encoding + facet_row : str, optional + Column name used for creating row facets + facet_col : str, optional + Column name used for creating column facets + stacked : bool + Whether to create stacked charts + palette : dict, optional + Color palette to use for the plot + kind : str, optional + Kind of plot to create ('area' or 'bar') + ylim : tuple, optional + Y-axis limits for the plot + **kwargs : additional keyword arguments + Passed to plotting function + + """ + custom_case = color not in [x, y, facet_col, facet_row, None] or kind == "area" + + if custom_case: + # Store the color palette from FacetGrid for consistent colors + color_order = g.hue_names if hasattr(g, "hue_names") else None + + split_by_sign = df["value"].min() < 0 and df["value"].max() > 0 + + if kind == "bar" and x == "value": + kind = "barh" + x_var, y_var = y, x + else: + x_var, y_var = x, y + + for ax, facet_data in facet_iter(g, df, facet_row, facet_col, split_by_sign): + # Pivot data to have x as index, color as columns, and y as values + if color is None: + pivoted = facet_data.set_index(x_var)[[y_var]] + color_dict = None + else: + pivoted = facet_data.pivot(index=x_var, columns=color, values=y_var) + color_dict = palette + + # Special case of duplicate indices, e.g. carriers in groupers, but not plotted + if not pivoted.index.is_unique: + pivoted = pivoted.groupby(level=0).sum() + + # Ensure columns are ordered according to the hue order + if color_order: + # Get only the columns that exist in this facet + available_cols = [c for c in color_order if c in pivoted.columns] + if available_cols: # Only reorder if we have columns + pivoted = pivoted[available_cols] + + # Weird behavior in pandas plotting, have to correct the ylim if None + # https://github.com/pandas-dev/pandas/blob/c0371cedf3a9682596481dab87b43653a48da186/pandas/plotting/_matplotlib/core.py#L1817 + if y == "value" and ylim is None and len(list(ax.get_shared_y_axes())) == 0: + ax._shared_axes["y"].join(ax, ax) # type: ignore + + # Plot with pandas - no legend to avoid duplicates + pivoted.plot( + kind=kind, + ax=ax, + stacked=stacked, + legend=False, + color=color_dict, + **kwargs, + ) + g._update_legend_data(ax) + g._finalize_grid([x, y]) + + elif kind == "bar": + palette = palette if color is not None else None + g.map_dataframe(sns.barplot, x=x, y=y, hue=color, palette=palette, **kwargs) + + return g + + +class ChartGenerator(PlotsGenerator, ABC): + """Base class for generating charts based on statistics functions.""" + + def _to_title(self, s: str) -> str: + """Convert string to title case.""" + return s.replace("_", " ").title() + + def _validate(self, data: pd.DataFrame) -> pd.DataFrame: + """Validate data has required columns and types.""" + if "value" not in data.columns: + msg = "Data must contain 'value' column" + raise ValueError(msg) + + return data + + def _to_long_format(self, data: pd.DataFrame | pd.Series) -> pd.DataFrame: + """Convert data to long format suitable for plotting. + + Parameters + ---------- + data : pd.DataFrame | pd.Series + Input data from statistics functions, typically with multiindex + + Returns + ------- + pd.DataFrame + Long format DataFrame with multiindex levels as columns and values in 'value' column + + """ + if isinstance(data, pd.Series): + df = data.rename("value").reset_index() + else: + df = data.fillna(0).melt(ignore_index=False).reset_index() + + return df + + def plot( + self, + data: pd.DataFrame, + kind: str, + x: str, + y: str, + color: str | None = None, + facet_col: str | None = None, + facet_row: str | None = None, + stacked: bool = True, + nice_names: bool = True, + query: str | None = None, + sharex: bool | None = None, + sharey: bool | None = None, + height: float = 3, + aspect: float = 2, + row_order: Sequence[str] | None = None, + col_order: Sequence[str] | None = None, + hue_order: Sequence[str] | None = None, + hue_kws: dict[str, Any] | None = None, + despine: bool = True, + margin_titles: bool = False, + xlim: tuple[float, float] | None = None, + ylim: tuple[float, float] | None = None, + subplot_kws: dict[str, Any] | None = None, + gridspec_kws: dict[str, Any] | None = None, + **kwargs: Any, + ) -> tuple[Figure, Axes | np.ndarray, sns.FacetGrid]: + """Plot method to be implemented by subclasses.""" + self._n.consistency_check_plots(strict="all") + ldata = self._to_long_format(data) + if query: + ldata = ldata.query(query) + ldata = self._validate(ldata) + palette = self.get_carrier_colors(nice_names=nice_names) + + # set shared axis to the one where "value" is plotted + if sharex is None: + sharex = x == "value" + if sharey is None: + sharey = y == "value" + + # Always use FacetGrid for consistency + g = sns.FacetGrid( + ldata, + row=facet_row, + col=facet_col, + palette=palette, + sharex=sharex, + sharey=sharey, + height=height, + aspect=aspect, + row_order=row_order, + col_order=col_order, + hue_order=hue_order, + hue_kws=hue_kws, + despine=despine, + margin_titles=margin_titles, + xlim=xlim, + ylim=ylim, + subplot_kws=subplot_kws, + gridspec_kws=gridspec_kws, + ) + + # Handle special case for area and bar plots + if kind == "area": + g = map_dataframe_pandas_plot( + g, + ldata, + x, + y, + color, + facet_row, + facet_col, + stacked, + kind="area", + ylim=ylim, + palette=palette, + **kwargs, + ) + elif kind == "bar": + g = map_dataframe_pandas_plot( + g, + ldata, + x, + y, + color, + facet_row, + facet_col, + stacked, + kind="bar", + ylim=ylim, + palette=palette, + **kwargs, + ) + # Other plot types remain the same + elif kind == "scatter": + g.map_dataframe(sns.scatterplot, x=x, y=y, hue=color, **kwargs) + elif kind == "line": + g.map_dataframe(sns.lineplot, x=x, y=y, hue=color, **kwargs) + elif kind in ["box", "violin"]: + # FacetGrid adds color internally, remove to avoid conflict with hue + kwargs.pop("color", None) + plot_func = sns.boxplot if kind == "box" else sns.violinplot + plot_kwargs = {"x": x, "y": y, "hue": color, **kwargs} + if color is not None: + plot_kwargs["palette"] = palette + g.map_dataframe(plot_func, **plot_kwargs) + elif kind == "histogram": + if y is None: + g.map_dataframe(sns.histplot, x=x, hue=color, **kwargs) + else: + g.map_dataframe(sns.histplot, x=x, y=y, hue=color, **kwargs) + else: + msg = f"Unsupported plot type: {kind}" + raise ValueError(msg) + + # Add legend if color is specified (for non-area plots, area plots handle this separately) + if color is not None: + g.add_legend() + + # Set axis labels + # Get unit for legends + label = data.attrs.get("name", "Value") + unit = data.attrs.get("unit", "") + if unit != "carrier dependent": + label += f" [{unit}]" + if x == "value": + g.set_axis_labels(x_var=label) + elif y == "value": + g.set_axis_labels(y_var=label) + + # Get the figure and axes from the FacetGrid + fig = g.fig + ax = g.axes # This will be a 2D array of axes objects + + # If there's only one subplot, return the single Axes object for convenience + if ax.size == 1: + ax = ax.flat[0] + + return fig, ax, g + + def _create_category_orders( + self, + facet_row: str | None, + facet_col: str | None, + color: str | None, + row_order: Sequence[str] | None, + col_order: Sequence[str] | None, + color_order: Sequence[str] | None, + ) -> dict[str, Sequence[str]]: + """Create a filtered dictionary of category orders for Plotly Express.""" + category_orders = {} + if facet_row is not None and row_order is not None: + category_orders[facet_row] = row_order + if facet_col is not None and col_order is not None: + category_orders[facet_col] = col_order + if color is not None and color_order is not None: + category_orders[color] = color_order + return category_orders + + def iplot( + self, + data: pd.DataFrame, + kind: str, + x: str, + y: str, + color: str | None = None, + facet_col: str | None = None, + facet_row: str | None = None, + stacked: bool = True, + nice_names: bool = True, + query: str | None = None, + sharex: bool | None = None, + sharey: bool | None = None, + height: int = 500, + width: int = 800, + row_order: Sequence[str] | None = None, + col_order: Sequence[str] | None = None, + color_order: Sequence[str] | None = None, + color_discrete_map: dict[str, str] | None = None, + range_x: list[float] | None = None, + range_y: list[float] | None = None, + labels: dict[str, str] | None = None, + title: str | None = None, + **kwargs: Any, + ) -> go.Figure: + """Interactive plot method creating charts with Plotly Express.""" + self._n.consistency_check_plots(strict="all") + ldata = self._to_long_format(data) + if query: + ldata = ldata.query(query) + ldata = self._validate(ldata) + + # Get carrier colors for the plot + carrier_colors = self.get_carrier_colors(nice_names=nice_names) + + # Set up labels dictionary for axis labels + if labels is None: + labels = {} + + # Get unit for legends + data_label = data.attrs.get("name", "Value") + unit = data.attrs.get("unit", "") + if unit != "carrier dependent": + data_label += f" [{unit}]" + + if x == "value": + labels[x] = data_label + elif y == "value": + labels[y] = data_label + + # Handle categorical axes to avoid auto-sorting + if x != "value" and ldata[x].dtype.name == "object": + ldata[x] = pd.Categorical( + ldata[x], categories=ldata[x].unique(), ordered=True + ) + if y != "value" and ldata[y].dtype.name == "object": + ldata.loc[:, y] = pd.Categorical( + ldata[y], categories=ldata[y].unique(), ordered=True + ) + + # Prepare color mapping if color column is provided + if color and color_discrete_map is None and color in ldata.columns: + color_values = ldata[color].unique() + color_discrete_map = { + col: carrier_colors.get(col, "#AAAAAA") for col in color_values + } + + # Set default title if none is provided + if title is None: + title = self._to_title(data.attrs.get("name", "")) + + # Create category orders dict + category_orders = self._create_category_orders( + facet_row, facet_col, color, row_order, col_order, color_order + ) + + # Create appropriate plot based on kind + if kind == "bar": + # Handle regular vs stacked bar charts + if stacked and color is not None: + barmode = "stack" + else: + barmode = "group" + + fig = px.bar( + ldata, + x=x, + y=y, + color=color, + facet_col=facet_col, + facet_row=facet_row, + height=height, + width=width, + facet_col_wrap=kwargs.get("facet_col_wrap", 0), + category_orders=category_orders, + color_discrete_map=color_discrete_map, + barmode=barmode, + range_x=range_x, + range_y=range_y, + labels=labels, + title=title, + **{k: v for k, v in kwargs.items() if k not in ["facet_col_wrap"]}, + ) + + elif kind == "line": + fig = px.line( + ldata, + x=x, + y=y, + color=color, + facet_col=facet_col, + facet_row=facet_row, + height=height, + width=width, + facet_col_wrap=kwargs.get("facet_col_wrap", 0), + category_orders=category_orders, + color_discrete_map=color_discrete_map, + range_x=range_x, + range_y=range_y, + labels=labels, + title=title, + **{k: v for k, v in kwargs.items() if k not in ["facet_col_wrap"]}, + ) + + elif kind == "area": + kwargs = dict( + x=x, + y=y, + color=color, + facet_col=facet_col, + facet_row=facet_row, + height=height, + width=width, + facet_col_wrap=kwargs.get("facet_col_wrap", 0), + category_orders=category_orders, + color_discrete_map=color_discrete_map, + range_x=range_x, + range_y=range_y, + labels=labels, + title=title, + **{k: v for k, v in kwargs.items() if k not in ["facet_col_wrap"]}, + ) + + if stacked: + pos = ldata[ldata.value > 0] + neg = ldata[ldata.value < 0] + positives = px.area(pos, **kwargs) + positives.update_traces( + stackgroup="positive", + showlegend=False, + ) + negatives = px.area(neg, **kwargs) + negatives.update_traces( + stackgroup="negative", + showlegend=False, + ) + + # In order to not bloat the hover display with zeros, we need to + # filter out zeros in ldata as done below. However, then the legend + # only shows for the latest traces (ignoring the positive values). + # To fix this, we need to add an artificial trace with the last value + # of each color and use that for the legend. + unique_colors = ldata[color].unique() if color else [] + artificial_zeros = pd.DataFrame( + {x: ldata[x].iloc[-1], y: np.nan, color: unique_colors} + ) + if facet_col: + artificial_zeros[facet_col] = ldata[facet_col].iloc[-1] + if facet_row: + artificial_zeros[facet_row] = ldata[facet_row].iloc[-1] + + artificials = px.area( + artificial_zeros, + **kwargs, + ) + + # Combine the figures + fig = positives.add_traces(negatives.data).add_traces(artificials.data) + else: + fig = px.area(ldata, **kwargs) + fig.update_traces(line={"width": 0}) + fig.update_layout(hovermode="x") + else: + msg = f"Unsupported plot type: {kind}" + raise ValueError(msg) + + # Update layout + fig.update_layout( + template="plotly_white", margin={"l": 50, "r": 50, "t": 50, "b": 50} + ) + + if not sharex and sharex is not None: + fig.update_xaxes(matches=None) + if not sharey and sharey is not None: + fig.update_yaxes(matches=None) + + return fig + + def derive_statistic_parameters( + self, + *args: Any, + method_name: str = "", # make required + ) -> dict[str, Any]: + """Extract plotting specification rules including groupby columns and component aggregation. + + Parameters + ---------- + *args : tuple of (str | None) + Arguments representing x, y, color, facet_col, facet_row parameters + method_name : str, optional + Name of the statistics function to allow for specific rules + + Returns + ------- + tuple + List of groupby columns and boolean for component aggregation + + """ + filtered = ["value", "name", "snapshot"] + filtered_cols = [c for c in args if c not in filtered and c is not None] + + stats_kwargs: dict[str, str | bool | list] = {} + + # `groupby` + filtered_cols = list(set(filtered_cols)) # Remove duplicates + if filtered_cols: + stats_kwargs["groupby"] = filtered_cols + if method_name == "prices": + stats_kwargs.pop("groupby", None) # prices does not support groupby + + # `aggregate_across_components` + if method_name != "prices": + stats_kwargs["aggregate_across_components"] = "component" not in args + + # `groupby_time` is only relevant for time series data + if "snapshot" in args: + derived_agg_time: str | bool = "snapshot" not in args # Check in args tuple + if derived_agg_time: + # Convert to list since groupby_time expects a list of strings + stats_kwargs["groupby_time"] = "sum" + else: + stats_kwargs["groupby_time"] = False + + return stats_kwargs diff --git a/PyPSA/source/pypsa/plot/statistics/maps.py b/PyPSA/source/pypsa/plot/statistics/maps.py new file mode 100644 index 0000000000000000000000000000000000000000..2f9a16f4d61c844acd5b83469103ed45ea33ee7f --- /dev/null +++ b/PyPSA/source/pypsa/plot/statistics/maps.py @@ -0,0 +1,337 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Maps plots based on statistics functions.""" + +import warnings +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Literal + +import pandas as pd +from matplotlib.axes import Axes +from matplotlib.figure import Figure, SubFigure + +from pypsa.common import deprecated_kwargs +from pypsa.plot.maps.static import ( + MapPlotter, + add_legend_arrows, + add_legend_circles, + add_legend_lines, + add_legend_patches, + add_legend_semicircles, + get_legend_representatives, +) +from pypsa.plot.statistics.base import PlotsGenerator + +if TYPE_CHECKING: + from pypsa import Network + + +class MapPlotGenerator(PlotsGenerator, MapPlotter): + """Main statistics map plot accessor providing access to different plot types. + + This class combines functionality from both StatisticsPlotAccessor and NetworkMapPlotter + to create geographic visualizations of network statistics. + """ + + _n: "Network" + + def __init__(self, n: "Network") -> None: + """Initialize the MapPlotter with a PyPSA network. + + Parameters + ---------- + n : Network + PyPSA network object + + """ + PlotsGenerator.__init__(self, n) + MapPlotter.__init__(self, n) + + def derive_statistic_parameters( + self, + *args: str | None, + method_name: str = "", # make required + ) -> dict[str, Any]: + """Handle default statistics kwargs based on provided plot kwargs.""" + return {} + + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + bus_sizes="bus_size", + bus_colors="bus_color", + bus_split_circles="bus_split_circle", + branch_colors="branch_color", + branch_widths="branch_width", + arrow_colors="arrow_color", + geomap_colors="geomap_color", + line_colors="line_color", + line_widths="line_width", + link_colors="link_color", + link_widths="link_width", + transformer_colors="transformer_color", + transformer_widths="transformer_width", + ) + def plot( + self, + func: Callable, + bus_carrier: str | None = None, + ax: Axes | None = None, + projection: Any = None, + geomap: bool = True, + geomap_resolution: Literal["10m", "50m", "110m"] = "50m", + geomap_color: dict | bool | None = None, + boundaries: tuple[float, float, float, float] | None = None, + title: str = "", + carrier: str | None = None, + transmission_flow: bool = False, + bus_area_fraction: float = 0.02, + branch_area_fraction: float = 0.02, + flow_area_fraction: float = 0.02, + draw_legend_circles: bool = True, + draw_legend_lines: bool = True, + draw_legend_arrows: bool = False, + draw_legend_patches: bool = True, + legend_circles_kw: dict | None = None, + legend_lines_kw: dict | None = None, + legend_arrows_kw: dict | None = None, + legend_patches_kw: dict | None = None, + bus_split_circle: bool = False, + stats_kwargs: dict | None = None, + nice_names: bool = True, + **kwargs: Any, + ) -> tuple[Figure | SubFigure | Any, Axes | Any]: + """Plot network statistics on a map.""" + if func.__name__ == "prices": + msg = "Plotting 'prices' on a map is not yet implemented." + raise NotImplementedError(msg) + n = self._n + colors = self.get_carrier_colors(nice_names=False) + n.consistency_check_plots() + boundaries = boundaries or self.boundaries + (x_min, x_max, y_min, y_max) = boundaries # type: ignore + + # Get non-transmission carriers + # TODO solve circular import by refactoring to descriptors.py + from pypsa.statistics.expressions import ( # noqa: PLC0415 + get_transmission_carriers, + ) + + trans_carriers = get_transmission_carriers(n, bus_carrier=bus_carrier).unique( + "carrier" + ) + non_transmission_carriers = n.c.carriers.static.index.difference(trans_carriers) + + # Get bus sizes from statistics function + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + message=".*Passing `aggregate_across_components` was deprecated.*", + category=DeprecationWarning, + ) + bus_size = func( + bus_carrier=bus_carrier, + groupby=["bus", "carrier"], + carrier=list(non_transmission_carriers), + nice_names=False, + aggregate_across_components=True, + **(stats_kwargs or {}), + ) + if bus_size.empty: + # TODO: this fallback case should be handled in the statistics function + bus_size = ( + pd.DataFrame({"bus": [], "carrier": [], "value": []}) + .set_index(["bus", "carrier"]) + .value + ) + + # Calculate scaling factors for visual elements + bus_size_scaling_factor = self.scaling_factor_from_area_contribution( + bus_size, x_min, x_max, y_min, y_max, bus_area_fraction + ) + + # Handle transmission flows or branch widths + if transmission_flow: + branch_flows = n.statistics.transmission( + groupby=False, bus_carrier=bus_carrier, nice_names=False + ) + branch_flows = MapPlotGenerator.aggregate_flow_by_connection( + branch_flows, n.branches() + ) + branch_flow_scaling_factor = self.scaling_factor_from_area_contribution( + branch_flows, x_min, x_max, y_min, y_max, flow_area_fraction + ) + + branch_flow_scaled = branch_flows * branch_flow_scaling_factor + branch_width_scaled = self.flow_to_width(branch_flow_scaled) + else: + branch_flow_scaled = {} + branch_width = func( + components=n.branch_components, + bus_carrier=bus_carrier, + groupby=False, + carrier=list(trans_carriers), + nice_names=False, + **(stats_kwargs or {}), + ) + branch_width_scaling_factor = self.scaling_factor_from_area_contribution( + branch_width, x_min, x_max, y_min, y_max, branch_area_fraction + ) + branch_width_scaled = branch_width * branch_width_scaling_factor + + # Get branch colors from carrier colors + branch_color = n.branches().carrier[branch_width_scaled.index].map(colors) + + # Set default plot arguments + plot_args = { + "bus_size": bus_size * bus_size_scaling_factor, + "bus_split_circle": bus_split_circle, + "bus_color": colors, + "line_flow": branch_flow_scaled.get("Line"), + "line_width": branch_width_scaled.get("Line", 0), + "line_color": branch_color.get("Line", "k"), + "link_flow": branch_flow_scaled.get("Link"), + "link_width": branch_width_scaled.get("Link", 0), + "link_color": branch_color.get("Link", "k"), + "transformer_flow": branch_flow_scaled.get("Transformer"), + "transformer_width": branch_width_scaled.get("Transformer", 0), + "transformer_color": branch_color.get("Transformer", "k"), + "auto_scale_branches": False, + } + + # Override with user-provided arguments + if kwargs: + plot_args.update(kwargs) + + # Draw the map + self.draw_map( + ax=ax, + projection=projection, + geomap=geomap, + geomap_resolution=geomap_resolution, + geomap_color=geomap_color, + title=title, + boundaries=boundaries, + **plot_args, + ) + + # Get unit for legends + unit = bus_size.attrs.get("unit", "") + if unit == "carrier dependent": + unit = "" + + # Add legends if requested + if draw_legend_circles and hasattr(self.ax, "figure"): + legend_representatives = get_legend_representatives( + bus_size, group_on_first_level=True, base_unit=unit + ) + + if legend_representatives: + if bus_split_circle: + add_legend_semicircles( + self.ax, # type: ignore + [ + s * bus_size_scaling_factor + for s, label in legend_representatives + ], + [label for s, label in legend_representatives], + legend_kw={ + "bbox_to_anchor": (0, 0.9), + "loc": "lower left", + "frameon": True, + **(legend_circles_kw or {}), + }, + ) + else: + add_legend_circles( + self.ax, # type: ignore + [ + s * bus_size_scaling_factor + for s, label in legend_representatives + ], + [label for s, label in legend_representatives], + legend_kw={ + "bbox_to_anchor": (0, 0.9), + "loc": "lower left", + "frameon": True, + **(legend_circles_kw or {}), + }, + ) + + if draw_legend_arrows and hasattr(self.ax, "figure"): + if not transmission_flow: + msg = "Cannot draw arrow legend if transmission_flow is False. Use draw_legend_lines instead." + raise ValueError(msg) + + legend_representatives = get_legend_representatives( + branch_flows, n_significant=1, base_unit=unit + ) + + if legend_representatives: + add_legend_arrows( + self.ax, # type: ignore + [ + s * branch_flow_scaling_factor + for s, label in legend_representatives + ], + [label for s, label in legend_representatives], + legend_kw={ + "bbox_to_anchor": (0, 0.9), + "loc": "upper left", + "frameon": True, + **(legend_arrows_kw or {}), + }, + ) + + if draw_legend_lines and hasattr(self.ax, "figure"): + if transmission_flow: + msg = "Cannot draw line legend if transmission_flow is True. Use draw_legend_arrows instead." + raise ValueError(msg) + + legend_representatives = get_legend_representatives( + branch_width, n_significant=1, base_unit=unit + ) + + if legend_representatives: + add_legend_lines( + self.ax, # type: ignore + [ + s * branch_width_scaling_factor + for s, label in legend_representatives + ], + [label for s, label in legend_representatives], + legend_kw={ + "bbox_to_anchor": (0, 0.9), + "loc": "upper left", + "frameon": True, + **(legend_lines_kw or {}), + }, + ) + + if draw_legend_patches and hasattr(self.ax, "figure"): + carriers = bus_size.index.get_level_values("carrier").drop_duplicates() + colors = self.get_carrier_colors(carriers, nice_names=False) + labels = self.get_carrier_labels(carriers, nice_names=nice_names) + + add_legend_patches( + self.ax, # type: ignore + colors=[colors[c] for c in carriers], + labels=labels, + legend_kw={ + "bbox_to_anchor": (1, 1), + "loc": "upper left", + "frameon": False, + **(legend_patches_kw or {}), + }, + ) + + # Ensure ax has a figure (might be None if initialization failed) + if self.ax is None or not hasattr(self.ax, "figure"): + import matplotlib.pyplot as plt # noqa: PLC0415 + + fig = plt.gcf() + return fig, self.ax or plt.gca() + + return self.ax.figure, self.ax diff --git a/PyPSA/source/pypsa/plot/statistics/plotter.py b/PyPSA/source/pypsa/plot/statistics/plotter.py new file mode 100644 index 0000000000000000000000000000000000000000..04618dd7b3bbe1f957e5a5c8c23df6852ebaa592 --- /dev/null +++ b/PyPSA/source/pypsa/plot/statistics/plotter.py @@ -0,0 +1,751 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Statistics Accessor.""" + +from __future__ import annotations + +import warnings +from typing import TYPE_CHECKING, Any, Literal + +from pypsa.common import deprecated_kwargs +from pypsa.plot.statistics.charts import CHART_TYPES, ChartGenerator +from pypsa.plot.statistics.maps import MapPlotGenerator +from pypsa.plot.statistics.schema import ( + apply_parameter_schema, + get_relevant_plot_values, +) + +if TYPE_CHECKING: + from collections.abc import Callable, Sequence + + import numpy as np + import plotly.graph_objects as go + import seaborn as sns + from matplotlib.axes import Axes + from matplotlib.figure import Figure, SubFigure + + from pypsa.networks import Network + + +class StatisticPlotter: + """Create plots based on output of statistics functions. + + Passed arguments and the specified statistics function are stored and called + later when the plot is created, depending on the plot type. Also some checks + are performed to validated the arguments. + """ + + def __init__(self, bound_method: Callable, n: Network) -> None: + """Initialize the statistic handler. + + Parameters + ---------- + bound_method : Callable + The bound method/ underlying statistic function to call. + n : Network + The network object to use for the statistic calculation. + + """ + self._bound_method = bound_method + self._n = n + + def __call__( + self, kind: str | None = None + ) -> ( + tuple[Figure, Axes | np.ndarray, sns.FacetGrid] + | tuple[Figure | SubFigure | Any, Axes | Any] + ): + """Create simple visualization of the statistic. + + This function builds up on any statistics function and allows for a simple + exploration without any further arguments. If a fine grained control is + needed, the plot functions should be used directly (e.g. `.plot.bar()` instead + of `.plot(kind="bar")`). + + Parameters + ---------- + kind : str | None, default: None + Type of chart ("bar", "line", "area", "map"). If None, the default per + statistics function, defined in the schema, is used. + + Returns + ------- + tuple[Figure | SubFigure | Any, Axes | Any] + The figure and axes of the plot. + + Examples + -------- + >>> fig, ax, g = n.statistics.installed_capacity.plot(kind="bar") # doctest: +ELLIPSIS + + """ + # Get the correct plot function + if kind not in CHART_TYPES + ["map", None]: + msg = f"Unknown plot type '{kind}'." + raise ValueError(msg) + # Apply schema to kind kwarg + stats_name = self._bound_method.__name__ + kind_ = apply_parameter_schema(stats_name, "plot", {"kind": kind})["kind"] + plot_func = getattr(self, kind_) + return plot_func() + + def area(self, **kwargs: Any) -> tuple[Figure, Axes | np.ndarray, sns.FacetGrid]: + """See [`chart`][pypsa.plot.statistics.plotter.StatisticPlotter.chart] for parameters.""" + return self.chart(chart_type="area", **kwargs) + + def bar(self, **kwargs: Any) -> tuple[Figure, Axes | np.ndarray, sns.FacetGrid]: + """See [`chart`][pypsa.plot.statistics.plotter.StatisticPlotter.chart] for parameters.""" + return self.chart(chart_type="bar", **kwargs) + + def scatter(self, **kwargs: Any) -> tuple[Figure, Axes | np.ndarray, sns.FacetGrid]: + """See [`chart`][pypsa.plot.statistics.plotter.StatisticPlotter.chart] for parameters.""" + return self.chart(chart_type="scatter", **kwargs) + + def line(self, **kwargs: Any) -> tuple[Figure, Axes | np.ndarray, sns.FacetGrid]: + """See [`chart`][pypsa.plot.statistics.plotter.StatisticPlotter.chart] for parameters.""" + return self.chart(chart_type="line", **kwargs) + + def box(self, **kwargs: Any) -> tuple[Figure, Axes | np.ndarray, sns.FacetGrid]: + """See [`chart`][pypsa.plot.statistics.plotter.StatisticPlotter.chart] for parameters.""" + return self.chart(chart_type="box", **kwargs) + + def violin(self, **kwargs: Any) -> tuple[Figure, Axes | np.ndarray, sns.FacetGrid]: + """See [`chart`][pypsa.plot.statistics.plotter.StatisticPlotter.chart] for parameters.""" + return self.chart(chart_type="violin", **kwargs) + + def histogram( + self, **kwargs: Any + ) -> tuple[Figure, Axes | np.ndarray, sns.FacetGrid]: + """See [`chart`][pypsa.plot.statistics.plotter.StatisticPlotter.chart] for parameters.""" + return self.chart(chart_type="histogram", **kwargs) + + def chart( # noqa: D417 + self, + chart_type: str, + x: str | None = None, + y: str | None = None, + color: str | None = None, + facet_col: str | None = None, + facet_row: str | None = None, + stacked: bool = True, + query: str | None = None, + nice_names: bool = True, + carrier: Sequence[str] | str | None = None, + bus_carrier: Sequence[str] | str | None = None, + storage: bool | None = None, + sharex: bool | None = None, + sharey: bool | None = None, + height: float | None = None, + aspect: float | None = None, + row_order: Sequence[str] | None = None, + col_order: Sequence[str] | None = None, + hue_order: Sequence[str] | None = None, + hue_kws: dict[str, Any] | None = None, + despine: bool = True, + margin_titles: bool = False, + xlim: tuple[float, float] | None = None, + ylim: tuple[float, float] | None = None, + subplot_kws: dict[str, Any] | None = None, + gridspec_kws: dict[str, Any] | None = None, + **kwargs: Any, + ) -> tuple[Figure, Axes | np.ndarray, sns.FacetGrid]: + """Plot statistics as chart plot. + + This function builds up on any statistics function and creates a chart plot + based on it's output. Seaborn is used to create the plot. + + Parameters + ---------- + x : str, default: None + Data to show on x-axis. E.g. "carrier". Default depends on underlying + statistics function. + y : str, default: "value" + Data to show on y-axis. E.g. "value". + color : str | None, default: "carrier" + Data to show as color. Pass None to disable color mapping. + facet_col : str | None, default: None + Whether to create subplots with conditional subsets of the data. See + `seaborn.objects.Plot.facet` for more information. + facet_row : str | None, default: None + Whether to create subplots with conditional subsets of the data. See + `seaborn.objects.Plot.facet` for more information. + stacked : bool, default: False + Whether to stack the bars. See `seaborn.objects.Stack` for more + query : str | None, default: None + Pandas query string to filter the data before plotting. E.g. "value > 0". + nice_names : bool, default: True + Whether to use nice names for components, as defined in + `c.static.nice_names.` + carrier: Sequence[str] | str | None, default: None + Filter by carrier of components. If specified, only considers assets with + the given carrier(s). More information can be found in the + documentation of the statistics functions. + bus_carrier: Sequence[str] | str | None, default: None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). More information can be found + in the documentation of the statistics functions. + storage: bool | None, default: None + Whether to include storage components in the statistics. Can only be used + when chosen statistics function supports it (e.g. `optimal_capacity`, + `installed_capacity`). Default is False for those functions. + sharex : bool | None, default: None + Whether to share x axes across all facets. If None, will be True when x is "value". + sharey : bool | None, default: None + Whether to share y axes across all facets. If None, will be True when y is "value". + height : float, default: 3 + Height (in inches) of each facet. + aspect : float, default: 2 + Aspect ratio of each facet, so that aspect * height gives the width. + row_order : Sequence[str] | None, default: None + Order to organize the rows of the grid. If None, the order is determined by + the data. + col_order : Sequence[str] | None, default: None + Order to organize the columns of the grid. If None, the order is determined by + the data. + hue_order : Sequence[str] | None, default: None + Order for the levels of the hue variable. If None, the order is determined by + the data. + hue_kws : dict[str, Any] | None, default: None + Other keyword arguments to be passed to the function that maps the hue semantic. + despine : bool, default: True + Remove the top and right spines from the plots. + margin_titles : bool, default: False + Whether to place the row/column titles in the margins, rather than centered + over the grid. + xlim : tuple[float, float] | None, default: None + Limits for the x axis. If None, uses the default xlim. + ylim : tuple[float, float] | None, default: None + Limits for the y axis. If None, uses the default ylim. + subplot_kws : dict[str, Any] | None, default: None + Dictionary of keyword arguments for the subplots. Passed to the underlying + function. + gridspec_kws : dict[str, Any] | None, default: None + Dictionary of keyword arguments passed to the gridspec module for creating + the grid for the figure. + **kwargs: Any + Additional keyword arguments for the plot function. These are passed to + the seaborn plot object (`seaborn.objects.Plot`). + + Returns + ------- + tuple[Figure, Axes | np.ndarray, sns.FacetGrid] + The figure, axes and FacetGrid of the plot. + + Examples + -------- + >>> import pypsa + >>> n = pypsa.examples.ac_dc_meshed() + >>> fig, ax, g = n.statistics.installed_capacity.plot.bar(x="carrier", y="value", color=None) # doctest: +ELLIPSIS + + """ + plot_kwargs = { + "x": x, + "y": y, + "color": color, + "facet_col": facet_col, + "facet_row": facet_row, + "stacked": stacked, + "query": query, + "nice_names": nice_names, + "sharex": sharex, + "sharey": sharey, + "height": height, + "aspect": aspect, + "row_order": row_order, + "col_order": col_order, + "hue_order": hue_order, + "hue_kws": hue_kws, + "despine": despine, + "margin_titles": margin_titles, + "xlim": xlim, + "ylim": ylim, + "subplot_kws": subplot_kws, + "gridspec_kws": gridspec_kws, + } + stats_kwargs = { + "carrier": carrier, + "bus_carrier": bus_carrier, + "storage": storage, + "nice_names": nice_names, + } + + if any( + key in kwargs + for key in ["groupby_time", "aggregate_across_components", "groupby"] + ): + msg = ( + "'groupby_time', 'aggregate_across_components', and 'groupby' " + "can not be set and are automatically derived from the plot kwargs." + ) + raise ValueError(msg) + + plotter = ChartGenerator(self._n) + + # Create context for schema application + context = {"index_names": self._n._index_names} + + # Apply schema to plotting kwargs + stats_name = self._bound_method.__name__ + plot_kwargs = apply_parameter_schema( + stats_name, chart_type, plot_kwargs, context + ) + + # Use helper for filtering + relevant_plot_kwargs = get_relevant_plot_values(plot_kwargs, context) + # Derive base statistics kwargs + base_stats_kwargs = plotter.derive_statistic_parameters( + *relevant_plot_kwargs, + method_name=stats_name, + ) + + # Add provided kwargs + stats_kwargs.update(base_stats_kwargs) + + # Apply schema to statistics kwargs + stats_kwargs = apply_parameter_schema(stats_name, chart_type, stats_kwargs) + + # Get statistics data and return plot + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + message=".*Passing `aggregate_across_components` was deprecated.*", + category=DeprecationWarning, + ) + data = self._bound_method(**stats_kwargs) + if data.empty: + msg = ( + f"The statistics function '{stats_name}' returned an empty DataFrame. " + ) + raise ValueError(msg) + return plotter.plot(data, chart_type, **plot_kwargs, **kwargs) # type: ignore + + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + bus_sizes="bus_size", + bus_colors="bus_color", + bus_split_circles="bus_split_circle", + branch_colors="branch_color", + branch_widths="branch_width", + arrow_colors="arrow_color", + geomap_colors="geomap_color", + line_colors="line_color", + line_widths="line_width", + link_colors="link_color", + link_widths="link_width", + transformer_colors="transformer_color", + transformer_widths="transformer_width", + ) + def map( + self, + ax: Axes | None = None, + projection: Any = None, + geomap: bool = True, + geomap_resolution: Literal["10m", "50m", "110m"] = "50m", + geomap_color: dict | bool | None = None, + boundaries: tuple[float, float, float, float] | None = None, + title: str = "", + bus_carrier: str | None = None, + carrier: str | None = None, + transmission_flow: bool | None = None, + bus_area_fraction: float = 0.02, + branch_area_fraction: float = 0.02, + flow_area_fraction: float = 0.02, + draw_legend_circles: bool = True, + draw_legend_lines: bool | None = None, + draw_legend_arrows: bool | None = None, + draw_legend_patches: bool = True, + legend_circles_kw: dict | None = None, + legend_lines_kw: dict | None = None, + legend_arrows_kw: dict | None = None, + legend_patches_kw: dict | None = None, + bus_split_circle: bool | None = None, + storage: bool | None = None, + **kwargs: Any, + ) -> tuple[Figure | SubFigure | Any, Axes | Any]: + """Plot statistics on a geographic map. + + This function builds upon any statistics function and creates a geographical + visualization based on its output. It uses the MapPlotGenerator to render the + network components with sizes and colors based on the statistics results. + + Parameters + ---------- + ax : matplotlib.axes.Axes, optional + Axis to plot on. If None, creates a new figure and axis. + projection : cartopy.crs.Projection, optional + Map projection to use. If None and geomap is True, uses the network's CRS. + geomap : bool, default True + Whether to add geographic features with cartopy. + geomap_resolution : {'10m', '50m', '110m'}, default '50m' + Resolution of geographic features. + geomap_color : dict or bool, optional + Colors for geographic features. If True, uses defaults. If a dict, keys + can include 'ocean', 'land', 'border', 'coastline'. + boundaries : tuple(float, float, float, float), optional + Plot boundaries as (xmin, xmax, ymin, ymax). + title : str, default "" + Plot title. + bus_carrier : str, optional + Filter by carrier of connected buses. + carrier : str, optional + Filter by carrier of components. + transmission_flow : bool, optional + Whether to plot transmission flows. If True, draws flow arrows instead of lines. + bus_area_fraction : float, default 0.02 + Fraction of plot area to be covered by bus circles. + branch_area_fraction : float, default 0.02 + Fraction of plot area to be covered by branch lines. + flow_area_fraction : float, default 0.02 + Fraction of plot area to be covered by flow arrows. + draw_legend_circles : bool, default True + Whether to draw a legend for bus sizes. + draw_legend_lines : bool, optional + Whether to draw a legend for line widths. Only valid when transmission_flow is False. + draw_legend_arrows : bool, optional + Whether to draw a legend for flow arrows. Only valid when transmission_flow is True. + draw_legend_patches : bool, default True + Whether to draw a legend for carrier colors. + legend_circles_kw : dict, optional + Additional keyword arguments for the circles legend. + legend_lines_kw : dict, optional + Additional keyword arguments for the lines legend. + legend_arrows_kw : dict, optional + Additional keyword arguments for the arrows legend. + legend_patches_kw : dict, optional + Additional keyword arguments for the patches legend. + bus_split_circle : bool, optional + Whether to draw half circles for positive/negative values. + storage : bool, optional + Whether to show storage capacity in capacity plots. Only valid when + chosen statistics function supports it (e.g. `optimal_capacity`, + `installed_capacity`). + **kwargs : + Additional keyword arguments passed to the MapPlotGenerator.draw_map method. + + Returns + ------- + tuple(matplotlib.figure.Figure, matplotlib.axes.Axes) + The figure and axes of the plot. + + Examples + -------- + >>> import pypsa + >>> n = pypsa.examples.ac_dc_meshed() + >>> fig, ax = n.statistics.installed_capacity.plot.map(geomap=True, title="Installed Capacity") + + """ + plot_kwargs = { + "ax": ax, + "projection": projection, + "geomap": geomap, + "geomap_resolution": geomap_resolution, + "geomap_color": geomap_color, + "boundaries": boundaries, + "title": title, + "bus_carrier": bus_carrier, + "carrier": carrier, + "transmission_flow": transmission_flow, + "bus_area_fraction": bus_area_fraction, + "branch_area_fraction": branch_area_fraction, + "flow_area_fraction": flow_area_fraction, + "draw_legend_circles": draw_legend_circles, + "draw_legend_lines": draw_legend_lines, + "draw_legend_arrows": draw_legend_arrows, + "draw_legend_patches": draw_legend_patches, + "legend_circles_kw": legend_circles_kw, + "legend_lines_kw": legend_lines_kw, + "legend_arrows_kw": legend_arrows_kw, + "legend_patches_kw": legend_patches_kw, + "bus_split_circle": bus_split_circle, + } + + plotter = MapPlotGenerator(self._n) + + # Apply schema to plotting kwargs + stats_name = self._bound_method.__name__ + plot_kwargs = apply_parameter_schema(stats_name, "map", plot_kwargs) + + # Apply schema to statistics kwargs + stats_kwargs = apply_parameter_schema(stats_name, "map", {"storage": storage}) + + # Note that instead of passing the data to the plotter, we pass the + # statistics function. This gives the map plotter the ability to + # determine the data for buses and branches itself. + return plotter.plot( + func=self._bound_method, **plot_kwargs, stats_kwargs=stats_kwargs, **kwargs + ) + + +class StatisticInteractivePlotter: + """Create interactive plots based on output of statistics functions. + + Passed arguments and the specified statistics function are stored and called + later when the plot is created, depending on the plot type. Also some checks + are performed to validate the arguments. + """ + + def __init__(self, bound_method: Callable, n: Network) -> None: + """Initialize the interactive statistic handler. + + Parameters + ---------- + bound_method : Callable + The bound method/ underlying statistic function to call. + n : Network + The network object to use for the statistic calculation. + + """ + self._bound_method = bound_method + self._n = n + + def __call__( + self, kind: str | None = None + ) -> tuple[go.Figure, go.Figure | np.ndarray]: + """Create simple visualization of the statistic. + + This function builds up on any statistics function and allows for a simple + exploration without any further arguments. If a fine grained control is + needed, the plot functions should be used directly (e.g. `.plot.bar()` instead + of `.plot(kind="bar")`). + + Parameters + ---------- + kind : str | None, default: None + Type of chart ("bar", "line", "area"). If None, the default per + statistics function, defined in the schema, is used. + + Returns + ------- + tuple[Figure | SubFigure | Any, Axes | Any] + The figure and axes of the plot. + + Examples + -------- + >>> fig = n.statistics.installed_capacity.plot(kind="bar") # doctest: +ELLIPSIS + + """ + # Get the correct plot function + if kind not in ["bar", "line", "area", None]: + msg = f"Unknown plot type '{kind}'." + raise ValueError(msg) + # Apply schema to kind kwarg + stats_name = self._bound_method.__name__ + kind_ = apply_parameter_schema(stats_name, "plot", {"kind": kind})["kind"] + plot_func = getattr(self, kind_) + return plot_func() + + def area(self, **kwargs: Any) -> go.Figure: + """See [`chart`][pypsa.plot.statistics.plotter.StatisticInteractivePlotter.chart] for parameters.""" + return self.chart(chart_type="area", **kwargs) + + def bar(self, **kwargs: Any) -> go.Figure: + """See [`chart`][pypsa.plot.statistics.plotter.StatisticInteractivePlotter.chart] for parameters.""" + return self.chart(chart_type="bar", **kwargs) + + def scatter(self, **kwargs: Any) -> go.Figure: + """See [`chart`][pypsa.plot.statistics.plotter.StatisticInteractivePlotter.chart] for parameters.""" + return self.chart(chart_type="scatter", **kwargs) + + def line(self, **kwargs: Any) -> go.Figure: + """See [`chart`][pypsa.plot.statistics.plotter.StatisticInteractivePlotter.chart] for parameters.""" + return self.chart(chart_type="line", **kwargs) + + def box(self, **kwargs: Any) -> go.Figure: + """See [`chart`][pypsa.plot.statistics.plotter.StatisticInteractivePlotter.chart] for parameters.""" + return self.chart(chart_type="box", **kwargs) + + def violin(self, **kwargs: Any) -> go.Figure: + """See [`chart`][pypsa.plot.statistics.plotter.StatisticInteractivePlotter.chart] for parameters.""" + return self.chart(chart_type="violin", **kwargs) + + def histogram(self, **kwargs: Any) -> go.Figure: + """See [`chart`][pypsa.plot.statistics.plotter.StatisticInteractivePlotter.chart] for parameters.""" + return self.chart(chart_type="histogram", **kwargs) + + def chart( # noqa: D417 + self, + chart_type: str, + x: str | None = None, + y: str | None = None, + color: str | None = None, + facet_col: str | None = None, + facet_row: str | None = None, + stacked: bool = True, + query: str | None = None, + nice_names: bool = True, + carrier: Sequence[str] | str | None = None, + bus_carrier: Sequence[str] | str | None = None, + storage: bool | None = None, + sharex: bool | None = None, + sharey: bool | None = None, + height: int = 500, + width: int = 800, + row_order: Sequence[str] | None = None, + col_order: Sequence[str] | None = None, + color_order: Sequence[str] | None = None, + color_discrete_map: dict[str, str] | None = None, + range_x: list[float] | None = None, + range_y: list[float] | None = None, + labels: dict[str, str] | None = None, + title: str | None = None, + **kwargs: Any, + ) -> go.Figure: + """Plot statistics as interactive chart. + + This function builds up on any statistics function and creates an interactive chart + plot based on its output. Plotly is used to create the plot. + + Parameters + ---------- + x : str, default: None + Data to show on x-axis. E.g. "carrier". Default depends on underlying + statistics function. + y : str, default: "value" + Data to show on y-axis. E.g. "value". + color : str | None, default: "carrier" + Data to show as color. Pass None to disable color mapping. + facet_col : str | None, default: None + Whether to create subplots with conditional subsets of the data. + facet_row : str | None, default: None + Whether to create subplots with conditional subsets of the data. + stacked : bool, default: False + Whether to stack the bars or areas. + query : str | None, default: None + Pandas query string to filter the data before plotting. E.g. "value > 0". + nice_names : bool, default: True + Whether to use nice names for components, as defined in + `c.static.nice_names.` + carrier: Sequence[str] | str | None, default: None + Filter by carrier of components. If specified, only considers assets with + the given carrier(s). More information can be found in the + documentation of the statistics functions. + bus_carrier: Sequence[str] | str | None, default: None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). More information can be found + in the documentation of the statistics functions. + storage: bool | None, default: None + Whether to include storage components in the statistics. Can only be used + when chosen statistics function supports it (e.g. `optimal_capacity`, + `installed_capacity`). Default is False for those functions. + sharex : bool | None, default: None + Whether to share x axes across all facets. If None, will be True when x is "value". + sharey : bool | None, default: None + Whether to share y axes across all facets. If None, will be True when y is "value". + height : int, default: 500 + Height of the plot in pixels. + width : int, default: 800 + Width of the plot in pixels. + row_order : Sequence[str] | None, default: None + Order to organize the rows of the grid. If None, the order is determined by + the data. + col_order : Sequence[str] | None, default: None + Order to organize the columns of the grid. If None, the order is determined by + the data. + color_order : Sequence[str] | None, default: None + Order for the levels of the color variable. If None, the order is determined by + the data. + color_discrete_map : dict[str, str] | None, default: None + Mapping from discrete values to colors. If None, uses the default carrier colors. + range_x : list[float] | None, default: None + Limits for the x axis. If None, uses the default x range. + range_y : list[float] | None, default: None + Limits for the y axis. If None, uses the default y range. + labels : dict[str, str] | None, default: None + Dictionary of axis labels to override the default labels. + title : str | None, default: None + Title of the plot. If None, uses the statistics function name. + **kwargs: Any + Additional keyword arguments for the plot function. These are passed to + the Plotly Express function. + + Returns + ------- + plotly.graph_objects.Figure + The interactive Plotly figure. + + Examples + -------- + >>> import pypsa + >>> n = pypsa.examples.ac_dc_meshed() + >>> fig = n.statistics.installed_capacity.iplot.bar(x="carrier", y="value", color="carrier") # doctest: +ELLIPSIS + + """ + plot_kwargs = { + "x": x, + "y": y, + "color": color, + "facet_col": facet_col, + "facet_row": facet_row, + "stacked": stacked, + "query": query, + "nice_names": nice_names, + "sharex": sharex, + "sharey": sharey, + "height": height, + "width": width, + "row_order": row_order, + "col_order": col_order, + "color_order": color_order, + "color_discrete_map": color_discrete_map, + "range_x": range_x, + "range_y": range_y, + "labels": labels, + "title": title, + } + stats_kwargs = { + "carrier": carrier, + "bus_carrier": bus_carrier, + "storage": storage, + "nice_names": nice_names, + } + + if any( + key in kwargs + for key in ["groupby_time", "aggregate_across_components", "groupby"] + ): + msg = ( + "'groupby_time', 'aggregate_across_components', and 'groupby' " + "can not be set and are automatically derived from the plot kwargs." + ) + raise ValueError(msg) + + plotter = ChartGenerator(self._n) + + # Create context for schema application + context = {"index_names": self._n._index_names} + + # Apply schema to plotting kwargs + stats_name = self._bound_method.__name__ + plot_kwargs = apply_parameter_schema( + stats_name, chart_type, plot_kwargs, context + ) + # Use helper for filtering + relevant_plot_kwargs = get_relevant_plot_values(plot_kwargs, context) + # Derive base statistics kwargs + base_stats_kwargs = plotter.derive_statistic_parameters( + *relevant_plot_kwargs, + method_name=stats_name, + ) + + # Add provided kwargs + stats_kwargs.update(base_stats_kwargs) + + # Apply schema to statistics kwargs + stats_kwargs = apply_parameter_schema(stats_name, chart_type, stats_kwargs) + + # Get statistics data and return plot + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + message=".*Passing `aggregate_across_components` was deprecated.*", + category=DeprecationWarning, + ) + data = self._bound_method(**stats_kwargs) + if data.empty: + msg = ( + f"The statistics function '{stats_name}' returned an empty DataFrame. " + ) + raise ValueError(msg) + return plotter.iplot(data, chart_type, **plot_kwargs, **kwargs) # type: ignore diff --git a/PyPSA/source/pypsa/plot/statistics/schema.py b/PyPSA/source/pypsa/plot/statistics/schema.py new file mode 100644 index 0000000000000000000000000000000000000000..f61446a1fbd4d0cabde4ffbb5cd12aa9e5fe4958 --- /dev/null +++ b/PyPSA/source/pypsa/plot/statistics/schema.py @@ -0,0 +1,309 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Schema for plotting statistics in PyPSA. + +This module defines default parameters for different statistics/plot combinations. +The schema system allows different default values based on the specific combination +of statistic function and plot type being used. + +The module contains the following configuration dictionaries: + +1. DEFAULTS: + General default values for all statistics/plot combinations. + +2. METHOD_OVERRIDES: + Plot-type-specific defaults that override the general defaults. + +3. ALLOWED_PARAMS: + Additional parameters that are allowed for specific statistics functions. + Parameters listed here are restricted by default and only enabled for the + specified statistics. + +4. EXCLUDED_PARAMS: + Parameters that should be excluded for specific statistics functions + across all plot types. + +5. STAT_OVERRIDES: + Statistic and plot-type specific overrides for parameter defaults. +""" + +from pypsa.plot.statistics.charts import CHART_TYPES + +# Base defaults for all parameters +DEFAULTS = { + "x": "carrier", + "y": "value", + "color": "carrier", + "height": 4, + "aspect": 1, + "bus_split_circle": False, + "transmission_flow": False, + "draw_legend_arrows": False, + "draw_legend_lines": True, + # general plot method + "kind": "bar", + # Optional parameters + "storage": None, + "direction": None, +} + +# Method-specific overrides +METHOD_OVERRIDES: dict = { + "area": { + "x": "carrier", + "y": "value", + "color": None, + "height": 3, + "aspect": 2, + "linewidth": 0, + }, + "line": {"x": "carrier", "y": "value", "color": None, "height": 3, "aspect": 2}, + "bar": {"x": "value", "y": "carrier", "color": "carrier"}, +} + +# Additional allowed params per statistic +ALLOWED_PARAMS = { + "optimal_capacity": ["storage"], + "installed_capacity": ["storage"], + "energy_balance": ["direction"], + "revenue": ["direction"], +} + +# Excluded params per statistic +EXCLUDED_PARAMS = { + "prices": ["carrier", "nice_names"], +} + +# Statistic-specific overrides per plot type +STAT_OVERRIDES: dict = { + "optimal_capacity": { + "line": {"storage": False}, + "area": {"storage": False}, + "plot": {"storage": False}, + }, + "installed_capacity": { + "line": {"storage": False}, + "area": {"storage": False}, + "plot": {"storage": False}, + }, + "opex": {"line": {"x": "snapshot"}, "area": {"x": "snapshot", "color": "carrier"}}, + "supply": { + "line": {"x": "snapshot"}, + "area": {"x": "snapshot", "color": "carrier"}, + "map": { + "transmission_flow": True, + "draw_legend_arrows": True, + "draw_legend_lines": False, + "kind": "supply", + }, + }, + "withdrawal": { + "line": {"x": "snapshot"}, + "area": {"x": "snapshot", "color": "carrier"}, + "map": { + "transmission_flow": True, + "draw_legend_arrows": True, + "draw_legend_lines": False, + "kind": "withdrawal", + }, + }, + "transmission": { + "line": {"x": "snapshot"}, + "area": {"x": "snapshot", "color": "carrier"}, + }, + "energy_balance": { + "plot": {"kind": "area"}, + "line": {"x": "snapshot"}, + "area": {"x": "snapshot", "color": "carrier"}, + "map": { + "bus_split_circle": True, + "transmission_flow": True, + "draw_legend_arrows": True, + "draw_legend_lines": False, + }, + }, + "curtailment": { + "line": {"x": "snapshot"}, + "area": {"x": "snapshot", "color": "carrier"}, + }, + "capacity_factor": { + "line": {"x": "snapshot"}, + "area": {"x": "snapshot", "color": "carrier"}, + }, + "revenue": { + "line": {"x": "snapshot"}, + "area": {"x": "snapshot", "color": "carrier"}, + }, + "market_value": { + "line": {"x": "snapshot"}, + "area": {"x": "snapshot", "color": "carrier"}, + }, + # Statistics with no special overrides + "capex": {}, + "installed_capex": {}, + "expanded_capex": {}, + "expanded_capacity": {}, + "system_cost": {}, + "prices": { + "area": {"x": "snapshot", "y": "value", "color": None}, + "line": {"x": "snapshot", "y": "value", "color": "name"}, + "bar": {"y": "name", "color": None}, + "box": {"x": "value", "y": "name", "color": None}, + "violin": {"x": "value", "y": "name", "color": None}, + "histogram": {"x": "value", "color": None}, + "scatter": {"x": "name", "color": None}, + }, +} + + +def _combine_schemas() -> dict: + """Build the complete schema by combining all configuration dictionaries. + + Returns + ------- + dict + Combined schema with defaults and allowed values for all combinations + of statistics and plot types. + + """ + schema: dict = {} + plot_types = ["map", "plot"] + CHART_TYPES + all_stats = list(STAT_OVERRIDES.keys()) + + # Gather all additional params to determine which are restricted by default + restricted_params = {p for params in ALLOWED_PARAMS.values() for p in params} + + for stat in all_stats: + schema[stat] = {} + + for plot_type in plot_types: + schema[stat][plot_type] = {} + + # Start with global defaults + for param, default in DEFAULTS.items(): + schema[stat][plot_type][param] = { + "default": default, + "allowed": param not in restricted_params, + } + + # Apply method-specific defaults + if plot_type in METHOD_OVERRIDES: + for param, default in METHOD_OVERRIDES[plot_type].items(): + schema[stat][plot_type][param] = { + "default": default, + "allowed": True, + } + + # Enable additional params for this stat + for param in ALLOWED_PARAMS.get(stat, []): + if param in schema[stat][plot_type]: + schema[stat][plot_type][param]["allowed"] = True + + # Apply stat-specific overrides + if stat in STAT_OVERRIDES and plot_type in STAT_OVERRIDES[stat]: + for param, value in STAT_OVERRIDES[stat][plot_type].items(): + schema[stat][plot_type][param] = {"default": value, "allowed": True} + + # Apply exclusions + for param in EXCLUDED_PARAMS.get(stat, []): + if param in schema[stat][plot_type]: + schema[stat][plot_type][param]["allowed"] = False + + return schema + + +# Generate the schema +schema = _combine_schemas() + + +def apply_parameter_schema( + stats_name: str, plot_name: str, kwargs: dict, context: dict | None = None +) -> dict: + """Apply parameter schema to kwargs. + + Filters and sets default values for parameters based on the schema + for the given statistics function and plot type combination. + + Parameters + ---------- + stats_name : str + Name of the statistics function. + plot_name : str + Name of the plot type. + kwargs : dict + Dictionary of keyword arguments to be filtered based on the schema. + context : dict | None, optional + Additional context for parameter processing (e.g., {"index_names": [...]}) + + Returns + ------- + dict + Filtered dictionary of keyword arguments with defaults applied. + + """ + to_remove = [] + + for param, value in kwargs.items(): + # Check if parameter is explicitly excluded for this statistic + if param in EXCLUDED_PARAMS.get(stats_name, []): + to_remove.append(param) + continue + + if param not in schema[stats_name][plot_name]: + continue + + # Check if parameter is not allowed and remove it + if not schema[stats_name][plot_name][param]["allowed"]: + to_remove.append(param) + continue + + # Apply default if value is None + if value is None: + kwargs[param] = schema[stats_name][plot_name][param]["default"] + + for param in to_remove: + kwargs.pop(param) + + # Auto-faceting logic + if ( + context + and context.get("index_names") + and not kwargs.get("facet_col") + and not kwargs.get("facet_row") + ): + index_names = context["index_names"] + if len(index_names) == 1: + kwargs["facet_col"] = index_names[0] + elif len(index_names) >= 2: + kwargs["facet_row"] = index_names[0] + kwargs["facet_col"] = index_names[1] + + return kwargs + + +def get_relevant_plot_values(plot_kwargs: dict, context: dict | None = None) -> list: + """Extract values relevant for statistics, excluding index names. + + Parameters + ---------- + plot_kwargs : dict + Plot keyword arguments + context : dict | None + Context containing index_names + + Returns + ------- + list + Values that should be passed to derive_statistic_parameters + + """ + index_names = context.get("index_names", []) if context else [] + relevant_keys = {"x", "y", "color", "facet_col", "facet_row"} + values = [ + v + for k, v in plot_kwargs.items() + if k in relevant_keys and v not in index_names and v is not None + ] + return list(set(values)) diff --git a/PyPSA/source/pypsa/py.typed b/PyPSA/source/pypsa/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/PyPSA/source/pypsa/statistics/__init__.py b/PyPSA/source/pypsa/statistics/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1c2f559bb0d6609313a5413230336fb267f6c651 --- /dev/null +++ b/PyPSA/source/pypsa/statistics/__init__.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Statistics package for PyPSA networks.""" + +from pypsa.statistics.expressions import ( + StatisticsAccessor, + get_operation, + get_transmission_branches, + get_transmission_carriers, + port_efficiency, +) +from pypsa.statistics.grouping import groupers + +__all__ = [ + "groupers", + "StatisticsAccessor", + "get_transmission_branches", + "get_transmission_carriers", + "get_operation", + "port_efficiency", +] diff --git a/PyPSA/source/pypsa/statistics/abstract.py b/PyPSA/source/pypsa/statistics/abstract.py new file mode 100644 index 0000000000000000000000000000000000000000..4c8cef95fa99f1c6ba839e2d76c930c5080838a2 --- /dev/null +++ b/PyPSA/source/pypsa/statistics/abstract.py @@ -0,0 +1,297 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Statistics Accessor.""" + +from __future__ import annotations + +import logging +import warnings +from abc import ABC, abstractmethod +from collections.abc import Callable, Collection, Sequence +from typing import TYPE_CHECKING, Any, Literal + +import pandas as pd + +from pypsa._options import options +from pypsa.constants import RE_PORTS +from pypsa.statistics.grouping import groupers + +if TYPE_CHECKING: + from pypsa import Network, NetworkCollection + +logger = logging.getLogger(__name__) + + +class AbstractStatisticsAccessor(ABC): + """Abstract accessor to calculate different statistical values.""" + + def __init__(self, n: Network | NetworkCollection) -> None: + """Initialize the statistics accessor.""" + self._n = n + + @property + def n(self) -> Network | NetworkCollection: + """Get the network instance.""" + warnings.warn( + "Accessing the network instance via `n` is deprecated. Use the network instance directly.", + DeprecationWarning, + stacklevel=2, + ) + return self._n + + def _get_grouping( + self, + n: Network | NetworkCollection, + c: str, + groupby: Callable | Sequence[str] | str | bool, + port: str | None = None, + nice_names: bool = False, + ) -> dict: + by = None + level = None + if callable(groupby): + try: + by = groupby(n, c, port=port, nice_names=nice_names) + except TypeError: + by = groupby(n, c, nice_names=nice_names) + elif isinstance(groupby, (str | list)): + by = groupers[groupby](n, c, port=port, nice_names=nice_names) + elif groupby is not False: + msg = f"Argument `groupby` must be a string, list, callable, or False, got {repr(groupby)}." + raise ValueError(msg) + return {"by": by, "level": level} + + @property + def is_multi_indexed(self) -> bool: + """Check if the snapshots are multi-indexed. + + Returns + ------- + bool + True if the snapshots are multi-indexed, False otherwise. + + """ + # TODO could be moved to Network + return isinstance(self._n.snapshots, pd.MultiIndex) + + @classmethod + def _aggregate_timeseries( + cls, obj: Any, weights: pd.Series, agg: str | Callable | bool = "sum" + ) -> Any: + """Calculate the weighted sum or average of a DataFrame or Series.""" + if not agg: + return obj.T if isinstance(obj, pd.DataFrame) else obj + + if agg == "mean": + if isinstance(weights.index, pd.MultiIndex): + weights = weights.groupby(level=0).transform(lambda w: w / w.sum()) + else: + weights = weights / weights.sum() + agg = "sum" + + return cls._aggregate_with_weights(obj, weights, agg) + + # The following methods are implemented in the concrete classes + @abstractmethod + def _aggregate_with_weights(self, *args: Any, **kwargs: Any) -> Any: + pass + + @abstractmethod + def _aggregate_components_groupby(self, *args: Any, **kwargs: Any) -> Any: + pass + + @abstractmethod + def _aggregate_components_concat_values(self, *args: Any, **kwargs: Any) -> Any: + pass + + @abstractmethod + def _aggregate_components_concat_data(self, *args: Any, **kwargs: Any) -> Any: + pass + + @abstractmethod + def _apply_option_kwargs(self, *args: Any, **kwargs: Any) -> Any: + pass + + @abstractmethod + def _aggregate_across_components(self, *args: Any, **kwargs: Any) -> Any: + pass + + @abstractmethod + def _get_component_index(self, *args: Any, **kwargs: Any) -> Any: + pass + + @abstractmethod + def _concat_periods(self, *args: Any, **kwargs: Any) -> Any: + pass + + def _aggregate_components( + self, + func: Callable, + agg: Callable | str = "sum", + components: Collection[str] | str | None = None, + groupby: str | Sequence[str] | Callable | Literal[False] = "carrier", + aggregate_across_components: bool = False, + at_port: str | Sequence[str] | bool | None = None, + bus_carrier: str | Sequence[str] | None = None, + carrier: str | Sequence[str] | None = None, + nice_names: bool | None = True, + drop_zero: bool | None = None, + round: int | None = None, + ) -> pd.Series | pd.DataFrame: + """Apply a function and group the result for a collection of components.""" + d = {} + n = self._n + + if is_one_component := isinstance(components, str): + components = [components] + if components is None: + components = sorted(n.branch_components | n.one_port_components) + if nice_names is None: + # TODO move to _apply_option_kwargs + nice_names = options.params.statistics.nice_names + for c in components: + if n.c[c].static.empty: + continue + + ports = [ + match.group(1) + for col in n.c[c].static + if (match := RE_PORTS.search(str(col))) + ] + if not at_port: + ports = [ports[0]] + + values = [] + for port in ports: + vals = func(n, c, port) + if self._aggregate_components_skip_iteration(vals): + continue + + vals = self._filter_active_assets(n, c, vals) # for multiinvest + vals = self._filter_bus_carrier(n, c, port, bus_carrier, vals) + vals = self._filter_carrier(n, c, carrier, vals) + + if self._aggregate_components_skip_iteration(vals): + continue + + if groupby is not False: + if groupby is None: + warnings.warn( + "Passing `groupby=None` is deprecated. Drop the " + "argument to get the default grouping (by carrier), which " + "was also the previous default behavior. Deprecated in " + "version 0.34 and will be removed in version 1.0.", + DeprecationWarning, + stacklevel=2, + ) + groupby = "carrier" + grouping = self._get_grouping( + n, c, groupby, port=port, nice_names=nice_names + ) + vals = self._aggregate_components_groupby(vals, grouping, agg, c) + # Avoid having 'component' as index name in multiindex + elif isinstance(vals, pd.DataFrame | pd.Series): + vals = vals.rename_axis(c, axis=0) + values.append(vals) + + if not values: + continue + + df = self._aggregate_components_concat_values(values, agg) + + d[c] = df + df = self._aggregate_components_concat_data(d, is_one_component) + if not df.empty: + df = self._apply_option_kwargs( + df, + drop_zero=drop_zero, + round=round, + nice_names=nice_names, # TODO: nice_names does not have effect here + ) + + if aggregate_across_components: + df = self._aggregate_across_components(df, agg) + + if isinstance(df, pd.Series): + df.name = None + + return df + + def _aggregate_components_skip_iteration(self, vals: Any) -> bool: + return False + + def _filter_active_assets( + self, n: Network | NetworkCollection, c: str, obj: Any + ) -> Any: + """For static values iterate over periods and concat values.""" + if isinstance(obj, pd.DataFrame) or "snapshot" in getattr(obj, "dims", []): + return obj + idx = self._get_component_index(obj, c) + + if not self.is_multi_indexed: + mask = n.c[c].get_active_assets() + return obj.loc[mask.index[mask].intersection(idx)] + + per_period = {} + for p in n.investment_periods: + mask = n.c[c].get_active_assets(p) + per_period[p] = obj.loc[mask.index[mask].intersection(idx)] + return self._concat_periods(per_period, c) + + def _filter_bus_carrier( + self, + n: Network | NetworkCollection, + c: str, + port: str, + bus_carrier: str | Sequence[str] | None, + obj: Any, + ) -> Any: + """Filter for components which are connected to bus with `bus_carrier`.""" + if bus_carrier is None: + return obj + + idx = self._get_component_index(obj, c) + ports = n.c[c].static.loc[idx, f"bus{port}"] + port_carriers = ports.map(n.c.buses.static.carrier) + if isinstance(bus_carrier, str): + if bus_carrier in n.c.buses.static.carrier.unique(): + mask = port_carriers == bus_carrier + else: + mask = port_carriers.str.contains(bus_carrier, regex=True) + elif isinstance(bus_carrier, list): + mask = port_carriers.isin(bus_carrier) + else: + msg = f"Argument `bus_carrier` must be a string or list, got {type(bus_carrier)}" + raise TypeError(msg) + # links may have empty ports which results in NaNs + mask = mask.where(mask.notnull(), False) + return obj.loc[ports.index[mask]] + + def _filter_carrier( + self, + n: Network | NetworkCollection, + c: str, + carrier: str | Sequence[str] | None, + obj: Any, + ) -> Any: + """Filter the DataFrame for components which have the specified carrier.""" + if carrier is None or "carrier" not in n.c[c].static: + return obj + + idx = self._get_component_index(obj, c) + carriers = n.c[c].static.loc[idx, "carrier"] + + if isinstance(carrier, str): + if carrier in carriers.unique(): + mask = carriers == carrier + else: + mask = carriers.str.contains(carrier) + elif isinstance(carrier, Sequence): + mask = carriers.isin(carrier) + else: + msg = f"Argument `carrier` must be a string or list, got {type(carrier)}" + raise TypeError(msg) + + return obj.loc[carriers.index[mask]] diff --git a/PyPSA/source/pypsa/statistics/expressions.py b/PyPSA/source/pypsa/statistics/expressions.py new file mode 100644 index 0000000000000000000000000000000000000000..75ce867e2335bd75939265fa4ecbf18d05ce8702 --- /dev/null +++ b/PyPSA/source/pypsa/statistics/expressions.py @@ -0,0 +1,2604 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Statistics Accessor.""" + +from __future__ import annotations + +import logging +import warnings +from typing import TYPE_CHECKING, Any, Literal + +import pandas as pd + +from pypsa._options import options +from pypsa.common import ( + MethodHandlerWrapper, + deprecated_kwargs, + pass_empty_series_if_keyerror, +) +from pypsa.descriptors import nominal_attrs +from pypsa.plot.statistics.plotter import StatisticInteractivePlotter, StatisticPlotter +from pypsa.statistics.abstract import AbstractStatisticsAccessor + +if TYPE_CHECKING: + from collections.abc import Callable, Collection, Sequence + + from pypsa import Network, NetworkCollection + +logger = logging.getLogger(__name__) + + +def get_operation(n: Network, c: str) -> pd.DataFrame: + """Get the operation data for a network component.""" + if c in n.branch_components: + return n.c[c].dynamic.p0 + if c == "Store": + return n.c[c].dynamic.e + return n.c[c].dynamic.p + + +def port_efficiency( + n: Network, c_name: str, port: str = "", dynamic: bool = False +) -> pd.Series | pd.DataFrame: + """Get the efficiency of a component at a specific port.""" + ones = pd.Series(1, index=n.c[c_name].static.index) + if port == "": + efficiency = ones + elif port == "0": + efficiency = -ones + else: + key = "efficiency" if port == "1" else f"efficiency{port}" + if dynamic and key in n.c[c_name].static: + efficiency = n.get_switchable_as_dense(c_name, key) + else: + efficiency = n.c[c_name].static.get(key, ones) + return efficiency + + +def get_transmission_branches( + n: Network | NetworkCollection, bus_carrier: str | Sequence[str] | None = None +) -> pd.MultiIndex: + """Get list of assets which transport between buses of the carrier `bus_carrier`.""" + # Check if this is a NetworkCollection (has MultiIndex buses) + is_network_collection = isinstance(n.c.buses.static.carrier.index, pd.MultiIndex) + + if is_network_collection: + # For NetworkCollection, process each network separately and combine results + network_results: list[tuple[str, pd.Index]] = [] + + # Get the bus carrier mapping - drop network levels for mapping + bus_carrier_series = n.c.buses.static.carrier + bus_carrier_map = bus_carrier_series.droplevel( + list(range(bus_carrier_series.index.nlevels - 1)) + ) + bus_carrier_map = bus_carrier_map[~bus_carrier_map.index.duplicated()] + + for c in n.branch_components: + bus_map = ( + n.c[c] + .static.filter(like="bus") + .apply(lambda ds: ds.map(bus_carrier_map)) + ) + if isinstance(bus_carrier, str): + bus_carrier_list = [bus_carrier] + elif bus_carrier is None: + bus_carrier_list = bus_carrier_map.unique() + else: + bus_carrier_list = list(bus_carrier) + + for carrier in bus_carrier_list: + matching_idx = ( + bus_map.eq(carrier).astype(int).sum(axis=1)[lambda ds: ds > 1].index + ) + # Keep the full MultiIndex for NetworkCollection + network_results.extend((c, idx) for idx in matching_idx) + + # Create MultiIndex with network levels + component and name + if network_results: + # Get network index names + network_names = list( + n.c.buses.static.carrier.index.names[:-1] + ) # All except last level + result_names = network_names + ["component", "name"] + + # Flatten tuples: (component, (network_levels..., component_name)) + flattened_results = [] + for component, full_idx in network_results: + if isinstance(full_idx, tuple): + network_parts = full_idx[:-1] + component_name = full_idx[-1] + flattened_results.append( + network_parts + (component, component_name) + ) + else: + # Single level case - shouldn't happen with NetworkCollection but handle it + flattened_results.append((full_idx, component, full_idx)) + + return pd.MultiIndex.from_tuples(flattened_results, names=result_names) + else: + # Empty result - create empty MultiIndex with correct structure + network_names = list(n.c.buses.static.carrier.index.names[:-1]) + result_names = network_names + ["component", "name"] + return pd.MultiIndex.from_tuples([], names=result_names) + + else: + # Original logic for regular Network + index = {} + bus_carrier_map = n.c.buses.static.carrier + + for c in n.branch_components: + bus_map = ( + n.c[c] + .static.filter(like="bus") + .apply(lambda ds: ds.map(bus_carrier_map)) + ) + if isinstance(bus_carrier, str): + bus_carrier = [bus_carrier] + elif bus_carrier is None: + bus_carrier = bus_carrier_map.unique() + res = set() + for carrier in bus_carrier: + matching_idx = ( + bus_map.eq(carrier).astype(int).sum(axis=1)[lambda ds: ds > 1].index + ) + res |= set(matching_idx) + index[c] = pd.Index(res) + return pd.MultiIndex.from_tuples( + [(c, i) for c, idx in index.items() for i in idx], + names=["component", "name"], + ) + + +def get_transmission_carriers( + n: Network, bus_carrier: str | Sequence[str] | None = None +) -> pd.MultiIndex: + """Get the carriers which transport between buses of the carrier `bus_carrier`.""" + branches = get_transmission_branches(n, bus_carrier) + + # Check if this is a NetworkCollection + is_network_collection = isinstance(n.c.buses.static.carrier.index, pd.MultiIndex) + + if is_network_collection and len(branches) > 0: + # For NetworkCollection, branches has structure: (network_levels..., component, name) + # We need to extract carriers for each (network, component, name) combination + network_results = [] + + # Process each branch + for branch_tuple in branches: + network_part = branch_tuple[:-2] + component = branch_tuple[-2] + component_name = branch_tuple[-1] + + # Find carrier for this specific component in this network + try: + # Build the full index for this component + full_component_idx = network_part + (component_name,) + carrier = n.c[component].static.carrier.loc[full_component_idx] + network_results.append(network_part + (component, carrier)) + except KeyError: + # Component not found, skip + continue + + if network_results: + # Get network index names + network_names = list( + n.c.buses.static.carrier.index.names[:-1] + ) # All except last level + result_names = network_names + ["component", "carrier"] + return pd.MultiIndex.from_tuples(network_results, names=result_names) + else: + # Empty result + network_names = list(n.c.buses.static.carrier.index.names[:-1]) + result_names = network_names + ["component", "carrier"] + return pd.MultiIndex.from_tuples([], names=result_names) + + elif not is_network_collection and len(branches) > 0: + # Original logic for regular Network + carriers = {} + for c in branches.unique(0): + idx = branches[branches.get_loc(c)].get_level_values(1) + if "carrier" not in n.c[c].static: + continue + carriers[c] = n.c[c].static.carrier[idx].unique() + return pd.MultiIndex.from_tuples( + [(c, i) for c, idx in carriers.items() for i in idx], + names=["component", "carrier"], + ) + + else: + # Empty branches - return empty MultiIndex with correct structure + if is_network_collection: + network_names = list(n.c.buses.static.carrier.index.names[:-1]) + result_names = network_names + ["component", "carrier"] + else: + result_names = ["component", "carrier"] + return pd.MultiIndex.from_tuples([], names=result_names) + + +class StatisticHandler: + """Statistic method handler. + + This class wraps a statistic method and provides a callable instance. To get + the statistic output as a DataFrame, call the instance with the desired arguments. + + See Also + -------- + [pypsa.common.MethodHandlerWrapper][] + + """ + + def __init__(self, bound_method: Callable, n: Network) -> None: + """Initialize the statistic handler. + + Parameters + ---------- + bound_method : Callable + The bound method/ underlying statistic function to call. + n : Network + The network object to use for the statistic calculation. + + """ + self._bound_method = bound_method + self._n = n + self.plot = StatisticPlotter(n=n, bound_method=bound_method) + self.iplot = StatisticInteractivePlotter(n=n, bound_method=bound_method) + + def __call__(self, *args: Any, **kwargs: Any) -> pd.DataFrame: # noqa: D102 + return self._bound_method(*args, **kwargs) + + def __repr__(self) -> str: + """Get representation of the statistic handler. + + Examples + -------- + >>> handler = StatisticHandler(lambda x: x,n=n) + >>> handler + StatisticHandler() + + """ + return f"StatisticHandler({self._bound_method.__name__})" + + +class StatisticsAccessor(AbstractStatisticsAccessor): + """Accessor to calculate different metrics from the network. + + + + All methods and attributes of the accessor can be used with any [pypsa.Network][] + instance via `n.statistics`, which is the main facade to the statistics module. + + The statistics methods are another level of accessors, which means that they can + yield statistics as pandas DataFrames or plots based on them. See the examples for + more details. + + Examples + -------- + The examples below can be used with any statistical method. The default arguments + used and the plot type yielded will vary. + + Get aggregated statistics in a single DataFrame: + + >>> n.statistics() # doctest: +ELLIPSIS + Optimal Capacity ... Market Value + Generator gas 982.03448 ... 1559.511099 + wind 7292.13406 ... 589.813549 + Line AC 5613.82931 ... -43.277041 + Link DC 4003.90110 ... 0.132018 + Load load 0.00000 ... NaN + + [5 rows x 12 columns] + + Get the energy balance: + + >>> n.statistics.energy_balance() + component carrier bus_carrier + Generator gas AC 1465.27439 + wind AC 31082.35370 + Load load AC -32547.62808 + dtype: float64 + + Get the optimal capacity: + + >>> n.statistics.optimal_capacity() + component carrier + Generator gas 982.03448 + wind 7292.13406 + Line AC 5613.82931 + Link DC 4003.90110 + dtype: float64 + + Create a basic plot on any statistic: + + >>> n.statistics.energy_balance.plot() # doctest: +SKIP + #TODO Add plot + + Choose a specific plot type: + + >>> n.statistics.energy_balance.plot("bar") # doctest: +SKIP + #TODO Add plot + + Create a interactive plot: + + >>> n.statistics.energy_balance.iplot() # doctest: +SKIP + #TODO Add plot + + """ + + _methods = [ + "system_cost", + "capex", + "installed_capex", + "expanded_capex", + "optimal_capacity", + "installed_capacity", + "expanded_capacity", + "opex", + "supply", + "withdrawal", + "transmission", + "energy_balance", + "curtailment", + "capacity_factor", + "revenue", + "market_value", + "prices", + ] + + def _get_component_index(self, df: pd.DataFrame | pd.Series, c: str) -> pd.Index: + return df.index + + def _concat_periods( + self, dfs: list[pd.DataFrame] | dict[str, pd.DataFrame], c: str + ) -> pd.DataFrame: + return pd.concat(dfs, axis=1) + + @staticmethod + def _aggregate_with_weights( + df: pd.DataFrame, + weights: pd.Series, + agg: str | Callable, + ) -> pd.Series | pd.DataFrame: + if agg == "sum": + if isinstance(weights.index, pd.MultiIndex): + return df.multiply(weights, axis=0).groupby(level=0).sum().T + return weights @ df + # Todo: here we leave out the weights, is that correct? + return df.agg(agg) + + def _aggregate_components_groupby( + self, vals: pd.DataFrame, grouping: dict, agg: Callable | str, c: str + ) -> pd.DataFrame: + if isinstance(vals.index, pd.MultiIndex): + levels = vals.index.names + keep_levels = [l for l in levels if l not in ["name", c]] + grouping_df = grouping["by"] + if isinstance(grouping_df, pd.Series): + grouping_df = grouping_df.to_frame() + elif isinstance(grouping_df, list): + grouping_df = pd.concat(grouping_df, axis=1) + elif not isinstance(grouping_df, pd.DataFrame): + msg = "grouping_df must be a DataFrame or Series" + raise TypeError(msg) + + was_series = False + if isinstance(vals, pd.Series): + vals = vals.rename("value").to_frame() + was_series = True + res = ( + vals.assign(**grouping_df) + .groupby([*keep_levels, *grouping_df.columns]) + .agg(agg) + ) + return res["value"] if was_series else res + return vals.groupby(**grouping).agg(agg) + + def _aggregate_components_concat_values( + self, values: list[pd.DataFrame], agg: Callable | str + ) -> pd.DataFrame: + """Concatenate a list of DataFrames.""" + df = pd.concat(values, copy=False) if len(values) > 1 else values[0] + if not df.index.is_unique: + df = df.groupby(level=df.index.names).agg(agg) + return df + + def _aggregate_components_concat_data( + self, d: dict[str, pd.DataFrame], is_one_component: bool + ) -> pd.DataFrame | pd.Series: + if d == {}: + idx = pd.MultiIndex.from_tuples([], names=["component", "name"]) + return pd.Series([], index=idx) + first_key = next(iter(d)) + if is_one_component: + return d[first_key] + index_names = [x.index.names for x in d.values()] + # If index names are the same, use them + if all(x == index_names[0] for x in index_names): + col_names = ["component"] + index_names[0] + # Otherwise, use default column names + elif all(len(x) == 1 for x in index_names): + col_names = ["component", "name"] + elif all(len(x) == 3 for x in index_names): + # TODO Handle better + col_names = ["network", "component", "carrier"] + else: + msg = "Multi-indexed data must have the same index names." + raise AssertionError(msg) + + df = pd.concat(d, names=col_names) + return df + + def _apply_option_kwargs( + self, + df: pd.DataFrame, + nice_names: bool | None, + drop_zero: bool | None, + round: int | None, + ) -> pd.DataFrame: + # TODO move nice names here and drop from groupers + round_ = options.params.statistics.round if round is None else round + drop_zero_ = ( + options.params.statistics.drop_zero if drop_zero is None else drop_zero + ) + if round_: + df = df.round(round_) + if drop_zero_: + df = df[df != 0] + + return df + + def _aggregate_across_components( + self, df: pd.Series | pd.DataFrame, agg: Callable | str + ) -> pd.Series | pd.DataFrame: + warnings.warn( + "Passing `aggregate_across_components` was Deprecated in .0 and " + "will be removed with v2.0.0. Use e.g. " + "`n.statistics.installed_capacity.groupby().sum() instead.", + DeprecationWarning, + stacklevel=2, + ) + levels = [l for l in df.index.names if l != "component"] + return df.groupby(level=levels).agg(agg) + + def _aggregate_components_skip_iteration( + self, vals: pd.Series | pd.DataFrame + ) -> bool: + return vals.empty + + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def __call__( + self, + components: str | Sequence[str] | None = None, + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: bool | str | Sequence[str] = False, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + ) -> pd.DataFrame: + """Calculate **multiple statistical values** for a network. + + This function calls multiple function in the background in order to + derive a full table of relevant network information. It groups the + values to components according to the groupby argument. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Returns + ------- + df : + pandas.DataFrame with columns given the different quantities. + + Examples + -------- + >>> n.statistics.optimal_capacity() + component carrier + Generator gas 982.03448 + wind 7292.13406 + Line AC 5613.82931 + Link DC 4003.90110 + dtype: float64 + + """ + funcs: list[Callable] = [ + self.optimal_capacity, + self.installed_capacity, + self.supply, + self.withdrawal, + self.energy_balance, + self.transmission, + self.capacity_factor, + self.curtailment, + self.capex, + self.opex, + self.revenue, + self.market_value, + ] + + res = {} + for func in funcs: + df = func( + components=components, + groupby_method=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + ) + res[df.attrs["name"]] = df + index = pd.Index(set.union(*[set(df.index) for df in res.values()])) + res = {k: v.reindex(index, fill_value=0.0) for k, v in res.items()} + return pd.concat(res, axis=1).sort_index(axis=0) + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def capex( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: bool | str | Sequence[str] = False, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + cost_attribute: str = "capital_cost", + ) -> pd.DataFrame: + """Calculate the **capital expenditure**. + + Includes newly installed and existing assets, measured in the specified + currency. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Other Parameters + ---------------- + cost_attribute : str + Network attribute that should be used to calculate Capital Expenditure. + Defaults to `capital_cost`. + + Returns + ------- + pd.DataFrame + Capital expenditure with components as rows and a single column of + aggregated values. + + Examples + -------- + >>> n.statistics.capex() + Series([], dtype: float64) + + """ + + @pass_empty_series_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series: + col = n.c[c].static.eval(f"{nominal_attrs[c]}_opt * {cost_attribute}") + return col + + df = self._aggregate_components( + func, + components=components, + agg=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + ) + df.attrs["name"] = "Capital Expenditure" + df.attrs["unit"] = "currency" + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def installed_capex( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: bool | str | Sequence[str] = False, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + cost_attribute: str = "capital_cost", + ) -> pd.DataFrame: + """Calculate the **capital expenditure** of already built capacities. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Other Parameters + ---------------- + cost_attribute : str + Network attribute that should be used to calculate Capital Expenditure. + Defaults to `capital_cost`. + + Returns + ------- + pd.DataFrame + Capital expenditure of already built capacities with components as rows and + a single column of aggregated values. + + Examples + -------- + >>> n.statistics.installed_capex() + component carrier + Generator gas 2.120994e+07 + wind 6.761698e+05 + Line AC 1.653634e+04 + Link DC 1.476534e+03 + dtype: float64 + + """ + + @pass_empty_series_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series: + col = n.c[c].static.eval(f"{nominal_attrs[c]} * {cost_attribute}") + return col + + df = self._aggregate_components( + func, + components=components, + agg=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + ) + df.attrs["name"] = "Capital Expenditure Fixed" + df.attrs["unit"] = "currency" + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def expanded_capex( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: bool | str | Sequence[str] = False, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + cost_attribute: str = "capital_cost", + ) -> pd.DataFrame: + """Calculate the **capital expenditure** of expanded capacities. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Other Parameters + ---------------- + cost_attribute : str + Network attribute that should be used to calculate Capital Expenditure. + Defaults to `capital_cost`. + + Returns + ------- + pd.DataFrame + Capital expenditure of expanded capacities with components as rows and + a single column of aggregated values. + + Examples + -------- + >>> n.statistics.expanded_capex() + component carrier + Generator gas -2.120994e+07 + wind -6.761698e+05 + ... + + """ + df = self.capex( + components=components, + groupby_method=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + cost_attribute=cost_attribute, + ).sub( + self.installed_capex( + components=components, + groupby_method=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + cost_attribute=cost_attribute, + ), + fill_value=0, + ) + df.attrs["name"] = "Capital Expenditure Expanded" + df.attrs["unit"] = "currency" + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def optimal_capacity( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: str | Sequence[str] | bool | None = None, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + storage: bool = False, + ) -> pd.DataFrame: + """Calculate the **optimal capacity** of the network components in MW. + + Positive capacity values correspond to production capacities and + negative values to consumption capacities. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Other Parameters + ---------------- + storage : bool, default=False + Whether to consider only storage capacities of the components + `Store` and `StorageUnit`. + + Returns + ------- + pd.DataFrame + Optimal capacity of the network components with components as rows and + a single column of aggregated values. + + Examples + -------- + >>> n.statistics.optimal_capacity() + component carrier + Generator gas 982.03448 + wind 7292.13406 + Line AC 5613.82931 + Link DC 4003.90110 + dtype: float64 + + """ + if storage: + components = ("Store", "StorageUnit") + if bus_carrier and at_port is None: + at_port = True + + @pass_empty_series_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series: + efficiency = port_efficiency(n, c, port=port) + if not at_port: + efficiency = abs(efficiency) + col = n.c[c].static[f"{nominal_attrs[c]}_opt"] * efficiency + if storage and (c == "StorageUnit"): + col = col * n.c[c].static.max_hours + return col + + df = self._aggregate_components( + func, + components=components, + agg=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + ) + df.attrs["name"] = "Optimal Capacity" + df.attrs["unit"] = "MW" + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def installed_capacity( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: str | Sequence[str] | bool | None = None, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + storage: bool = False, + ) -> pd.DataFrame: + """Calculate the **installed capacity** of the network components in MW. + + Positive capacity values correspond to production capacities and + negative values to consumption capacities. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Other Parameters + ---------------- + storage : bool, default=False + Whether to consider only storage capacities of the components + `Store` and `StorageUnit`. + + Returns + ------- + pd.DataFrame + Installed capacity of the network components with components as rows and + a single column of aggregated values. + + Examples + -------- + >>> n.statistics.installed_capacity() + component carrier + Generator gas 150000.0 + wind 290.0 + Line AC 160000.0 + Link DC 4000.0 + dtype: float64 + + """ + if storage: + components = ("Store", "StorageUnit") + if bus_carrier and at_port is None: + at_port = True + + @pass_empty_series_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series: + efficiency = port_efficiency(n, c, port=port) + if not at_port: + efficiency = abs(efficiency) + col = n.c[c].static[f"{nominal_attrs[c]}"] * efficiency + if storage and (c == "StorageUnit"): + col = col * n.c[c].static.max_hours + return col + + df = self._aggregate_components( + func, + components=components, + agg=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + ) + df.attrs["name"] = "Installed Capacity" + df.attrs["unit"] = "MW" + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def expanded_capacity( + self, + components: str | Sequence[str] | None = None, + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: str | Sequence[str] | bool | None = None, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + ) -> pd.DataFrame: + """Calculate the **expanded capacity** of the network components in MW. + + Positive capacity values correspond to production capacities and + negative values to consumption capacities. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Returns + ------- + pd.DataFrame + Expanded capacity of the network components with components as rows and + a single column of aggregated values. + + Examples + -------- + >>> n.statistics.expanded_capacity() + Series([], dtype: float64) + + """ + optimal = self.optimal_capacity( + components=components, + groupby_method=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + ) + installed = self.installed_capacity( + components=components, + groupby_method=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + ) + installed = installed.reindex(optimal.index, fill_value=0) + df = optimal.sub(installed).where(optimal.abs() > installed.abs(), 0) + df.attrs["name"] = "Expanded Capacity" + df.attrs["unit"] = "MW" + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def opex( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_time: str | bool = "sum", + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: bool | str | Sequence[str] = False, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + cost_types: str | Sequence[str] | None = None, + ) -> pd.DataFrame: + """Calculate the **operational expenditure** in the network in given currency. + + Operational expenditures include the marginal, marginal quadratic, + storage holding, spillage, start-up, shut-down and stand-by costs. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Other Parameters + ---------------- + groupby_time : str | bool, default="sum" + Type of aggregation when aggregating time series. Deactivate by setting to + False. Any pandas aggregation function can be used. Note that when + aggregating the time series are aggregated to MWh using snapshot weightings. + With False the time series is given in MW. + cost_types : str | Sequence[str] | None, default=None + List of cost types to include in the calculation. Available options + are: 'marginal_cost', 'marginal_cost_quadratic', + 'marginal_cost_storage', 'spill_cost', 'start_up_cost', + 'shut_down_cost', 'stand_by_cost'. Defaults to all (when None). + + Returns + ------- + pd.DataFrame + Ongoing operational costs with components as rows and + either time steps as columns (if groupby_time=False) or a single column + of aggregated values. + + Examples + -------- + >>> n.statistics.opex() + Series([], dtype: float64) + + """ + from pypsa.optimization.optimize import lookup # noqa: PLC0415 + + if cost_types is None: + cost_types_ = [ + "marginal_cost", + "marginal_cost_quadratic", + "marginal_cost_storage", + "spill_cost", + "start_up_cost", + "shut_down_cost", + "stand_by_cost", + ] + elif isinstance(cost_types, str): + cost_types_ = [cost_types] + else: + cost_types_ = list(cost_types) + + @pass_empty_series_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series: + result = [] + weights = n.snapshot_weightings.objective + weights_one = pd.Series(1.0, index=weights.index) + com_i = n.components[c].committables + + for cost_type in [ + "marginal_cost", + "marginal_cost_storage", + "marginal_cost_quadratic", + "spill_cost", + ]: + if cost_type in cost_types_ and cost_type in n.c[c].static: + attr = lookup.query(cost_type).loc[c].index.item() + port + cost = n.get_switchable_as_dense(c, cost_type) + p = n.c[c].dynamic[attr] + var = p * p if cost_type == "marginal_cost_quadratic" else p + opex = var * cost + term = self._aggregate_timeseries(opex, weights, agg=groupby_time) + result.append(term) + + mapping = { + "start_up_cost": "start_up", + "shut_down_cost": "shut_down", + "stand_by_cost": "status", + } + for cost_type, attr in mapping.items(): + if ( + cost_type in cost_types_ + and cost_type in n.c[c].static + and not com_i.empty + ): + cost = n.get_switchable_as_dense(c, cost_type, inds=com_i) + var = n.c[c].dynamic[attr].loc[:, com_i] + opex = var * cost + w = weights if attr == "status" else weights_one + term = self._aggregate_timeseries(opex, w, agg=groupby_time) + result.append(term) + if not result: + return pd.Series() + result = pd.concat(result) + return result.groupby(level=list(range(result.index.nlevels))).sum() + + df = self._aggregate_components( + func, + components=components, + agg=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + ) + df.attrs["name"] = "Operational Expenditure" + df.attrs["unit"] = "currency" + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def system_cost( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_time: str | bool = "sum", + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: bool | str | Sequence[str] = False, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + ) -> pd.DataFrame: + """Calculate the **total system cost**. + + Sum of the capital and operational expenditures. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Other Parameters + ---------------- + groupby_time : str | bool, default="sum" + Type of aggregation when aggregating time series. Deactivate by setting to + False. Any pandas aggregation function can be used. Note that when + aggregating the time series are aggregated to MWh using snapshot weightings. + With False the time series is given in MW. + + Returns + ------- + pd.DataFrame + System cost with components as rows and a single column of + aggregated values. + + Examples + -------- + >>> n.statistics.system_cost() + Series([], dtype: float64) + + """ + capex = self.capex( + components=components, + groupby_method=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + ) + opex = self.opex( + components=components, + groupby_time=groupby_time, + groupby_method=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + ) + # TODO It would be better if the empty series return has index names + if not capex.empty and not opex.empty: + df = capex.add(opex, fill_value=0) + elif not capex.empty: + df = capex + else: + df = opex + df.attrs["name"] = "System Cost" + df.attrs["unit"] = "currency" + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def supply( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_time: str | bool = "sum", + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: bool | str | Sequence[str] = True, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + ) -> pd.DataFrame: + """Calculate the **supply** of components in the network. + + Units depend on the regarded bus carrier. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Other Parameters + ---------------- + groupby_time : str | bool, default="sum" + Type of aggregation when aggregating time series. Deactivate by setting to + False. Any pandas aggregation function can be used. Note that when + aggregating the time series are aggregated to MWh using snapshot weightings. + With False the time series is given in MW. + + Returns + ------- + pd.DataFrame + Supply of components in the network with components as rows and + either time steps as columns (if groupby_time=False) or a single column + of aggregated values. + + Examples + -------- + >>> n.statistics.supply() + Series([], dtype: float64) + + """ + df = self.energy_balance( + components=components, + groupby_time=groupby_time, + groupby_method=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + direction="supply", + ) + df.attrs["name"] = "Supply" + df.attrs["unit"] = "carrier dependent" + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def withdrawal( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_time: str | bool = "sum", + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: bool | str | Sequence[str] = True, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + ) -> pd.DataFrame: + """Calculate the **withdrawal** of components in the network. + + Units depend on the regarded bus carrier. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Other Parameters + ---------------- + groupby_time : str | bool, default="sum" + Type of aggregation when aggregating time series. Deactivate by setting to + False. Any pandas aggregation function can be used. Note that when + aggregating the time series are aggregated to MWh using snapshot weightings. + With False the time series is given in MW. + + Returns + ------- + pd.DataFrame + Withdrawal of components in the network with components as rows and + either time steps as columns (if groupby_time=False) or a single column + of aggregated values. + + Examples + -------- + >>> n.statistics.withdrawal() + Series([], dtype: float64) + + """ + df = self.energy_balance( + components=components, + groupby_time=groupby_time, + groupby_method=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + direction="withdrawal", + ) + df.attrs["name"] = "Withdrawal" + df.attrs["unit"] = "carrier dependent" + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def transmission( # noqa: D417 + self, + components: Collection[str] | str | None = None, + groupby_time: str | bool = "sum", + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable | Literal[False] = "carrier", + at_port: bool | str | Sequence[str] = False, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + ) -> pd.DataFrame: + """Calculate the **transmission** of branch components in the network. + + Units depend on the regarded bus carrier. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Other Parameters + ---------------- + groupby_time : str | bool, default="sum" + Type of aggregation when aggregating time series. Deactivate by setting to + False. Any pandas aggregation function can be used. Note that when + aggregating the time series are aggregated to MWh using snapshot weightings. + With False the time series is given in MW. + + Returns + ------- + pd.DataFrame + Transmission of branch components in the network with components as rows and + either time steps as columns (if groupby_time=False) or a single column + of aggregated values. + + Examples + -------- + >>> n.statistics.transmission() + Series([], dtype: object) + + """ + n = self._n + + if components is None: + components = n.branch_components + + transmission_branches = get_transmission_branches(n, bus_carrier) + + @pass_empty_series_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series: + idx = transmission_branches.get_loc_level(c, level="component")[1] + p = n.c[c].dynamic[f"p{port}"][idx] + weights = n.snapshot_weightings.generators + return self._aggregate_timeseries(p, weights, agg=groupby_time) + + df = self._aggregate_components( + func, + components=components, + agg=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + ) + df.attrs["name"] = "Transmission" + df.attrs["unit"] = "carrier dependent" + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def energy_balance( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_time: str | bool = "sum", + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable | None = None, + at_port: bool | str | Sequence[str] = True, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + direction: str | None = None, + ) -> pd.DataFrame: + """Calculate the **energy balance** of components in network. + + This method computes the energy balance across various network components, where + positive values represent supply and negative values represent withdrawal. Units + are inherited from the respective bus carriers. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Other Parameters + ---------------- + groupby_time : str | bool, default="sum" + Type of aggregation when aggregating time series. Deactivate by setting to + False. Any pandas aggregation function can be used. Note that when + aggregating the time series are aggregated to MWh using snapshot weightings. + With False the time series is given in MW. + direction : str | None, default=None + Type of energy balance to calculate: + - 'supply': Only consider positive values (energy production) + - 'withdrawal': Only consider negative values (energy consumption) + - None: Consider both supply and withdrawal + + Returns + ------- + pd.DataFrame + Energy balance with components as rows and either time steps as columns + (if groupby_time=False) or a single column of aggregated values. + Units depend on the bus carrier and aggregation method. + + Examples + -------- + >>> n.statistics.energy_balance() + Series([], dtype: float64) + + """ + if groupby is None: + groupby = ["carrier", "bus_carrier"] + n = self._n + + if ( + n.c.buses.static.carrier.unique().size > 1 + and groupby is None + and bus_carrier is None + ): + logger.warning( + "Network has multiple bus carriers which are aggregated together. " + "To separate bus carriers set `bus_carrier` or use `bus_carrier` in the groupby argument." + ) + + @pass_empty_series_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series: + sign = -1.0 if c in n.branch_components else n.c[c].static.get("sign", 1.0) + weights = n.snapshot_weightings.generators + p = sign * n.c[c].dynamic[f"p{port}"] + if direction == "supply": + p = p.clip(lower=0) + elif direction == "withdrawal": + p = -p.clip(upper=0) + elif direction is not None: + logger.warning( + "Argument 'direction' is not recognized. Falling back to energy balance." + ) + return self._aggregate_timeseries(p, weights, agg=groupby_time) + + df = self._aggregate_components( + func, + components=components, + agg=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + ) + + df.attrs["name"] = "Energy Balance" + df.attrs["unit"] = n.bus_carrier_unit(bus_carrier) + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def curtailment( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_time: str | bool = "sum", + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: bool | str | Sequence[str] = False, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + ) -> pd.DataFrame: + """Calculate the **curtailment** of components in the network in MWh. + + The calculation only considers assets with a `p_max_pu` time + series, which is used to quantify the available power potential. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Other Parameters + ---------------- + groupby_time : str | bool, default="sum" + Type of aggregation when aggregating time series. Deactivate by setting to + False. Any pandas aggregation function can be used. Note that when + aggregating the time series are aggregated to MWh using snapshot weightings. + With False the time series is given in MW. + + Returns + ------- + pd.DataFrame + Curtailment of components in the network with components as rows and + either time steps as columns (if aggregate_time=False) or a single column + of aggregated values. + + Examples + -------- + >>> n.statistics.curtailment() + Series([], Name: generators, dtype: float64) + + """ + + @pass_empty_series_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series: + p = ( + n.get_switchable_as_dense(c, "p_max_pu") * n.c[c].static.p_nom_opt + - n.c[c].dynamic.p + ).clip(lower=0) + weights = n.snapshot_weightings.generators + return self._aggregate_timeseries(p, weights, agg=groupby_time) + + df = self._aggregate_components( + func, + components=components, + agg=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + ) + df.attrs["name"] = "Curtailment" + df.attrs["unit"] = "MWh" + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def capacity_factor( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_time: str | bool = "mean", + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + at_port: bool | str | Sequence[str] = False, + groupby: str | Sequence[str] | Callable = "carrier", + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + ) -> pd.DataFrame: + """Calculate the **capacity factor** of components in the network. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Other Parameters + ---------------- + groupby_time : str | bool, default="sum" + Type of aggregation when aggregating time series. Deactivate by setting to + False. Any pandas aggregation function can be used. Note that when + aggregating the time series are aggregated to MWh using snapshot weightings. + With False the time series is given in MW. + + Returns + ------- + pd.DataFrame + Capacity factor of components in the network with components as rows and + either time steps as columns (if groupby_time=False) or a single column + of aggregated values. + + Examples + -------- + >>> n.statistics.capacity_factor() + Series([], dtype: float64) + + """ + + # TODO: Why not just take p_max_pu, s_max_pu, etc. directly from the network? + @pass_empty_series_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series: + p = get_operation(n, c).abs() + weights = n.snapshot_weightings.generators + return self._aggregate_timeseries(p, weights, agg=groupby_time) + + kwargs = { + "components": components, + "groupby": groupby, + "aggregate_across_components": aggregate_across_components, + "at_port": at_port, + "carrier": carrier, + "bus_carrier": bus_carrier, + "nice_names": nice_names, + "drop_zero": drop_zero, + "round": round, + } + df = self._aggregate_components(func, agg=groupby_method, **kwargs) # type: ignore + capacity = self.optimal_capacity(groupby_method=groupby_method, **kwargs) + df = df.div(capacity.reindex(df.index), axis=0) + df.attrs["name"] = "Capacity Factor" + df.attrs["unit"] = "p.u." + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def revenue( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_time: str | bool = "sum", + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: bool | str | Sequence[str] = True, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + direction: str | None = None, + ) -> pd.DataFrame: + """Calculate the **revenue** of components in the network in given currency. + + The revenue is defined as the net revenue of an asset, i.e cost + of input - revenue of output. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Other Parameters + ---------------- + groupby_time : str | bool, default="sum" + Type of aggregation when aggregating time series. Deactivate by setting to + False. Any pandas aggregation function can be used. Note that when + aggregating the time series are aggregated to MWh using snapshot weightings. + With False the time series is given in MW. + direction : str, optional, default=None + Type of revenue to consider. If 'input' only the revenue of the input is considered. + If 'output' only the revenue of the output is considered. Defaults to None. + + Returns + ------- + pd.DataFrame + Revenue of components in the network with components as rows and + either time steps as columns (if groupby_time=False) or a single column + of aggregated values. + + Examples + -------- + >>> n.statistics.revenue() + Series([], dtype: float64) + + """ + + @pass_empty_series_if_keyerror + def func(n: Network, c: str, port: str) -> pd.Series: + sign = -1.0 if c in n.branch_components else n.c[c].static.get("sign", 1.0) + df = sign * n.c[c].dynamic[f"p{port}"] + buses = n.c[c].static[f"bus{port}"][df.columns] + # catch multiindex case + buses = ( + buses.to_frame("bus") + .set_index("bus", append=True) + .droplevel("name") + .index + ) + prices = n.c.buses.dynamic.marginal_price.reindex( + columns=buses, fill_value=0 + ).values + if direction is not None: + if direction == "input": + df = df.clip(upper=0) + elif direction == "output": + df = df.clip(lower=0) + else: + msg = f"Argument 'direction' must be 'input', 'output' or None, got {direction}" + raise ValueError(msg) + revenue = df * prices + weights = n.snapshot_weightings.objective + return self._aggregate_timeseries(revenue, weights, agg=groupby_time) + + df = self._aggregate_components( + func, + components=components, + agg=groupby_method, + aggregate_across_components=aggregate_across_components, + groupby=groupby, + at_port=at_port, + carrier=carrier, + bus_carrier=bus_carrier, + nice_names=nice_names, + drop_zero=drop_zero, + round=round, + ) + df.attrs["name"] = "Revenue" + df.attrs["unit"] = "currency" + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + @deprecated_kwargs( + deprecated_in="1.0", + removed_in="2.0", + comps="components", + aggregate_groups="groupby_method", + aggregate_time="groupby_time", + ) + def market_value( # noqa: D417 + self, + components: str | Sequence[str] | None = None, + groupby_time: str | bool = "mean", + groupby_method: Callable | str = "sum", + aggregate_across_components: bool = False, + groupby: str | Sequence[str] | Callable = "carrier", + at_port: bool | str | Sequence[str] = True, + carrier: str | Sequence[str] | None = None, + bus_carrier: str | Sequence[str] | None = None, + nice_names: bool | None = None, + drop_zero: bool | None = None, + round: int | None = None, + ) -> pd.DataFrame: + """Calculate the **market value** of components in the network. + + Curreny is currency/MWh or currency/unit_{bus_carrier} where unit_{bus_carrier} + is the unit of the bus carrier. + + Parameters + ---------- + components : str | Sequence[str] | None, default=None + Components to include in the calculation. If None, includes all one-port + and branch components. Available components are 'Generator', 'StorageUnit', + 'Store', 'Load', 'Line', 'Transformer' and'Link'. + groupby_method : Callable | str, default="sum" + Function to aggregate groups when using the groupby parameter. + Any pandas aggregation function can be used. + aggregate_across_components : bool, default=False + Whether to aggregate across components. If there are different components + which would be grouped together due to the same index, this is avoided. + groupby : str | Sequence[str] | Callable, default=["carrier", "bus_carrier"] + How to group components: + - `False`: No grouping, return all components individually + - string or list of strings: Group by column names from [c.static][pypsa.Components] + - callable: Function that takes network and component name as arguments + at_port : bool | str | Sequence[str], default=True + Which ports to consider: + - True: All ports of components + - False: Exclude first port ("bus"/"bus0") + - str or list of str: Specific ports to include + carrier : str | Sequence[str] | None, default=None + Filter by carrier. If specified, only considers assets with given + carrier(s). + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of connected buses. If specified, only considers assets + connected to buses with the given carrier(s). + nice_names : bool | None, default=None + Whether to use carrier nice names defined in n.carriers.nice_name. Defaults + to module wide option (default: True). + See `https://go.pypsa.org/options-params` for more information. + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `https://go.pypsa.org/options-params` for more information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `https://go.pypsa.org/options-params` for more information. + + Other Parameters + ---------------- + groupby_time : str | bool, default="sum" + Type of aggregation when aggregating time series. Deactivate by setting to + False. Any pandas aggregation function can be used. Note that when + aggregating the time series are aggregated to MWh using snapshot weightings. + With False the time series is given in MW. + + Returns + ------- + pd.DataFrame + Market value of components in the network with components as rows and + either time steps as columns (if groupby_time=False) or a single column + of aggregated values. + + Examples + -------- + >>> n.statistics.market_value() + Series([], dtype: float64) + + """ + kwargs = { + "components": components, + "groupby_time": groupby_time, + "groupby_method": groupby_method, + "aggregate_across_components": aggregate_across_components, + "groupby": groupby, + "at_port": at_port, + "carrier": carrier, + "bus_carrier": bus_carrier, + "nice_names": nice_names, + "drop_zero": drop_zero, + "round": round, + } + df = self.revenue(**kwargs) / self.supply(**kwargs) + df.attrs["name"] = "Market Value" + df.attrs["unit"] = "currency / MWh" + return df + + @MethodHandlerWrapper(handler_class=StatisticHandler, inject_attrs={"n": "_n"}) + def prices( # noqa: D417 + self, + groupby: bool = False, + weighting: str = "load", + groupby_time: bool = True, + bus_carrier: Sequence[str] | str | None = None, + drop_zero: bool | None = None, + round: int | None = None, + ) -> pd.Series: + """Calculate the average marginal prices in the network per bus. + + Currency is currency/MWh or currency/unit_{bus_carrier} where + unit_{bus_carrier} is the unit of the bus carrier. + + !!! note + + The functions signature of [n.statistics.prices][pypsa.statistics.StatisticsAccessor.prices] + is different from other statistics functions as it does not take the same + parameters, since prices are only defined for buses. + + Parameters + ---------- + groupby : bool | str, optional + How to group components: + - `False`: No grouping, return all buses individually + - `"bus_carrier"`: Prices are aggregated to each bus carrier with weights + applied. + Other grouping options are not supported and the groupby method can not be + set. See `weighting` for different weighting options. Defaults to False. + weighting : str, optional + Type of weighting to use. If 'load' the prices are weighted by the + load of the buses and if time they are weighted by snapshot + weightings. Defaults to 'load'. + groupby_time : bool, default: True + Whether to group the time series by time or return the full time series. + No aggregation method can be set. Defaults to True. + bus_carrier : str | Sequence[str] | None, default=None + Filter by carrier of buses. If specified, only considers buses with + the given carrier(s). + drop_zero : bool | None, default=None + Whether to drop zero values from the result. Defaults to module wide option + (default: True). See `pypsa.options.params.statistics.describe()` for more + information. + round : int | None, default=None + Number of decimal places to round the result to. Defaults to module wide + option (default: 2). See `pypsa.options.params.statistics.describe()` for + more information. + + Returns + ------- + pd.DataFrame + Time-averaged or load-weighted prices per bus or bus carrier. + + Examples + -------- + >>> n.statistics.prices() + Series([], dtype: float64) + + """ + n = self._n + sns_weights = n.snapshot_weightings.objective + + prices = n.c.buses.dynamic.marginal_price + + if bus_carrier is not None: + if isinstance(bus_carrier, str): + bus_carrier = [bus_carrier] + mask = n.c.buses.static.carrier.isin(bus_carrier) + prices = prices.loc[:, mask] + + if not groupby_time: + return prices.T + + if weighting == "load": + weights = ( + n.statistics.withdrawal( + groupby="bus", + bus_carrier=bus_carrier, + nice_names=False, + groupby_time=False, + ) + .groupby("bus") + .sum() + .T + ) + weights = weights.reindex(prices.columns, axis=1, fill_value=1) + elif weighting == "time": + weights = pd.DataFrame(1, index=prices.index, columns=prices.columns) + else: + msg = f"Weighting '{weighting}' is not supported. Use 'load' or 'time'." + raise ValueError(msg) + + a = sns_weights @ (weights * prices) + b = sns_weights @ weights + df = a / b + + if groupby == "bus_carrier": + df = df.groupby(n.c.buses.static.carrier).apply( + lambda g: (g * b.loc[g.index]).sum() / b.loc[g.index].sum() + ) + df.index.name = "bus_carrier" + elif groupby is not False: + msg = "Only groupby=False and groupby='bus_carrier' are supported." + raise ValueError(msg) + + df.attrs["name"] = "Prices" + df.attrs["unit"] = "currency / MWh" + + df = self._apply_option_kwargs( + df, + drop_zero=drop_zero, + round=round, + nice_names=False, # Add once integrated in function + ) + + return df diff --git a/PyPSA/source/pypsa/statistics/grouping.py b/PyPSA/source/pypsa/statistics/grouping.py new file mode 100644 index 0000000000000000000000000000000000000000..53bce7d89fe6390c7f211a6f5e51a5bfca67fc59 --- /dev/null +++ b/PyPSA/source/pypsa/statistics/grouping.py @@ -0,0 +1,432 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Groupers for PyPSA statistics. + +Use them via the groupers instance via `pypsa.statistics.groupers`. Do not use the +grouping module directly. +""" + +from __future__ import annotations + +import logging +from inspect import signature +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Callable, Sequence + + from pypsa import Network + + +import pandas as pd + +logger = logging.getLogger(__name__) + + +class Groupers: + """Container for all the get_ methods.""" + + def _map_with_multiindex( + self, component_series: pd.Series, mapping_series: pd.Series + ) -> pd.Series: + """Map values across MultiIndex DataFrames. + + This handles the case where both series have MultiIndex (NetworkCollection case) + by dropping scenario levels from the mapping series and removing duplicates. + + Parameters + ---------- + component_series : pd.Series + Series from component (e.g., generator buses) + mapping_series : pd.Series + Series to map to (e.g., bus carriers) + + Returns + ------- + pd.Series + Mapped series with same index as component_series + + """ + # Handle MultiIndex case (NetworkCollection) + if isinstance(component_series.index, pd.MultiIndex) and isinstance( + mapping_series.index, pd.MultiIndex + ): + # Drop all levels except the last one from mapping series + n_levels_to_drop = len(mapping_series.index.names) - 1 + if n_levels_to_drop > 0: + simplified_mapping = mapping_series.droplevel( + list(range(n_levels_to_drop)) + ) + # Remove duplicates (keep first occurrence) + simplified_mapping = simplified_mapping[ + ~simplified_mapping.index.duplicated(keep="first") + ] + else: + simplified_mapping = mapping_series + # Do the mapping + return component_series.map(simplified_mapping) + else: + # Original behavior for single networks + return component_series.map(mapping_series) + + def __repr__(self) -> str: + """Get representation of the grouper container. + + Examples + -------- + >>> pypsa.statistics.groupers # doctest: +ELLIPSIS + Grouper container with the following groupers: bus, bus_carrier, ... + + """ + return ( + f"Grouper container with the following groupers: " + f"{', '.join(self.list_groupers())}" + ) + + def __getitem__(self, keys: str | Callable | Sequence[str | Callable]) -> Callable: + """Get a single or multi-indexed grouper method. + + Parameters + ---------- + keys : str | Callable | Sequence[str | Callable] + Single or multiple keys to get the grouper method. + + Returns + ------- + Callable + Grouper method. + + Examples + -------- + Single indexed grouper + + >>> pypsa.statistics.groupers["carrier"] # doctest: +ELLIPSIS + .multi_grouper at 0x...> + + Multi-indexed grouper + + >>> pypsa.statistics.groupers[["carrier", "bus"]] # doctest: +ELLIPSIS + .multi_grouper at 0x...> + + """ + return self._multi_grouper(keys) + + def __setitem__(self, key: str, value: Callable) -> None: + """Set a custom grouper method. + + Parameters + ---------- + key : str + Name of the custom grouper. + + value : Callable + Custom grouper function. + + + + """ + raise NotImplementedError() + + def _get_generic_grouper(self, n: Network, c: str, key: str) -> pd.Series: + try: + return n.c[c].static[key].rename(key) + except KeyError as e: + msg = f"Unknown grouper {key}." + raise KeyError(msg) from e + + def list_groupers(self) -> dict: + """List all available groupers which are avaliable on the module level. + + Returns + ------- + dict + Dictionary with all available groupers. The keys are the grouper names and + the values are the grouper methods. The keys can be used to directly + access the grouper in any `groupby` argument. + + Examples + -------- + >>> pypsa.statistics.groupers.list_groupers().keys() # doctest: +ELLIPSIS + dict_keys(['bus', 'bus_carrier', ... + + """ + no_groupers = ["add_grouper", "list_groupers"] + return { + key: getattr(self, key) + for key in dir(self) + if not key.startswith("_") and key not in no_groupers + } + + def _multi_grouper( + self, keys: str | Callable | Sequence[str | Callable] + ) -> Callable: + """Get a single or multi-indexed grouper method. + + Should be used via groupers __getitem__ method and not directly. + + Parameters + ---------- + keys : str | Callable | Sequence[str | Callable] + Single or multiple keys to get the grouper method. + + Returns + ------- + Callable + Grouper method. + + Examples + -------- + Single indexed grouper + + >>> pypsa.statistics.groupers["carrier"] # doctest: +ELLIPSIS + .multi_grouper at 0x...> + + Multi-indexed grouper + + >>> pypsa.statistics.groupers[["carrier", "bus"]] # doctest: +ELLIPSIS + .multi_grouper at 0x...> + + """ + keys_: Sequence[str | Callable] + if scalar_passed := isinstance(keys, str) or callable(keys): + keys_ = (keys,) + else: + keys_ = keys + + def multi_grouper( + n: Network, c: str, port: str = "", nice_names: bool = False + ) -> list: + grouped_data = [] + for key in keys_: + if isinstance(key, str): + if key not in self.list_groupers(): + grouped_data.append(self._get_generic_grouper(n, c, key)) + continue + method = self.list_groupers()[key] + else: + method = key + + kwargs: dict[str, str | bool] = {} + if "port" in signature(method).parameters: + kwargs["port"] = port + if "nice_names" in signature(method).parameters: + kwargs["nice_names"] = nice_names + grouped_data.append(method(n, c, **kwargs)) + + return grouped_data[0] if scalar_passed else grouped_data + + return multi_grouper + + def add_grouper(self, name: str, func: Callable) -> None: + """Add a custom grouper to groupers on module level. + + After registering a custom grouper, it can be accessed via the groupers module + level object and used in the statistics methods or as a groupers method. + + Parameters + ---------- + name : str + Name of the custom grouper. This will be used as the key in the groupby + argument. + func : Callable + Custom grouper function, which must return a pandas Series with the same + length as the component index and accept as arguments: + * n (Network): The PyPSA network instance + * c (str): Component name + * port (str): Component port as integer string + * nice_names (bool, optional): Whether to use nice carrier names + + + """ + setattr(self, name, func) + + def carrier(self, n: Network, c: str, nice_names: bool = True) -> pd.Series: + """Grouper method to group by the carrier of the components. + + Parameters + ---------- + n : Network + PyPSA network instance. + c : str + Components type name. E.g. "Generator", "StorageUnit", etc. + nice_names : bool, optional + Whether to use nice carrier names. + + Returns + ------- + pd.Series + Series with the carrier of the components. + + """ + static = n.c[c].static + fall_back = pd.Series("", index=static.index) + carrier_series = static.get("carrier", fall_back).rename("carrier") + if nice_names: + carrier_series = carrier_series.replace( + n.c.carriers.static.nice_name[lambda ds: ds != ""] + ).replace("", "-") + return carrier_series + + def bus_carrier( + self, n: Network, c: str, port: str = "", nice_names: bool = True + ) -> pd.Series: + """Grouper method to group by the carrier of the attached bus of a component. + + Parameters + ---------- + n : Network + PyPSA network instance. + c : str + Components type name. E.g. "Generator", "StorageUnit", etc. + port : str, optional + Port of corresponding bus, which should be used. + nice_names : bool, optional + Whether to use nice carrier names. + + Returns + ------- + pd.Series + Series with the bus and carrier of the components. + + """ + bus = f"bus{port}" + buses_carrier = self.carrier(n, "Bus", nice_names=nice_names) + component_buses = n.c[c].static[bus] + + return self._map_with_multiindex(component_buses, buses_carrier).rename( + "bus_carrier" + ) + + def bus(self, n: Network, c: str, port: str = "") -> pd.Series: + """Grouper method to group by the attached bus of the components. + + Parameters + ---------- + n : Network + PyPSA network instance. + c : str + Components type name. E.g. "Generator", "StorageUnit", etc. + port : str, optional + Port of corresponding bus, which should be used. + + Returns + ------- + pd.Series + Series with the bus of the components. + + """ + bus = f"bus{port}" + return n.c[c].static[bus].rename("bus") + + def country(self, n: Network, c: str, port: str = "") -> pd.Series: + """Grouper method to group by the country of the components corresponding bus. + + Parameters + ---------- + n : Network + PyPSA network instance. + c : str + Components type name. E.g. "Generator", "StorageUnit", etc. + port : str, optional + Port of corresponding bus, which should be used. + + Returns + ------- + pd.Series + Series with the country of the components corresponding bus. + + """ + bus = f"bus{port}" + component_buses = n.c[c].static[bus] + buses_country = n.c.buses.static.country + return self._map_with_multiindex(component_buses, buses_country).rename( + "country" + ) + + def location(self, n: Network, c: str, port: str = "") -> pd.Series: + """Grouper method to group by the location of the components corresponding bus. + + Parameters + ---------- + n : Network + PyPSA network instance. + c : str + Components type name. E.g. "Generator", "StorageUnit", etc. + port : str, optional + Port of corresponding bus, which should be used. + + Returns + ------- + pd.Series + Series with the location of the components corresponding bus. + + """ + bus = f"bus{port}" + component_buses = n.c[c].static[bus] + buses_location = n.c.buses.static.location + return self._map_with_multiindex(component_buses, buses_location).rename( + "location" + ) + + def unit(self, n: Network, c: str, port: str = "") -> pd.Series: + """Grouper method to group by the unit of the components corresponding bus. + + Parameters + ---------- + n : Network + PyPSA network instance. + c : str + Components type name. E.g. "Generator", "StorageUnit", etc. + port : str, optional + Port of corresponding bus, which should be used. + + Returns + ------- + pd.Series + Series with the unit of the components corresponding bus. + + """ + bus = f"bus{port}" + component_buses = n.c[c].static[bus] + buses_unit = n.c.buses.static.unit + return self._map_with_multiindex(component_buses, buses_unit).rename("unit") + + def name(self, n: Network, c: str) -> pd.Series: + """Grouper method to group by the name of components. + + Parameters + ---------- + n : Network + PyPSA network instance. + c : str + Components type name. E.g. "Generator", "StorageUnit", etc. + + Returns + ------- + pd.Series + Series with the component names. + + """ + return n.c[c].static.index.to_series().rename("name") + + +groupers = Groupers() + +new_grouper_access = { + "get_carrier": ".carrier", + "get_bus_carrier": ".bus_carrier", + "get_bus": ".bus", + "get_country": ".country", + "get_location": ".location", + "get_unit": ".unit", + "get_name": ".name", + "get_bus_and_carrier": '["bus", "carrier"]', + "get_bus_unit_and_carrier": '["bus", "unit", "carrier"]', + "get_name_bus_and_carrier": '["name", "bus", "carrier"]', + "get_country_and_carrier": '["country", "carrier"]', + "get_location_and_carrier": '["location", "carrier"]', + "get_bus_and_carrier_and_bus_carrier": '["bus", "carrier", "bus_carrier"]', + "get_carrier_and_bus_carrier": '["carrier", "bus_carrier"]', +} diff --git a/PyPSA/source/pypsa/type_utils.py b/PyPSA/source/pypsa/type_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..2d1521c506eff0aee5ba38f71973697b767abfd2 --- /dev/null +++ b/PyPSA/source/pypsa/type_utils.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Typing utilities.""" + +from typing import Any, TypeVar + +import numpy as np +import pandas as pd +from pandas.api.types import is_list_like + +from pypsa.network.abstract import _NetworkABC + +NetworkType = TypeVar("NetworkType", bound=_NetworkABC) + + +def is_1d_list_like(x: Any) -> bool: + """Check if x is a 1D list-like object. + + Parameters + ---------- + x : Any + Object to check. + + Returns + ------- + bool + True if x is a 1D list-like object. + + Examples + -------- + >>> pypsa.type_utils.is_1d_list_like([1, 2, 3]) + True + >>> pypsa.type_utils.is_1d_list_like(np.array([1, 2, 3])) + True + >>> pypsa.type_utils.is_1d_list_like(np.array([[1, 2], [3, 4]])) + False + >>> pypsa.type_utils.is_1d_list_like(pd.DataFrame({'a': [1, 2]})) + False + >>> pypsa.type_utils.is_1d_list_like(pd.Series([1, 2, 3])) + True + + """ + if isinstance(x, np.ndarray): + return x.ndim == 1 + + if isinstance(x, pd.DataFrame): + return False # DataFrame has always 2 dimensions + + return is_list_like(x) diff --git a/PyPSA/source/pypsa/version.py b/PyPSA/source/pypsa/version.py new file mode 100644 index 0000000000000000000000000000000000000000..a7177a94cb635467b23ec987cff27fe5431784d6 --- /dev/null +++ b/PyPSA/source/pypsa/version.py @@ -0,0 +1,54 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Version information for PyPSA package. + +Examples +-------- +>>> pypsa.__version__ # doctest: +SKIP +'0.34.0.post1.dev44+gf5e415b6' +>>> pypsa.__version_base__ # doctest: +SKIP +'0.34.0' +>>> pypsa.__version_major_minor__ # doctest: +SKIP +'0.34' + +""" + +import logging +import re +from importlib.metadata import version + +logger = logging.getLogger(__name__) + + +def check_pypsa_version(version_string: str) -> None: + """Check if the installed PyPSA version was resolved correctly.""" + if version_string.startswith("0.0"): + logger.warning( + "The correct version of PyPSA could not be resolved. This is likely due to " + "a local clone without pulling tags. Please run `git fetch --tags`." + ) + + +# e.g. "0.17.1" or "0.17.1.dev4+ga3890dc0" (if installed from git) +__version__ = version("pypsa") + +# e.g. "0.17.0" +match = re.match(r"(\d+\.\d+(?:\.\d+)?(?:[a-z]+\d*)?)", __version__) +if not match: + msg = f"Could not determine release_version of pypsa: {__version__}" + raise ValueError(msg) + +__version_base__ = match.group(0) +# e.g. "0.17" +match = re.match(r"(\d+\.\d+)", __version__) + +if not match: + msg = f"Could not determine major_minor version of pypsa: {__version__}" + raise ValueError(msg) + +__version_major_minor__ = match.group(1) + +# Check pypsa version +check_pypsa_version(__version__) diff --git a/PyPSA/source/test/baseline/test_plot_types_area-capacity_factor.png b/PyPSA/source/test/baseline/test_plot_types_area-capacity_factor.png new file mode 100644 index 0000000000000000000000000000000000000000..d7d6c56aa219692b10a218d1b7cf3c5c7580dd02 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-capacity_factor.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-capex.png b/PyPSA/source/test/baseline/test_plot_types_area-capex.png new file mode 100644 index 0000000000000000000000000000000000000000..2915713247cdba185d5cee13c16615c677e2bd99 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-curtailment.png b/PyPSA/source/test/baseline/test_plot_types_area-curtailment.png new file mode 100644 index 0000000000000000000000000000000000000000..ca02b5e1d2b388d20c1ee276eea2b83ec8370110 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-curtailment.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-energy_balance.png b/PyPSA/source/test/baseline/test_plot_types_area-energy_balance.png new file mode 100644 index 0000000000000000000000000000000000000000..27669a90e8da580f4a2d3d4c04b24c6d9ac5a276 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-energy_balance.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-expanded_capacity.png b/PyPSA/source/test/baseline/test_plot_types_area-expanded_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..62367da3f1a50ebb7fcd677ec0b1e61f64594787 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-expanded_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-expanded_capex.png b/PyPSA/source/test/baseline/test_plot_types_area-expanded_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..5074009bbd7dd069542c48afb690b6f6fab820b8 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-expanded_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-installed_capacity.png b/PyPSA/source/test/baseline/test_plot_types_area-installed_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..3e8bb56c1cc641ea9157619fc9a495abbd7c25cd Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-installed_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-installed_capex.png b/PyPSA/source/test/baseline/test_plot_types_area-installed_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..15f0376196a9f460b8b331140a8a0dc6cc198fb7 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-installed_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-market_value.png b/PyPSA/source/test/baseline/test_plot_types_area-market_value.png new file mode 100644 index 0000000000000000000000000000000000000000..5787294acf24cd23f06a2e5798f819e6efb69cb9 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-market_value.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-opex.png b/PyPSA/source/test/baseline/test_plot_types_area-opex.png new file mode 100644 index 0000000000000000000000000000000000000000..251c0348ffbb9eec896e9faec0ea82fa835ce368 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-opex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-optimal_capacity.png b/PyPSA/source/test/baseline/test_plot_types_area-optimal_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..0343286f87f093ba5f4e4e99896430dad36f3071 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-optimal_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-prices.png b/PyPSA/source/test/baseline/test_plot_types_area-prices.png new file mode 100644 index 0000000000000000000000000000000000000000..0f4ea3fc33f939dd95d5040872f3094ca8e795b9 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-prices.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-revenue.png b/PyPSA/source/test/baseline/test_plot_types_area-revenue.png new file mode 100644 index 0000000000000000000000000000000000000000..e478db5a1b8d34e816adec6d4ed4f2347aeddbfe Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-revenue.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-supply.png b/PyPSA/source/test/baseline/test_plot_types_area-supply.png new file mode 100644 index 0000000000000000000000000000000000000000..cba0e240cbd5929662fbd590e33576a14cefb70b Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-supply.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-system_cost.png b/PyPSA/source/test/baseline/test_plot_types_area-system_cost.png new file mode 100644 index 0000000000000000000000000000000000000000..2516b9627274b1b8d932c94428e62d411a439205 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-system_cost.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-transmission.png b/PyPSA/source/test/baseline/test_plot_types_area-transmission.png new file mode 100644 index 0000000000000000000000000000000000000000..1e2b9b40d297e727dc22fa83e760e73a18b31120 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-transmission.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area-withdrawal.png b/PyPSA/source/test/baseline/test_plot_types_area-withdrawal.png new file mode 100644 index 0000000000000000000000000000000000000000..b8fe6c2a13f82aad4252ad61e95cdfb24eb75e1d Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area-withdrawal.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_capacity_factor.png b/PyPSA/source/test/baseline/test_plot_types_area_capacity_factor.png new file mode 100644 index 0000000000000000000000000000000000000000..4fbc5ff084a5a6286920c4a4a45441022a6c0ad7 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_capacity_factor.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_capex.png b/PyPSA/source/test/baseline/test_plot_types_area_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..1c61e1474daed689d253bad1784b5c926dcb311a Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_curtailment.png b/PyPSA/source/test/baseline/test_plot_types_area_curtailment.png new file mode 100644 index 0000000000000000000000000000000000000000..2eaf2d07c7d59818673244e8e6bdf91bbeb4894a Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_curtailment.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_energy_balance.png b/PyPSA/source/test/baseline/test_plot_types_area_energy_balance.png new file mode 100644 index 0000000000000000000000000000000000000000..b2618e6f3e46d9f60b2af0afde2c4ea4421986a1 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_energy_balance.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_expanded_capacity.png b/PyPSA/source/test/baseline/test_plot_types_area_expanded_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..c08e398ba598b19ffd47e08c3ac8becacc52a3c1 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_expanded_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_expanded_capex.png b/PyPSA/source/test/baseline/test_plot_types_area_expanded_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..ac3d5a60180310eb48e70eb5a0e49d971c612f52 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_expanded_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_installed_capacity.png b/PyPSA/source/test/baseline/test_plot_types_area_installed_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..0504a3b7e018b754f8d90762e588e24f2727e14a Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_installed_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_installed_capex.png b/PyPSA/source/test/baseline/test_plot_types_area_installed_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..67f7ca886b61d8b4a1e96f45f42a6b6341dfa0bf Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_installed_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_market_value.png b/PyPSA/source/test/baseline/test_plot_types_area_market_value.png new file mode 100644 index 0000000000000000000000000000000000000000..10fae75b766b18118d892f4316831d87633219e7 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_market_value.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_opex.png b/PyPSA/source/test/baseline/test_plot_types_area_opex.png new file mode 100644 index 0000000000000000000000000000000000000000..ad8b3a0bf41180e0b95e449ebb39a67157a90bb8 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_opex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_optimal_capacity.png b/PyPSA/source/test/baseline/test_plot_types_area_optimal_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..3412b82d92947b0f55042a2b2bd283ea7d54078f Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_optimal_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_revenue.png b/PyPSA/source/test/baseline/test_plot_types_area_revenue.png new file mode 100644 index 0000000000000000000000000000000000000000..8d8d2d3a6df6f0b8c8079b767b72a4d6389a0a91 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_revenue.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_supply.png b/PyPSA/source/test/baseline/test_plot_types_area_supply.png new file mode 100644 index 0000000000000000000000000000000000000000..b897f0e21a7fe8374b7194cdd7d7b688cea96b84 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_supply.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_system_cost.png b/PyPSA/source/test/baseline/test_plot_types_area_system_cost.png new file mode 100644 index 0000000000000000000000000000000000000000..2516b9627274b1b8d932c94428e62d411a439205 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_system_cost.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_transmission.png b/PyPSA/source/test/baseline/test_plot_types_area_transmission.png new file mode 100644 index 0000000000000000000000000000000000000000..41c98c621a9a7a9a89d751af66970419b5c27934 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_transmission.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_area_withdrawal.png b/PyPSA/source/test/baseline/test_plot_types_area_withdrawal.png new file mode 100644 index 0000000000000000000000000000000000000000..b86dab2c1769d5c0b88ca7d7fbf6449da16ac41b Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_area_withdrawal.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-capacity_factor.png b/PyPSA/source/test/baseline/test_plot_types_bar-capacity_factor.png new file mode 100644 index 0000000000000000000000000000000000000000..2463cebd572f106be78e8ae12a104fd30f28ccf7 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-capacity_factor.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-capex.png b/PyPSA/source/test/baseline/test_plot_types_bar-capex.png new file mode 100644 index 0000000000000000000000000000000000000000..68e59973e6331ba3d1ca30f75892015f2af4494a Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-curtailment.png b/PyPSA/source/test/baseline/test_plot_types_bar-curtailment.png new file mode 100644 index 0000000000000000000000000000000000000000..94d7bff92a7047eafba4aab08bdc2f95f57c6e93 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-curtailment.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-energy_balance.png b/PyPSA/source/test/baseline/test_plot_types_bar-energy_balance.png new file mode 100644 index 0000000000000000000000000000000000000000..5ca33ca0aa5327e7ee11fe0f40f5707dec255061 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-energy_balance.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-expanded_capacity.png b/PyPSA/source/test/baseline/test_plot_types_bar-expanded_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..41892445222b1f4855e2f76849bc5be52ade9f56 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-expanded_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-expanded_capex.png b/PyPSA/source/test/baseline/test_plot_types_bar-expanded_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..8e912587d3881902e41c7ecc34f0b13ba34793f0 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-expanded_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-installed_capacity.png b/PyPSA/source/test/baseline/test_plot_types_bar-installed_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..2248372db3dc3de0b62ffad9d5ded5a8982b5bdc Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-installed_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-installed_capex.png b/PyPSA/source/test/baseline/test_plot_types_bar-installed_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..edb472095a85da8587852d85fc06ef5561eb0a7c Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-installed_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-market_value.png b/PyPSA/source/test/baseline/test_plot_types_bar-market_value.png new file mode 100644 index 0000000000000000000000000000000000000000..5081d4eebdd90080537978483ac1d467a61c10cc Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-market_value.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-opex.png b/PyPSA/source/test/baseline/test_plot_types_bar-opex.png new file mode 100644 index 0000000000000000000000000000000000000000..df40c332ef0180ffae3c14026c445ac2ab1bb929 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-opex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-optimal_capacity.png b/PyPSA/source/test/baseline/test_plot_types_bar-optimal_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..b1006d71a6e8ceb825d7f3284b05d4b8acdb42b0 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-optimal_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-prices.png b/PyPSA/source/test/baseline/test_plot_types_bar-prices.png new file mode 100644 index 0000000000000000000000000000000000000000..e468c161bf26aad00cfa37ea70f6ac72b2ce014d Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-prices.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-revenue.png b/PyPSA/source/test/baseline/test_plot_types_bar-revenue.png new file mode 100644 index 0000000000000000000000000000000000000000..5788d22cde2b0f6b54b0a2db181c6aaab1dbdbef Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-revenue.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-supply.png b/PyPSA/source/test/baseline/test_plot_types_bar-supply.png new file mode 100644 index 0000000000000000000000000000000000000000..11d7e43a0c1078629dcf4f7008a136e2da825576 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-supply.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-system_cost.png b/PyPSA/source/test/baseline/test_plot_types_bar-system_cost.png new file mode 100644 index 0000000000000000000000000000000000000000..142d5cbbcd7f28381d8eb71260da1ae4b1ffe98e Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-system_cost.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-transmission.png b/PyPSA/source/test/baseline/test_plot_types_bar-transmission.png new file mode 100644 index 0000000000000000000000000000000000000000..78ec4c6c3ef6f2c562da4e409627ae8e77bf3b5f Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-transmission.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar-withdrawal.png b/PyPSA/source/test/baseline/test_plot_types_bar-withdrawal.png new file mode 100644 index 0000000000000000000000000000000000000000..42a65b395906d3d2880d436e9daa4879a24af367 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar-withdrawal.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_capacity_factor.png b/PyPSA/source/test/baseline/test_plot_types_bar_capacity_factor.png new file mode 100644 index 0000000000000000000000000000000000000000..2fcb556295814d14961b627d9e32e38dd5094ed1 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_capacity_factor.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_capex.png b/PyPSA/source/test/baseline/test_plot_types_bar_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..858b10fd8af7322eb04eecab914d7fca88567ef6 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_curtailment.png b/PyPSA/source/test/baseline/test_plot_types_bar_curtailment.png new file mode 100644 index 0000000000000000000000000000000000000000..3d5490a46dbf7981789032a549d053b39b15ebe6 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_curtailment.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_energy_balance.png b/PyPSA/source/test/baseline/test_plot_types_bar_energy_balance.png new file mode 100644 index 0000000000000000000000000000000000000000..f7b1df5d77e5c6c023c87cdc995fb72084240303 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_energy_balance.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_expanded_capacity.png b/PyPSA/source/test/baseline/test_plot_types_bar_expanded_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..d4a43ffc446e4cb4906da8f1e3215c5d4e3c7fb6 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_expanded_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_expanded_capex.png b/PyPSA/source/test/baseline/test_plot_types_bar_expanded_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..c870f2016c837f98c7a214e6563e9a3f0e02f913 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_expanded_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_installed_capacity.png b/PyPSA/source/test/baseline/test_plot_types_bar_installed_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..f1bef03ba122d1eb325e09e75736fbfca4f08a22 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_installed_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_installed_capex.png b/PyPSA/source/test/baseline/test_plot_types_bar_installed_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..88f972b0a7146b9636735e696e1eb76bfea52ce8 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_installed_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_market_value.png b/PyPSA/source/test/baseline/test_plot_types_bar_market_value.png new file mode 100644 index 0000000000000000000000000000000000000000..345a4b77d75ea73759d99b9b5eca56c14006f669 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_market_value.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_opex.png b/PyPSA/source/test/baseline/test_plot_types_bar_opex.png new file mode 100644 index 0000000000000000000000000000000000000000..e7d5b104705395582e54cd78f108ad0a6cb008d9 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_opex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_optimal_capacity.png b/PyPSA/source/test/baseline/test_plot_types_bar_optimal_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..5cbb282450e8971965f8bdd9b794b7cee654b352 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_optimal_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_revenue.png b/PyPSA/source/test/baseline/test_plot_types_bar_revenue.png new file mode 100644 index 0000000000000000000000000000000000000000..6a9dd3e5ab442dd957ea8689c2fe0e786a7cb9e9 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_revenue.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_supply.png b/PyPSA/source/test/baseline/test_plot_types_bar_supply.png new file mode 100644 index 0000000000000000000000000000000000000000..e661a2670a3b2605b106c504419aaf930eca5d15 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_supply.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_system_cost.png b/PyPSA/source/test/baseline/test_plot_types_bar_system_cost.png new file mode 100644 index 0000000000000000000000000000000000000000..142d5cbbcd7f28381d8eb71260da1ae4b1ffe98e Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_system_cost.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_transmission.png b/PyPSA/source/test/baseline/test_plot_types_bar_transmission.png new file mode 100644 index 0000000000000000000000000000000000000000..609d5bdfb1b1e6d6feb74a20c5d760fa1f628165 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_transmission.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_bar_withdrawal.png b/PyPSA/source/test/baseline/test_plot_types_bar_withdrawal.png new file mode 100644 index 0000000000000000000000000000000000000000..7ced50d2571d3f0366f096071b73e6a0a72a0cfa Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_bar_withdrawal.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-capacity_factor.png b/PyPSA/source/test/baseline/test_plot_types_box-capacity_factor.png new file mode 100644 index 0000000000000000000000000000000000000000..83640e3c8c6a6e53fad95a61b83036c2e1249f68 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-capacity_factor.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-capex.png b/PyPSA/source/test/baseline/test_plot_types_box-capex.png new file mode 100644 index 0000000000000000000000000000000000000000..597cdfc4493d2aee2a459f3fecc5d4ee46f7162d Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-curtailment.png b/PyPSA/source/test/baseline/test_plot_types_box-curtailment.png new file mode 100644 index 0000000000000000000000000000000000000000..b438b6813c996192a651130937cd47c49c23f96f Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-curtailment.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-energy_balance.png b/PyPSA/source/test/baseline/test_plot_types_box-energy_balance.png new file mode 100644 index 0000000000000000000000000000000000000000..9848eb29e0f4ef249f1e09e40815fc6dffc66090 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-energy_balance.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-expanded_capacity.png b/PyPSA/source/test/baseline/test_plot_types_box-expanded_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..ddcd97dc237080fe4bcb418540f62a6fba9bda4f Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-expanded_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-expanded_capex.png b/PyPSA/source/test/baseline/test_plot_types_box-expanded_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..c88569d38c3838f7c6a23ae193a38315d7516176 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-expanded_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-installed_capacity.png b/PyPSA/source/test/baseline/test_plot_types_box-installed_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..6a153dd999e2e51c603084bbf7bd8bae33ad49ea Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-installed_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-installed_capex.png b/PyPSA/source/test/baseline/test_plot_types_box-installed_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..b13187fcb3e3e548147c140430b3ae99c7cba0b4 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-installed_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-market_value.png b/PyPSA/source/test/baseline/test_plot_types_box-market_value.png new file mode 100644 index 0000000000000000000000000000000000000000..00fa2d1964cdd10309aca04ad5bc31890cf6a90f Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-market_value.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-opex.png b/PyPSA/source/test/baseline/test_plot_types_box-opex.png new file mode 100644 index 0000000000000000000000000000000000000000..775cf4f89efafac1d5b81ec290ebc2bac9649207 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-opex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-optimal_capacity.png b/PyPSA/source/test/baseline/test_plot_types_box-optimal_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..2f0407ac2be9a3c8a96e579800872f37055b4dd0 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-optimal_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-prices.png b/PyPSA/source/test/baseline/test_plot_types_box-prices.png new file mode 100644 index 0000000000000000000000000000000000000000..4e0bd12544b43caf50a06efb7d2dbda0d5f9ed4c Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-prices.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-revenue.png b/PyPSA/source/test/baseline/test_plot_types_box-revenue.png new file mode 100644 index 0000000000000000000000000000000000000000..5a3d175412cec16b9339486c57c5ea2a0eed2f0e Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-revenue.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-supply.png b/PyPSA/source/test/baseline/test_plot_types_box-supply.png new file mode 100644 index 0000000000000000000000000000000000000000..9600ce5fb0228aeb917325feec1ea0c49f0df016 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-supply.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-system_cost.png b/PyPSA/source/test/baseline/test_plot_types_box-system_cost.png new file mode 100644 index 0000000000000000000000000000000000000000..3f9f9c93ddea2cceb0ee2112b01a163394ae27ec Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-system_cost.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-transmission.png b/PyPSA/source/test/baseline/test_plot_types_box-transmission.png new file mode 100644 index 0000000000000000000000000000000000000000..b857f02848130ff9ab7abaafeafccfe2d526e39b Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-transmission.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_box-withdrawal.png b/PyPSA/source/test/baseline/test_plot_types_box-withdrawal.png new file mode 100644 index 0000000000000000000000000000000000000000..bef547410078cc3545c1f1fc0296c0b01c028396 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_box-withdrawal.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-capacity_factor.png b/PyPSA/source/test/baseline/test_plot_types_histogram-capacity_factor.png new file mode 100644 index 0000000000000000000000000000000000000000..07ff1be65a7c5339f6d1448cf0d115e57641698d Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-capacity_factor.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-capex.png b/PyPSA/source/test/baseline/test_plot_types_histogram-capex.png new file mode 100644 index 0000000000000000000000000000000000000000..598ab950b514967b5009ce45d44273549a52a7af Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-curtailment.png b/PyPSA/source/test/baseline/test_plot_types_histogram-curtailment.png new file mode 100644 index 0000000000000000000000000000000000000000..24cf750f6b31a5e682a54f2ed90c4790fb150bf3 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-curtailment.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-energy_balance.png b/PyPSA/source/test/baseline/test_plot_types_histogram-energy_balance.png new file mode 100644 index 0000000000000000000000000000000000000000..38e61e49db673e1cb6fa65cf61311bf2e208fd26 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-energy_balance.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-expanded_capacity.png b/PyPSA/source/test/baseline/test_plot_types_histogram-expanded_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..4fe4a95d05a04fee54c2cfe88cbedbd6b9ae2dbc Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-expanded_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-expanded_capex.png b/PyPSA/source/test/baseline/test_plot_types_histogram-expanded_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..28044791dd671dc9dbf8ec2c92e5ed3a6d41995f Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-expanded_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-installed_capacity.png b/PyPSA/source/test/baseline/test_plot_types_histogram-installed_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..5bedb134b8cea66e6ea6f4cc624e108d27befe6f Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-installed_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-installed_capex.png b/PyPSA/source/test/baseline/test_plot_types_histogram-installed_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..a97e0ad8a618ad3f517a84c7210e22f8c16486ac Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-installed_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-market_value.png b/PyPSA/source/test/baseline/test_plot_types_histogram-market_value.png new file mode 100644 index 0000000000000000000000000000000000000000..fd997b032d68fbf2aad0f1a5f5dec7162f8fc3e3 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-market_value.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-opex.png b/PyPSA/source/test/baseline/test_plot_types_histogram-opex.png new file mode 100644 index 0000000000000000000000000000000000000000..8f424187e38dcd43fb2658a423fb72994131baff Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-opex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-optimal_capacity.png b/PyPSA/source/test/baseline/test_plot_types_histogram-optimal_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..6ff73f27ca4ab4df8e4892ffcca62c60bdbc25eb Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-optimal_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-prices.png b/PyPSA/source/test/baseline/test_plot_types_histogram-prices.png new file mode 100644 index 0000000000000000000000000000000000000000..e9c6c8e65196cbf603fa7bd3ffbb9c930bdaaa7a Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-prices.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-revenue.png b/PyPSA/source/test/baseline/test_plot_types_histogram-revenue.png new file mode 100644 index 0000000000000000000000000000000000000000..9252a09567881c5916d687b08732a1a1f9fd4905 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-revenue.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-supply.png b/PyPSA/source/test/baseline/test_plot_types_histogram-supply.png new file mode 100644 index 0000000000000000000000000000000000000000..7648c6fa28099ee8e2c49ae6c72771d0f669c549 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-supply.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-system_cost.png b/PyPSA/source/test/baseline/test_plot_types_histogram-system_cost.png new file mode 100644 index 0000000000000000000000000000000000000000..ccccd1e7d0c8b9415706e1282a2383ed14c1ac90 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-system_cost.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-transmission.png b/PyPSA/source/test/baseline/test_plot_types_histogram-transmission.png new file mode 100644 index 0000000000000000000000000000000000000000..a720ea5cbc7cd5c7d45bbd12cc53c09de97e7031 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-transmission.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_histogram-withdrawal.png b/PyPSA/source/test/baseline/test_plot_types_histogram-withdrawal.png new file mode 100644 index 0000000000000000000000000000000000000000..48450725097e48e229908f8b082c6b9376ffb950 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_histogram-withdrawal.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-capacity_factor.png b/PyPSA/source/test/baseline/test_plot_types_line-capacity_factor.png new file mode 100644 index 0000000000000000000000000000000000000000..0b1544f6e6ed1fee41ed16d6284e06afb6222472 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-capacity_factor.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-capex.png b/PyPSA/source/test/baseline/test_plot_types_line-capex.png new file mode 100644 index 0000000000000000000000000000000000000000..45e785119a49639c89fa51c596e585923a761d90 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-curtailment.png b/PyPSA/source/test/baseline/test_plot_types_line-curtailment.png new file mode 100644 index 0000000000000000000000000000000000000000..a2fd361c803010079784e647de99fddcbdb8d9ac Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-curtailment.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-energy_balance.png b/PyPSA/source/test/baseline/test_plot_types_line-energy_balance.png new file mode 100644 index 0000000000000000000000000000000000000000..07c484c592c7220e6e6972dce84bbba61c4faaca Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-energy_balance.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-expanded_capacity.png b/PyPSA/source/test/baseline/test_plot_types_line-expanded_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..069cbe2129fdf13e9b559502d483d49cbdf3a22b Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-expanded_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-expanded_capex.png b/PyPSA/source/test/baseline/test_plot_types_line-expanded_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..b9e5a57d25fbd44e14a482c3707a61f4ace71b92 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-expanded_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-installed_capacity.png b/PyPSA/source/test/baseline/test_plot_types_line-installed_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..a5675ca73ba6a44aa7e93992cfded5fbd4d8d62c Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-installed_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-installed_capex.png b/PyPSA/source/test/baseline/test_plot_types_line-installed_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..94c6e11e1aa054afc8913135f4236caa4027d0ca Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-installed_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-market_value.png b/PyPSA/source/test/baseline/test_plot_types_line-market_value.png new file mode 100644 index 0000000000000000000000000000000000000000..841eaa76bafaf226de89f20b9334e6303c6b59f2 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-market_value.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-opex.png b/PyPSA/source/test/baseline/test_plot_types_line-opex.png new file mode 100644 index 0000000000000000000000000000000000000000..b92db988258e40cfa238c1e44f74b7480b4f8387 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-opex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-optimal_capacity.png b/PyPSA/source/test/baseline/test_plot_types_line-optimal_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..57bad69b4fd0945898c50b22a8f976599cabeade Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-optimal_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-prices.png b/PyPSA/source/test/baseline/test_plot_types_line-prices.png new file mode 100644 index 0000000000000000000000000000000000000000..483510d9fefdd073cb650229e0fd591a16202599 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-prices.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-revenue.png b/PyPSA/source/test/baseline/test_plot_types_line-revenue.png new file mode 100644 index 0000000000000000000000000000000000000000..319e0dde5a79ebf750034dab8d83a89b96ea022d Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-revenue.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-supply.png b/PyPSA/source/test/baseline/test_plot_types_line-supply.png new file mode 100644 index 0000000000000000000000000000000000000000..dc7c2de5b0e26962921e26a6017e18407e1b3112 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-supply.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-system_cost.png b/PyPSA/source/test/baseline/test_plot_types_line-system_cost.png new file mode 100644 index 0000000000000000000000000000000000000000..a38853f0eed42dce55731ae5810cde5c108afccf Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-system_cost.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-transmission.png b/PyPSA/source/test/baseline/test_plot_types_line-transmission.png new file mode 100644 index 0000000000000000000000000000000000000000..ca869c94fe6a91ef015e17e3e6b3c6d93b596f75 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-transmission.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line-withdrawal.png b/PyPSA/source/test/baseline/test_plot_types_line-withdrawal.png new file mode 100644 index 0000000000000000000000000000000000000000..1c397dbdedfb24a6b2ce828c7cb4e7d6385f0ce3 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line-withdrawal.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_capacity_factor.png b/PyPSA/source/test/baseline/test_plot_types_line_capacity_factor.png new file mode 100644 index 0000000000000000000000000000000000000000..62f1a290a6eeac6a50c0ef1166fbe19c88324cce Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_capacity_factor.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_capex.png b/PyPSA/source/test/baseline/test_plot_types_line_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..95c7b634770610a19f61e4bec7e008edcedb38fc Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_curtailment.png b/PyPSA/source/test/baseline/test_plot_types_line_curtailment.png new file mode 100644 index 0000000000000000000000000000000000000000..8546e3ce1a153f5d10ee050b15448639c9500e71 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_curtailment.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_energy_balance.png b/PyPSA/source/test/baseline/test_plot_types_line_energy_balance.png new file mode 100644 index 0000000000000000000000000000000000000000..fb22f9ec06612c7db0e1164e07340fbdf13e0797 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_energy_balance.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_expanded_capacity.png b/PyPSA/source/test/baseline/test_plot_types_line_expanded_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..945b7d16b488f7c50eba480da6dbd2f89f53631e Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_expanded_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_expanded_capex.png b/PyPSA/source/test/baseline/test_plot_types_line_expanded_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..c17b251fe8dce518826940908da9090143e5f905 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_expanded_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_installed_capacity.png b/PyPSA/source/test/baseline/test_plot_types_line_installed_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..8b4dcdcd812f99f1d8933faceb09ebed42dcf514 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_installed_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_installed_capex.png b/PyPSA/source/test/baseline/test_plot_types_line_installed_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..22ced8a15d0fc90e9baa44565687195e6a2ecf40 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_installed_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_market_value.png b/PyPSA/source/test/baseline/test_plot_types_line_market_value.png new file mode 100644 index 0000000000000000000000000000000000000000..aba48c223f5cc43c26006b3bfe2aa0bce4b304c1 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_market_value.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_opex.png b/PyPSA/source/test/baseline/test_plot_types_line_opex.png new file mode 100644 index 0000000000000000000000000000000000000000..e86632da6e1d14021cc6f8c1a23baae66a81155a Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_opex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_optimal_capacity.png b/PyPSA/source/test/baseline/test_plot_types_line_optimal_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..47d13ca029c75fcaea20825e15172763c7b529ea Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_optimal_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_revenue.png b/PyPSA/source/test/baseline/test_plot_types_line_revenue.png new file mode 100644 index 0000000000000000000000000000000000000000..64ec9ba05a998b50e895a79ab222638b4d8ab5d8 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_revenue.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_supply.png b/PyPSA/source/test/baseline/test_plot_types_line_supply.png new file mode 100644 index 0000000000000000000000000000000000000000..36ea9f6591bc4845202a2a7c091bd31b522397d1 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_supply.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_system_cost.png b/PyPSA/source/test/baseline/test_plot_types_line_system_cost.png new file mode 100644 index 0000000000000000000000000000000000000000..a38853f0eed42dce55731ae5810cde5c108afccf Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_system_cost.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_transmission.png b/PyPSA/source/test/baseline/test_plot_types_line_transmission.png new file mode 100644 index 0000000000000000000000000000000000000000..f2c1ed81e8bc0843287c7ccb9ad45a6d3b0f66d6 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_transmission.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_line_withdrawal.png b/PyPSA/source/test/baseline/test_plot_types_line_withdrawal.png new file mode 100644 index 0000000000000000000000000000000000000000..d84b158e9e282e94483cc6de967672f6b3381a73 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_line_withdrawal.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-capacity_factor.png b/PyPSA/source/test/baseline/test_plot_types_map-capacity_factor.png new file mode 100644 index 0000000000000000000000000000000000000000..7833532bf2f52b8cafcf03e659dc97f9c91ac2dc Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-capacity_factor.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-capex.png b/PyPSA/source/test/baseline/test_plot_types_map-capex.png new file mode 100644 index 0000000000000000000000000000000000000000..ce30d475e654a0822150f7abc82f42cf0b1f167e Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-curtailment.png b/PyPSA/source/test/baseline/test_plot_types_map-curtailment.png new file mode 100644 index 0000000000000000000000000000000000000000..c2a42ff41927107e789e65ed03430d2a68681bc3 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-curtailment.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-energy_balance.png b/PyPSA/source/test/baseline/test_plot_types_map-energy_balance.png new file mode 100644 index 0000000000000000000000000000000000000000..8a7dd86cbc1f64e87143eedbc3eb53e0ce4574aa Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-energy_balance.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-expanded_capacity.png b/PyPSA/source/test/baseline/test_plot_types_map-expanded_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..364280f7d4ddb23da6f74825e7072c44837be3ec Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-expanded_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-expanded_capex.png b/PyPSA/source/test/baseline/test_plot_types_map-expanded_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..ab5f5c476d8031c15868e507bd861b471d145f1f Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-expanded_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-installed_capacity.png b/PyPSA/source/test/baseline/test_plot_types_map-installed_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..1315d812b86f5a9ed54861aaf51428503f4c66c9 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-installed_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-installed_capex.png b/PyPSA/source/test/baseline/test_plot_types_map-installed_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..d0b6c2ec0407724acc31a504fd938dcfba4fb6b9 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-installed_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-market_value.png b/PyPSA/source/test/baseline/test_plot_types_map-market_value.png new file mode 100644 index 0000000000000000000000000000000000000000..72568b2ca467a91cb97f3191afbfba655516a32b Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-market_value.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-opex.png b/PyPSA/source/test/baseline/test_plot_types_map-opex.png new file mode 100644 index 0000000000000000000000000000000000000000..9fb62475cf1d353c82fad010b3453f6219cca88e Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-opex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-optimal_capacity.png b/PyPSA/source/test/baseline/test_plot_types_map-optimal_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..328f1b63cd3115b3796d77a872abe86eeac3bbd7 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-optimal_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-revenue.png b/PyPSA/source/test/baseline/test_plot_types_map-revenue.png new file mode 100644 index 0000000000000000000000000000000000000000..8ca858b692dd3272fb675568d4faf4b200b89540 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-revenue.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-supply.png b/PyPSA/source/test/baseline/test_plot_types_map-supply.png new file mode 100644 index 0000000000000000000000000000000000000000..589c28f228983388cca66e6effcccf0b8bb2afd0 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-supply.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-system_cost.png b/PyPSA/source/test/baseline/test_plot_types_map-system_cost.png new file mode 100644 index 0000000000000000000000000000000000000000..3640802dc8c487fe0dccc8c6a5f0152ec28411c4 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-system_cost.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-transmission.png b/PyPSA/source/test/baseline/test_plot_types_map-transmission.png new file mode 100644 index 0000000000000000000000000000000000000000..7769e1747bf3adf8b32507878c38072ef5bd33e8 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-transmission.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map-withdrawal.png b/PyPSA/source/test/baseline/test_plot_types_map-withdrawal.png new file mode 100644 index 0000000000000000000000000000000000000000..5211a03f29bb52bf6eb3ba01b8f3f2ca05542db2 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map-withdrawal.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map_capacity_factor.png b/PyPSA/source/test/baseline/test_plot_types_map_capacity_factor.png new file mode 100644 index 0000000000000000000000000000000000000000..7833532bf2f52b8cafcf03e659dc97f9c91ac2dc Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map_capacity_factor.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map_capex.png b/PyPSA/source/test/baseline/test_plot_types_map_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..ce30d475e654a0822150f7abc82f42cf0b1f167e Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map_curtailment.png b/PyPSA/source/test/baseline/test_plot_types_map_curtailment.png new file mode 100644 index 0000000000000000000000000000000000000000..c2a42ff41927107e789e65ed03430d2a68681bc3 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map_curtailment.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map_energy_balance.png b/PyPSA/source/test/baseline/test_plot_types_map_energy_balance.png new file mode 100644 index 0000000000000000000000000000000000000000..8a7dd86cbc1f64e87143eedbc3eb53e0ce4574aa Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map_energy_balance.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map_expanded_capacity.png b/PyPSA/source/test/baseline/test_plot_types_map_expanded_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..364280f7d4ddb23da6f74825e7072c44837be3ec Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map_expanded_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map_expanded_capex.png b/PyPSA/source/test/baseline/test_plot_types_map_expanded_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..ab5f5c476d8031c15868e507bd861b471d145f1f Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map_expanded_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map_installed_capacity.png b/PyPSA/source/test/baseline/test_plot_types_map_installed_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..1315d812b86f5a9ed54861aaf51428503f4c66c9 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map_installed_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map_installed_capex.png b/PyPSA/source/test/baseline/test_plot_types_map_installed_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..d0b6c2ec0407724acc31a504fd938dcfba4fb6b9 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map_installed_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map_market_value.png b/PyPSA/source/test/baseline/test_plot_types_map_market_value.png new file mode 100644 index 0000000000000000000000000000000000000000..72568b2ca467a91cb97f3191afbfba655516a32b Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map_market_value.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map_opex.png b/PyPSA/source/test/baseline/test_plot_types_map_opex.png new file mode 100644 index 0000000000000000000000000000000000000000..9fb62475cf1d353c82fad010b3453f6219cca88e Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map_opex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map_optimal_capacity.png b/PyPSA/source/test/baseline/test_plot_types_map_optimal_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..328f1b63cd3115b3796d77a872abe86eeac3bbd7 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map_optimal_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map_revenue.png b/PyPSA/source/test/baseline/test_plot_types_map_revenue.png new file mode 100644 index 0000000000000000000000000000000000000000..8ca858b692dd3272fb675568d4faf4b200b89540 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map_revenue.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map_supply.png b/PyPSA/source/test/baseline/test_plot_types_map_supply.png new file mode 100644 index 0000000000000000000000000000000000000000..589c28f228983388cca66e6effcccf0b8bb2afd0 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map_supply.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map_transmission.png b/PyPSA/source/test/baseline/test_plot_types_map_transmission.png new file mode 100644 index 0000000000000000000000000000000000000000..7769e1747bf3adf8b32507878c38072ef5bd33e8 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map_transmission.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_map_withdrawal.png b/PyPSA/source/test/baseline/test_plot_types_map_withdrawal.png new file mode 100644 index 0000000000000000000000000000000000000000..5211a03f29bb52bf6eb3ba01b8f3f2ca05542db2 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_map_withdrawal.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-capacity_factor.png b/PyPSA/source/test/baseline/test_plot_types_scatter-capacity_factor.png new file mode 100644 index 0000000000000000000000000000000000000000..3912e1be03057e5b787e95abb001f00a770d8c66 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-capacity_factor.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-capex.png b/PyPSA/source/test/baseline/test_plot_types_scatter-capex.png new file mode 100644 index 0000000000000000000000000000000000000000..75627d816871898e4f678b80e353a014997c802b Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-curtailment.png b/PyPSA/source/test/baseline/test_plot_types_scatter-curtailment.png new file mode 100644 index 0000000000000000000000000000000000000000..637da2cee6842af8648e4b6a925147056a5087ad Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-curtailment.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-energy_balance.png b/PyPSA/source/test/baseline/test_plot_types_scatter-energy_balance.png new file mode 100644 index 0000000000000000000000000000000000000000..cd31ed1760a477d1061c8d1949ffb171dfd1133d Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-energy_balance.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-expanded_capacity.png b/PyPSA/source/test/baseline/test_plot_types_scatter-expanded_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..b2be271aa19ab979a49b6494e2dabc9e3abd6e18 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-expanded_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-expanded_capex.png b/PyPSA/source/test/baseline/test_plot_types_scatter-expanded_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..607ad4e65509d341bfc2012020109c857821ad13 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-expanded_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-installed_capacity.png b/PyPSA/source/test/baseline/test_plot_types_scatter-installed_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..4744cf539c5f54d352406faf66eddfd7f685f084 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-installed_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-installed_capex.png b/PyPSA/source/test/baseline/test_plot_types_scatter-installed_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..055845400732f0da5cc1b61fb2420abe13abc1e7 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-installed_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-market_value.png b/PyPSA/source/test/baseline/test_plot_types_scatter-market_value.png new file mode 100644 index 0000000000000000000000000000000000000000..e4d9b19fcc890fbf21731a7c466e27e68fb54db9 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-market_value.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-opex.png b/PyPSA/source/test/baseline/test_plot_types_scatter-opex.png new file mode 100644 index 0000000000000000000000000000000000000000..e9d9062fc7527f4afc0dbe541a41e68b14aeb0a9 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-opex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-optimal_capacity.png b/PyPSA/source/test/baseline/test_plot_types_scatter-optimal_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..2372d341f29de1799812125bc0bc5e1613e1c064 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-optimal_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-prices.png b/PyPSA/source/test/baseline/test_plot_types_scatter-prices.png new file mode 100644 index 0000000000000000000000000000000000000000..ff29e773638b3100381e6c5e42ef6dd467988859 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-prices.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-revenue.png b/PyPSA/source/test/baseline/test_plot_types_scatter-revenue.png new file mode 100644 index 0000000000000000000000000000000000000000..4ab854130a7964239d8b52ab2efeda6c15e2ac7d Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-revenue.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-supply.png b/PyPSA/source/test/baseline/test_plot_types_scatter-supply.png new file mode 100644 index 0000000000000000000000000000000000000000..d80b584cbd85990b448846d10813fd832ba45a54 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-supply.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-system_cost.png b/PyPSA/source/test/baseline/test_plot_types_scatter-system_cost.png new file mode 100644 index 0000000000000000000000000000000000000000..35489698b19f93fe6dda998f6ce73b38f1be31a4 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-system_cost.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-transmission.png b/PyPSA/source/test/baseline/test_plot_types_scatter-transmission.png new file mode 100644 index 0000000000000000000000000000000000000000..9dcbb538b844f0849be85904016b8155e5408757 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-transmission.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_scatter-withdrawal.png b/PyPSA/source/test/baseline/test_plot_types_scatter-withdrawal.png new file mode 100644 index 0000000000000000000000000000000000000000..f8e029c12b18a72ad916618826febdf1c6602fad Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_scatter-withdrawal.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-capacity_factor.png b/PyPSA/source/test/baseline/test_plot_types_violin-capacity_factor.png new file mode 100644 index 0000000000000000000000000000000000000000..fddfeac68e72f9b1ebd19e5b1652a817b1fa6818 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-capacity_factor.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-capex.png b/PyPSA/source/test/baseline/test_plot_types_violin-capex.png new file mode 100644 index 0000000000000000000000000000000000000000..10c35ab3e3541dc7753d6e544d73913abc1a64cd Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-curtailment.png b/PyPSA/source/test/baseline/test_plot_types_violin-curtailment.png new file mode 100644 index 0000000000000000000000000000000000000000..d289d8ed95211f162c3e8fe9278cf8345b507fa9 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-curtailment.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-energy_balance.png b/PyPSA/source/test/baseline/test_plot_types_violin-energy_balance.png new file mode 100644 index 0000000000000000000000000000000000000000..8dea1ec8d791f287f180a8952598a035373849b6 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-energy_balance.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-expanded_capacity.png b/PyPSA/source/test/baseline/test_plot_types_violin-expanded_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..ba074546c20733c4f71936f54c74b0834a3fb34f Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-expanded_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-expanded_capex.png b/PyPSA/source/test/baseline/test_plot_types_violin-expanded_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..917117e1b23146daaf1d87762e20ca2f450fb0bb Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-expanded_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-installed_capacity.png b/PyPSA/source/test/baseline/test_plot_types_violin-installed_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..64710a65c6d71c12267181e24881370ab9e2f3fc Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-installed_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-installed_capex.png b/PyPSA/source/test/baseline/test_plot_types_violin-installed_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..a486b42d8cacef4a71217bd1d020a73b1372d984 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-installed_capex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-market_value.png b/PyPSA/source/test/baseline/test_plot_types_violin-market_value.png new file mode 100644 index 0000000000000000000000000000000000000000..3836e68f904a05502ae66e45d52e67f2dff121af Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-market_value.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-opex.png b/PyPSA/source/test/baseline/test_plot_types_violin-opex.png new file mode 100644 index 0000000000000000000000000000000000000000..a92654316e6158e7747c45200261bae2a33f2b96 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-opex.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-optimal_capacity.png b/PyPSA/source/test/baseline/test_plot_types_violin-optimal_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..7b7e970a8616fd404eef575aadc3f46541b0bb57 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-optimal_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-prices.png b/PyPSA/source/test/baseline/test_plot_types_violin-prices.png new file mode 100644 index 0000000000000000000000000000000000000000..660cc7539aacc8a068fa8b499b050ada7e8c3b18 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-prices.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-revenue.png b/PyPSA/source/test/baseline/test_plot_types_violin-revenue.png new file mode 100644 index 0000000000000000000000000000000000000000..5f11d23cd39730e8d174a49a8dfd8e9ecf2c5c19 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-revenue.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-supply.png b/PyPSA/source/test/baseline/test_plot_types_violin-supply.png new file mode 100644 index 0000000000000000000000000000000000000000..d7e6a83394de30f5c763f6f69ac402458b3d32f6 Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-supply.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-system_cost.png b/PyPSA/source/test/baseline/test_plot_types_violin-system_cost.png new file mode 100644 index 0000000000000000000000000000000000000000..bd9c31bcb812642bf42809445bd8356267a8470f Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-system_cost.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-transmission.png b/PyPSA/source/test/baseline/test_plot_types_violin-transmission.png new file mode 100644 index 0000000000000000000000000000000000000000..ba4f4fd4491485071ce60e6191ef59118c5f329f Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-transmission.png differ diff --git a/PyPSA/source/test/baseline/test_plot_types_violin-withdrawal.png b/PyPSA/source/test/baseline/test_plot_types_violin-withdrawal.png new file mode 100644 index 0000000000000000000000000000000000000000..97a018183c97a2063df83c3210a391481d34764f Binary files /dev/null and b/PyPSA/source/test/baseline/test_plot_types_violin-withdrawal.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_capacity_factor.png b/PyPSA/source/test/baseline/test_simple_plot_capacity_factor.png new file mode 100644 index 0000000000000000000000000000000000000000..2fcb556295814d14961b627d9e32e38dd5094ed1 Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_capacity_factor.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_capex.png b/PyPSA/source/test/baseline/test_simple_plot_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..858b10fd8af7322eb04eecab914d7fca88567ef6 Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_capex.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_curtailment.png b/PyPSA/source/test/baseline/test_simple_plot_curtailment.png new file mode 100644 index 0000000000000000000000000000000000000000..3d5490a46dbf7981789032a549d053b39b15ebe6 Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_curtailment.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_energy_balance.png b/PyPSA/source/test/baseline/test_simple_plot_energy_balance.png new file mode 100644 index 0000000000000000000000000000000000000000..b2618e6f3e46d9f60b2af0afde2c4ea4421986a1 Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_energy_balance.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_expanded_capacity.png b/PyPSA/source/test/baseline/test_simple_plot_expanded_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..d4a43ffc446e4cb4906da8f1e3215c5d4e3c7fb6 Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_expanded_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_expanded_capex.png b/PyPSA/source/test/baseline/test_simple_plot_expanded_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..c870f2016c837f98c7a214e6563e9a3f0e02f913 Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_expanded_capex.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_installed_capacity.png b/PyPSA/source/test/baseline/test_simple_plot_installed_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..f1bef03ba122d1eb325e09e75736fbfca4f08a22 Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_installed_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_installed_capex.png b/PyPSA/source/test/baseline/test_simple_plot_installed_capex.png new file mode 100644 index 0000000000000000000000000000000000000000..88f972b0a7146b9636735e696e1eb76bfea52ce8 Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_installed_capex.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_market_value.png b/PyPSA/source/test/baseline/test_simple_plot_market_value.png new file mode 100644 index 0000000000000000000000000000000000000000..345a4b77d75ea73759d99b9b5eca56c14006f669 Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_market_value.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_opex.png b/PyPSA/source/test/baseline/test_simple_plot_opex.png new file mode 100644 index 0000000000000000000000000000000000000000..e7d5b104705395582e54cd78f108ad0a6cb008d9 Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_opex.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_optimal_capacity.png b/PyPSA/source/test/baseline/test_simple_plot_optimal_capacity.png new file mode 100644 index 0000000000000000000000000000000000000000..5cbb282450e8971965f8bdd9b794b7cee654b352 Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_optimal_capacity.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_prices.png b/PyPSA/source/test/baseline/test_simple_plot_prices.png new file mode 100644 index 0000000000000000000000000000000000000000..e468c161bf26aad00cfa37ea70f6ac72b2ce014d Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_prices.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_revenue.png b/PyPSA/source/test/baseline/test_simple_plot_revenue.png new file mode 100644 index 0000000000000000000000000000000000000000..6a9dd3e5ab442dd957ea8689c2fe0e786a7cb9e9 Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_revenue.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_supply.png b/PyPSA/source/test/baseline/test_simple_plot_supply.png new file mode 100644 index 0000000000000000000000000000000000000000..e661a2670a3b2605b106c504419aaf930eca5d15 Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_supply.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_system_cost.png b/PyPSA/source/test/baseline/test_simple_plot_system_cost.png new file mode 100644 index 0000000000000000000000000000000000000000..142d5cbbcd7f28381d8eb71260da1ae4b1ffe98e Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_system_cost.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_transmission.png b/PyPSA/source/test/baseline/test_simple_plot_transmission.png new file mode 100644 index 0000000000000000000000000000000000000000..609d5bdfb1b1e6d6feb74a20c5d760fa1f628165 Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_transmission.png differ diff --git a/PyPSA/source/test/baseline/test_simple_plot_withdrawal.png b/PyPSA/source/test/baseline/test_simple_plot_withdrawal.png new file mode 100644 index 0000000000000000000000000000000000000000..7ced50d2571d3f0366f096071b73e6a0a72a0cfa Binary files /dev/null and b/PyPSA/source/test/baseline/test_simple_plot_withdrawal.png differ diff --git a/PyPSA/source/test/conftest.py b/PyPSA/source/test/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..93fa3c243cea93b2cd7392d5d69c7904bdd6a4b5 --- /dev/null +++ b/PyPSA/source/test/conftest.py @@ -0,0 +1,364 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +from pathlib import Path + +import geopandas as gpd +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import pytest +from shapely.geometry import Polygon + +import pypsa +from pypsa.constants import DEFAULT_EPSG + +pypsa.options.debug.runtime_verification = True + + +@pytest.fixture(autouse=True) +def close_matplotlib_figures(): + """Close all matplotlib figures before and after each test.""" + plt.close("all") + yield + plt.close("all") + + +def pytest_addoption(parser): + """Add custom pytest command line options.""" + parser.addoption( + "--new-components-api", + action="store_true", + default=False, + help="Activate the new components API (options.api.new_components_api)", + ) + parser.addoption( + "--test-docs", + action="store_true", + default=False, + help="Run documentation tests (doctest tests)", + ) + parser.addoption( + "--fix-notebooks", + action="store_true", + default=False, + help="Auto-fix notebook issues found during validation (self-healing mode)", + ) + + +def pytest_configure(config): + """Configure pytest session with custom options.""" + if config.getoption("--new-components-api"): + pypsa.options.api.new_components_api = True + + +COMPONENT_NAMES = [ + "sub_networks", + "buses", + "carriers", + "global_constraints", + "lines", + "line_types", + "transformers", + "transformer_types", + "links", + "loads", + "generators", + "storage_units", + "stores", + "shunt_impedances", + "shapes", +] + + +@pytest.fixture(params=COMPONENT_NAMES) +def component_name(request): + return request.param + + +# Example Networks + + +@pytest.fixture +def ac_dc_network(): + return pypsa.examples.ac_dc_meshed() + + +@pytest.fixture +def storage_hvdc_network(): + return pypsa.examples.storage_hvdc() + + +@pytest.fixture +def scigrid_de_network(): + return pypsa.examples.scigrid_de() + + +@pytest.fixture +def model_energy_network(): + return pypsa.examples.model_energy() + + +@pytest.fixture +def stochastic_network(): + return pypsa.examples.stochastic_network() + + +# AC-DC-Meshed types + + +@pytest.fixture +def ac_dc_solved(): + n = pypsa.examples.ac_dc_meshed() + n.optimize() + del n.model.solver_model + return n + + +@pytest.fixture +def ac_dc_periods(ac_dc_network): + n = ac_dc_network + n.snapshots = pd.MultiIndex.from_product([[2013], n.snapshots]) + n.investment_periods = [2013] + gens_i = n.c.generators.static.index + rng = np.random.default_rng() # Create a random number generator + n.c.generators.dynamic.p[gens_i] = rng.random(size=(len(n.snapshots), len(gens_i))) + return n + + +@pytest.fixture +def ac_dc_stochastic(): + n = pypsa.examples.ac_dc_meshed() + n.set_scenarios({"low": 0.3, "high": 0.7}) + return n + + +AC_DC_MESHED_TYPES = [ + "ac_dc_network", + "ac_dc_solved", + "ac_dc_periods", + "ac_dc_stochastic", +] + + +@pytest.fixture(params=AC_DC_MESHED_TYPES) +def ac_dc_types(request): + return request.getfixturevalue(request.param) + + +# AC-DC-Meshed results + + +@pytest.fixture +def ac_dc_network_r(): + csv_folder = Path(__file__).parent / "data" / "ac-dc-meshed" / "results-lopf" + return pypsa.Network(csv_folder) + + +@pytest.fixture +def ac_dc_stochastic_r(ac_dc_network_r): + n = ac_dc_network_r.copy() + n.set_scenarios({"low": 0.3, "high": 0.7}) + return n + + +@pytest.fixture +def ac_dc_shapes(ac_dc_network): + n = ac_dc_network + + # Create bounding boxes around points + def create_bbox(x, y, delta=0.1): + return Polygon( + [ + (x - delta, y - delta), + (x - delta, y + delta), + (x + delta, y + delta), + (x + delta, y - delta), + ] + ) + + bboxes = n.c.buses.static.apply(lambda row: create_bbox(row["x"], row["y"]), axis=1) + + # Convert to GeoSeries + geo_series = gpd.GeoSeries(bboxes, crs=DEFAULT_EPSG) + + n.add( + "Shape", + name=geo_series.index, + geometry=geo_series, + idx=geo_series.index, + component="Bus", + ) + + return n + + +# Other network fixtures + + +@pytest.fixture +def scipy_network(): + n = pypsa.examples.scigrid_de() + n.c.generators.static.control = "PV" + g = n.c.generators.static[n.c.generators.static.bus == "492"] + n.c.generators.static.loc[g.index, "control"] = "PQ" + n.calculate_dependent_values() + n.determine_network_topology() + return n + + +@pytest.fixture +def network_only_component_names(): + n = pypsa.Network() + n.add("Bus", "bus1", x=0, y=0) + n.add("Bus", "bus2", x=1, y=0) + n.add("Bus", "bus3", x=0, y=1) + # Add components with no extra data + n.add("Carrier", "carrier1") + n.add("Carrier", "carrier2") + return n + + +# Other fixture collections +UNSOLVED_NETWORKS = [ + "ac_dc_network", + "scigrid_de_network", + "storage_hvdc_network", + "model_energy_network", + "stochastic_network", + "network_only_component_names", +] + +SOLVED_NETWORKS = [ + "ac_dc_solved", +] + + +@pytest.fixture(params=UNSOLVED_NETWORKS) +def networks(request): + return request.getfixturevalue(request.param) + + +@pytest.fixture(params=UNSOLVED_NETWORKS + SOLVED_NETWORKS) +def networks_including_solved(request): + return request.getfixturevalue(request.param) + + +# Network collections + + +@pytest.fixture +def network_collection(ac_dc_network_r): + return pypsa.NetworkCollection( + [ac_dc_network_r], + index=pd.MultiIndex.from_tuples([("a", 2030)], names=["scenario", "year"]), + ) + + +# Pandapower networks +@pytest.fixture(scope="module") +def pandapower_custom_network(): + try: + import pandapower as pp + except ImportError: + pytest.skip("pandapower not installed") + net = pp.create_empty_network() + bus1 = pp.create_bus(net, vn_kv=20.0, name="Bus 1") + bus2 = pp.create_bus(net, vn_kv=0.4, name="Bus 2") + bus3 = pp.create_bus(net, vn_kv=0.4, name="Bus 3") + # create bus elements + pp.create_ext_grid(net, bus=bus1, vm_pu=1.02, name="Grid Connection") + pp.create_load(net, bus=bus3, p_mw=0.100, q_mvar=0.05, name="Load") + pp.create_shunt(net, bus=bus3, p_mw=0.0, q_mvar=0.0, name="Shunt") + # create branch elements + pp.create_transformer( + net, hv_bus=bus1, lv_bus=bus2, std_type="0.4 MVA 20/0.4 kV", name="Trafo" + ) + pp.create_line( + net, + from_bus=bus2, + to_bus=bus3, + length_km=0.1, + std_type="NAYY 4x50 SE", + name="Line", + ) + return net + + +@pytest.fixture(scope="module") +def pandapower_cigre_network(): + try: + import pandapower.networks as pn + except ImportError: + pytest.skip("pandapower not installed") + return pn.create_cigre_network_mv(with_der="all") + + +# Complex stochastic network + + +@pytest.fixture +def stochastic_benchmark_network(): + """ + Create a network for benchmarking stochastic problems. + This optimization problem is also uploaded to the pypsa examples repository + with stochastic problem solved in two ways: out-of-the-box using PyPSA + functionality and hardcoded using linopy. + """ + # Configuration + GAS_PRICE = 40 # Default scenario + FREQ = "3h" + LOAD_MW = 1 + TS_URL = "https://tubcloud.tu-berlin.de/s/pKttFadrbTKSJKF/download/time-series-lecture-2.csv" + + # Technology specs + TECH = { + "solar": {"profile": "solar", "inv": 1e6, "m_cost": 0.01}, + "wind": {"profile": "onwind", "inv": 2e6, "m_cost": 0.02}, + "gas": {"inv": 7e5, "eff": 0.6}, + "lignite": {"inv": 1.3e6, "eff": 0.4, "m_cost": 130}, + } + FOM, DR, LIFE = 3.0, 0.03, 25 + + for cfg in TECH.values(): + cfg["fixed_cost"] = (pypsa.common.annuity(DR, LIFE) + FOM / 100) * cfg["inv"] + + # Load time series data from URL - same as in the original script + ts = pd.read_csv(TS_URL, index_col=0, parse_dates=True).resample(FREQ).asfreq() + + n = pypsa.Network() + n.set_snapshots(ts.index) + n.snapshot_weightings = pd.Series(int(FREQ[:-1]), index=ts.index) + + n.add("Bus", "DE") + n.add("Load", "DE_load", bus="DE", p_set=LOAD_MW) + + for tech in ["solar", "wind"]: + cfg = TECH[tech] + n.add( + "Generator", + tech, + bus="DE", + p_nom_extendable=True, + p_max_pu=ts[cfg["profile"]], + capital_cost=cfg["fixed_cost"], + marginal_cost=cfg["m_cost"], + ) + + for tech in ["gas", "lignite"]: + cfg = TECH[tech] + mc = (GAS_PRICE / cfg["eff"]) if tech == "gas" else cfg["m_cost"] + n.add( + "Generator", + tech, + bus="DE", + p_nom_extendable=True, + efficiency=cfg["eff"], + capital_cost=cfg["fixed_cost"], + marginal_cost=mc, + ) + # Set up scenarios + n.set_scenarios({"low": 0.4, "medium": 0.3, "high": 0.3}) + + return n diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/buses-marginal_price.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/buses-marginal_price.csv new file mode 100644 index 0000000000000000000000000000000000000000..24977710c3efb09041ea55f034866b926783e3a6 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/buses-marginal_price.csv @@ -0,0 +1,11 @@ +,London,Norwich,Norwich DC,Manchester,Bremen,Bremen DC,Frankfurt,Norway,Norway DC +0,0.11,0.11,0.11,0.11,0.103369905875,0.103369905872,0.103369905875,0.0899999999985,0.0900000000053 +1,1421.94478751,1421.92659133,1421.92659133,1421.93258651,1421.92659133,1421.92659133,1421.92659133,1421.92659133,1421.92659133 +2,0.110000000022,0.110000000033,0.110000000023,0.110000000029,0.110000000022,0.110000000023,0.110000000022,0.110000000023,0.110000000023 +3,0.109999999986,0.109999999986,0.109999999986,0.109999999986,0.109999999984,0.109999999985,0.109999999984,0.109999999981,0.109999999981 +4,0.110000000001,0.110000000001,0.109999999997,0.110000000001,0.109999999998,0.109999999997,0.109999999998,0.110000000003,0.109999999997 +5,1593.57344059,1593.57344059,1593.57344059,1593.57344059,1593.57344059,1593.57344059,1593.37344059,1593.57344059,1593.57344059 +6,0.109999999999,0.11,0.343333333329,0.11,0.350371267659,0.35037126766,0.350371267661,0.347073222335,0.347073222335 +7,1007.08995447,1007.08995447,1007.08995447,1007.08995447,1007.07223612,1007.07223612,1007.07223612,1006.81428398,1007.03650621 +8,0.244915637666,0.2443761968,0.2443761968,0.109999999992,0.467259295843,0.256148184719,0.467259295843,0.217457095515,0.217457095511 +9,1490.69648758,1490.69648758,1490.69648758,1490.69648758,1490.69648758,1490.69648758,1490.69648758,1490.69648758,1490.69648758 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/buses-p.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/buses-p.csv new file mode 100644 index 0000000000000000000000000000000000000000..f7d76817eb5710b238cd53eb9fc5ed8e1e98f08e --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/buses-p.csv @@ -0,0 +1,11 @@ +,London,Norwich,Norwich DC,Manchester,Bremen,Bremen DC,Frankfurt,Norway,Norway DC +0,-35.7962441027,151.364696943,-566.827261209,-115.568452841,-534.340859769,-105.745517772,534.340859769,0.0,672.57277898 +1,-976.82456147,-259.676766524,-2.92937992895,1236.50132799,-823.210905516,119.65657181,823.210905516,1.4210854715199998e-14,-116.727191881 +2,-250.587312046,-451.23998096,32.7636277701,701.827293007,173.898184856,-614.734315174,-173.898184856,-1.13686837722e-13,581.970687404 +3,-130.753144583,-149.189401193,-403.770138117,279.942545776,-743.788494916,131.212189234,743.788494916,0.0,272.557948882 +4,-151.1001686,-217.528588597,-0.631269494317,368.628757197,-883.817537523,80.3807567175,883.817537523,-7.1054273576e-14,-79.7494872231 +5,-931.857051942,-860.725901626,68.7496360424,1792.58295357,-1030.84876824,425.448080886,1030.84876824,-1.13686837722e-13,-494.197716928 +6,-289.848287145,-1749.86867144,1217.99799061,2039.71695858,319.386680339,-960.477270579,-319.386680339,-5.6843418860799994e-14,-257.520720035 +7,-864.343321715,-60.3320439409,36.8185772223,924.675365656,600.728881448,-1008.73742262,-600.728881448,1.02318153949e-12,971.918845399 +8,-689.57726377,-1532.77288665,562.71381819,2222.35015042,501.346378971,-1413.59415514,-501.346378971,1.13686837722e-13,850.880336946 +9,-627.878985943,-737.262881789,736.338048098,1365.14186773,-248.566846939,-649.486244703,248.566846939,-3.97903932026e-13,-86.8518033948 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/buses-v_ang.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/buses-v_ang.csv new file mode 100644 index 0000000000000000000000000000000000000000..e81b3fb58d8a9703c15c6b999044a191b5e02fd6 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/buses-v_ang.csv @@ -0,0 +1,11 @@ +,Bremen,London,Norwich +0,-0.00148016858662,0.000141918652458,0.000243645137703 +1,-0.00228036261916,-0.00277400990662,-0.00198988105439 +2,0.000481712423424,-0.00129394605803,-0.0012672958343 +3,-0.0020603559416,-0.000544563619803,-0.000491520916355 +4,-0.00244824802638,-0.000697622012181,-0.000656797556392 +5,-0.00285553675414,-0.0035743981745,-0.00310448616478 +6,0.000884727646368,-0.00335576509585,-0.00388205222309 +7,0.00166406892368,-0.00219804596932,-0.00142733307741 +8,0.00138877113288,-0.00400135917712,-0.0040600627447 +9,-0.000688550822545,-0.00264657540065,-0.00240132423456 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/buses-v_mag_pu.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/buses-v_mag_pu.csv new file mode 100644 index 0000000000000000000000000000000000000000..0d541d671d2aa2c4309cff40ec3ef80a922963a0 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/buses-v_mag_pu.csv @@ -0,0 +1,11 @@ +,Bremen DC,Norway DC +0,1.001085347,1.00440739981 +1,1.00024854596,0.999467251883 +2,0.998682500719,1.0026145242 +3,1.00119055415,1.00218501018 +4,1.00016390058,0.999632879715 +5,1.00070229182,0.997558474019 +6,0.995267395371,0.996018424025 +7,0.997876906091,1.00440739981 +8,0.995853413246,1.00264275742 +9,0.996999801376,0.997911057113 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/buses.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/buses.csv new file mode 100644 index 0000000000000000000000000000000000000000..d083bb2ab5c213ad42ee2c759bf9a78255c1e43d --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/buses.csv @@ -0,0 +1,10 @@ +name,v_nom,x,y,carrier,country +London,380.0,-0.13,51.5,AC,UK +Norwich,380.0,1.3,52.6,AC,UK +Norwich DC,200.0,1.3,52.5,DC,UK +Manchester,380.0,-2.2,53.47,AC,UK +Bremen,380.0,8.8,53.08,AC,DE +Bremen DC,200.0,8.8,52.98,DC,DE +Frankfurt,380.0,8.7,50.12,AC,DE +Norway,380.0,10.75,60.0,AC,NO +Norway DC,200.0,10.75,60.0,DC,NO diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/carriers.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/carriers.csv new file mode 100644 index 0000000000000000000000000000000000000000..8528563482e274fb08a566181c5a7e72071ad194 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/carriers.csv @@ -0,0 +1,7 @@ +name,co2_emissions,color,marginal_cost,efficiency,capital_cost +gas,0.24,red,0.0,1.0,0.0 +wind,0.0,blue,0.0,1.0,0.0 +battery,0.0,green,0.0,1.0,0.0 +load,0.0,black,,, +AC,0.0,orange,,, +DC,0.0,purple,,, diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/crs.json b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/crs.json new file mode 100644 index 0000000000000000000000000000000000000000..1b11562e30ffd94e434bc1b28f75fe83ef1b3010 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/crs.json @@ -0,0 +1 @@ +{"_crs": "GEOGCRS[\"WGS 84\",ENSEMBLE[\"World Geodetic System 1984 ensemble\",MEMBER[\"World Geodetic System 1984 (Transit)\"],MEMBER[\"World Geodetic System 1984 (G730)\"],MEMBER[\"World Geodetic System 1984 (G873)\"],MEMBER[\"World Geodetic System 1984 (G1150)\"],MEMBER[\"World Geodetic System 1984 (G1674)\"],MEMBER[\"World Geodetic System 1984 (G1762)\"],MEMBER[\"World Geodetic System 1984 (G2139)\"],MEMBER[\"World Geodetic System 1984 (G2296)\"],ELLIPSOID[\"WGS 84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]],ENSEMBLEACCURACY[2.0]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],CS[ellipsoidal,2],AXIS[\"geodetic latitude (Lat)\",north,ORDER[1],ANGLEUNIT[\"degree\",0.0174532925199433]],AXIS[\"geodetic longitude (Lon)\",east,ORDER[2],ANGLEUNIT[\"degree\",0.0174532925199433]],USAGE[SCOPE[\"Horizontal component of 3D system.\"],AREA[\"World.\"],BBOX[-90,-180,90,180]],ID[\"EPSG\",4326]]"} diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/generators-p.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/generators-p.csv new file mode 100644 index 0000000000000000000000000000000000000000..bede36724cf9a37f6c58392262817a18bcd4256f --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/generators-p.csv @@ -0,0 +1,11 @@ +,Frankfurt Gas,Frankfurt Wind,Manchester Wind,Norway Gas,Norway Wind +0,0.0,932.388706733,741.982987361,0.0,1492.60861492 +1,0.0,1255.64701176,1987.10095171,0.0,738.106854981 +2,0.0,205.90574341,858.392169021,0.0,624.521431755 +3,0.0,1612.1502592,807.813367895,0.0,920.106181668 +4,0.0,1432.58829214,452.52651616,0.0,804.324386107 +5,982.034482721,877.479528124,2469.20627292,0.0,14.8647316237 +6,0.0,129.904071569,2770.85405906,0.0,338.08724478 +7,0.0,98.4348849251,1478.02025484,0.0,1263.5612951 +8,0.0,414.520401281,2520.68823268,0.0,853.033829495 +9,483.239904362,180.21458898,2133.43245372,1.03948925711e-12,673.888373109 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/generators-p_max_pu.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/generators-p_max_pu.csv new file mode 100644 index 0000000000000000000000000000000000000000..3126d895dbbc44a955d31565faa848e82a34da12 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/generators-p_max_pu.csv @@ -0,0 +1,11 @@ +,Manchester Wind,Frankfurt Wind,Norway Wind +0,0.930019875,0.5590784039,0.9745832033 +1,0.4857475804,0.7529103711,0.4812903778 +2,0.2336917351,0.1234650887,0.4072258018 +3,0.2576042221,0.9666766524,0.5999649628 +4,0.6269055694,0.8590078044,0.524468219 +5,0.6035984088,0.5261537924,0.0096927054 +6,0.6789075462,0.077893008,0.2204533621 +7,0.3613026112,0.0590234716,0.8239185004 +8,0.6216040549,0.2485544952,0.5562297265 +9,0.5215183715,0.1080601728,0.4394160378 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/generators.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/generators.csv new file mode 100644 index 0000000000000000000000000000000000000000..8f2e33fc61063c3cecd79ed47294f54158081662 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/generators.csv @@ -0,0 +1,7 @@ +name,bus,control,p_nom,p_nom_extendable,p_nom_min,carrier,marginal_cost,capital_cost,efficiency,p_nom_opt +Manchester Wind,Manchester,Slack,80.0,True,100.0,wind,0.11,2793.65160293,1.0,4090.80977835 +Manchester Gas,Manchester,PQ,50000.0,True,0.0,gas,4.5323676307,196.615167969,0.3500264336,0.0 +Norway Wind,Norway,Slack,100.0,True,100.0,wind,0.09,2184.37479609,1.0,1533.59985785 +Norway Gas,Norway,PQ,20000.0,True,0.0,gas,5.8928445406,158.251249717,0.3568363832,1.03421493746e-12 +Frankfurt Wind,Frankfurt,Slack,110.0,True,100.0,wind,0.1,2129.45612248,1.0,1667.72441974 +Frankfurt Gas,Frankfurt,PQ,80000.0,True,0.0,gas,4.0863219899,102.676953008,0.3516658529,982.034482721 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/global_constraints.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/global_constraints.csv new file mode 100644 index 0000000000000000000000000000000000000000..7111ff652cb8ffdd8240ad0693646db4f4084393 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/global_constraints.csv @@ -0,0 +1,2 @@ +name,sense,constant,mu +co2_limit,<=,1000.0,2178.29179922 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/investment_periods.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/investment_periods.csv new file mode 100644 index 0000000000000000000000000000000000000000..34dd42197768b92fdb696fcfe70396544f01bb62 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/investment_periods.csv @@ -0,0 +1 @@ +period,objective,years diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/lines-p0.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/lines-p0.csv new file mode 100644 index 0000000000000000000000000000000000000000..a2f7542eb0569b979581aee9ec6c81205be3ca0b --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/lines-p0.csv @@ -0,0 +1,11 @@ +,0,1,2,3,4,5,6 +0,25.7166669836,-89.8517858569,204.200487571,-362.626773638,309.946005342,61.5129110863,-534.340859769 +1,-502.670281476,733.831046518,46.7621927555,43.8328128266,-72.8943790545,474.154279994,-823.210905516 +2,-234.4722085,467.355084506,-247.878325394,-215.114697624,366.855989781,16.1151035461,173.898184856 +3,-98.6787925286,181.263753247,223.994481702,-179.775656414,92.7822924682,32.0743520541,-743.788494916 +4,-126.414059441,242.214697755,30.8367535372,30.2054840429,-49.5440031803,24.6861091586,-883.817537523 +5,-647.706315753,1144.87663781,132.131320535,200.880956577,-293.316760351,284.150736189,-1030.84876824 +6,-608.088450322,1431.62850826,-890.406641269,327.591349344,70.0706293093,-318.240163178,319.386680339 +7,-398.301528577,526.373837078,-399.44535086,-362.626773638,609.292071761,466.041793137,600.728881448 +8,-725.074679455,1497.27547096,-780.151454417,-217.437636227,633.442700719,-35.4974156846,501.346378971 +9,-479.578244626,885.563623107,-564.466501931,171.871546168,85.0197427727,148.300741318,-248.566846939 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/lines-p1.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/lines-p1.csv new file mode 100644 index 0000000000000000000000000000000000000000..31f7b4d8b4f8c3299753f5465347cf7fcc567988 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/lines-p1.csv @@ -0,0 +1,11 @@ +,0,1,2,3,4,5,6 +0,-25.7166669836,89.8517858569,-204.200487571,362.626773638,-309.946005342,-61.5129110863,534.340859769 +1,502.670281476,-733.831046518,-46.7621927555,-43.8328128266,72.8943790545,-474.154279994,823.210905516 +2,234.4722085,-467.355084506,247.878325394,215.114697624,-366.855989781,-16.1151035461,-173.898184856 +3,98.6787925286,-181.263753247,-223.994481702,179.775656414,-92.7822924682,-32.0743520541,743.788494916 +4,126.414059441,-242.214697755,-30.8367535372,-30.2054840429,49.5440031803,-24.6861091586,883.817537523 +5,647.706315753,-1144.87663781,-132.131320535,-200.880956577,293.316760351,-284.150736189,1030.84876824 +6,608.088450322,-1431.62850826,890.406641269,-327.591349344,-70.0706293093,318.240163178,-319.386680339 +7,398.301528577,-526.373837078,399.44535086,362.626773638,-609.292071761,-466.041793137,-600.728881448 +8,725.074679455,-1497.27547096,780.151454417,217.437636227,-633.442700719,35.4974156846,-501.346378971 +9,479.578244626,-885.563623107,564.466501931,-171.871546168,-85.0197427727,-148.300741318,248.566846939 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/lines.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/lines.csv new file mode 100644 index 0000000000000000000000000000000000000000..c1971a1aa4ebe4f63ce912c5215d7929828272cf --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/lines.csv @@ -0,0 +1,8 @@ +name,bus0,bus1,x,r,s_nom,s_nom_extendable,capital_cost,carrier,s_nom_opt,v_nom +0,London,Manchester,0.7968782824,0.0,40000.0,True,0.1367157553,AC,725.074679455,380.0 +1,Manchester,Norwich,0.3915599178,0.0,40000.0,True,0.1334916779,AC,1497.27547096,380.0 +2,Bremen DC,Norwich DC,0.0,0.2126041927,40000.0,True,0.0086734246,AC,890.406641269,200.0 +3,Norwich DC,Norway DC,0.0,0.4861637504,40000.0,True,0.1291260515,AC,362.626773638,200.0 +4,Norway DC,Bremen DC,0.0,0.4287266497,40000.0,True,0.0624298729,AC,633.442700719,200.0 +5,Norwich,London,0.2388003463,0.0,40000.0,True,0.0218524519,AC,474.154279994,380.0 +6,Bremen,Frankfurt,0.4,0.0,40000.0,True,0.2,AC,1030.84876824,380.0 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/links-p0.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/links-p0.csv new file mode 100644 index 0000000000000000000000000000000000000000..1c0066b0fd9fe6404c0feae1040ee827d4142500 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/links-p0.csv @@ -0,0 +1,11 @@ +,Norwich Converter,Norway Converter,Bremen Converter,DC link +0,-566.827261209,672.57277898,-105.745517772,-5.38633062877e-12 +1,-2.92937992895,-116.727191881,119.65657181,-3.52250887955e-12 +2,32.7636277701,581.970687404,-614.734315174,8.65602999559e-13 +3,-403.770138117,272.557948882,131.212189234,5.41369192553e-14 +4,-0.631269494317,-79.7494872231,80.3807567175,1.82260961493e-12 +5,68.7496360424,-494.197716928,425.448080886,9.01069336616e-13 +6,1217.99799061,-257.520720035,-960.477270579,-5.460071428650001e-12 +7,36.8185772223,971.918845399,-1008.73742262,8.65602999559e-13 +8,562.71381819,850.880336946,-1413.59415514,-1.03293241939e-11 +9,736.338048098,-86.8518033948,-649.486244703,2.1585902683600001e-13 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/links-p1.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/links-p1.csv new file mode 100644 index 0000000000000000000000000000000000000000..1929d91556ede85e38a924faabbec904c5a1133b --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/links-p1.csv @@ -0,0 +1,11 @@ +,Norwich Converter,Norway Converter,Bremen Converter,DC link +0,566.827261209,-672.57277898,105.745517772,5.38633062877e-12 +1,2.92937992895,116.727191881,-119.65657181,3.52250887955e-12 +2,-32.7636277701,-581.970687404,614.734315174,-8.65602999559e-13 +3,403.770138117,-272.557948882,-131.212189234,-5.41369192553e-14 +4,0.631269494317,79.7494872231,-80.3807567175,-1.82260961493e-12 +5,-68.7496360424,494.197716928,-425.448080886,-9.01069336616e-13 +6,-1217.99799061,257.520720035,960.477270579,5.460071428650001e-12 +7,-36.8185772223,-971.918845399,1008.73742262,-8.65602999559e-13 +8,-562.71381819,-850.880336946,1413.59415514,1.03293241939e-11 +9,-736.338048098,86.8518033948,649.486244703,-2.1585902683600001e-13 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/links.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/links.csv new file mode 100644 index 0000000000000000000000000000000000000000..22a38e11b7f4763e04614bf772daffee2d45df24 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/links.csv @@ -0,0 +1,5 @@ +name,bus0,bus1,carrier,p_nom,p_nom_extendable,p_min_pu,p_max_pu,capital_cost,p_nom_opt +Norwich Converter,Norwich,Norwich DC,DC,1000.0,True,-0.9,0.9,0.21,1353.33110068 +Norway Converter,Norway,Norway DC,DC,1000.0,True,-0.9,0.9,0.2,1079.90982822 +Bremen Converter,Bremen,Bremen DC,DC,1000.0,True,-0.9,0.9,0.19,1570.66017237 +DC link,London,Bremen,DC,1000.0,True,-0.9,0.9,0.8765342,-9.61781110621e-13 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/loads-p.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/loads-p.csv new file mode 100644 index 0000000000000000000000000000000000000000..7b850ad127305eabc57b593a4a3532a89e202ff1 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/loads-p.csv @@ -0,0 +1,11 @@ +,London,Frankfurt,Norway,Norwich,Bremen,Manchester +0,35.7962441027,398.047846964,820.035835936,415.462564265,640.086377541,857.551440201 +1,976.82456147,432.436106242,854.834046862,262.606146453,703.554333706,750.599623717 +2,250.587312046,379.803928266,42.550744351,418.47635319,440.836130318,156.564876014 +3,130.753144583,868.361764284,647.548232785,552.95953931,612.576305682,527.870822119 +4,151.1001686,548.770754622,884.073873331,218.159858091,803.436780805,83.8977589634 +5,931.857051942,828.665242601,509.062448552,791.976265584,605.400687358,676.623319347 +6,289.848287145,449.290751908,595.607964815,531.870680822,641.09059024,731.137100483 +7,864.343321715,699.163766373,291.642449698,23.5134667186,408.008541172,553.344889185 +8,689.57726377,915.866780252,2.1534925491,970.059068457,912.247776165,298.338082262 +9,627.878985943,414.887646403,760.740176504,0.9248336907,898.053091642,768.290585989 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/loads-p_set.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/loads-p_set.csv new file mode 100644 index 0000000000000000000000000000000000000000..714303446cf6c8eb3a2bc1f31ccc04af16a0bdf2 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/loads-p_set.csv @@ -0,0 +1,11 @@ +,London,Norwich,Frankfurt,Bremen,Norway,Manchester +0,35.7962441027,415.462564265,398.047846964,640.086377541,820.035835936,857.551440201 +1,976.82456147,262.606146453,432.436106242,703.554333706,854.834046862,750.599623717 +2,250.587312046,418.47635319,379.803928266,440.836130318,42.550744351,156.564876014 +3,130.753144583,552.95953931,868.361764284,612.576305682,647.548232785,527.870822119 +4,151.1001686,218.159858091,548.770754622,803.436780805,884.073873331,83.8977589634 +5,931.857051942,791.976265584,828.665242601,605.400687358,509.062448552,676.623319347 +6,289.848287145,531.870680822,449.290751908,641.09059024,595.607964815,731.137100483 +7,864.343321715,23.5134667186,699.163766373,408.008541172,291.642449698,553.344889185 +8,689.57726377,970.059068457,915.866780252,912.247776165,2.1534925491,298.338082262 +9,627.878985943,0.9248336907,414.887646403,898.053091642,760.740176504,768.290585989 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/loads.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/loads.csv new file mode 100644 index 0000000000000000000000000000000000000000..89d320822f6c273b6d70c9e14bca53a9f3521bf0 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/loads.csv @@ -0,0 +1,7 @@ +name,bus,carrier +London,London,load +Frankfurt,Frankfurt,load +Norway,Norway,load +Norwich,Norwich,load +Bremen,Bremen,load +Manchester,Manchester,load diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/meta.json b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/meta.json new file mode 100644 index 0000000000000000000000000000000000000000..0967ef424bce6791893e9a57bb952f80fd536e93 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/meta.json @@ -0,0 +1 @@ +{} diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/network.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/network.csv new file mode 100644 index 0000000000000000000000000000000000000000..ad05d83085459cf3f7cfb71a309d250f13039faf --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/network.csv @@ -0,0 +1,2 @@ +name,_multi_invest,max_memusage,now,objective,pypsa_version,srid +AC-DC,0,157376,now,-3474256.04055,0.34.0,4326 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lopf/snapshots.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/snapshots.csv new file mode 100644 index 0000000000000000000000000000000000000000..7df49f40635b0e2b704ce489a52e0f84505a1acc --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lopf/snapshots.csv @@ -0,0 +1,11 @@ +,snapshot,objective,generators,stores +0,2015-01-01 00:00:00,1.0,1.0,1.0 +1,2015-01-01 01:00:00,1.0,1.0,1.0 +2,2015-01-01 02:00:00,1.0,1.0,1.0 +3,2015-01-01 03:00:00,1.0,1.0,1.0 +4,2015-01-01 04:00:00,1.0,1.0,1.0 +5,2015-01-01 05:00:00,1.0,1.0,1.0 +6,2015-01-01 06:00:00,1.0,1.0,1.0 +7,2015-01-01 07:00:00,1.0,1.0,1.0 +8,2015-01-01 08:00:00,1.0,1.0,1.0 +9,2015-01-01 09:00:00,1.0,1.0,1.0 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/buses-p.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/buses-p.csv new file mode 100644 index 0000000000000000000000000000000000000000..7b1f1f17d9d3fc7e2ff313aeaad33c0ec2034c46 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/buses-p.csv @@ -0,0 +1,11 @@ +name,London,Norwich,Norwich DC,Manchester,Bremen,Bremen DC,Frankfurt,Norway DC +2015-01-01 00:00:00,-19.6879342565,-451.258808368,35.7962441027,470.946742624,-204.935879019,-451.258808368,204.935879019,415.462564265 +2015-01-01 01:00:00,-537.253508808,-1239.43070792,976.82456147,1776.68421673,96.305321555,-1239.43070792,-96.305321555,262.606146453 +2015-01-01 02:00:00,-137.823021626,-669.063665237,250.587312046,806.886686862,115.463244497,-669.063665237,-115.463244497,418.47635319 +2015-01-01 03:00:00,-71.9142295205,-683.712683892,130.753144583,755.626913413,12.2974631485,-683.712683892,-12.2974631485,552.95953931 +2015-01-01 04:00:00,-83.10509273,-369.260026691,151.1001686,452.365119421,-502.171829984,-369.260026691,502.171829984,218.159858091 +2015-01-01 05:00:00,-512.521378568,-1723.83331753,931.857051942,2236.35469609,699.096956793,-1723.83331753,-699.096956793,791.976265584 +2015-01-01 06:00:00,-159.41655793,-821.718967967,289.848287145,981.135525896,50.1966485118,-821.718967967,-50.1966485118,531.870680822 +2015-01-01 07:00:00,-475.388826943,-887.856788433,864.343321715,1363.24561538,90.8937524892,-887.856788433,-90.8937524892,23.5134667186 +2015-01-01 08:00:00,-379.267495074,-1659.63633223,689.57726377,2038.9038273,437.078787366,-1659.63633223,-437.078787366,970.059068457 +2015-01-01 09:00:00,-345.333442269,-628.803819634,627.878985943,974.137261903,-551.794815683,-628.803819634,551.794815683,0.9248336907 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/buses-v_ang.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/buses-v_ang.csv new file mode 100644 index 0000000000000000000000000000000000000000..ee9bac9631296e7858263abdad428f457a2aeb64 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/buses-v_ang.csv @@ -0,0 +1,11 @@ +name,London,Norwich,Bremen +2015-01-01 00:00:00,-0.000731193049797,-0.000917750752361,-0.000567689415566 +2015-01-01 01:00:00,-0.00318597293246,-0.00325223406937,0.00026677374392 +2015-01-01 02:00:00,-0.00134888604694,-0.0015251827224,0.000319842782541 +2015-01-01 03:00:00,-0.00121042220645,-0.00145422159048,3.40649948712e-05 +2015-01-01 04:00:00,-0.000761616240252,-0.000852415234716,-0.00139105770079 +2015-01-01 05:00:00,-0.00385907872952,-0.00416794990101,0.00193655666702 +2015-01-01 06:00:00,-0.00163263739067,-0.00185825563313,0.000139048887844 +2015-01-01 07:00:00,-0.00250290371014,-0.00246677786106,0.000251783247893 +2015-01-01 08:00:00,-0.00343709863642,-0.00383988266786,0.00121074456334 +2015-01-01 09:00:00,-0.00179370710685,-0.00176013451501,-0.00152851749497 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/buses-v_mag_pu.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/buses-v_mag_pu.csv new file mode 100644 index 0000000000000000000000000000000000000000..694095e33cac4001659d4b728715d98d0a8a4d77 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/buses-v_mag_pu.csv @@ -0,0 +1,11 @@ +name,Bremen DC,Norway DC +2015-01-01 00:00:00,0.999005943289,1.00183804672 +2015-01-01 01:00:00,0.995256340872,0.998974946909 +2015-01-01 02:00:00,0.998073486161,1.0013597133 +2015-01-01 03:00:00,0.998318517692,1.0022558734 +2015-01-01 04:00:00,0.998907411732,1.00066194493 +2015-01-01 05:00:00,0.994380394505,1.00152451997 +2015-01-01 06:00:00,0.99767498263,1.00179379302 +2015-01-01 07:00:00,0.996224677051,0.998127752166 +2015-01-01 08:00:00,0.995065400999,1.00290280127 +2015-01-01 09:00:00,0.997290169991,0.998565290431 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/buses.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/buses.csv new file mode 100644 index 0000000000000000000000000000000000000000..3535306ebc21d4c6dac212308e2e5ed4624ec7c4 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/buses.csv @@ -0,0 +1,10 @@ +name,carrier,control,v_nom,x,y,generator +London,AC,PQ,380.0,-0.13,51.5, +Norwich,AC,PQ,380.0,1.3,52.6, +Norwich DC,DC,Slack,200.0,1.3,52.5, +Manchester,AC,Slack,380.0,-2.2,53.47,Manchester Wind +Bremen,AC,PQ,380.0,8.8,53.08, +Bremen DC,DC,PQ,200.0,8.8,52.98, +Frankfurt,AC,Slack,380.0,8.7,50.12,Frankfurt Wind +Norway,AC,Slack,380.0,10.75,60.0,Norway Wind +Norway DC,DC,PQ,200.0,10.75,60.0, diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/carriers.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/carriers.csv new file mode 100644 index 0000000000000000000000000000000000000000..f13d8f7fce651e84bacb61685632f05d50bcae83 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/carriers.csv @@ -0,0 +1,4 @@ +name,capital_cost,co2_emissions,efficiency,marginal_cost +gas,0.0,0.24,1.0,0.0 +wind,0.0,0.0,1.0,0.0 +battery,0.0,0.0,1.0,0.0 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/generators-p.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/generators-p.csv new file mode 100644 index 0000000000000000000000000000000000000000..7554ad150a4dcd330afa677f8596b39eb82af593 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/generators-p.csv @@ -0,0 +1,11 @@ +name,Manchester Wind,Norway Wind,Frankfurt Wind +2015-01-01 00:00:00,1328.49818283,1235.4984002,602.983725983 +2015-01-01 01:00:00,2527.28384045,1117.44019331,336.130784688 +2015-01-01 02:00:00,963.451562876,461.027097541,264.340683769 +2015-01-01 03:00:00,1283.49773553,1200.50777209,856.064301135 +2015-01-01 04:00:00,536.262878384,1102.23373142,1050.94258461 +2015-01-01 05:00:00,2912.97801544,1301.03871414,129.568285808 +2015-01-01 06:00:00,1712.27262638,1127.47864564,399.094103396 +2015-01-01 07:00:00,1916.59050456,315.155916417,608.270013884 +2015-01-01 08:00:00,2337.24190956,972.212561006,478.787992886 +2015-01-01 09:00:00,1742.42784789,761.665010195,966.682462086 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/generators-p_max_pu.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/generators-p_max_pu.csv new file mode 100644 index 0000000000000000000000000000000000000000..349a3b2d909b33d98f6636d457ddd1587eb2596e --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/generators-p_max_pu.csv @@ -0,0 +1,11 @@ +name,Manchester Wind,Frankfurt Wind,Norway Wind +2015-01-01 00:00:00,0.930019875,0.5590784039,0.9745832033 +2015-01-01 01:00:00,0.4857475804,0.7529103711,0.4812903778 +2015-01-01 02:00:00,0.2336917351,0.1234650887,0.4072258018 +2015-01-01 03:00:00,0.2576042221,0.9666766524,0.5999649628 +2015-01-01 04:00:00,0.6269055694,0.8590078044,0.524468219 +2015-01-01 05:00:00,0.6035984088,0.5261537924,0.0096927054 +2015-01-01 06:00:00,0.6789075462,0.077893008,0.2204533621 +2015-01-01 07:00:00,0.3613026112,0.0590234716,0.8239185004 +2015-01-01 08:00:00,0.6216040549,0.2485544952,0.5562297265 +2015-01-01 09:00:00,0.5215183715,0.1080601728,0.4394160378 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/generators.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/generators.csv new file mode 100644 index 0000000000000000000000000000000000000000..235f86b4914d2939b83b33661e6510477d61e107 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/generators.csv @@ -0,0 +1,7 @@ +name,bus,capital_cost,carrier,control,efficiency,marginal_cost,p_nom,p_nom_extendable,p_nom_min +Manchester Wind,Manchester,2793.65160293,wind,Slack,1.0,0.11,80.0,True,100.0 +Manchester Gas,Manchester,196.615167969,gas,PQ,0.3500264336,4.5323676307,50000.0,True,0.0 +Norway Wind,Norway,2184.37479609,wind,Slack,1.0,0.09,100.0,True,100.0 +Norway Gas,Norway,158.251249717,gas,PQ,0.3568363832,5.8928445406,20000.0,True,0.0 +Frankfurt Wind,Frankfurt,2129.45612248,wind,Slack,1.0,0.1,110.0,True,100.0 +Frankfurt Gas,Frankfurt,102.676953008,gas,PQ,0.3516658529,4.0863219899,80000.0,True,0.0 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/global_constraints.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/global_constraints.csv new file mode 100644 index 0000000000000000000000000000000000000000..4794cc6f661bd664ad55b861a8a38e638e20bd9e --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/global_constraints.csv @@ -0,0 +1,2 @@ +name,sense,constant +co2_limit,<=,1000.0 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/lines-p0.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/lines-p0.csv new file mode 100644 index 0000000000000000000000000000000000000000..71212953bb20c6ed22f903e749160be50309b58b --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/lines-p0.csv @@ -0,0 +1,11 @@ +name,0,1,2,3,4,5,6 +2015-01-01 00:00:00,-132.497369702,338.449372922,-187.024855542,-151.228611439,264.233952826,-112.809435446,-204.935879019 +2015-01-01 01:00:00,-577.320905349,1199.36331138,-892.486468366,84.3380931038,346.944239556,-40.0673965403,96.305321555 +2015-01-01 02:00:00,-244.427724384,562.458962478,-362.46017823,-111.872866184,306.603487006,-106.604702758,115.463244497 +2015-01-01 03:00:00,-219.337093848,536.289819565,-316.359200077,-185.606055494,367.353483815,-147.422864327,12.2974631485 +2015-01-01 04:00:00,-138.010267718,314.354851703,-205.562882735,-54.4627141349,163.697143956,-54.9051749876,-502.171829984 +2015-01-01 05:00:00,-699.292452625,1537.06224347,-1057.2896843,-125.432632359,666.543633224,-186.771074056,699.096956793 +2015-01-01 06:00:00,-295.845481574,685.290044322,-437.435845463,-147.587558318,384.283122503,-136.428923644,50.1966485118 +2015-01-01 07:00:00,-453.543914706,909.70170067,-710.300752084,154.042569631,177.556036349,21.8449122369,90.8937524892 +2015-01-01 08:00:00,-622.826665076,1416.07716223,-928.410477412,-238.833213642,731.225854815,-243.559170002,437.078787366 +2015-01-01 09:00:00,-325.032457214,649.104804689,-509.835666783,118.043319161,118.968152852,20.3009850549,-551.794815683 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/lines-p1.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/lines-p1.csv new file mode 100644 index 0000000000000000000000000000000000000000..41ddb7d74ce1912710dbb887970f253ee4f9958f --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/lines-p1.csv @@ -0,0 +1,11 @@ +name,0,1,2,3,4,5,6 +2015-01-01 00:00:00,132.497369702,-338.449372922,187.024855542,151.228611439,-264.233952826,112.809435446,204.935879019 +2015-01-01 01:00:00,577.320905349,-1199.36331138,892.486468366,-84.3380931038,-346.944239556,40.0673965403,-96.305321555 +2015-01-01 02:00:00,244.427724384,-562.458962478,362.46017823,111.872866184,-306.603487006,106.604702758,-115.463244497 +2015-01-01 03:00:00,219.337093848,-536.289819565,316.359200077,185.606055494,-367.353483815,147.422864327,-12.2974631485 +2015-01-01 04:00:00,138.010267718,-314.354851703,205.562882735,54.4627141349,-163.697143956,54.9051749876,502.171829984 +2015-01-01 05:00:00,699.292452625,-1537.06224347,1057.2896843,125.432632359,-666.543633224,186.771074056,-699.096956793 +2015-01-01 06:00:00,295.845481574,-685.290044322,437.435845463,147.587558318,-384.283122503,136.428923644,-50.1966485118 +2015-01-01 07:00:00,453.543914706,-909.70170067,710.300752084,-154.042569631,-177.556036349,-21.8449122369,-90.8937524892 +2015-01-01 08:00:00,622.826665076,-1416.07716223,928.410477412,238.833213642,-731.225854815,243.559170002,-437.078787366 +2015-01-01 09:00:00,325.032457214,-649.104804689,509.835666783,-118.043319161,-118.968152852,-20.3009850549,551.794815683 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/lines.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/lines.csv new file mode 100644 index 0000000000000000000000000000000000000000..0d33a293f6890aa375b1464a7cf987c7f7c0b25e --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/lines.csv @@ -0,0 +1,8 @@ +name,bus0,bus1,capital_cost,r,s_nom,s_nom_extendable,x,v_nom +0,London,Manchester,0.1367157553,0.0,40000.0,True,0.7968782824,380.0 +1,Manchester,Norwich,0.1334916779,0.0,40000.0,True,0.3915599178,380.0 +2,Bremen DC,Norwich DC,0.0086734246,0.2126041927,40000.0,True,0.0,200.0 +3,Norwich DC,Norway DC,0.1291260515,0.4861637504,40000.0,True,0.0,200.0 +4,Norway DC,Bremen DC,0.0624298729,0.4287266497,40000.0,True,0.0,200.0 +5,Norwich,London,0.0218524519,0.0,40000.0,True,0.2388003463,380.0 +6,Bremen,Frankfurt,0.2,0.0,40000.0,True,0.4,380.0 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/links-p0.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/links-p0.csv new file mode 100644 index 0000000000000000000000000000000000000000..4224b995f9c747356740b900b448acb580a62882 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/links-p0.csv @@ -0,0 +1,11 @@ +name,Norwich Converter,Norway Converter,Bremen Converter,DC link +2015-01-01 00:00:00,35.7962441027,415.462564265,-451.258808368,-16.1083098462 +2015-01-01 01:00:00,976.82456147,262.606146453,-1239.43070792,-439.571052661 +2015-01-01 02:00:00,250.587312046,418.47635319,-669.063665237,-112.764290421 +2015-01-01 03:00:00,130.753144583,552.95953931,-683.712683892,-58.8389150622 +2015-01-01 04:00:00,151.1001686,218.159858091,-369.260026691,-67.99507587 +2015-01-01 05:00:00,931.857051942,791.976265584,-1723.83331753,-419.335673374 +2015-01-01 06:00:00,289.848287145,531.870680822,-821.718967967,-130.431729215 +2015-01-01 07:00:00,864.343321715,23.5134667186,-887.856788433,-388.954494772 +2015-01-01 08:00:00,689.57726377,970.059068457,-1659.63633223,-310.309768697 +2015-01-01 09:00:00,627.878985943,0.9248336907,-628.803819634,-282.545543674 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/links-p1.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/links-p1.csv new file mode 100644 index 0000000000000000000000000000000000000000..8c432a7e18bc158b4d6cbba2a08d0093e7d07cd8 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/links-p1.csv @@ -0,0 +1,11 @@ +name,Norwich Converter,Norway Converter,Bremen Converter,DC link +2015-01-01 00:00:00,-35.7962441027,-415.462564265,451.258808368,16.1083098462 +2015-01-01 01:00:00,-976.82456147,-262.606146453,1239.43070792,439.571052661 +2015-01-01 02:00:00,-250.587312046,-418.47635319,669.063665237,112.764290421 +2015-01-01 03:00:00,-130.753144583,-552.95953931,683.712683892,58.8389150622 +2015-01-01 04:00:00,-151.1001686,-218.159858091,369.260026691,67.99507587 +2015-01-01 05:00:00,-931.857051942,-791.976265584,1723.83331753,419.335673374 +2015-01-01 06:00:00,-289.848287145,-531.870680822,821.718967967,130.431729215 +2015-01-01 07:00:00,-864.343321715,-23.5134667186,887.856788433,388.954494772 +2015-01-01 08:00:00,-689.57726377,-970.059068457,1659.63633223,310.309768697 +2015-01-01 09:00:00,-627.878985943,-0.9248336907,628.803819634,282.545543674 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/links-p_set.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/links-p_set.csv new file mode 100644 index 0000000000000000000000000000000000000000..4224b995f9c747356740b900b448acb580a62882 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/links-p_set.csv @@ -0,0 +1,11 @@ +name,Norwich Converter,Norway Converter,Bremen Converter,DC link +2015-01-01 00:00:00,35.7962441027,415.462564265,-451.258808368,-16.1083098462 +2015-01-01 01:00:00,976.82456147,262.606146453,-1239.43070792,-439.571052661 +2015-01-01 02:00:00,250.587312046,418.47635319,-669.063665237,-112.764290421 +2015-01-01 03:00:00,130.753144583,552.95953931,-683.712683892,-58.8389150622 +2015-01-01 04:00:00,151.1001686,218.159858091,-369.260026691,-67.99507587 +2015-01-01 05:00:00,931.857051942,791.976265584,-1723.83331753,-419.335673374 +2015-01-01 06:00:00,289.848287145,531.870680822,-821.718967967,-130.431729215 +2015-01-01 07:00:00,864.343321715,23.5134667186,-887.856788433,-388.954494772 +2015-01-01 08:00:00,689.57726377,970.059068457,-1659.63633223,-310.309768697 +2015-01-01 09:00:00,627.878985943,0.9248336907,-628.803819634,-282.545543674 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/links.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/links.csv new file mode 100644 index 0000000000000000000000000000000000000000..bd25cbe734dc3dba88558e48b846c4f521324177 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/links.csv @@ -0,0 +1,5 @@ +name,bus0,bus1,capital_cost,p_max_pu,p_min_pu,p_nom,p_nom_extendable +Norwich Converter,Norwich,Norwich DC,0.21,0.9,-0.9,1000.0,True +Norway Converter,Norway,Norway DC,0.2,0.9,-0.9,1000.0,True +Bremen Converter,Bremen,Bremen DC,0.19,0.9,-0.9,1000.0,True +DC link,London,Bremen,0.8765342,0.9,-0.9,1000.0,True diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/loads-p.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/loads-p.csv new file mode 100644 index 0000000000000000000000000000000000000000..20a52543bbca6c30be48e4a0bf40cc5504175cfb --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/loads-p.csv @@ -0,0 +1,11 @@ +name,London,Frankfurt,Norway,Norwich,Bremen,Manchester +2015-01-01 00:00:00,35.7962441027,398.047846964,820.035835936,415.462564265,640.086377541,857.551440201 +2015-01-01 01:00:00,976.82456147,432.436106242,854.834046862,262.606146453,703.554333706,750.599623717 +2015-01-01 02:00:00,250.587312046,379.803928266,42.550744351,418.47635319,440.836130318,156.564876014 +2015-01-01 03:00:00,130.753144583,868.361764284,647.548232785,552.95953931,612.576305682,527.870822119 +2015-01-01 04:00:00,151.1001686,548.770754622,884.073873331,218.159858091,803.436780805,83.8977589634 +2015-01-01 05:00:00,931.857051942,828.665242601,509.062448552,791.976265584,605.400687358,676.623319347 +2015-01-01 06:00:00,289.848287145,449.290751908,595.607964815,531.870680822,641.09059024,731.137100483 +2015-01-01 07:00:00,864.343321715,699.163766373,291.642449698,23.5134667186,408.008541172,553.344889185 +2015-01-01 08:00:00,689.57726377,915.866780252,2.1534925491,970.059068457,912.247776165,298.338082262 +2015-01-01 09:00:00,627.878985943,414.887646403,760.740176504,0.9248336907,898.053091642,768.290585989 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/loads-p_set.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/loads-p_set.csv new file mode 100644 index 0000000000000000000000000000000000000000..12b2d05bf7199528f2e1676ab163b6c43843f907 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/loads-p_set.csv @@ -0,0 +1,11 @@ +name,London,Norwich,Frankfurt,Bremen,Norway,Manchester +2015-01-01 00:00:00,35.7962441027,415.462564265,398.047846964,640.086377541,820.035835936,857.551440201 +2015-01-01 01:00:00,976.82456147,262.606146453,432.436106242,703.554333706,854.834046862,750.599623717 +2015-01-01 02:00:00,250.587312046,418.47635319,379.803928266,440.836130318,42.550744351,156.564876014 +2015-01-01 03:00:00,130.753144583,552.95953931,868.361764284,612.576305682,647.548232785,527.870822119 +2015-01-01 04:00:00,151.1001686,218.159858091,548.770754622,803.436780805,884.073873331,83.8977589634 +2015-01-01 05:00:00,931.857051942,791.976265584,828.665242601,605.400687358,509.062448552,676.623319347 +2015-01-01 06:00:00,289.848287145,531.870680822,449.290751908,641.09059024,595.607964815,731.137100483 +2015-01-01 07:00:00,864.343321715,23.5134667186,699.163766373,408.008541172,291.642449698,553.344889185 +2015-01-01 08:00:00,689.57726377,970.059068457,915.866780252,912.247776165,2.1534925491,298.338082262 +2015-01-01 09:00:00,627.878985943,0.9248336907,414.887646403,898.053091642,760.740176504,768.290585989 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/loads.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/loads.csv new file mode 100644 index 0000000000000000000000000000000000000000..a2a665e5481085c349920bbf83e5b282a044be91 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/loads.csv @@ -0,0 +1,7 @@ +name,bus +London,London +Frankfurt,Frankfurt +Norway,Norway +Norwich,Norwich +Bremen,Bremen +Manchester,Manchester diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/network.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/network.csv new file mode 100644 index 0000000000000000000000000000000000000000..0d996a1b53a8a75a7d09500949681aae511bea75 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/network.csv @@ -0,0 +1,2 @@ +name,now,pypsa_version,srid +AC-DC,now,0.10.0,4326 diff --git a/PyPSA/source/test/data/ac-dc-meshed/results-lpf/snapshots.csv b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/snapshots.csv new file mode 100644 index 0000000000000000000000000000000000000000..83fe6bd9265f169ace125949e6a76be5fb152633 --- /dev/null +++ b/PyPSA/source/test/data/ac-dc-meshed/results-lpf/snapshots.csv @@ -0,0 +1,11 @@ +,snapshot,weightings +0,2015-01-01 00:00:00,1.0 +1,2015-01-01 01:00:00,1.0 +2,2015-01-01 02:00:00,1.0 +3,2015-01-01 03:00:00,1.0 +4,2015-01-01 04:00:00,1.0 +5,2015-01-01 05:00:00,1.0 +6,2015-01-01 06:00:00,1.0 +7,2015-01-01 07:00:00,1.0 +8,2015-01-01 08:00:00,1.0 +9,2015-01-01 09:00:00,1.0 diff --git a/PyPSA/source/test/data/benchmark-sp/buses-marginal_price.csv b/PyPSA/source/test/data/benchmark-sp/buses-marginal_price.csv new file mode 100644 index 0000000000000000000000000000000000000000..e770c47c9574a0b173d80f46536fd962361938ea --- /dev/null +++ b/PyPSA/source/test/data/benchmark-sp/buses-marginal_price.csv @@ -0,0 +1,2921 @@ +,DE +0,26.666666666666668 +1,26.666666666666668 +2,26.666666666666668 +3,26.666666666666668 +4,26.666666666666668 +5,26.666666666666668 +6,26.666666666666668 +7,26.666666666666668 +8,26.666666666666668 +9,26.666666666666668 +10,26.666666666666668 +11,26.666666666666668 +12,0.008 +13,26.666666666666668 +14,26.666666666666668 +15,26.666666666666668 +16,26.666666666666668 +17,26.666666666666668 +18,26.666666666666668 +19,26.666666666666668 +20,26.666666666666668 +21,26.666666666666668 +22,26.666666666666668 +23,26.666666666666668 +24,26.666666666666668 +25,26.666666666666668 +26,26.666666666666668 +27,26.666666666666668 +28,26.666666666666668 +29,26.666666666666668 +30,26.666666666666668 +31,26.666666666666668 +32,26.666666666666668 +33,26.666666666666668 +34,26.666666666666668 +35,26.666666666666668 +36,26.666666666666668 +37,26.666666666666668 +38,26.666666666666668 +39,52.0 +40,52.0 +41,52.0 +42,52.0 +43,26.666666666666668 +44,26.666666666666668 +45,26.666666666666668 +46,26.666666666666668 +47,26.666666666666668 +48,26.666666666666668 +49,26.666666666666668 +50,26.666666666666668 +51,26.666666666666668 +52,26.666666666666668 +53,26.666666666666668 +54,26.666666666666668 +55,26.666666666666668 +56,26.666666666666668 +57,26.666666666666668 +58,26.666666666666668 +59,26.666666666666668 +60,26.666666666666668 +61,26.666666666666668 +62,26.666666666666668 +63,26.666666666666668 +64,26.666666666666668 +65,26.666666666666668 +66,26.666666666666668 +67,0.008 +68,0.008 +69,0.008 +70,26.666666666666668 +71,26.666666666666668 +72,26.666666666666668 +73,26.666666666666668 +74,26.666666666666668 +75,0.008 +76,0.008 +77,0.008 +78,0.008 +79,26.666666666666668 +80,26.666666666666668 +81,26.666666666666668 +82,26.666666666666668 +83,0.008 +84,0.008 +85,26.666666666666668 +86,26.666666666666668 +87,26.666666666666668 +88,26.666666666666668 +89,26.666666666666668 +90,26.666666666666668 +91,26.666666666666668 +92,0.008 +93,26.666666666666668 +94,26.666666666666668 +95,26.666666666666668 +96,26.666666666666668 +97,26.666666666666668 +98,26.666666666666668 +99,0.008 +100,0.008 +101,26.666666666666668 +102,26.666666666666668 +103,26.666666666666668 +104,26.666666666666668 +105,26.666666666666668 +106,26.666666666666668 +107,26.666666666666668 +108,26.666666666666668 +109,26.666666666666668 +110,26.666666666666668 +111,26.666666666666668 +112,26.666666666666668 +113,26.666666666666668 +114,26.666666666666668 +115,26.666666666666668 +116,0.008 +117,26.666666666666668 +118,26.666666666666668 +119,26.666666666666668 +120,26.666666666666668 +121,26.666666666666668 +122,26.666666666666668 +123,26.666666666666668 +124,26.666666666666668 +125,26.666666666666668 +126,26.666666666666668 +127,26.666666666666668 +128,52.0 +129,26.666666666666668 +130,26.666666666666668 +131,26.666666666666668 +132,26.666666666666668 +133,26.666666666666668 +134,26.666666666666668 +135,26.666666666666668 +136,26.666666666666668 +137,26.666666666666668 +138,26.666666666666668 +139,26.666666666666668 +140,26.666666666666668 +141,26.666666666666668 +142,26.666666666666668 +143,26.666666666666668 +144,26.666666666666668 +145,52.0 +146,52.0 +147,26.666666666666668 +148,26.666666666666668 +149,34.42579213333374 +150,52.0 +151,52.0 +152,52.0 +153,52.0 +154,52.0 +155,26.666666666666668 +156,26.666666666666668 +157,52.0 +158,52.0 +159,52.0 +160,52.0 +161,52.0 +162,52.0 +163,26.666666666666668 +164,26.666666666666668 +165,26.666666666666668 +166,26.666666666666668 +167,26.666666666666668 +168,52.0 +169,52.0 +170,52.0 +171,26.666666666666668 +172,26.666666666666668 +173,26.666666666666668 +174,26.666666666666668 +175,52.0 +176,52.0 +177,52.0 +178,52.0 +179,26.666666666666668 +180,26.666666666666668 +181,52.0 +182,52.0 +183,52.0 +184,26.666666666666668 +185,26.666666666666668 +186,26.666666666666668 +187,26.666666666666668 +188,26.666666666666668 +189,26.666666666666668 +190,26.666666666666668 +191,26.666666666666668 +192,26.666666666666668 +193,26.666666666666668 +194,26.666666666666668 +195,26.666666666666668 +196,26.666666666666668 +197,26.666666666666668 +198,52.0 +199,52.0 +200,52.0 +201,26.666666666666668 +202,26.666666666666668 +203,26.666666666666668 +204,26.666666666666668 +205,26.666666666666668 +206,26.666666666666668 +207,26.666666666666668 +208,26.666666666666668 +209,26.666666666666668 +210,26.666666666666668 +211,26.666666666666668 +212,26.666666666666668 +213,26.666666666666668 +214,26.666666666666668 +215,26.666666666666668 +216,26.666666666666668 +217,26.666666666666668 +218,26.666666666666668 +219,26.666666666666668 +220,26.666666666666668 +221,26.666666666666668 +222,26.666666666666668 +223,26.666666666666668 +224,26.666666666666668 +225,26.666666666666668 +226,26.666666666666668 +227,26.666666666666668 +228,26.666666666666668 +229,26.666666666666668 +230,26.666666666666668 +231,26.666666666666668 +232,26.666666666666668 +233,26.666666666666668 +234,26.666666666666668 +235,26.666666666666668 +236,26.666666666666668 +237,26.666666666666668 +238,26.666666666666668 +239,26.666666666666668 +240,26.666666666666668 +241,26.666666666666668 +242,26.666666666666668 +243,26.666666666666668 +244,26.666666666666668 +245,26.666666666666668 +246,26.666666666666668 +247,26.666666666666668 +248,26.666666666666668 +249,26.666666666666668 +250,26.666666666666668 +251,26.666666666666668 +252,26.666666666666668 +253,26.666666666666668 +254,26.666666666666668 +255,26.666666666666668 +256,26.666666666666668 +257,26.666666666666668 +258,26.666666666666668 +259,26.666666666666668 +260,26.666666666666668 +261,26.666666666666668 +262,26.666666666666668 +263,26.666666666666668 +264,26.666666666666668 +265,26.666666666666668 +266,26.666666666666668 +267,26.666666666666668 +268,26.666666666666668 +269,26.666666666666668 +270,26.666666666666668 +271,52.0 +272,52.0 +273,52.0 +274,52.0 +275,26.666666666666668 +276,26.666666666666668 +277,26.666666666666668 +278,52.0 +279,52.0 +280,52.0 +281,52.0 +282,52.0 +283,26.666666666666668 +284,26.666666666666668 +285,26.666666666666668 +286,26.666666666666668 +287,26.666666666666668 +288,26.666666666666668 +289,26.666666666666668 +290,26.666666666666668 +291,26.666666666666668 +292,26.666666666666668 +293,26.666666666666668 +294,26.666666666666668 +295,26.666666666666668 +296,52.0 +297,26.666666666666668 +298,26.666666666666668 +299,26.666666666666668 +300,26.666666666666668 +301,26.666666666666668 +302,26.666666666666668 +303,26.666666666666668 +304,26.666666666666668 +305,26.666666666666668 +306,26.666666666666668 +307,26.666666666666668 +308,0.008 +309,26.666666666666668 +310,26.666666666666668 +311,26.666666666666668 +312,26.666666666666668 +313,26.666666666666668 +314,26.666666666666668 +315,26.666666666666668 +316,26.666666666666668 +317,26.666666666666668 +318,26.666666666666668 +319,26.666666666666668 +320,26.666666666666668 +321,26.666666666666668 +322,26.666666666666668 +323,26.666666666666668 +324,26.666666666666668 +325,26.666666666666668 +326,26.666666666666668 +327,26.666666666666668 +328,52.0 +329,52.0 +330,52.0 +331,26.666666666666668 +332,26.666666666666668 +333,26.666666666666668 +334,52.0 +335,52.0 +336,52.0 +337,52.0 +338,52.0 +339,26.666666666666668 +340,26.666666666666668 +341,26.666666666666668 +342,52.0 +343,52.0 +344,52.0 +345,52.0 +346,52.0 +347,26.666666666666668 +348,26.666666666666668 +349,26.666666666666668 +350,26.666666666666668 +351,26.666666666666668 +352,26.666666666666668 +353,26.666666666666668 +354,26.666666666666668 +355,26.666666666666668 +356,26.666666666666668 +357,26.666666666666668 +358,26.666666666666668 +359,26.666666666666668 +360,26.666666666666668 +361,26.666666666666668 +362,26.666666666666668 +363,26.666666666666668 +364,26.666666666666668 +365,26.666666666666668 +366,26.666666666666668 +367,26.666666666666668 +368,26.666666666666668 +369,26.666666666666668 +370,26.666666666666668 +371,26.666666666666668 +372,26.666666666666668 +373,26.666666666666668 +374,26.666666666666668 +375,26.666666666666668 +376,52.0 +377,52.0 +378,52.0 +379,26.666666666666668 +380,26.666666666666668 +381,26.666666666666668 +382,26.666666666666668 +383,26.666666666666668 +384,52.0 +385,52.0 +386,52.0 +387,26.666666666666668 +388,26.666666666666668 +389,26.666666666666668 +390,26.666666666666668 +391,26.666666666666668 +392,26.666666666666668 +393,26.666666666666668 +394,26.666666666666668 +395,26.666666666666668 +396,26.666666666666668 +397,26.666666666666668 +398,26.666666666666668 +399,26.666666666666668 +400,26.666666666666668 +401,26.666666666666668 +402,26.666666666666668 +403,26.666666666666668 +404,0.008 +405,26.666666666666668 +406,26.666666666666668 +407,26.666666666666668 +408,26.666666666666668 +409,26.666666666666668 +410,26.666666666666668 +411,26.666666666666668 +412,26.666666666666668 +413,26.666666666666668 +414,26.666666666666668 +415,26.666666666666668 +416,26.666666666666668 +417,52.0 +418,26.666666666666668 +419,26.666666666666668 +420,26.666666666666668 +421,26.666666666666668 +422,26.666666666666668 +423,26.666666666666668 +424,26.666666666666668 +425,26.666666666666668 +426,26.666666666666668 +427,26.666666666666668 +428,26.666666666666668 +429,26.666666666666668 +430,26.666666666666668 +431,26.666666666666668 +432,26.666666666666668 +433,26.666666666666668 +434,26.666666666666668 +435,26.666666666666668 +436,26.666666666666668 +437,26.666666666666668 +438,26.666666666666668 +439,26.666666666666668 +440,26.666666666666668 +441,26.666666666666668 +442,26.666666666666668 +443,26.666666666666668 +444,26.666666666666668 +445,26.666666666666668 +446,52.0 +447,52.0 +448,52.0 +449,52.0 +450,26.666666666666668 +451,26.666666666666668 +452,0.008 +453,26.666666666666668 +454,26.666666666666668 +455,26.666666666666668 +456,26.666666666666668 +457,26.666666666666668 +458,26.666666666666668 +459,26.666666666666668 +460,26.666666666666668 +461,26.666666666666668 +462,26.666666666666668 +463,26.666666666666668 +464,26.666666666666668 +465,52.0 +466,52.0 +467,26.666666666666668 +468,26.666666666666668 +469,26.666666666666668 +470,26.666666666666668 +471,26.666666666666668 +472,26.666666666666668 +473,26.666666666666668 +474,26.666666666666668 +475,26.666666666666668 +476,26.666666666666668 +477,26.666666666666668 +478,26.666666666666668 +479,26.666666666666668 +480,26.666666666666668 +481,26.666666666666668 +482,26.666666666666668 +483,0.008 +484,0.008 +485,26.666666666666668 +486,26.666666666666668 +487,26.666666666666668 +488,26.666666666666668 +489,26.666666666666668 +490,26.666666666666668 +491,0.008 +492,0.008 +493,26.666666666666668 +494,26.666666666666668 +495,26.666666666666668 +496,26.666666666666668 +497,26.666666666666668 +498,26.666666666666668 +499,26.666666666666668 +500,26.666666666666668 +501,26.666666666666668 +502,26.666666666666668 +503,26.666666666666668 +504,26.666666666666668 +505,26.666666666666668 +506,26.666666666666668 +507,26.666666666666668 +508,26.666666666666668 +509,26.666666666666668 +510,26.666666666666668 +511,26.666666666666668 +512,26.666666666666668 +513,26.666666666666668 +514,26.666666666666668 +515,26.666666666666668 +516,26.666666666666668 +517,26.666666666666668 +518,26.666666666666668 +519,26.666666666666668 +520,26.666666666666668 +521,26.666666666666668 +522,26.666666666666668 +523,26.666666666666668 +524,0.008 +525,26.666666666666668 +526,26.666666666666668 +527,26.666666666666668 +528,26.666666666666668 +529,26.666666666666668 +530,26.666666666666668 +531,26.666666666666668 +532,0.008 +533,26.666666666666668 +534,26.666666666666668 +535,26.666666666666668 +536,26.666666666666668 +537,26.666666666666668 +538,52.0 +539,26.666666666666668 +540,26.666666666666668 +541,26.666666666666668 +542,52.0 +543,26.666666666666668 +544,26.666666666666668 +545,26.666666666666668 +546,26.666666666666668 +547,26.666666666666668 +548,26.666666666666668 +549,26.666666666666668 +550,26.666666666666668 +551,26.666666666666668 +552,26.666666666666668 +553,26.666666666666668 +554,26.666666666666668 +555,26.666666666666668 +556,26.666666666666668 +557,26.666666666666668 +558,26.666666666666668 +559,26.666666666666668 +560,52.0 +561,52.0 +562,52.0 +563,26.666666666666668 +564,26.666666666666668 +565,26.666666666666668 +566,26.666666666666668 +567,26.666666666666668 +568,26.666666666666668 +569,26.666666666666668 +570,26.666666666666668 +571,26.666666666666668 +572,26.666666666666668 +573,26.666666666666668 +574,26.666666666666668 +575,26.666666666666668 +576,26.666666666666668 +577,26.666666666666668 +578,26.666666666666668 +579,26.666666666666668 +580,26.666666666666668 +581,26.666666666666668 +582,26.666666666666668 +583,26.666666666666668 +584,26.666666666666668 +585,26.666666666666668 +586,26.666666666666668 +587,26.666666666666668 +588,26.666666666666668 +589,26.666666666666668 +590,26.666666666666668 +591,26.666666666666668 +592,26.666666666666668 +593,26.666666666666668 +594,26.666666666666668 +595,0.008 +596,0.008 +597,26.666666666666668 +598,26.666666666666668 +599,26.666666666666668 +600,26.666666666666668 +601,26.666666666666668 +602,26.666666666666668 +603,26.666666666666668 +604,0.008 +605,26.666666666666668 +606,26.666666666666668 +607,26.666666666666668 +608,26.666666666666668 +609,26.666666666666668 +610,26.666666666666668 +611,26.666666666666668 +612,0.004 +613,26.666666666666668 +614,26.666666666666668 +615,26.666666666666668 +616,52.0 +617,52.0 +618,26.666666666666668 +619,26.666666666666668 +620,0.008 +621,26.666666666666668 +622,52.0 +623,52.0 +624,52.0 +625,52.0 +626,26.666666666666668 +627,26.666666666666668 +628,26.666666666666668 +629,26.666666666666668 +630,52.0 +631,52.0 +632,26.666666666666668 +633,52.0 +634,26.666666666666668 +635,26.666666666666668 +636,26.666666666666668 +637,26.666666666666668 +638,26.666666666666668 +639,26.666666666666668 +640,26.666666666666668 +641,26.666666666666668 +642,26.666666666666668 +643,26.666666666666668 +644,26.666666666666668 +645,26.666666666666668 +646,52.0 +647,26.666666666666668 +648,26.666666666666668 +649,52.0 +650,26.666666666666668 +651,26.666666666666668 +652,26.666666666666668 +653,26.666666666666668 +654,52.0 +655,52.0 +656,52.0 +657,52.0 +658,26.666666666666668 +659,26.666666666666668 +660,26.666666666666668 +661,26.666666666666668 +662,52.0 +663,52.0 +664,52.0 +665,26.666666666666668 +666,26.666666666666668 +667,26.666666666666668 +668,0.008 +669,26.666666666666668 +670,26.666666666666668 +671,26.666666666666668 +672,26.666666666666668 +673,26.666666666666668 +674,26.666666666666668 +675,26.666666666666668 +676,26.666666666666668 +677,26.666666666666668 +678,52.0 +679,26.666666666666668 +680,26.666666666666668 +681,26.666666666666668 +682,26.666666666666668 +683,26.666666666666668 +684,26.666666666666668 +685,26.666666666666668 +686,26.666666666666668 +687,26.666666666666668 +688,26.666666666666668 +689,26.666666666666668 +690,26.666666666666668 +691,26.666666666666668 +692,26.666666666666668 +693,26.666666666666668 +694,26.666666666666668 +695,26.666666666666668 +696,26.666666666666668 +697,26.666666666666668 +698,26.666666666666668 +699,26.666666666666668 +700,26.666666666666668 +701,26.666666666666668 +702,26.666666666666668 +703,26.666666666666668 +704,26.666666666666668 +705,26.666666666666668 +706,26.666666666666668 +707,0.008 +708,0.008 +709,0.008 +710,26.666666666666668 +711,26.666666666666668 +712,26.666666666666668 +713,26.666666666666668 +714,26.666666666666668 +715,0.008 +716,0.008 +717,0.008 +718,0.008 +719,26.666666666666668 +720,26.666666666666668 +721,26.666666666666668 +722,26.666666666666668 +723,0.008 +724,0.008 +725,0.008 +726,26.666666666666668 +727,26.666666666666668 +728,26.666666666666668 +729,26.666666666666668 +730,26.666666666666668 +731,26.666666666666668 +732,0.008 +733,0.008 +734,26.666666666666668 +735,26.666666666666668 +736,26.666666666666668 +737,26.666666666666668 +738,26.666666666666668 +739,26.666666666666668 +740,26.666666666666668 +741,26.666666666666668 +742,26.666666666666668 +743,26.666666666666668 +744,52.0 +745,52.0 +746,26.666666666666668 +747,26.666666666666668 +748,26.666666666666668 +749,26.666666666666668 +750,26.666666666666668 +751,26.666666666666668 +752,52.0 +753,52.0 +754,26.666666666666668 +755,26.666666666666668 +756,26.666666666666668 +757,26.666666666666668 +758,26.666666666666668 +759,26.666666666666668 +760,26.666666666666668 +761,52.0 +762,26.666666666666668 +763,26.666666666666668 +764,26.666666666666668 +765,26.666666666666668 +766,26.666666666666668 +767,52.0 +768,52.0 +769,52.0 +770,26.666666666666668 +771,26.666666666666668 +772,0.008 +773,26.666666666666668 +774,26.666666666666668 +775,26.666666666666668 +776,26.666666666666668 +777,26.666666666666668 +778,26.666666666666668 +779,26.666666666666668 +780,26.666666666666668 +781,26.666666666666668 +782,52.0 +783,52.0 +784,52.0 +785,52.0 +786,26.666666666666668 +787,26.666666666666668 +788,0.004 +789,26.666666666666668 +790,52.0 +791,52.0 +792,52.0 +793,52.0 +794,26.666666666666668 +795,26.666666666666668 +796,0.004 +797,26.666666666666668 +798,52.0 +799,26.666666666666668 +800,26.666666666666668 +801,26.666666666666668 +802,26.666666666666668 +803,0.008 +804,0.008 +805,26.666666666666668 +806,26.666666666666668 +807,26.666666666666668 +808,26.666666666666668 +809,26.666666666666668 +810,26.666666666666668 +811,0.008 +812,0.008 +813,26.666666666666668 +814,26.666666666666668 +815,26.666666666666668 +816,26.666666666666668 +817,26.666666666666668 +818,26.666666666666668 +819,0.008 +820,0.008 +821,26.666666666666668 +822,26.666666666666668 +823,26.666666666666668 +824,52.0 +825,26.666666666666668 +826,26.666666666666668 +827,0.008 +828,0.008 +829,26.666666666666668 +830,26.666666666666668 +831,26.666666666666668 +832,26.666666666666668 +833,26.666666666666668 +834,26.666666666666668 +835,0.004 +836,0.004 +837,0.008 +838,26.666666666666668 +839,26.666666666666668 +840,26.666666666666668 +841,26.666666666666668 +842,26.666666666666668 +843,26.666666666666668 +844,0.008 +845,26.666666666666668 +846,26.666666666666668 +847,26.666666666666668 +848,26.666666666666668 +849,26.666666666666668 +850,26.666666666666668 +851,26.666666666666668 +852,26.666666666666668 +853,26.666666666666668 +854,26.666666666666668 +855,26.666666666666668 +856,26.666666666666668 +857,52.0 +858,26.666666666666668 +859,26.666666666666668 +860,26.666666666666668 +861,26.666666666666668 +862,26.666666666666668 +863,52.0 +864,52.0 +865,52.0 +866,26.666666666666668 +867,0.004 +868,0.004 +869,26.666666666666668 +870,26.666666666666668 +871,26.666666666666668 +872,26.666666666666668 +873,26.666666666666668 +874,26.666666666666668 +875,0.004 +876,0.004 +877,26.666666666666668 +878,26.666666666666668 +879,26.666666666666668 +880,52.0 +881,26.666666666666668 +882,26.666666666666668 +883,0.004 +884,0.004 +885,0.008 +886,26.666666666666668 +887,26.666666666666668 +888,26.666666666666668 +889,26.666666666666668 +890,26.666666666666668 +891,26.666666666666668 +892,26.666666666666668 +893,26.666666666666668 +894,52.0 +895,52.0 +896,52.0 +897,52.0 +898,26.666666666666668 +899,26.666666666666668 +900,0.004 +901,26.666666666666668 +902,52.0 +903,52.0 +904,52.0 +905,52.0 +906,26.666666666666668 +907,0.004 +908,0.004 +909,26.666666666666668 +910,52.0 +911,26.666666666666668 +912,26.666666666666668 +913,26.666666666666668 +914,26.666666666666668 +915,26.666666666666668 +916,26.666666666666668 +917,26.666666666666668 +918,26.666666666666668 +919,26.666666666666668 +920,26.666666666666668 +921,26.666666666666668 +922,26.666666666666668 +923,26.666666666666668 +924,26.666666666666668 +925,26.666666666666668 +926,52.0 +927,52.0 +928,52.0 +929,26.666666666666668 +930,26.666666666666668 +931,26.666666666666668 +932,26.666666666666668 +933,26.666666666666668 +934,26.666666666666668 +935,26.666666666666668 +936,26.666666666666668 +937,26.666666666666668 +938,26.666666666666668 +939,26.666666666666668 +940,26.666666666666668 +941,26.666666666666668 +942,26.666666666666668 +943,26.666666666666668 +944,26.666666666666668 +945,26.666666666666668 +946,26.666666666666668 +947,0.004 +948,0.004 +949,26.666666666666668 +950,26.666666666666668 +951,26.666666666666668 +952,26.666666666666668 +953,26.666666666666668 +954,26.666666666666668 +955,26.666666666666668 +956,26.666666666666668 +957,26.666666666666668 +958,52.0 +959,26.666666666666668 +960,26.666666666666668 +961,52.0 +962,26.666666666666668 +963,26.666666666666668 +964,26.666666666666668 +965,26.666666666666668 +966,52.0 +967,52.0 +968,52.0 +969,52.0 +970,26.666666666666668 +971,26.666666666666668 +972,26.666666666666668 +973,26.666666666666668 +974,52.0 +975,52.0 +976,26.666666666666668 +977,26.666666666666668 +978,26.666666666666668 +979,26.666666666666668 +980,26.666666666666668 +981,26.666666666666668 +982,26.666666666666668 +983,26.666666666666668 +984,26.666666666666668 +985,26.666666666666668 +986,26.666666666666668 +987,26.666666666666668 +988,0.008 +989,26.666666666666668 +990,52.0 +991,26.666666666666668 +992,26.666666666666668 +993,26.666666666666668 +994,26.666666666666668 +995,26.666666666666668 +996,0.008 +997,26.666666666666668 +998,26.666666666666668 +999,26.666666666666668 +1000,26.666666666666668 +1001,26.666666666666668 +1002,26.666666666666668 +1003,0.008 +1004,0.008 +1005,26.666666666666668 +1006,26.666666666666668 +1007,26.666666666666668 +1008,26.666666666666668 +1009,26.666666666666668 +1010,26.666666666666668 +1011,0.008 +1012,0.008 +1013,26.666666666666668 +1014,52.0 +1015,52.0 +1016,52.0 +1017,52.0 +1018,26.666666666666668 +1019,26.666666666666668 +1020,26.666666666666668 +1021,26.666666666666668 +1022,26.666666666666668 +1023,26.666666666666668 +1024,26.666666666666668 +1025,26.666666666666668 +1026,26.666666666666668 +1027,26.666666666666668 +1028,0.008 +1029,26.666666666666668 +1030,26.666666666666668 +1031,26.666666666666668 +1032,26.666666666666668 +1033,26.666666666666668 +1034,26.666666666666668 +1035,0.008 +1036,0.008 +1037,26.666666666666668 +1038,52.0 +1039,52.0 +1040,26.666666666666668 +1041,26.666666666666668 +1042,26.666666666666668 +1043,0.008 +1044,0.008 +1045,26.666666666666668 +1046,26.666666666666668 +1047,26.666666666666668 +1048,26.666666666666668 +1049,26.666666666666668 +1050,26.666666666666668 +1051,26.666666666666668 +1052,0.008 +1053,26.666666666666668 +1054,26.666666666666668 +1055,26.666666666666668 +1056,26.666666666666668 +1057,26.666666666666668 +1058,26.666666666666668 +1059,0.008 +1060,0.008 +1061,26.666666666666668 +1062,26.666666666666668 +1063,26.666666666666668 +1064,26.666666666666668 +1065,26.666666666666668 +1066,26.666666666666668 +1067,26.666666666666668 +1068,26.666666666666668 +1069,26.666666666666668 +1070,52.0 +1071,52.0 +1072,52.0 +1073,52.0 +1074,26.666666666666668 +1075,26.666666666666668 +1076,0.008 +1077,26.666666666666668 +1078,26.666666666666668 +1079,52.0 +1080,52.0 +1081,52.0 +1082,26.666666666666668 +1083,26.666666666666668 +1084,26.666666666666668 +1085,26.666666666666668 +1086,26.666666666666668 +1087,26.666666666666668 +1088,26.666666666666668 +1089,26.666666666666668 +1090,26.666666666666668 +1091,0.008 +1092,0.008 +1093,26.666666666666668 +1094,26.666666666666668 +1095,26.666666666666668 +1096,26.666666666666668 +1097,52.0 +1098,26.666666666666668 +1099,26.666666666666668 +1100,0.008 +1101,26.666666666666668 +1102,26.666666666666668 +1103,26.666666666666668 +1104,26.666666666666668 +1105,26.666666666666668 +1106,26.666666666666668 +1107,0.008 +1108,0.008 +1109,26.666666666666668 +1110,26.666666666666668 +1111,26.666666666666668 +1112,26.666666666666668 +1113,26.666666666666668 +1114,26.666666666666668 +1115,26.666666666666668 +1116,26.666666666666668 +1117,26.666666666666668 +1118,26.666666666666668 +1119,26.666666666666668 +1120,26.666666666666668 +1121,26.666666666666668 +1122,26.666666666666668 +1123,26.666666666666668 +1124,26.666666666666668 +1125,26.666666666666668 +1126,26.666666666666668 +1127,52.0 +1128,52.0 +1129,52.0 +1130,26.666666666666668 +1131,0.008 +1132,26.666666666666668 +1133,26.666666666666668 +1134,52.0 +1135,52.0 +1136,52.0 +1137,52.0 +1138,26.666666666666668 +1139,26.666666666666668 +1140,26.666666666666668 +1141,26.666666666666668 +1142,26.666666666666668 +1143,26.666666666666668 +1144,52.0 +1145,52.0 +1146,26.666666666666668 +1147,26.666666666666668 +1148,26.666666666666668 +1149,26.666666666666668 +1150,52.0 +1151,52.0 +1152,52.0 +1153,52.0 +1154,26.666666666666668 +1155,26.666666666666668 +1156,26.666666666666668 +1157,26.666666666666668 +1158,26.666666666666668 +1159,26.666666666666668 +1160,26.666666666666668 +1161,26.666666666666668 +1162,26.666666666666668 +1163,26.666666666666668 +1164,26.666666666666668 +1165,26.666666666666668 +1166,26.666666666666668 +1167,26.666666666666668 +1168,26.666666666666668 +1169,26.666666666666668 +1170,26.666666666666668 +1171,26.666666666666668 +1172,26.666666666666668 +1173,26.666666666666668 +1174,52.0 +1175,26.666666666666668 +1176,26.666666666666668 +1177,26.666666666666668 +1178,26.666666666666668 +1179,0.008 +1180,0.008 +1181,26.666666666666668 +1182,26.666666666666668 +1183,26.666666666666668 +1184,52.0 +1185,52.0 +1186,26.666666666666668 +1187,26.666666666666668 +1188,26.666666666666668 +1189,26.666666666666668 +1190,26.666666666666668 +1191,26.666666666666668 +1192,26.666666666666668 +1193,26.666666666666668 +1194,26.666666666666668 +1195,0.008 +1196,0.008 +1197,26.666666666666668 +1198,26.666666666666668 +1199,26.666666666666668 +1200,26.666666666666668 +1201,26.666666666666668 +1202,26.666666666666668 +1203,0.008 +1204,0.008 +1205,26.666666666666668 +1206,26.666666666666668 +1207,26.666666666666668 +1208,26.666666666666668 +1209,26.666666666666668 +1210,26.666666666666668 +1211,26.666666666666668 +1212,26.666666666666668 +1213,26.666666666666668 +1214,52.0 +1215,26.666666666666668 +1216,26.666666666666668 +1217,26.666666666666668 +1218,26.666666666666668 +1219,0.008 +1220,0.008 +1221,0.008 +1222,26.666666666666668 +1223,26.666666666666668 +1224,26.666666666666668 +1225,26.666666666666668 +1226,26.666666666666668 +1227,0.008 +1228,0.008 +1229,0.008 +1230,26.666666666666668 +1231,26.666666666666668 +1232,52.0 +1233,52.0 +1234,26.666666666666668 +1235,0.004 +1236,0.004 +1237,26.666666666666668 +1238,26.666666666666668 +1239,26.666666666666668 +1240,26.666666666666668 +1241,26.666666666666668 +1242,26.666666666666668 +1243,0.004 +1244,0.004 +1245,26.666666666666668 +1246,26.666666666666668 +1247,26.666666666666668 +1248,26.666666666666668 +1249,26.666666666666668 +1250,26.666666666666668 +1251,0.008 +1252,0.008 +1253,26.666666666666668 +1254,26.666666666666668 +1255,26.666666666666668 +1256,26.666666666666668 +1257,26.666666666666668 +1258,26.666666666666668 +1259,26.666666666666668 +1260,0.008 +1261,26.666666666666668 +1262,26.666666666666668 +1263,26.666666666666668 +1264,26.666666666666668 +1265,26.666666666666668 +1266,26.666666666666668 +1267,26.666666666666668 +1268,26.666666666666668 +1269,26.666666666666668 +1270,26.666666666666668 +1271,26.666666666666668 +1272,26.666666666666668 +1273,26.666666666666668 +1274,26.666666666666668 +1275,26.666666666666668 +1276,26.666666666666668 +1277,26.666666666666668 +1278,26.666666666666668 +1279,26.666666666666668 +1280,26.666666666666668 +1281,52.0 +1282,26.666666666666668 +1283,26.666666666666668 +1284,26.666666666666668 +1285,26.666666666666668 +1286,26.666666666666668 +1287,26.666666666666668 +1288,26.666666666666668 +1289,52.0 +1290,26.666666666666668 +1291,26.666666666666668 +1292,26.666666666666668 +1293,26.666666666666668 +1294,26.666666666666668 +1295,26.666666666666668 +1296,26.666666666666668 +1297,52.0 +1298,26.666666666666668 +1299,0.004 +1300,0.004 +1301,26.666666666666668 +1302,52.0 +1303,26.666666666666668 +1304,26.666666666666668 +1305,26.666666666666668 +1306,26.666666666666668 +1307,26.666666666666668 +1308,26.666666666666668 +1309,26.666666666666668 +1310,26.666666666666668 +1311,26.666666666666668 +1312,26.666666666666668 +1313,26.666666666666668 +1314,26.666666666666668 +1315,26.666666666666668 +1316,26.666666666666668 +1317,26.666666666666668 +1318,26.666666666666668 +1319,26.666666666666668 +1320,26.666666666666668 +1321,26.666666666666668 +1322,26.666666666666668 +1323,26.666666666666668 +1324,26.666666666666668 +1325,26.666666666666668 +1326,26.666666666666668 +1327,26.666666666666668 +1328,52.0 +1329,52.0 +1330,26.666666666666668 +1331,26.666666666666668 +1332,26.666666666666668 +1333,26.666666666666668 +1334,26.666666666666668 +1335,26.666666666666668 +1336,52.0 +1337,52.0 +1338,26.666666666666668 +1339,0.008 +1340,0.004 +1341,26.666666666666668 +1342,26.666666666666668 +1343,26.666666666666668 +1344,26.666666666666668 +1345,26.666666666666668 +1346,26.666666666666668 +1347,26.666666666666668 +1348,26.666666666666668 +1349,26.666666666666668 +1350,26.666666666666668 +1351,26.666666666666668 +1352,26.666666666666668 +1353,26.666666666666668 +1354,26.666666666666668 +1355,26.666666666666668 +1356,26.666666666666668 +1357,26.666666666666668 +1358,26.666666666666668 +1359,26.666666666666668 +1360,26.666666666666668 +1361,26.666666666666668 +1362,26.666666666666668 +1363,26.666666666666668 +1364,26.666666666666668 +1365,26.666666666666668 +1366,26.666666666666668 +1367,26.666666666666668 +1368,52.0 +1369,52.0 +1370,26.666666666666668 +1371,26.666666666666668 +1372,26.666666666666668 +1373,26.666666666666668 +1374,26.666666666666668 +1375,26.666666666666668 +1376,26.666666666666668 +1377,26.666666666666668 +1378,26.666666666666668 +1379,26.666666666666668 +1380,26.666666666666668 +1381,26.666666666666668 +1382,26.666666666666668 +1383,26.666666666666668 +1384,26.666666666666668 +1385,26.666666666666668 +1386,26.666666666666668 +1387,26.666666666666668 +1388,26.666666666666668 +1389,26.666666666666668 +1390,26.666666666666668 +1391,26.666666666666668 +1392,26.666666666666668 +1393,26.666666666666668 +1394,26.666666666666668 +1395,26.666666666666668 +1396,26.666666666666668 +1397,26.666666666666668 +1398,26.666666666666668 +1399,26.666666666666668 +1400,26.666666666666668 +1401,26.666666666666668 +1402,26.666666666666668 +1403,26.666666666666668 +1404,26.666666666666668 +1405,26.666666666666668 +1406,52.0 +1407,52.0 +1408,52.0 +1409,52.0 +1410,26.666666666666668 +1411,26.666666666666668 +1412,26.666666666666668 +1413,26.666666666666668 +1414,52.0 +1415,52.0 +1416,26.666666666666668 +1417,26.666666666666668 +1418,26.666666666666668 +1419,26.666666666666668 +1420,26.666666666666668 +1421,26.666666666666668 +1422,26.666666666666668 +1423,26.666666666666668 +1424,26.666666666666668 +1425,26.666666666666668 +1426,26.666666666666668 +1427,26.666666666666668 +1428,0.008 +1429,26.666666666666668 +1430,52.0 +1431,52.0 +1432,52.0 +1433,52.0 +1434,26.666666666666668 +1435,26.666666666666668 +1436,0.008 +1437,26.666666666666668 +1438,26.666666666666668 +1439,52.0 +1440,52.0 +1441,52.0 +1442,26.666666666666668 +1443,0.004 +1444,0.004 +1445,26.666666666666668 +1446,26.666666666666668 +1447,26.666666666666668 +1448,26.666666666666668 +1449,26.666666666666668 +1450,26.666666666666668 +1451,0.004 +1452,0.004 +1453,26.666666666666668 +1454,26.666666666666668 +1455,26.666666666666668 +1456,26.666666666666668 +1457,26.666666666666668 +1458,26.666666666666668 +1459,0.004 +1460,0.004 +1461,26.666666666666668 +1462,26.666666666666668 +1463,26.666666666666668 +1464,26.666666666666668 +1465,26.666666666666668 +1466,26.666666666666668 +1467,26.666666666666668 +1468,0.004 +1469,26.666666666666668 +1470,26.666666666666668 +1471,26.666666666666668 +1472,26.666666666666668 +1473,26.666666666666668 +1474,26.666666666666668 +1475,0.008 +1476,0.004 +1477,26.666666666666668 +1478,26.666666666666668 +1479,26.666666666666668 +1480,52.0 +1481,52.0 +1482,26.666666666666668 +1483,26.666666666666668 +1484,26.666666666666668 +1485,26.666666666666668 +1486,26.666666666666668 +1487,26.666666666666668 +1488,26.666666666666668 +1489,26.666666666666668 +1490,26.666666666666668 +1491,0.008 +1492,0.004 +1493,26.666666666666668 +1494,26.666666666666668 +1495,52.0 +1496,52.0 +1497,52.0 +1498,26.666666666666668 +1499,0.008 +1500,0.008 +1501,26.666666666666668 +1502,26.666666666666668 +1503,26.666666666666668 +1504,26.666666666666668 +1505,26.666666666666668 +1506,26.666666666666668 +1507,0.008 +1508,0.008 +1509,0.008 +1510,26.666666666666668 +1511,26.666666666666668 +1512,26.666666666666668 +1513,26.666666666666668 +1514,26.666666666666668 +1515,0.008 +1516,0.008 +1517,0.008 +1518,26.666666666666668 +1519,26.666666666666668 +1520,26.666666666666668 +1521,26.666666666666668 +1522,26.666666666666668 +1523,0.008 +1524,0.008 +1525,26.666666666666668 +1526,26.666666666666668 +1527,52.0 +1528,52.0 +1529,52.0 +1530,26.666666666666668 +1531,26.666666666666668 +1532,8.778064527470347 +1533,26.666666666666668 +1534,26.666666666666668 +1535,26.666666666666668 +1536,52.0 +1537,52.0 +1538,26.666666666666668 +1539,26.666666666666668 +1540,26.666666666666668 +1541,26.666666666666668 +1542,26.666666666666668 +1543,26.666666666666668 +1544,26.666666666666668 +1545,26.666666666666668 +1546,26.666666666666668 +1547,26.666666666666668 +1548,26.666666666666668 +1549,26.666666666666668 +1550,26.666666666666668 +1551,26.666666666666668 +1552,26.666666666666668 +1553,26.666666666666668 +1554,26.666666666666668 +1555,26.666666666666668 +1556,26.666666666666668 +1557,26.666666666666668 +1558,26.666666666666668 +1559,26.666666666666668 +1560,26.666666666666668 +1561,26.666666666666668 +1562,26.666666666666668 +1563,26.666666666666668 +1564,26.666666666666668 +1565,26.666666666666668 +1566,52.0 +1567,52.0 +1568,52.0 +1569,52.0 +1570,26.666666666666668 +1571,26.666666666666668 +1572,26.666666666666668 +1573,26.666666666666668 +1574,26.666666666666668 +1575,26.666666666666668 +1576,26.666666666666668 +1577,26.666666666666668 +1578,26.666666666666668 +1579,26.666666666666668 +1580,0.008 +1581,26.666666666666668 +1582,26.666666666666668 +1583,26.666666666666668 +1584,26.666666666666668 +1585,26.666666666666668 +1586,26.666666666666668 +1587,26.666666666666668 +1588,26.666666666666668 +1589,26.666666666666668 +1590,26.666666666666668 +1591,52.0 +1592,52.0 +1593,52.0 +1594,26.666666666666668 +1595,26.666666666666668 +1596,26.666666666666668 +1597,26.666666666666668 +1598,26.666666666666668 +1599,26.666666666666668 +1600,26.666666666666668 +1601,26.666666666666668 +1602,26.666666666666668 +1603,26.666666666666668 +1604,26.666666666666668 +1605,26.666666666666668 +1606,52.0 +1607,52.0 +1608,26.666666666666668 +1609,26.666666666666668 +1610,26.666666666666668 +1611,0.008 +1612,0.008 +1613,26.666666666666668 +1614,26.666666666666668 +1615,52.0 +1616,52.0 +1617,52.0 +1618,26.666666666666668 +1619,26.666666666666668 +1620,26.666666666666668 +1621,26.666666666666668 +1622,26.666666666666668 +1623,26.666666666666668 +1624,52.0 +1625,52.0 +1626,26.666666666666668 +1627,26.666666666666668 +1628,26.666666666666668 +1629,26.666666666666668 +1630,26.666666666666668 +1631,52.0 +1632,52.0 +1633,52.0 +1634,26.666666666666668 +1635,26.666666666666668 +1636,26.666666666666668 +1637,26.666666666666668 +1638,26.666666666666668 +1639,26.666666666666668 +1640,26.666666666666668 +1641,26.666666666666668 +1642,26.666666666666668 +1643,0.008 +1644,0.008 +1645,0.008 +1646,26.666666666666668 +1647,26.666666666666668 +1648,26.666666666666668 +1649,26.666666666666668 +1650,26.666666666666668 +1651,0.008 +1652,0.008 +1653,26.666666666666668 +1654,52.0 +1655,26.666666666666668 +1656,26.666666666666668 +1657,26.666666666666668 +1658,26.666666666666668 +1659,26.666666666666668 +1660,26.666666666666668 +1661,26.666666666666668 +1662,26.666666666666668 +1663,26.666666666666668 +1664,26.666666666666668 +1665,26.666666666666668 +1666,26.666666666666668 +1667,0.008 +1668,0.008 +1669,0.008 +1670,26.666666666666668 +1671,26.666666666666668 +1672,26.666666666666668 +1673,26.666666666666668 +1674,26.666666666666668 +1675,0.008 +1676,0.008 +1677,0.008 +1678,26.666666666666668 +1679,26.666666666666668 +1680,26.666666666666668 +1681,26.666666666666668 +1682,26.666666666666668 +1683,0.008 +1684,0.008 +1685,26.666666666666668 +1686,26.666666666666668 +1687,26.666666666666668 +1688,26.666666666666668 +1689,26.666666666666668 +1690,26.666666666666668 +1691,26.666666666666668 +1692,26.666666666666668 +1693,26.666666666666668 +1694,26.666666666666668 +1695,26.666666666666668 +1696,52.0 +1697,52.0 +1698,26.666666666666668 +1699,26.666666666666668 +1700,26.666666666666668 +1701,26.666666666666668 +1702,26.666666666666668 +1703,26.666666666666668 +1704,52.0 +1705,52.0 +1706,26.666666666666668 +1707,26.666666666666668 +1708,0.004 +1709,26.666666666666668 +1710,26.666666666666668 +1711,26.666666666666668 +1712,26.666666666666668 +1713,26.666666666666668 +1714,26.666666666666668 +1715,0.008 +1716,0.004 +1717,26.666666666666668 +1718,26.666666666666668 +1719,26.666666666666668 +1720,26.666666666666668 +1721,26.666666666666668 +1722,26.666666666666668 +1723,26.666666666666668 +1724,26.666666666666668 +1725,26.666666666666668 +1726,26.666666666666668 +1727,26.666666666666668 +1728,26.666666666666668 +1729,26.666666666666668 +1730,26.666666666666668 +1731,26.666666666666668 +1732,0.004 +1733,26.666666666666668 +1734,52.0 +1735,26.666666666666668 +1736,26.666666666666668 +1737,26.666666666666668 +1738,26.666666666666668 +1739,26.666666666666668 +1740,0.004 +1741,26.666666666666668 +1742,52.0 +1743,26.666666666666668 +1744,52.0 +1745,52.0 +1746,26.666666666666668 +1747,26.666666666666668 +1748,26.666666666666668 +1749,26.666666666666668 +1750,26.666666666666668 +1751,26.666666666666668 +1752,26.666666666666668 +1753,26.666666666666668 +1754,26.666666666666668 +1755,26.666666666666668 +1756,26.666666666666668 +1757,26.666666666666668 +1758,26.666666666666668 +1759,26.666666666666668 +1760,52.0 +1761,52.0 +1762,26.666666666666668 +1763,26.666666666666668 +1764,0.008 +1765,26.666666666666668 +1766,26.666666666666668 +1767,26.666666666666668 +1768,26.666666666666668 +1769,52.0 +1770,26.666666666666668 +1771,26.666666666666668 +1772,26.666666666666668 +1773,26.666666666666668 +1774,26.666666666666668 +1775,26.666666666666668 +1776,26.666666666666668 +1777,52.0 +1778,26.666666666666668 +1779,26.666666666666668 +1780,26.666666666666668 +1781,26.666666666666668 +1782,26.666666666666668 +1783,26.666666666666668 +1784,52.0 +1785,52.0 +1786,26.666666666666668 +1787,26.666666666666668 +1788,26.666666666666668 +1789,26.666666666666668 +1790,26.666666666666668 +1791,26.666666666666668 +1792,26.666666666666668 +1793,26.666666666666668 +1794,26.666666666666668 +1795,0.008 +1796,0.004 +1797,26.666666666666668 +1798,26.666666666666668 +1799,26.666666666666668 +1800,26.666666666666668 +1801,26.666666666666668 +1802,26.666666666666668 +1803,26.666666666666668 +1804,0.008 +1805,26.666666666666668 +1806,26.666666666666668 +1807,26.666666666666668 +1808,26.666666666666668 +1809,52.0 +1810,26.666666666666668 +1811,26.666666666666668 +1812,26.666666666666668 +1813,26.666666666666668 +1814,52.0 +1815,52.0 +1816,52.0 +1817,52.0 +1818,26.666666666666668 +1819,26.666666666666668 +1820,26.666666666666668 +1821,26.666666666666668 +1822,26.666666666666668 +1823,26.666666666666668 +1824,26.666666666666668 +1825,26.666666666666668 +1826,26.666666666666668 +1827,26.666666666666668 +1828,26.666666666666668 +1829,26.666666666666668 +1830,26.666666666666668 +1831,26.666666666666668 +1832,26.666666666666668 +1833,26.666666666666668 +1834,26.666666666666668 +1835,26.666666666666668 +1836,26.666666666666668 +1837,26.666666666666668 +1838,52.0 +1839,52.0 +1840,52.0 +1841,52.0 +1842,26.666666666666668 +1843,26.666666666666668 +1844,26.666666666666668 +1845,26.666666666666668 +1846,52.0 +1847,52.0 +1848,52.0 +1849,52.0 +1850,26.666666666666668 +1851,26.666666666666668 +1852,26.666666666666668 +1853,26.666666666666668 +1854,52.0 +1855,52.0 +1856,52.0 +1857,52.0 +1858,26.666666666666668 +1859,26.666666666666668 +1860,26.666666666666668 +1861,26.666666666666668 +1862,52.0 +1863,26.666666666666668 +1864,26.666666666666668 +1865,52.0 +1866,26.666666666666668 +1867,0.008 +1868,0.008 +1869,26.666666666666668 +1870,26.666666666666668 +1871,26.666666666666668 +1872,26.666666666666668 +1873,26.666666666666668 +1874,26.666666666666668 +1875,0.008 +1876,0.008 +1877,26.666666666666668 +1878,26.666666666666668 +1879,26.666666666666668 +1880,26.666666666666668 +1881,26.666666666666668 +1882,26.666666666666668 +1883,26.666666666666668 +1884,26.666666666666668 +1885,26.666666666666668 +1886,26.666666666666668 +1887,26.666666666666668 +1888,26.666666666666668 +1889,26.666666666666668 +1890,26.666666666666668 +1891,0.008 +1892,0.008 +1893,26.666666666666668 +1894,26.666666666666668 +1895,26.666666666666668 +1896,26.666666666666668 +1897,26.666666666666668 +1898,26.666666666666668 +1899,0.008 +1900,0.004 +1901,26.666666666666668 +1902,26.666666666666668 +1903,26.666666666666668 +1904,26.666666666666668 +1905,26.666666666666668 +1906,26.666666666666668 +1907,26.666666666666668 +1908,26.666666666666668 +1909,26.666666666666668 +1910,26.666666666666668 +1911,26.666666666666668 +1912,26.666666666666668 +1913,52.0 +1914,26.666666666666668 +1915,26.666666666666668 +1916,26.666666666666668 +1917,26.666666666666668 +1918,52.0 +1919,52.0 +1920,52.0 +1921,52.0 +1922,26.666666666666668 +1923,26.666666666666668 +1924,26.666666666666668 +1925,26.666666666666668 +1926,52.0 +1927,52.0 +1928,52.0 +1929,52.0 +1930,26.666666666666668 +1931,26.666666666666668 +1932,26.666666666666668 +1933,26.666666666666668 +1934,52.0 +1935,52.0 +1936,52.0 +1937,52.0 +1938,26.666666666666668 +1939,26.666666666666668 +1940,0.008 +1941,26.666666666666668 +1942,26.666666666666668 +1943,26.666666666666668 +1944,26.666666666666668 +1945,26.666666666666668 +1946,26.666666666666668 +1947,26.666666666666668 +1948,26.666666666666668 +1949,26.666666666666668 +1950,26.666666666666668 +1951,26.666666666666668 +1952,26.666666666666668 +1953,26.666666666666668 +1954,26.666666666666668 +1955,0.008 +1956,26.666666666666668 +1957,26.666666666666668 +1958,52.0 +1959,52.0 +1960,52.0 +1961,52.0 +1962,26.666666666666668 +1963,26.666666666666668 +1964,26.666666666666668 +1965,26.666666666666668 +1966,52.0 +1967,52.0 +1968,26.666666666666668 +1969,26.666666666666668 +1970,26.666666666666668 +1971,26.666666666666668 +1972,26.666666666666668 +1973,26.666666666666668 +1974,26.666666666666668 +1975,26.666666666666668 +1976,26.666666666666668 +1977,26.666666666666668 +1978,26.666666666666668 +1979,0.008 +1980,0.008 +1981,26.666666666666668 +1982,26.666666666666668 +1983,26.666666666666668 +1984,26.666666666666668 +1985,26.666666666666668 +1986,26.666666666666668 +1987,0.008 +1988,0.008 +1989,26.666666666666668 +1990,26.666666666666668 +1991,26.666666666666668 +1992,26.666666666666668 +1993,26.666666666666668 +1994,26.666666666666668 +1995,26.666666666666668 +1996,26.666666666666668 +1997,26.666666666666668 +1998,26.666666666666668 +1999,26.666666666666668 +2000,52.0 +2001,52.0 +2002,26.666666666666668 +2003,26.666666666666668 +2004,26.666666666666668 +2005,26.666666666666668 +2006,26.666666666666668 +2007,52.0 +2008,52.0 +2009,52.0 +2010,26.666666666666668 +2011,26.666666666666668 +2012,26.666666666666668 +2013,26.666666666666668 +2014,26.666666666666668 +2015,26.666666666666668 +2016,26.666666666666668 +2017,26.666666666666668 +2018,26.666666666666668 +2019,26.666666666666668 +2020,26.666666666666668 +2021,26.666666666666668 +2022,26.666666666666668 +2023,26.666666666666668 +2024,26.666666666666668 +2025,26.666666666666668 +2026,26.666666666666668 +2027,26.666666666666668 +2028,26.666666666666668 +2029,26.666666666666668 +2030,26.666666666666668 +2031,26.666666666666668 +2032,26.666666666666668 +2033,26.666666666666668 +2034,26.666666666666668 +2035,26.666666666666668 +2036,0.008 +2037,26.666666666666668 +2038,26.666666666666668 +2039,26.666666666666668 +2040,26.666666666666668 +2041,26.666666666666668 +2042,26.666666666666668 +2043,26.666666666666668 +2044,26.666666666666668 +2045,26.666666666666668 +2046,26.666666666666668 +2047,26.666666666666668 +2048,26.666666666666668 +2049,26.666666666666668 +2050,26.666666666666668 +2051,26.666666666666668 +2052,26.666666666666668 +2053,26.666666666666668 +2054,26.666666666666668 +2055,26.666666666666668 +2056,26.666666666666668 +2057,26.666666666666668 +2058,26.666666666666668 +2059,0.008 +2060,0.008 +2061,26.666666666666668 +2062,26.666666666666668 +2063,26.666666666666668 +2064,26.666666666666668 +2065,26.666666666666668 +2066,26.666666666666668 +2067,26.666666666666668 +2068,26.666666666666668 +2069,26.666666666666668 +2070,26.666666666666668 +2071,26.666666666666668 +2072,26.666666666666668 +2073,26.666666666666668 +2074,26.666666666666668 +2075,0.008 +2076,26.666666666666668 +2077,26.666666666666668 +2078,26.666666666666668 +2079,26.666666666666668 +2080,26.666666666666668 +2081,26.666666666666668 +2082,26.666666666666668 +2083,26.666666666666668 +2084,26.666666666666668 +2085,26.666666666666668 +2086,26.666666666666668 +2087,26.666666666666668 +2088,26.666666666666668 +2089,26.666666666666668 +2090,26.666666666666668 +2091,26.666666666666668 +2092,26.666666666666668 +2093,26.666666666666668 +2094,26.666666666666668 +2095,26.666666666666668 +2096,26.666666666666668 +2097,26.666666666666668 +2098,26.666666666666668 +2099,26.666666666666668 +2100,26.666666666666668 +2101,26.666666666666668 +2102,26.666666666666668 +2103,26.666666666666668 +2104,26.666666666666668 +2105,26.666666666666668 +2106,26.666666666666668 +2107,26.666666666666668 +2108,26.666666666666668 +2109,26.666666666666668 +2110,52.0 +2111,26.666666666666668 +2112,26.666666666666668 +2113,26.666666666666668 +2114,26.666666666666668 +2115,26.666666666666668 +2116,26.666666666666668 +2117,26.666666666666668 +2118,26.666666666666668 +2119,26.666666666666668 +2120,26.666666666666668 +2121,26.666666666666668 +2122,26.666666666666668 +2123,26.666666666666668 +2124,26.666666666666668 +2125,26.666666666666668 +2126,52.0 +2127,26.666666666666668 +2128,26.666666666666668 +2129,26.666666666666668 +2130,26.666666666666668 +2131,26.666666666666668 +2132,26.666666666666668 +2133,26.666666666666668 +2134,26.666666666666668 +2135,26.666666666666668 +2136,26.666666666666668 +2137,26.666666666666668 +2138,26.666666666666668 +2139,26.666666666666668 +2140,26.666666666666668 +2141,26.666666666666668 +2142,52.0 +2143,52.0 +2144,52.0 +2145,52.0 +2146,26.666666666666668 +2147,26.666666666666668 +2148,26.666666666666668 +2149,26.666666666666668 +2150,26.666666666666668 +2151,26.666666666666668 +2152,26.666666666666668 +2153,52.0 +2154,26.666666666666668 +2155,26.666666666666668 +2156,26.666666666666668 +2157,26.666666666666668 +2158,26.666666666666668 +2159,26.666666666666668 +2160,52.0 +2161,52.0 +2162,26.666666666666668 +2163,26.666666666666668 +2164,26.666666666666668 +2165,26.666666666666668 +2166,26.666666666666668 +2167,26.666666666666668 +2168,26.666666666666668 +2169,26.666666666666668 +2170,26.666666666666668 +2171,26.666666666666668 +2172,26.666666666666668 +2173,26.666666666666668 +2174,26.666666666666668 +2175,26.666666666666668 +2176,26.666666666666668 +2177,26.666666666666668 +2178,26.666666666666668 +2179,26.666666666666668 +2180,26.666666666666668 +2181,26.666666666666668 +2182,26.666666666666668 +2183,26.666666666666668 +2184,26.666666666666668 +2185,52.0 +2186,26.666666666666668 +2187,0.008 +2188,0.008 +2189,26.666666666666668 +2190,52.0 +2191,52.0 +2192,52.0 +2193,52.0 +2194,26.666666666666668 +2195,26.666666666666668 +2196,0.008 +2197,26.666666666666668 +2198,26.666666666666668 +2199,26.666666666666668 +2200,26.666666666666668 +2201,52.0 +2202,26.666666666666668 +2203,26.666666666666668 +2204,26.666666666666668 +2205,26.666666666666668 +2206,52.0 +2207,52.0 +2208,52.0 +2209,26.666666666666668 +2210,26.666666666666668 +2211,26.666666666666668 +2212,26.666666666666668 +2213,26.666666666666668 +2214,52.0 +2215,52.0 +2216,52.0 +2217,52.0 +2218,26.666666666666668 +2219,26.666666666666668 +2220,26.666666666666668 +2221,26.666666666666668 +2222,26.666666666666668 +2223,26.666666666666668 +2224,26.666666666666668 +2225,26.666666666666668 +2226,26.666666666666668 +2227,26.666666666666668 +2228,26.666666666666668 +2229,26.666666666666668 +2230,26.666666666666668 +2231,26.666666666666668 +2232,26.666666666666668 +2233,26.666666666666668 +2234,26.666666666666668 +2235,26.666666666666668 +2236,26.666666666666668 +2237,26.666666666666668 +2238,26.666666666666668 +2239,26.666666666666668 +2240,26.666666666666668 +2241,52.0 +2242,26.666666666666668 +2243,26.666666666666668 +2244,26.666666666666668 +2245,26.666666666666668 +2246,52.0 +2247,52.0 +2248,52.0 +2249,52.0 +2250,52.0 +2251,26.666666666666668 +2252,26.666666666666668 +2253,26.666666666666668 +2254,26.666666666666668 +2255,26.666666666666668 +2256,26.666666666666668 +2257,26.666666666666668 +2258,26.666666666666668 +2259,26.666666666666668 +2260,26.666666666666668 +2261,26.666666666666668 +2262,26.666666666666668 +2263,26.666666666666668 +2264,26.666666666666668 +2265,26.666666666666668 +2266,26.666666666666668 +2267,0.008 +2268,0.008 +2269,26.666666666666668 +2270,26.666666666666668 +2271,26.666666666666668 +2272,26.666666666666668 +2273,26.666666666666668 +2274,26.666666666666668 +2275,0.008 +2276,26.666666666666668 +2277,26.666666666666668 +2278,26.666666666666668 +2279,26.666666666666668 +2280,26.666666666666668 +2281,26.666666666666668 +2282,26.666666666666668 +2283,26.666666666666668 +2284,26.666666666666668 +2285,26.666666666666668 +2286,52.0 +2287,52.0 +2288,52.0 +2289,52.0 +2290,26.666666666666668 +2291,26.666666666666668 +2292,26.666666666666668 +2293,26.666666666666668 +2294,26.666666666666668 +2295,26.666666666666668 +2296,52.0 +2297,26.666666666666668 +2298,26.666666666666668 +2299,26.666666666666668 +2300,26.666666666666668 +2301,26.666666666666668 +2302,26.666666666666668 +2303,52.0 +2304,52.0 +2305,52.0 +2306,52.0 +2307,26.666666666666668 +2308,26.666666666666668 +2309,52.0 +2310,52.0 +2311,52.0 +2312,52.0 +2313,52.0 +2314,52.0 +2315,26.666666666666668 +2316,26.666666666666668 +2317,52.0 +2318,52.0 +2319,52.0 +2320,52.0 +2321,52.0 +2322,52.0 +2323,26.666666666666668 +2324,26.666666666666668 +2325,52.0 +2326,52.0 +2327,52.0 +2328,52.0 +2329,52.0 +2330,52.0 +2331,26.666666666666668 +2332,26.666666666666668 +2333,52.0 +2334,52.0 +2335,52.0 +2336,52.0 +2337,52.0 +2338,52.0 +2339,26.666666666666668 +2340,26.666666666666668 +2341,52.0 +2342,52.0 +2343,52.0 +2344,52.0 +2345,52.0 +2346,52.0 +2347,26.666666666666668 +2348,26.666666666666668 +2349,26.666666666666668 +2350,26.666666666666668 +2351,26.666666666666668 +2352,26.666666666666668 +2353,26.666666666666668 +2354,26.666666666666668 +2355,26.666666666666668 +2356,26.666666666666668 +2357,26.666666666666668 +2358,26.666666666666668 +2359,26.666666666666668 +2360,26.666666666666668 +2361,26.666666666666668 +2362,26.666666666666668 +2363,26.666666666666668 +2364,26.666666666666668 +2365,26.666666666666668 +2366,52.0 +2367,52.0 +2368,52.0 +2369,26.666666666666668 +2370,26.666666666666668 +2371,26.666666666666668 +2372,26.666666666666668 +2373,26.666666666666668 +2374,26.666666666666668 +2375,26.666666666666668 +2376,26.666666666666668 +2377,26.666666666666668 +2378,26.666666666666668 +2379,26.666666666666668 +2380,26.666666666666668 +2381,26.666666666666668 +2382,52.0 +2383,52.0 +2384,52.0 +2385,52.0 +2386,52.0 +2387,26.666666666666668 +2388,26.666666666666668 +2389,26.666666666666668 +2390,26.666666666666668 +2391,26.666666666666668 +2392,26.666666666666668 +2393,26.666666666666668 +2394,26.666666666666668 +2395,26.666666666666668 +2396,26.666666666666668 +2397,26.666666666666668 +2398,26.666666666666668 +2399,26.666666666666668 +2400,26.666666666666668 +2401,26.666666666666668 +2402,26.666666666666668 +2403,26.666666666666668 +2404,26.666666666666668 +2405,26.666666666666668 +2406,26.666666666666668 +2407,26.666666666666668 +2408,52.0 +2409,52.0 +2410,52.0 +2411,26.666666666666668 +2412,26.666666666666668 +2413,52.0 +2414,52.0 +2415,52.0 +2416,52.0 +2417,52.0 +2418,52.0 +2419,26.666666666666668 +2420,26.666666666666668 +2421,26.666666666666668 +2422,26.666666666666668 +2423,26.666666666666668 +2424,26.666666666666668 +2425,26.666666666666668 +2426,26.666666666666668 +2427,26.666666666666668 +2428,26.666666666666668 +2429,26.666666666666668 +2430,26.666666666666668 +2431,26.666666666666668 +2432,26.666666666666668 +2433,26.666666666666668 +2434,26.666666666666668 +2435,26.666666666666668 +2436,26.666666666666668 +2437,26.666666666666668 +2438,52.0 +2439,52.0 +2440,52.0 +2441,52.0 +2442,52.0 +2443,26.666666666666668 +2444,26.666666666666668 +2445,26.666666666666668 +2446,26.666666666666668 +2447,26.666666666666668 +2448,52.0 +2449,52.0 +2450,52.0 +2451,26.666666666666668 +2452,26.666666666666668 +2453,26.666666666666668 +2454,52.0 +2455,52.0 +2456,52.0 +2457,26.666666666666668 +2458,26.666666666666668 +2459,26.666666666666668 +2460,26.666666666666668 +2461,26.666666666666668 +2462,26.666666666666668 +2463,26.666666666666668 +2464,26.666666666666668 +2465,26.666666666666668 +2466,26.666666666666668 +2467,26.666666666666668 +2468,26.666666666666668 +2469,26.666666666666668 +2470,26.666666666666668 +2471,26.666666666666668 +2472,26.666666666666668 +2473,26.666666666666668 +2474,26.666666666666668 +2475,26.666666666666668 +2476,26.666666666666668 +2477,26.666666666666668 +2478,26.666666666666668 +2479,26.666666666666668 +2480,26.666666666666668 +2481,26.666666666666668 +2482,26.666666666666668 +2483,26.666666666666668 +2484,26.666666666666668 +2485,26.666666666666668 +2486,26.666666666666668 +2487,26.666666666666668 +2488,26.666666666666668 +2489,26.666666666666668 +2490,26.666666666666668 +2491,26.666666666666668 +2492,26.666666666666668 +2493,26.666666666666668 +2494,26.666666666666668 +2495,26.666666666666668 +2496,26.666666666666668 +2497,26.666666666666668 +2498,26.666666666666668 +2499,26.666666666666668 +2500,26.666666666666668 +2501,26.666666666666668 +2502,26.666666666666668 +2503,26.666666666666668 +2504,26.666666666666668 +2505,26.666666666666668 +2506,26.666666666666668 +2507,26.666666666666668 +2508,26.666666666666668 +2509,26.666666666666668 +2510,26.666666666666668 +2511,26.666666666666668 +2512,26.666666666666668 +2513,26.666666666666668 +2514,26.666666666666668 +2515,26.666666666666668 +2516,26.666666666666668 +2517,26.666666666666668 +2518,26.666666666666668 +2519,26.666666666666668 +2520,26.666666666666668 +2521,26.666666666666668 +2522,26.666666666666668 +2523,26.666666666666668 +2524,26.666666666666668 +2525,26.666666666666668 +2526,26.666666666666668 +2527,26.666666666666668 +2528,26.666666666666668 +2529,26.666666666666668 +2530,26.666666666666668 +2531,26.666666666666668 +2532,26.666666666666668 +2533,26.666666666666668 +2534,26.666666666666668 +2535,26.666666666666668 +2536,26.666666666666668 +2537,26.666666666666668 +2538,26.666666666666668 +2539,0.008 +2540,0.008 +2541,26.666666666666668 +2542,26.666666666666668 +2543,26.666666666666668 +2544,26.666666666666668 +2545,26.666666666666668 +2546,26.666666666666668 +2547,26.666666666666668 +2548,26.666666666666668 +2549,26.666666666666668 +2550,26.666666666666668 +2551,26.666666666666668 +2552,26.666666666666668 +2553,26.666666666666668 +2554,26.666666666666668 +2555,26.666666666666668 +2556,26.666666666666668 +2557,26.666666666666668 +2558,26.666666666666668 +2559,26.666666666666668 +2560,26.666666666666668 +2561,26.666666666666668 +2562,26.666666666666668 +2563,26.666666666666668 +2564,26.666666666666668 +2565,26.666666666666668 +2566,26.666666666666668 +2567,26.666666666666668 +2568,0.008 +2569,0.008 +2570,0.008 +2571,0.008 +2572,0.008 +2573,26.666666666666668 +2574,26.666666666666668 +2575,26.666666666666668 +2576,26.666666666666668 +2577,26.666666666666668 +2578,26.666666666666668 +2579,0.008 +2580,0.008 +2581,26.666666666666668 +2582,26.666666666666668 +2583,26.666666666666668 +2584,26.666666666666668 +2585,26.666666666666668 +2586,26.666666666666668 +2587,26.666666666666668 +2588,26.666666666666668 +2589,26.666666666666668 +2590,26.666666666666668 +2591,26.666666666666668 +2592,26.666666666666668 +2593,26.666666666666668 +2594,26.666666666666668 +2595,26.666666666666668 +2596,26.666666666666668 +2597,26.666666666666668 +2598,26.666666666666668 +2599,26.666666666666668 +2600,26.666666666666668 +2601,26.666666666666668 +2602,26.666666666666668 +2603,26.666666666666668 +2604,26.666666666666668 +2605,26.666666666666668 +2606,26.666666666666668 +2607,26.666666666666668 +2608,26.666666666666668 +2609,26.666666666666668 +2610,26.666666666666668 +2611,26.666666666666668 +2612,26.666666666666668 +2613,26.666666666666668 +2614,26.666666666666668 +2615,26.666666666666668 +2616,26.666666666666668 +2617,26.666666666666668 +2618,26.666666666666668 +2619,26.666666666666668 +2620,26.666666666666668 +2621,26.666666666666668 +2622,26.666666666666668 +2623,26.666666666666668 +2624,26.666666666666668 +2625,26.666666666666668 +2626,26.666666666666668 +2627,26.666666666666668 +2628,26.666666666666668 +2629,26.666666666666668 +2630,26.666666666666668 +2631,26.666666666666668 +2632,26.666666666666668 +2633,26.666666666666668 +2634,26.666666666666668 +2635,26.666666666666668 +2636,26.666666666666668 +2637,52.0 +2638,52.0 +2639,52.0 +2640,52.0 +2641,52.0 +2642,26.666666666666668 +2643,26.666666666666668 +2644,26.666666666666668 +2645,26.666666666666668 +2646,26.666666666666668 +2647,26.666666666666668 +2648,26.666666666666668 +2649,26.666666666666668 +2650,26.666666666666668 +2651,26.666666666666668 +2652,26.666666666666668 +2653,26.666666666666668 +2654,26.666666666666668 +2655,26.666666666666668 +2656,26.666666666666668 +2657,26.666666666666668 +2658,26.666666666666668 +2659,26.666666666666668 +2660,26.666666666666668 +2661,26.666666666666668 +2662,26.666666666666668 +2663,0.008 +2664,26.666666666666668 +2665,26.666666666666668 +2666,26.666666666666668 +2667,26.666666666666668 +2668,26.666666666666668 +2669,26.666666666666668 +2670,26.666666666666668 +2671,26.666666666666668 +2672,26.666666666666668 +2673,26.666666666666668 +2674,26.666666666666668 +2675,26.666666666666668 +2676,26.666666666666668 +2677,26.666666666666668 +2678,26.666666666666668 +2679,26.666666666666668 +2680,26.666666666666668 +2681,26.666666666666668 +2682,26.666666666666668 +2683,26.666666666666668 +2684,26.666666666666668 +2685,26.666666666666668 +2686,26.666666666666668 +2687,26.666666666666668 +2688,26.666666666666668 +2689,26.666666666666668 +2690,26.666666666666668 +2691,26.666666666666668 +2692,26.666666666666668 +2693,26.666666666666668 +2694,26.666666666666668 +2695,26.666666666666668 +2696,26.666666666666668 +2697,26.666666666666668 +2698,26.666666666666668 +2699,26.666666666666668 +2700,26.666666666666668 +2701,26.666666666666668 +2702,26.666666666666668 +2703,26.666666666666668 +2704,26.666666666666668 +2705,26.666666666666668 +2706,26.666666666666668 +2707,26.666666666666668 +2708,0.008 +2709,26.666666666666668 +2710,26.666666666666668 +2711,26.666666666666668 +2712,26.666666666666668 +2713,26.666666666666668 +2714,26.666666666666668 +2715,0.008 +2716,0.008 +2717,26.666666666666668 +2718,26.666666666666668 +2719,26.666666666666668 +2720,26.666666666666668 +2721,26.666666666666668 +2722,26.666666666666668 +2723,26.666666666666668 +2724,26.666666666666668 +2725,52.0 +2726,52.0 +2727,26.666666666666668 +2728,26.666666666666668 +2729,26.666666666666668 +2730,26.666666666666668 +2731,26.666666666666668 +2732,26.666666666666668 +2733,26.666666666666668 +2734,26.666666666666668 +2735,26.666666666666668 +2736,26.666666666666668 +2737,26.666666666666668 +2738,26.666666666666668 +2739,26.666666666666668 +2740,26.666666666666668 +2741,26.666666666666668 +2742,26.666666666666668 +2743,26.666666666666668 +2744,26.666666666666668 +2745,26.666666666666668 +2746,26.666666666666668 +2747,26.666666666666668 +2748,26.666666666666668 +2749,26.666666666666668 +2750,26.666666666666668 +2751,26.666666666666668 +2752,26.666666666666668 +2753,26.666666666666668 +2754,26.666666666666668 +2755,26.666666666666668 +2756,26.666666666666668 +2757,26.666666666666668 +2758,26.666666666666668 +2759,26.666666666666668 +2760,26.666666666666668 +2761,26.666666666666668 +2762,26.666666666666668 +2763,26.666666666666668 +2764,26.666666666666668 +2765,26.666666666666668 +2766,26.666666666666668 +2767,26.666666666666668 +2768,26.666666666666668 +2769,26.666666666666668 +2770,26.666666666666668 +2771,26.666666666666668 +2772,26.666666666666668 +2773,26.666666666666668 +2774,52.0 +2775,52.0 +2776,52.0 +2777,52.0 +2778,52.0 +2779,26.666666666666668 +2780,26.666666666666668 +2781,26.666666666666668 +2782,26.666666666666668 +2783,26.666666666666668 +2784,26.666666666666668 +2785,52.0 +2786,52.0 +2787,26.666666666666668 +2788,26.666666666666668 +2789,52.0 +2790,52.0 +2791,52.0 +2792,26.666666666666668 +2793,26.666666666666668 +2794,26.666666666666668 +2795,26.666666666666668 +2796,26.666666666666668 +2797,26.666666666666668 +2798,26.666666666666668 +2799,26.666666666666668 +2800,26.666666666666668 +2801,26.666666666666668 +2802,26.666666666666668 +2803,26.666666666666668 +2804,26.666666666666668 +2805,26.666666666666668 +2806,26.666666666666668 +2807,26.666666666666668 +2808,26.666666666666668 +2809,26.666666666666668 +2810,26.666666666666668 +2811,26.666666666666668 +2812,26.666666666666668 +2813,26.666666666666668 +2814,26.666666666666668 +2815,26.666666666666668 +2816,26.666666666666668 +2817,26.666666666666668 +2818,26.666666666666668 +2819,26.666666666666668 +2820,26.666666666666668 +2821,26.666666666666668 +2822,26.666666666666668 +2823,26.666666666666668 +2824,26.666666666666668 +2825,26.666666666666668 +2826,26.666666666666668 +2827,26.666666666666668 +2828,26.666666666666668 +2829,26.666666666666668 +2830,26.666666666666668 +2831,26.666666666666668 +2832,26.666666666666668 +2833,26.666666666666668 +2834,26.666666666666668 +2835,26.666666666666668 +2836,26.666666666666668 +2837,26.666666666666668 +2838,26.666666666666668 +2839,26.666666666666668 +2840,26.666666666666668 +2841,26.666666666666668 +2842,26.666666666666668 +2843,26.666666666666668 +2844,0.008 +2845,26.666666666666668 +2846,26.666666666666668 +2847,26.666666666666668 +2848,26.666666666666668 +2849,26.666666666666668 +2850,26.666666666666668 +2851,26.666666666666668 +2852,0.008 +2853,26.666666666666668 +2854,26.666666666666668 +2855,26.666666666666668 +2856,26.666666666666668 +2857,26.666666666666668 +2858,26.666666666666668 +2859,26.666666666666668 +2860,26.666666666666668 +2861,26.666666666666668 +2862,26.666666666666668 +2863,26.666666666666668 +2864,26.666666666666668 +2865,26.666666666666668 +2866,26.666666666666668 +2867,26.666666666666668 +2868,26.666666666666668 +2869,26.666666666666668 +2870,26.666666666666668 +2871,26.666666666666668 +2872,26.666666666666668 +2873,26.666666666666668 +2874,26.666666666666668 +2875,0.008 +2876,0.008 +2877,26.666666666666668 +2878,26.666666666666668 +2879,26.666666666666668 +2880,26.666666666666668 +2881,26.666666666666668 +2882,26.666666666666668 +2883,26.666666666666668 +2884,26.666666666666668 +2885,26.666666666666668 +2886,26.666666666666668 +2887,26.666666666666668 +2888,26.666666666666668 +2889,26.666666666666668 +2890,52.0 +2891,26.666666666666668 +2892,26.666666666666668 +2893,26.666666666666668 +2894,26.666666666666668 +2895,26.666666666666668 +2896,26.666666666666668 +2897,26.666666666666668 +2898,26.666666666666668 +2899,26.666666666666668 +2900,26.666666666666668 +2901,26.666666666666668 +2902,26.666666666666668 +2903,26.666666666666668 +2904,26.666666666666668 +2905,26.666666666666668 +2906,26.666666666666668 +2907,26.666666666666668 +2908,26.666666666666668 +2909,26.666666666666668 +2910,26.666666666666668 +2911,26.666666666666668 +2912,26.666666666666668 +2913,26.666666666666668 +2914,26.666666666666668 +2915,26.666666666666668 +2916,26.666666666666668 +2917,26.666666666666668 +2918,26.666666666666668 +2919,26.666666666666668 diff --git a/PyPSA/source/test/data/benchmark-sp/buses-p.csv b/PyPSA/source/test/data/benchmark-sp/buses-p.csv new file mode 100644 index 0000000000000000000000000000000000000000..504543d6da70f48bed039c854595658679d73ca4 --- /dev/null +++ b/PyPSA/source/test/data/benchmark-sp/buses-p.csv @@ -0,0 +1,2921 @@ +,DE +0,0.0 +1,0.0 +2,0.0 +3,-1.1102230246251565e-16 +4,0.0 +5,-1.1102230246251565e-16 +6,0.0 +7,0.0 +8,0.0 +9,0.0 +10,0.0 +11,0.0 +12,0.0 +13,0.0 +14,0.0 +15,0.0 +16,0.0 +17,0.0 +18,0.0 +19,0.0 +20,0.0 +21,-1.1102230246251565e-16 +22,0.0 +23,0.0 +24,0.0 +25,0.0 +26,0.0 +27,0.0 +28,0.0 +29,-1.1102230246251565e-16 +30,0.0 +31,0.0 +32,0.0 +33,0.0 +34,0.0 +35,0.0 +36,0.0 +37,0.0 +38,0.0 +39,0.0 +40,0.0 +41,0.0 +42,0.0 +43,0.0 +44,0.0 +45,0.0 +46,0.0 +47,0.0 +48,0.0 +49,0.0 +50,0.0 +51,0.0 +52,0.0 +53,-1.1102230246251565e-16 +54,0.0 +55,0.0 +56,0.0 +57,0.0 +58,0.0 +59,0.0 +60,0.0 +61,-1.1102230246251565e-16 +62,0.0 +63,0.0 +64,0.0 +65,0.0 +66,0.0 +67,0.0 +68,0.0 +69,0.0 +70,0.0 +71,0.0 +72,0.0 +73,0.0 +74,0.0 +75,0.0 +76,0.0 +77,0.0 +78,0.0 +79,0.0 +80,0.0 +81,0.0 +82,0.0 +83,0.0 +84,0.0 +85,0.0 +86,0.0 +87,0.0 +88,0.0 +89,0.0 +90,0.0 +91,0.0 +92,0.0 +93,0.0 +94,0.0 +95,0.0 +96,0.0 +97,0.0 +98,0.0 +99,0.0 +100,0.0 +101,0.0 +102,0.0 +103,0.0 +104,0.0 +105,0.0 +106,0.0 +107,0.0 +108,0.0 +109,0.0 +110,0.0 +111,0.0 +112,0.0 +113,0.0 +114,0.0 +115,0.0 +116,0.0 +117,0.0 +118,0.0 +119,0.0 +120,0.0 +121,0.0 +122,0.0 +123,0.0 +124,0.0 +125,-1.1102230246251565e-16 +126,0.0 +127,0.0 +128,0.0 +129,0.0 +130,0.0 +131,0.0 +132,0.0 +133,1.1102230246251565e-16 +134,0.0 +135,0.0 +136,0.0 +137,0.0 +138,0.0 +139,0.0 +140,0.0 +141,0.0 +142,0.0 +143,0.0 +144,0.0 +145,0.0 +146,0.0 +147,0.0 +148,0.0 +149,0.0 +150,0.0 +151,0.0 +152,0.0 +153,0.0 +154,0.0 +155,-1.1102230246251565e-16 +156,-1.1102230246251565e-16 +157,1.1102230246251565e-16 +158,0.0 +159,0.0 +160,0.0 +161,0.0 +162,0.0 +163,0.0 +164,-1.1102230246251565e-16 +165,0.0 +166,0.0 +167,0.0 +168,0.0 +169,0.0 +170,0.0 +171,0.0 +172,0.0 +173,-1.1102230246251565e-16 +174,0.0 +175,0.0 +176,0.0 +177,0.0 +178,0.0 +179,0.0 +180,1.1102230246251565e-16 +181,1.1102230246251565e-16 +182,0.0 +183,0.0 +184,0.0 +185,0.0 +186,0.0 +187,0.0 +188,-1.1102230246251565e-16 +189,0.0 +190,0.0 +191,0.0 +192,0.0 +193,0.0 +194,0.0 +195,0.0 +196,0.0 +197,-1.1102230246251565e-16 +198,0.0 +199,0.0 +200,0.0 +201,0.0 +202,0.0 +203,1.1102230246251565e-16 +204,0.0 +205,1.1102230246251565e-16 +206,0.0 +207,0.0 +208,0.0 +209,0.0 +210,0.0 +211,0.0 +212,0.0 +213,0.0 +214,0.0 +215,0.0 +216,0.0 +217,0.0 +218,0.0 +219,0.0 +220,0.0 +221,0.0 +222,0.0 +223,0.0 +224,0.0 +225,0.0 +226,0.0 +227,0.0 +228,0.0 +229,0.0 +230,0.0 +231,0.0 +232,0.0 +233,0.0 +234,0.0 +235,1.1102230246251565e-16 +236,0.0 +237,-1.1102230246251565e-16 +238,0.0 +239,0.0 +240,0.0 +241,0.0 +242,0.0 +243,-1.1102230246251565e-16 +244,0.0 +245,0.0 +246,0.0 +247,0.0 +248,0.0 +249,0.0 +250,0.0 +251,0.0 +252,0.0 +253,-1.1102230246251565e-16 +254,0.0 +255,0.0 +256,0.0 +257,0.0 +258,0.0 +259,0.0 +260,0.0 +261,0.0 +262,0.0 +263,0.0 +264,0.0 +265,0.0 +266,0.0 +267,0.0 +268,0.0 +269,1.1102230246251565e-16 +270,0.0 +271,0.0 +272,0.0 +273,0.0 +274,0.0 +275,0.0 +276,0.0 +277,-1.1102230246251565e-16 +278,0.0 +279,0.0 +280,0.0 +281,0.0 +282,0.0 +283,0.0 +284,0.0 +285,1.1102230246251565e-16 +286,0.0 +287,0.0 +288,0.0 +289,0.0 +290,0.0 +291,0.0 +292,0.0 +293,0.0 +294,0.0 +295,0.0 +296,0.0 +297,0.0 +298,0.0 +299,0.0 +300,0.0 +301,0.0 +302,0.0 +303,0.0 +304,0.0 +305,0.0 +306,0.0 +307,0.0 +308,0.0 +309,0.0 +310,0.0 +311,0.0 +312,0.0 +313,0.0 +314,0.0 +315,0.0 +316,0.0 +317,-1.1102230246251565e-16 +318,0.0 +319,0.0 +320,0.0 +321,0.0 +322,0.0 +323,0.0 +324,-1.1102230246251565e-16 +325,0.0 +326,0.0 +327,0.0 +328,0.0 +329,0.0 +330,0.0 +331,0.0 +332,0.0 +333,0.0 +334,0.0 +335,0.0 +336,0.0 +337,0.0 +338,0.0 +339,0.0 +340,0.0 +341,0.0 +342,0.0 +343,0.0 +344,0.0 +345,0.0 +346,0.0 +347,0.0 +348,0.0 +349,0.0 +350,0.0 +351,0.0 +352,0.0 +353,0.0 +354,0.0 +355,0.0 +356,0.0 +357,1.1102230246251565e-16 +358,0.0 +359,0.0 +360,0.0 +361,0.0 +362,-1.1102230246251565e-16 +363,0.0 +364,0.0 +365,0.0 +366,0.0 +367,0.0 +368,0.0 +369,0.0 +370,-1.1102230246251565e-16 +371,0.0 +372,0.0 +373,0.0 +374,0.0 +375,0.0 +376,0.0 +377,0.0 +378,0.0 +379,1.1102230246251565e-16 +380,0.0 +381,0.0 +382,0.0 +383,0.0 +384,0.0 +385,0.0 +386,0.0 +387,0.0 +388,0.0 +389,0.0 +390,0.0 +391,0.0 +392,0.0 +393,0.0 +394,1.1102230246251565e-16 +395,0.0 +396,0.0 +397,0.0 +398,0.0 +399,0.0 +400,0.0 +401,0.0 +402,-1.1102230246251565e-16 +403,0.0 +404,0.0 +405,0.0 +406,0.0 +407,0.0 +408,0.0 +409,0.0 +410,0.0 +411,0.0 +412,0.0 +413,-1.1102230246251565e-16 +414,0.0 +415,0.0 +416,0.0 +417,0.0 +418,0.0 +419,0.0 +420,0.0 +421,-1.1102230246251565e-16 +422,0.0 +423,0.0 +424,0.0 +425,0.0 +426,0.0 +427,0.0 +428,0.0 +429,1.1102230246251565e-16 +430,0.0 +431,0.0 +432,0.0 +433,0.0 +434,1.1102230246251565e-16 +435,0.0 +436,0.0 +437,1.1102230246251565e-16 +438,0.0 +439,0.0 +440,0.0 +441,0.0 +442,0.0 +443,0.0 +444,0.0 +445,1.1102230246251565e-16 +446,0.0 +447,0.0 +448,0.0 +449,0.0 +450,-1.1102230246251565e-16 +451,0.0 +452,0.0 +453,0.0 +454,0.0 +455,0.0 +456,0.0 +457,0.0 +458,1.1102230246251565e-16 +459,0.0 +460,0.0 +461,-1.1102230246251565e-16 +462,0.0 +463,0.0 +464,0.0 +465,0.0 +466,0.0 +467,0.0 +468,0.0 +469,0.0 +470,0.0 +471,0.0 +472,0.0 +473,0.0 +474,0.0 +475,0.0 +476,0.0 +477,0.0 +478,0.0 +479,0.0 +480,0.0 +481,0.0 +482,0.0 +483,0.0 +484,0.0 +485,0.0 +486,0.0 +487,0.0 +488,0.0 +489,0.0 +490,0.0 +491,0.0 +492,0.0 +493,0.0 +494,0.0 +495,0.0 +496,0.0 +497,0.0 +498,0.0 +499,0.0 +500,0.0 +501,0.0 +502,0.0 +503,0.0 +504,0.0 +505,0.0 +506,0.0 +507,0.0 +508,0.0 +509,0.0 +510,0.0 +511,0.0 +512,0.0 +513,0.0 +514,-1.1102230246251565e-16 +515,0.0 +516,0.0 +517,-1.1102230246251565e-16 +518,0.0 +519,0.0 +520,0.0 +521,0.0 +522,1.1102230246251565e-16 +523,0.0 +524,0.0 +525,0.0 +526,0.0 +527,0.0 +528,0.0 +529,0.0 +530,-1.1102230246251565e-16 +531,0.0 +532,0.0 +533,0.0 +534,0.0 +535,0.0 +536,0.0 +537,0.0 +538,0.0 +539,0.0 +540,0.0 +541,0.0 +542,0.0 +543,0.0 +544,0.0 +545,0.0 +546,0.0 +547,0.0 +548,0.0 +549,0.0 +550,0.0 +551,0.0 +552,0.0 +553,0.0 +554,-1.1102230246251565e-16 +555,0.0 +556,0.0 +557,0.0 +558,0.0 +559,0.0 +560,0.0 +561,0.0 +562,0.0 +563,0.0 +564,0.0 +565,-1.1102230246251565e-16 +566,0.0 +567,0.0 +568,0.0 +569,0.0 +570,0.0 +571,0.0 +572,0.0 +573,-1.1102230246251565e-16 +574,0.0 +575,0.0 +576,0.0 +577,0.0 +578,0.0 +579,0.0 +580,1.1102230246251565e-16 +581,0.0 +582,0.0 +583,0.0 +584,0.0 +585,0.0 +586,-1.1102230246251565e-16 +587,0.0 +588,0.0 +589,0.0 +590,0.0 +591,0.0 +592,0.0 +593,0.0 +594,0.0 +595,0.0 +596,0.0 +597,0.0 +598,0.0 +599,0.0 +600,0.0 +601,0.0 +602,0.0 +603,0.0 +604,0.0 +605,0.0 +606,0.0 +607,0.0 +608,0.0 +609,0.0 +610,-1.1102230246251565e-16 +611,0.0 +612,0.0 +613,0.0 +614,0.0 +615,0.0 +616,0.0 +617,0.0 +618,0.0 +619,0.0 +620,0.0 +621,0.0 +622,0.0 +623,0.0 +624,0.0 +625,0.0 +626,0.0 +627,0.0 +628,0.0 +629,0.0 +630,0.0 +631,0.0 +632,0.0 +633,0.0 +634,0.0 +635,0.0 +636,0.0 +637,0.0 +638,0.0 +639,0.0 +640,0.0 +641,0.0 +642,1.1102230246251565e-16 +643,0.0 +644,0.0 +645,0.0 +646,0.0 +647,0.0 +648,0.0 +649,0.0 +650,0.0 +651,0.0 +652,0.0 +653,0.0 +654,0.0 +655,0.0 +656,0.0 +657,0.0 +658,1.1102230246251565e-16 +659,0.0 +660,0.0 +661,1.1102230246251565e-16 +662,0.0 +663,0.0 +664,0.0 +665,0.0 +666,1.1102230246251565e-16 +667,0.0 +668,0.0 +669,0.0 +670,0.0 +671,0.0 +672,0.0 +673,0.0 +674,0.0 +675,0.0 +676,-1.1102230246251565e-16 +677,0.0 +678,0.0 +679,0.0 +680,0.0 +681,0.0 +682,0.0 +683,0.0 +684,0.0 +685,0.0 +686,0.0 +687,0.0 +688,0.0 +689,0.0 +690,0.0 +691,0.0 +692,0.0 +693,0.0 +694,0.0 +695,0.0 +696,0.0 +697,0.0 +698,0.0 +699,0.0 +700,0.0 +701,0.0 +702,0.0 +703,0.0 +704,0.0 +705,0.0 +706,0.0 +707,0.0 +708,0.0 +709,0.0 +710,0.0 +711,0.0 +712,0.0 +713,0.0 +714,0.0 +715,0.0 +716,0.0 +717,0.0 +718,0.0 +719,0.0 +720,0.0 +721,0.0 +722,0.0 +723,0.0 +724,0.0 +725,0.0 +726,0.0 +727,0.0 +728,0.0 +729,0.0 +730,-1.1102230246251565e-16 +731,0.0 +732,0.0 +733,0.0 +734,0.0 +735,0.0 +736,0.0 +737,0.0 +738,0.0 +739,0.0 +740,0.0 +741,0.0 +742,0.0 +743,0.0 +744,0.0 +745,0.0 +746,0.0 +747,0.0 +748,0.0 +749,0.0 +750,-1.1102230246251565e-16 +751,0.0 +752,0.0 +753,0.0 +754,0.0 +755,0.0 +756,0.0 +757,0.0 +758,0.0 +759,0.0 +760,0.0 +761,0.0 +762,0.0 +763,0.0 +764,0.0 +765,0.0 +766,1.1102230246251565e-16 +767,0.0 +768,0.0 +769,0.0 +770,0.0 +771,0.0 +772,0.0 +773,0.0 +774,0.0 +775,0.0 +776,0.0 +777,0.0 +778,0.0 +779,0.0 +780,0.0 +781,0.0 +782,0.0 +783,0.0 +784,0.0 +785,0.0 +786,0.0 +787,0.0 +788,0.0 +789,0.0 +790,0.0 +791,0.0 +792,0.0 +793,0.0 +794,-1.1102230246251565e-16 +795,0.0 +796,0.0 +797,0.0 +798,0.0 +799,0.0 +800,0.0 +801,0.0 +802,0.0 +803,0.0 +804,0.0 +805,0.0 +806,0.0 +807,0.0 +808,0.0 +809,0.0 +810,0.0 +811,0.0 +812,0.0 +813,0.0 +814,0.0 +815,0.0 +816,0.0 +817,0.0 +818,0.0 +819,0.0 +820,0.0 +821,0.0 +822,0.0 +823,0.0 +824,0.0 +825,0.0 +826,0.0 +827,0.0 +828,0.0 +829,0.0 +830,0.0 +831,0.0 +832,0.0 +833,0.0 +834,0.0 +835,0.0 +836,0.0 +837,0.0 +838,0.0 +839,0.0 +840,0.0 +841,0.0 +842,1.1102230246251565e-16 +843,0.0 +844,0.0 +845,0.0 +846,0.0 +847,0.0 +848,0.0 +849,0.0 +850,0.0 +851,0.0 +852,0.0 +853,0.0 +854,1.1102230246251565e-16 +855,0.0 +856,0.0 +857,0.0 +858,0.0 +859,0.0 +860,0.0 +861,0.0 +862,0.0 +863,0.0 +864,0.0 +865,0.0 +866,0.0 +867,0.0 +868,0.0 +869,0.0 +870,0.0 +871,0.0 +872,0.0 +873,0.0 +874,-1.1102230246251565e-16 +875,0.0 +876,0.0 +877,0.0 +878,0.0 +879,0.0 +880,0.0 +881,0.0 +882,0.0 +883,0.0 +884,0.0 +885,0.0 +886,0.0 +887,0.0 +888,0.0 +889,0.0 +890,0.0 +891,0.0 +892,0.0 +893,0.0 +894,0.0 +895,0.0 +896,0.0 +897,0.0 +898,1.1102230246251565e-16 +899,0.0 +900,0.0 +901,0.0 +902,0.0 +903,0.0 +904,0.0 +905,0.0 +906,0.0 +907,0.0 +908,0.0 +909,0.0 +910,0.0 +911,0.0 +912,0.0 +913,0.0 +914,0.0 +915,0.0 +916,0.0 +917,0.0 +918,0.0 +919,0.0 +920,0.0 +921,0.0 +922,0.0 +923,0.0 +924,0.0 +925,0.0 +926,0.0 +927,0.0 +928,0.0 +929,0.0 +930,0.0 +931,0.0 +932,0.0 +933,0.0 +934,0.0 +935,0.0 +936,0.0 +937,0.0 +938,0.0 +939,0.0 +940,0.0 +941,0.0 +942,0.0 +943,0.0 +944,0.0 +945,0.0 +946,1.1102230246251565e-16 +947,0.0 +948,0.0 +949,0.0 +950,0.0 +951,0.0 +952,0.0 +953,0.0 +954,0.0 +955,0.0 +956,0.0 +957,0.0 +958,0.0 +959,0.0 +960,0.0 +961,0.0 +962,0.0 +963,0.0 +964,0.0 +965,0.0 +966,0.0 +967,0.0 +968,0.0 +969,0.0 +970,0.0 +971,0.0 +972,0.0 +973,0.0 +974,0.0 +975,0.0 +976,0.0 +977,0.0 +978,0.0 +979,0.0 +980,0.0 +981,1.1102230246251565e-16 +982,0.0 +983,0.0 +984,0.0 +985,0.0 +986,0.0 +987,0.0 +988,0.0 +989,0.0 +990,0.0 +991,0.0 +992,0.0 +993,0.0 +994,0.0 +995,0.0 +996,0.0 +997,0.0 +998,0.0 +999,0.0 +1000,0.0 +1001,0.0 +1002,0.0 +1003,0.0 +1004,0.0 +1005,0.0 +1006,1.1102230246251565e-16 +1007,0.0 +1008,0.0 +1009,0.0 +1010,0.0 +1011,0.0 +1012,0.0 +1013,0.0 +1014,0.0 +1015,0.0 +1016,0.0 +1017,0.0 +1018,0.0 +1019,0.0 +1020,0.0 +1021,0.0 +1022,0.0 +1023,0.0 +1024,0.0 +1025,0.0 +1026,-1.1102230246251565e-16 +1027,0.0 +1028,0.0 +1029,0.0 +1030,0.0 +1031,0.0 +1032,0.0 +1033,0.0 +1034,0.0 +1035,0.0 +1036,0.0 +1037,0.0 +1038,0.0 +1039,0.0 +1040,0.0 +1041,0.0 +1042,0.0 +1043,0.0 +1044,0.0 +1045,0.0 +1046,0.0 +1047,0.0 +1048,0.0 +1049,0.0 +1050,0.0 +1051,0.0 +1052,0.0 +1053,0.0 +1054,0.0 +1055,0.0 +1056,0.0 +1057,0.0 +1058,0.0 +1059,0.0 +1060,0.0 +1061,0.0 +1062,0.0 +1063,0.0 +1064,0.0 +1065,0.0 +1066,0.0 +1067,0.0 +1068,0.0 +1069,0.0 +1070,0.0 +1071,0.0 +1072,0.0 +1073,0.0 +1074,0.0 +1075,0.0 +1076,0.0 +1077,0.0 +1078,0.0 +1079,0.0 +1080,0.0 +1081,0.0 +1082,0.0 +1083,0.0 +1084,0.0 +1085,0.0 +1086,0.0 +1087,0.0 +1088,0.0 +1089,0.0 +1090,0.0 +1091,0.0 +1092,0.0 +1093,0.0 +1094,1.1102230246251565e-16 +1095,0.0 +1096,0.0 +1097,0.0 +1098,1.1102230246251565e-16 +1099,0.0 +1100,0.0 +1101,0.0 +1102,1.1102230246251565e-16 +1103,0.0 +1104,0.0 +1105,-1.1102230246251565e-16 +1106,0.0 +1107,0.0 +1108,0.0 +1109,0.0 +1110,0.0 +1111,0.0 +1112,0.0 +1113,1.1102230246251565e-16 +1114,0.0 +1115,0.0 +1116,0.0 +1117,1.1102230246251565e-16 +1118,0.0 +1119,0.0 +1120,0.0 +1121,0.0 +1122,0.0 +1123,0.0 +1124,0.0 +1125,0.0 +1126,-1.1102230246251565e-16 +1127,0.0 +1128,0.0 +1129,0.0 +1130,0.0 +1131,0.0 +1132,0.0 +1133,0.0 +1134,0.0 +1135,0.0 +1136,0.0 +1137,0.0 +1138,0.0 +1139,0.0 +1140,0.0 +1141,0.0 +1142,0.0 +1143,0.0 +1144,0.0 +1145,0.0 +1146,0.0 +1147,0.0 +1148,0.0 +1149,0.0 +1150,0.0 +1151,0.0 +1152,0.0 +1153,0.0 +1154,0.0 +1155,0.0 +1156,0.0 +1157,0.0 +1158,0.0 +1159,0.0 +1160,0.0 +1161,0.0 +1162,-1.1102230246251565e-16 +1163,0.0 +1164,0.0 +1165,0.0 +1166,0.0 +1167,0.0 +1168,0.0 +1169,0.0 +1170,0.0 +1171,0.0 +1172,0.0 +1173,0.0 +1174,0.0 +1175,0.0 +1176,0.0 +1177,0.0 +1178,0.0 +1179,0.0 +1180,0.0 +1181,0.0 +1182,1.1102230246251565e-16 +1183,0.0 +1184,0.0 +1185,0.0 +1186,0.0 +1187,0.0 +1188,0.0 +1189,0.0 +1190,0.0 +1191,0.0 +1192,0.0 +1193,0.0 +1194,0.0 +1195,0.0 +1196,0.0 +1197,0.0 +1198,0.0 +1199,0.0 +1200,0.0 +1201,0.0 +1202,-1.1102230246251565e-16 +1203,0.0 +1204,0.0 +1205,0.0 +1206,0.0 +1207,0.0 +1208,0.0 +1209,0.0 +1210,0.0 +1211,0.0 +1212,0.0 +1213,0.0 +1214,1.1102230246251565e-16 +1215,0.0 +1216,0.0 +1217,0.0 +1218,0.0 +1219,0.0 +1220,0.0 +1221,0.0 +1222,0.0 +1223,0.0 +1224,0.0 +1225,1.1102230246251565e-16 +1226,0.0 +1227,0.0 +1228,0.0 +1229,0.0 +1230,0.0 +1231,0.0 +1232,0.0 +1233,0.0 +1234,0.0 +1235,0.0 +1236,0.0 +1237,0.0 +1238,0.0 +1239,0.0 +1240,0.0 +1241,0.0 +1242,0.0 +1243,0.0 +1244,0.0 +1245,0.0 +1246,-1.1102230246251565e-16 +1247,0.0 +1248,0.0 +1249,0.0 +1250,0.0 +1251,0.0 +1252,0.0 +1253,0.0 +1254,0.0 +1255,0.0 +1256,0.0 +1257,1.1102230246251565e-16 +1258,0.0 +1259,0.0 +1260,0.0 +1261,0.0 +1262,0.0 +1263,0.0 +1264,0.0 +1265,0.0 +1266,-1.1102230246251565e-16 +1267,0.0 +1268,0.0 +1269,0.0 +1270,0.0 +1271,0.0 +1272,0.0 +1273,0.0 +1274,-1.1102230246251565e-16 +1275,0.0 +1276,0.0 +1277,1.1102230246251565e-16 +1278,-1.1102230246251565e-16 +1279,0.0 +1280,0.0 +1281,0.0 +1282,-1.1102230246251565e-16 +1283,0.0 +1284,0.0 +1285,0.0 +1286,-1.1102230246251565e-16 +1287,0.0 +1288,0.0 +1289,0.0 +1290,0.0 +1291,0.0 +1292,0.0 +1293,0.0 +1294,0.0 +1295,0.0 +1296,0.0 +1297,0.0 +1298,-1.1102230246251565e-16 +1299,0.0 +1300,0.0 +1301,0.0 +1302,0.0 +1303,0.0 +1304,0.0 +1305,0.0 +1306,0.0 +1307,0.0 +1308,0.0 +1309,0.0 +1310,-1.1102230246251565e-16 +1311,0.0 +1312,0.0 +1313,0.0 +1314,0.0 +1315,0.0 +1316,0.0 +1317,0.0 +1318,0.0 +1319,0.0 +1320,0.0 +1321,1.1102230246251565e-16 +1322,-1.1102230246251565e-16 +1323,0.0 +1324,0.0 +1325,0.0 +1326,0.0 +1327,0.0 +1328,0.0 +1329,0.0 +1330,0.0 +1331,0.0 +1332,0.0 +1333,0.0 +1334,1.1102230246251565e-16 +1335,0.0 +1336,0.0 +1337,0.0 +1338,0.0 +1339,0.0 +1340,0.0 +1341,0.0 +1342,0.0 +1343,0.0 +1344,0.0 +1345,0.0 +1346,0.0 +1347,0.0 +1348,0.0 +1349,0.0 +1350,0.0 +1351,0.0 +1352,0.0 +1353,0.0 +1354,1.1102230246251565e-16 +1355,0.0 +1356,0.0 +1357,0.0 +1358,0.0 +1359,0.0 +1360,0.0 +1361,0.0 +1362,0.0 +1363,0.0 +1364,0.0 +1365,0.0 +1366,0.0 +1367,0.0 +1368,0.0 +1369,0.0 +1370,0.0 +1371,0.0 +1372,0.0 +1373,1.1102230246251565e-16 +1374,0.0 +1375,0.0 +1376,0.0 +1377,0.0 +1378,-1.1102230246251565e-16 +1379,0.0 +1380,0.0 +1381,0.0 +1382,0.0 +1383,0.0 +1384,0.0 +1385,0.0 +1386,-1.1102230246251565e-16 +1387,0.0 +1388,0.0 +1389,0.0 +1390,0.0 +1391,0.0 +1392,0.0 +1393,0.0 +1394,0.0 +1395,0.0 +1396,0.0 +1397,0.0 +1398,0.0 +1399,0.0 +1400,0.0 +1401,0.0 +1402,0.0 +1403,0.0 +1404,0.0 +1405,0.0 +1406,0.0 +1407,0.0 +1408,0.0 +1409,0.0 +1410,0.0 +1411,0.0 +1412,0.0 +1413,1.1102230246251565e-16 +1414,0.0 +1415,0.0 +1416,0.0 +1417,0.0 +1418,0.0 +1419,0.0 +1420,0.0 +1421,0.0 +1422,0.0 +1423,0.0 +1424,0.0 +1425,1.1102230246251565e-16 +1426,0.0 +1427,0.0 +1428,0.0 +1429,0.0 +1430,0.0 +1431,0.0 +1432,0.0 +1433,0.0 +1434,0.0 +1435,0.0 +1436,0.0 +1437,0.0 +1438,0.0 +1439,0.0 +1440,0.0 +1441,0.0 +1442,0.0 +1443,0.0 +1444,0.0 +1445,0.0 +1446,0.0 +1447,0.0 +1448,0.0 +1449,1.1102230246251565e-16 +1450,0.0 +1451,0.0 +1452,0.0 +1453,0.0 +1454,1.1102230246251565e-16 +1455,0.0 +1456,0.0 +1457,-1.1102230246251565e-16 +1458,0.0 +1459,0.0 +1460,0.0 +1461,0.0 +1462,0.0 +1463,0.0 +1464,0.0 +1465,0.0 +1466,0.0 +1467,0.0 +1468,0.0 +1469,0.0 +1470,-1.1102230246251565e-16 +1471,0.0 +1472,0.0 +1473,0.0 +1474,0.0 +1475,0.0 +1476,0.0 +1477,0.0 +1478,0.0 +1479,0.0 +1480,0.0 +1481,0.0 +1482,0.0 +1483,0.0 +1484,0.0 +1485,0.0 +1486,-1.1102230246251565e-16 +1487,0.0 +1488,0.0 +1489,0.0 +1490,0.0 +1491,0.0 +1492,0.0 +1493,0.0 +1494,0.0 +1495,0.0 +1496,0.0 +1497,0.0 +1498,0.0 +1499,0.0 +1500,0.0 +1501,0.0 +1502,0.0 +1503,0.0 +1504,0.0 +1505,0.0 +1506,0.0 +1507,0.0 +1508,0.0 +1509,0.0 +1510,0.0 +1511,0.0 +1512,0.0 +1513,0.0 +1514,0.0 +1515,0.0 +1516,0.0 +1517,0.0 +1518,0.0 +1519,0.0 +1520,0.0 +1521,0.0 +1522,0.0 +1523,0.0 +1524,0.0 +1525,0.0 +1526,1.1102230246251565e-16 +1527,0.0 +1528,0.0 +1529,0.0 +1530,1.1102230246251565e-16 +1531,0.0 +1532,0.0 +1533,0.0 +1534,-1.1102230246251565e-16 +1535,0.0 +1536,0.0 +1537,0.0 +1538,-1.1102230246251565e-16 +1539,0.0 +1540,0.0 +1541,0.0 +1542,0.0 +1543,0.0 +1544,0.0 +1545,0.0 +1546,0.0 +1547,0.0 +1548,0.0 +1549,0.0 +1550,0.0 +1551,0.0 +1552,0.0 +1553,0.0 +1554,0.0 +1555,0.0 +1556,0.0 +1557,0.0 +1558,0.0 +1559,0.0 +1560,0.0 +1561,0.0 +1562,0.0 +1563,0.0 +1564,0.0 +1565,0.0 +1566,0.0 +1567,0.0 +1568,0.0 +1569,0.0 +1570,0.0 +1571,0.0 +1572,0.0 +1573,0.0 +1574,0.0 +1575,0.0 +1576,0.0 +1577,0.0 +1578,1.1102230246251565e-16 +1579,0.0 +1580,0.0 +1581,0.0 +1582,1.1102230246251565e-16 +1583,0.0 +1584,0.0 +1585,0.0 +1586,0.0 +1587,0.0 +1588,0.0 +1589,0.0 +1590,-1.1102230246251565e-16 +1591,0.0 +1592,0.0 +1593,0.0 +1594,0.0 +1595,0.0 +1596,0.0 +1597,0.0 +1598,0.0 +1599,0.0 +1600,0.0 +1601,0.0 +1602,1.1102230246251565e-16 +1603,0.0 +1604,0.0 +1605,0.0 +1606,0.0 +1607,0.0 +1608,0.0 +1609,0.0 +1610,0.0 +1611,0.0 +1612,0.0 +1613,0.0 +1614,0.0 +1615,0.0 +1616,0.0 +1617,0.0 +1618,0.0 +1619,0.0 +1620,0.0 +1621,0.0 +1622,-1.1102230246251565e-16 +1623,0.0 +1624,0.0 +1625,0.0 +1626,0.0 +1627,0.0 +1628,0.0 +1629,0.0 +1630,1.1102230246251565e-16 +1631,0.0 +1632,0.0 +1633,0.0 +1634,0.0 +1635,0.0 +1636,0.0 +1637,0.0 +1638,1.1102230246251565e-16 +1639,0.0 +1640,0.0 +1641,0.0 +1642,-1.1102230246251565e-16 +1643,0.0 +1644,0.0 +1645,0.0 +1646,0.0 +1647,0.0 +1648,0.0 +1649,0.0 +1650,0.0 +1651,0.0 +1652,0.0 +1653,0.0 +1654,0.0 +1655,0.0 +1656,0.0 +1657,0.0 +1658,0.0 +1659,0.0 +1660,0.0 +1661,0.0 +1662,0.0 +1663,0.0 +1664,0.0 +1665,0.0 +1666,0.0 +1667,0.0 +1668,0.0 +1669,0.0 +1670,-1.1102230246251565e-16 +1671,0.0 +1672,0.0 +1673,0.0 +1674,0.0 +1675,0.0 +1676,0.0 +1677,0.0 +1678,0.0 +1679,0.0 +1680,0.0 +1681,0.0 +1682,0.0 +1683,0.0 +1684,0.0 +1685,0.0 +1686,-1.1102230246251565e-16 +1687,0.0 +1688,0.0 +1689,0.0 +1690,0.0 +1691,0.0 +1692,0.0 +1693,0.0 +1694,0.0 +1695,0.0 +1696,0.0 +1697,0.0 +1698,0.0 +1699,0.0 +1700,0.0 +1701,-1.1102230246251565e-16 +1702,0.0 +1703,0.0 +1704,0.0 +1705,0.0 +1706,0.0 +1707,0.0 +1708,0.0 +1709,0.0 +1710,0.0 +1711,0.0 +1712,0.0 +1713,0.0 +1714,0.0 +1715,0.0 +1716,0.0 +1717,0.0 +1718,0.0 +1719,0.0 +1720,0.0 +1721,0.0 +1722,0.0 +1723,0.0 +1724,0.0 +1725,1.1102230246251565e-16 +1726,0.0 +1727,0.0 +1728,0.0 +1729,0.0 +1730,-1.1102230246251565e-16 +1731,0.0 +1732,0.0 +1733,0.0 +1734,0.0 +1735,0.0 +1736,0.0 +1737,0.0 +1738,0.0 +1739,0.0 +1740,0.0 +1741,0.0 +1742,0.0 +1743,0.0 +1744,0.0 +1745,0.0 +1746,0.0 +1747,0.0 +1748,0.0 +1749,0.0 +1750,0.0 +1751,0.0 +1752,0.0 +1753,0.0 +1754,0.0 +1755,0.0 +1756,0.0 +1757,0.0 +1758,0.0 +1759,0.0 +1760,0.0 +1761,0.0 +1762,0.0 +1763,0.0 +1764,0.0 +1765,0.0 +1766,-1.1102230246251565e-16 +1767,0.0 +1768,0.0 +1769,0.0 +1770,0.0 +1771,0.0 +1772,0.0 +1773,0.0 +1774,0.0 +1775,0.0 +1776,0.0 +1777,0.0 +1778,1.1102230246251565e-16 +1779,0.0 +1780,0.0 +1781,0.0 +1782,0.0 +1783,0.0 +1784,0.0 +1785,0.0 +1786,0.0 +1787,0.0 +1788,0.0 +1789,0.0 +1790,0.0 +1791,0.0 +1792,0.0 +1793,0.0 +1794,1.1102230246251565e-16 +1795,0.0 +1796,0.0 +1797,0.0 +1798,-1.1102230246251565e-16 +1799,0.0 +1800,0.0 +1801,0.0 +1802,0.0 +1803,0.0 +1804,0.0 +1805,0.0 +1806,0.0 +1807,0.0 +1808,0.0 +1809,0.0 +1810,0.0 +1811,0.0 +1812,0.0 +1813,0.0 +1814,0.0 +1815,0.0 +1816,0.0 +1817,0.0 +1818,0.0 +1819,0.0 +1820,0.0 +1821,0.0 +1822,0.0 +1823,0.0 +1824,0.0 +1825,0.0 +1826,0.0 +1827,1.1102230246251565e-16 +1828,1.1102230246251565e-16 +1829,-1.1102230246251565e-16 +1830,0.0 +1831,0.0 +1832,0.0 +1833,0.0 +1834,0.0 +1835,0.0 +1836,0.0 +1837,-1.1102230246251565e-16 +1838,0.0 +1839,0.0 +1840,0.0 +1841,0.0 +1842,0.0 +1843,0.0 +1844,0.0 +1845,-1.1102230246251565e-16 +1846,0.0 +1847,0.0 +1848,0.0 +1849,0.0 +1850,0.0 +1851,0.0 +1852,0.0 +1853,0.0 +1854,0.0 +1855,0.0 +1856,0.0 +1857,0.0 +1858,0.0 +1859,0.0 +1860,0.0 +1861,0.0 +1862,0.0 +1863,0.0 +1864,0.0 +1865,0.0 +1866,0.0 +1867,0.0 +1868,0.0 +1869,0.0 +1870,1.1102230246251565e-16 +1871,0.0 +1872,0.0 +1873,0.0 +1874,0.0 +1875,0.0 +1876,0.0 +1877,0.0 +1878,0.0 +1879,0.0 +1880,0.0 +1881,0.0 +1882,0.0 +1883,0.0 +1884,0.0 +1885,0.0 +1886,0.0 +1887,0.0 +1888,0.0 +1889,0.0 +1890,0.0 +1891,0.0 +1892,0.0 +1893,0.0 +1894,0.0 +1895,0.0 +1896,0.0 +1897,0.0 +1898,0.0 +1899,0.0 +1900,0.0 +1901,0.0 +1902,0.0 +1903,0.0 +1904,0.0 +1905,0.0 +1906,1.1102230246251565e-16 +1907,0.0 +1908,0.0 +1909,0.0 +1910,0.0 +1911,0.0 +1912,0.0 +1913,0.0 +1914,0.0 +1915,0.0 +1916,0.0 +1917,-1.1102230246251565e-16 +1918,0.0 +1919,0.0 +1920,0.0 +1921,0.0 +1922,0.0 +1923,0.0 +1924,0.0 +1925,0.0 +1926,0.0 +1927,0.0 +1928,0.0 +1929,0.0 +1930,1.1102230246251565e-16 +1931,0.0 +1932,0.0 +1933,0.0 +1934,0.0 +1935,0.0 +1936,0.0 +1937,0.0 +1938,0.0 +1939,0.0 +1940,0.0 +1941,0.0 +1942,1.1102230246251565e-16 +1943,0.0 +1944,0.0 +1945,0.0 +1946,0.0 +1947,0.0 +1948,0.0 +1949,0.0 +1950,0.0 +1951,0.0 +1952,0.0 +1953,0.0 +1954,0.0 +1955,0.0 +1956,0.0 +1957,0.0 +1958,0.0 +1959,0.0 +1960,0.0 +1961,0.0 +1962,0.0 +1963,0.0 +1964,0.0 +1965,0.0 +1966,0.0 +1967,0.0 +1968,0.0 +1969,0.0 +1970,1.1102230246251565e-16 +1971,0.0 +1972,0.0 +1973,0.0 +1974,0.0 +1975,0.0 +1976,0.0 +1977,0.0 +1978,0.0 +1979,0.0 +1980,0.0 +1981,0.0 +1982,0.0 +1983,0.0 +1984,0.0 +1985,0.0 +1986,0.0 +1987,0.0 +1988,0.0 +1989,0.0 +1990,0.0 +1991,0.0 +1992,0.0 +1993,0.0 +1994,0.0 +1995,0.0 +1996,0.0 +1997,0.0 +1998,0.0 +1999,0.0 +2000,0.0 +2001,0.0 +2002,0.0 +2003,0.0 +2004,0.0 +2005,1.1102230246251565e-16 +2006,0.0 +2007,0.0 +2008,0.0 +2009,0.0 +2010,0.0 +2011,0.0 +2012,0.0 +2013,0.0 +2014,0.0 +2015,0.0 +2016,0.0 +2017,0.0 +2018,1.1102230246251565e-16 +2019,0.0 +2020,0.0 +2021,0.0 +2022,0.0 +2023,0.0 +2024,0.0 +2025,0.0 +2026,0.0 +2027,0.0 +2028,0.0 +2029,0.0 +2030,0.0 +2031,0.0 +2032,0.0 +2033,0.0 +2034,0.0 +2035,0.0 +2036,0.0 +2037,0.0 +2038,0.0 +2039,0.0 +2040,0.0 +2041,0.0 +2042,0.0 +2043,0.0 +2044,0.0 +2045,0.0 +2046,0.0 +2047,0.0 +2048,0.0 +2049,0.0 +2050,0.0 +2051,0.0 +2052,0.0 +2053,-1.1102230246251565e-16 +2054,0.0 +2055,0.0 +2056,0.0 +2057,0.0 +2058,0.0 +2059,0.0 +2060,0.0 +2061,0.0 +2062,0.0 +2063,0.0 +2064,0.0 +2065,0.0 +2066,1.1102230246251565e-16 +2067,0.0 +2068,0.0 +2069,0.0 +2070,0.0 +2071,0.0 +2072,0.0 +2073,0.0 +2074,0.0 +2075,0.0 +2076,0.0 +2077,0.0 +2078,0.0 +2079,0.0 +2080,0.0 +2081,0.0 +2082,0.0 +2083,0.0 +2084,0.0 +2085,1.1102230246251565e-16 +2086,0.0 +2087,0.0 +2088,0.0 +2089,0.0 +2090,0.0 +2091,0.0 +2092,0.0 +2093,0.0 +2094,0.0 +2095,0.0 +2096,0.0 +2097,0.0 +2098,0.0 +2099,0.0 +2100,0.0 +2101,0.0 +2102,0.0 +2103,0.0 +2104,0.0 +2105,0.0 +2106,1.1102230246251565e-16 +2107,0.0 +2108,0.0 +2109,0.0 +2110,0.0 +2111,0.0 +2112,0.0 +2113,0.0 +2114,1.1102230246251565e-16 +2115,0.0 +2116,0.0 +2117,0.0 +2118,0.0 +2119,0.0 +2120,0.0 +2121,0.0 +2122,0.0 +2123,0.0 +2124,0.0 +2125,0.0 +2126,0.0 +2127,0.0 +2128,0.0 +2129,0.0 +2130,0.0 +2131,0.0 +2132,0.0 +2133,0.0 +2134,0.0 +2135,0.0 +2136,0.0 +2137,0.0 +2138,0.0 +2139,0.0 +2140,0.0 +2141,0.0 +2142,0.0 +2143,0.0 +2144,0.0 +2145,0.0 +2146,-1.1102230246251565e-16 +2147,0.0 +2148,1.1102230246251565e-16 +2149,0.0 +2150,0.0 +2151,0.0 +2152,0.0 +2153,0.0 +2154,0.0 +2155,0.0 +2156,0.0 +2157,0.0 +2158,0.0 +2159,0.0 +2160,0.0 +2161,0.0 +2162,0.0 +2163,0.0 +2164,0.0 +2165,0.0 +2166,0.0 +2167,0.0 +2168,0.0 +2169,0.0 +2170,1.1102230246251565e-16 +2171,0.0 +2172,0.0 +2173,0.0 +2174,0.0 +2175,0.0 +2176,0.0 +2177,0.0 +2178,0.0 +2179,0.0 +2180,0.0 +2181,0.0 +2182,0.0 +2183,0.0 +2184,0.0 +2185,0.0 +2186,0.0 +2187,0.0 +2188,0.0 +2189,1.1102230246251565e-16 +2190,0.0 +2191,0.0 +2192,0.0 +2193,0.0 +2194,1.1102230246251565e-16 +2195,0.0 +2196,0.0 +2197,0.0 +2198,0.0 +2199,0.0 +2200,0.0 +2201,0.0 +2202,0.0 +2203,0.0 +2204,0.0 +2205,0.0 +2206,0.0 +2207,0.0 +2208,0.0 +2209,0.0 +2210,-1.1102230246251565e-16 +2211,0.0 +2212,0.0 +2213,1.1102230246251565e-16 +2214,0.0 +2215,0.0 +2216,0.0 +2217,0.0 +2218,0.0 +2219,0.0 +2220,0.0 +2221,0.0 +2222,0.0 +2223,0.0 +2224,0.0 +2225,0.0 +2226,0.0 +2227,0.0 +2228,0.0 +2229,0.0 +2230,0.0 +2231,0.0 +2232,0.0 +2233,0.0 +2234,0.0 +2235,0.0 +2236,0.0 +2237,0.0 +2238,0.0 +2239,0.0 +2240,0.0 +2241,0.0 +2242,0.0 +2243,1.1102230246251565e-16 +2244,-1.1102230246251565e-16 +2245,0.0 +2246,0.0 +2247,0.0 +2248,0.0 +2249,0.0 +2250,0.0 +2251,0.0 +2252,0.0 +2253,0.0 +2254,0.0 +2255,0.0 +2256,0.0 +2257,0.0 +2258,0.0 +2259,0.0 +2260,0.0 +2261,0.0 +2262,0.0 +2263,0.0 +2264,0.0 +2265,0.0 +2266,0.0 +2267,0.0 +2268,0.0 +2269,0.0 +2270,0.0 +2271,0.0 +2272,0.0 +2273,0.0 +2274,0.0 +2275,0.0 +2276,0.0 +2277,0.0 +2278,0.0 +2279,0.0 +2280,0.0 +2281,0.0 +2282,-1.1102230246251565e-16 +2283,0.0 +2284,0.0 +2285,0.0 +2286,0.0 +2287,0.0 +2288,0.0 +2289,0.0 +2290,0.0 +2291,0.0 +2292,0.0 +2293,0.0 +2294,0.0 +2295,0.0 +2296,0.0 +2297,0.0 +2298,-1.1102230246251565e-16 +2299,0.0 +2300,-1.1102230246251565e-16 +2301,1.1102230246251565e-16 +2302,0.0 +2303,0.0 +2304,0.0 +2305,0.0 +2306,0.0 +2307,0.0 +2308,0.0 +2309,0.0 +2310,0.0 +2311,0.0 +2312,0.0 +2313,0.0 +2314,0.0 +2315,0.0 +2316,0.0 +2317,0.0 +2318,0.0 +2319,0.0 +2320,0.0 +2321,0.0 +2322,0.0 +2323,0.0 +2324,0.0 +2325,0.0 +2326,0.0 +2327,0.0 +2328,0.0 +2329,0.0 +2330,0.0 +2331,1.1102230246251565e-16 +2332,-1.1102230246251565e-16 +2333,0.0 +2334,0.0 +2335,0.0 +2336,0.0 +2337,0.0 +2338,0.0 +2339,0.0 +2340,-1.1102230246251565e-16 +2341,0.0 +2342,0.0 +2343,0.0 +2344,0.0 +2345,0.0 +2346,0.0 +2347,0.0 +2348,0.0 +2349,0.0 +2350,0.0 +2351,0.0 +2352,0.0 +2353,0.0 +2354,-1.1102230246251565e-16 +2355,0.0 +2356,1.1102230246251565e-16 +2357,0.0 +2358,0.0 +2359,0.0 +2360,0.0 +2361,0.0 +2362,0.0 +2363,0.0 +2364,0.0 +2365,0.0 +2366,0.0 +2367,0.0 +2368,0.0 +2369,0.0 +2370,0.0 +2371,0.0 +2372,0.0 +2373,-1.1102230246251565e-16 +2374,0.0 +2375,0.0 +2376,0.0 +2377,0.0 +2378,0.0 +2379,0.0 +2380,-1.1102230246251565e-16 +2381,0.0 +2382,0.0 +2383,0.0 +2384,0.0 +2385,0.0 +2386,0.0 +2387,0.0 +2388,0.0 +2389,0.0 +2390,0.0 +2391,0.0 +2392,0.0 +2393,0.0 +2394,0.0 +2395,0.0 +2396,0.0 +2397,0.0 +2398,0.0 +2399,0.0 +2400,0.0 +2401,0.0 +2402,0.0 +2403,0.0 +2404,0.0 +2405,0.0 +2406,0.0 +2407,0.0 +2408,0.0 +2409,0.0 +2410,0.0 +2411,0.0 +2412,0.0 +2413,0.0 +2414,0.0 +2415,0.0 +2416,0.0 +2417,0.0 +2418,0.0 +2419,0.0 +2420,0.0 +2421,0.0 +2422,0.0 +2423,0.0 +2424,0.0 +2425,0.0 +2426,0.0 +2427,0.0 +2428,0.0 +2429,0.0 +2430,0.0 +2431,0.0 +2432,0.0 +2433,0.0 +2434,-1.1102230246251565e-16 +2435,0.0 +2436,0.0 +2437,0.0 +2438,0.0 +2439,0.0 +2440,0.0 +2441,0.0 +2442,0.0 +2443,0.0 +2444,0.0 +2445,0.0 +2446,0.0 +2447,0.0 +2448,0.0 +2449,0.0 +2450,0.0 +2451,0.0 +2452,0.0 +2453,0.0 +2454,0.0 +2455,0.0 +2456,0.0 +2457,0.0 +2458,-1.1102230246251565e-16 +2459,0.0 +2460,0.0 +2461,0.0 +2462,0.0 +2463,0.0 +2464,0.0 +2465,0.0 +2466,0.0 +2467,0.0 +2468,0.0 +2469,0.0 +2470,0.0 +2471,0.0 +2472,0.0 +2473,0.0 +2474,0.0 +2475,0.0 +2476,0.0 +2477,0.0 +2478,0.0 +2479,0.0 +2480,0.0 +2481,0.0 +2482,0.0 +2483,0.0 +2484,0.0 +2485,1.1102230246251565e-16 +2486,0.0 +2487,0.0 +2488,0.0 +2489,0.0 +2490,1.1102230246251565e-16 +2491,0.0 +2492,0.0 +2493,-1.1102230246251565e-16 +2494,0.0 +2495,0.0 +2496,0.0 +2497,0.0 +2498,0.0 +2499,0.0 +2500,0.0 +2501,0.0 +2502,0.0 +2503,0.0 +2504,0.0 +2505,0.0 +2506,0.0 +2507,0.0 +2508,0.0 +2509,0.0 +2510,0.0 +2511,0.0 +2512,0.0 +2513,0.0 +2514,0.0 +2515,0.0 +2516,0.0 +2517,0.0 +2518,0.0 +2519,0.0 +2520,0.0 +2521,0.0 +2522,0.0 +2523,0.0 +2524,0.0 +2525,0.0 +2526,0.0 +2527,0.0 +2528,0.0 +2529,0.0 +2530,-1.1102230246251565e-16 +2531,0.0 +2532,0.0 +2533,0.0 +2534,0.0 +2535,0.0 +2536,0.0 +2537,0.0 +2538,0.0 +2539,0.0 +2540,0.0 +2541,0.0 +2542,0.0 +2543,0.0 +2544,0.0 +2545,0.0 +2546,0.0 +2547,0.0 +2548,0.0 +2549,0.0 +2550,0.0 +2551,0.0 +2552,0.0 +2553,0.0 +2554,0.0 +2555,0.0 +2556,0.0 +2557,-1.1102230246251565e-16 +2558,0.0 +2559,0.0 +2560,0.0 +2561,0.0 +2562,-1.1102230246251565e-16 +2563,0.0 +2564,0.0 +2565,0.0 +2566,0.0 +2567,0.0 +2568,0.0 +2569,0.0 +2570,0.0 +2571,0.0 +2572,0.0 +2573,0.0 +2574,0.0 +2575,0.0 +2576,0.0 +2577,0.0 +2578,0.0 +2579,0.0 +2580,0.0 +2581,0.0 +2582,0.0 +2583,0.0 +2584,0.0 +2585,0.0 +2586,0.0 +2587,0.0 +2588,0.0 +2589,0.0 +2590,0.0 +2591,0.0 +2592,0.0 +2593,0.0 +2594,0.0 +2595,0.0 +2596,0.0 +2597,-1.1102230246251565e-16 +2598,0.0 +2599,0.0 +2600,0.0 +2601,0.0 +2602,0.0 +2603,0.0 +2604,0.0 +2605,0.0 +2606,0.0 +2607,0.0 +2608,0.0 +2609,0.0 +2610,0.0 +2611,0.0 +2612,0.0 +2613,0.0 +2614,0.0 +2615,0.0 +2616,0.0 +2617,0.0 +2618,0.0 +2619,0.0 +2620,0.0 +2621,0.0 +2622,0.0 +2623,0.0 +2624,0.0 +2625,0.0 +2626,0.0 +2627,0.0 +2628,0.0 +2629,0.0 +2630,0.0 +2631,0.0 +2632,0.0 +2633,0.0 +2634,0.0 +2635,0.0 +2636,0.0 +2637,0.0 +2638,0.0 +2639,0.0 +2640,0.0 +2641,0.0 +2642,0.0 +2643,-1.1102230246251565e-16 +2644,0.0 +2645,-1.1102230246251565e-16 +2646,0.0 +2647,0.0 +2648,0.0 +2649,0.0 +2650,0.0 +2651,0.0 +2652,0.0 +2653,0.0 +2654,0.0 +2655,0.0 +2656,0.0 +2657,0.0 +2658,0.0 +2659,0.0 +2660,0.0 +2661,0.0 +2662,0.0 +2663,0.0 +2664,0.0 +2665,0.0 +2666,0.0 +2667,0.0 +2668,0.0 +2669,0.0 +2670,0.0 +2671,0.0 +2672,0.0 +2673,0.0 +2674,0.0 +2675,0.0 +2676,0.0 +2677,0.0 +2678,0.0 +2679,0.0 +2680,0.0 +2681,0.0 +2682,0.0 +2683,0.0 +2684,-1.1102230246251565e-16 +2685,0.0 +2686,0.0 +2687,0.0 +2688,0.0 +2689,0.0 +2690,0.0 +2691,0.0 +2692,0.0 +2693,0.0 +2694,0.0 +2695,0.0 +2696,0.0 +2697,0.0 +2698,0.0 +2699,0.0 +2700,0.0 +2701,0.0 +2702,0.0 +2703,0.0 +2704,0.0 +2705,0.0 +2706,0.0 +2707,0.0 +2708,0.0 +2709,0.0 +2710,0.0 +2711,0.0 +2712,0.0 +2713,0.0 +2714,0.0 +2715,0.0 +2716,0.0 +2717,0.0 +2718,0.0 +2719,0.0 +2720,0.0 +2721,0.0 +2722,0.0 +2723,0.0 +2724,1.1102230246251565e-16 +2725,0.0 +2726,0.0 +2727,0.0 +2728,0.0 +2729,0.0 +2730,0.0 +2731,0.0 +2732,0.0 +2733,0.0 +2734,0.0 +2735,0.0 +2736,0.0 +2737,0.0 +2738,0.0 +2739,0.0 +2740,-1.1102230246251565e-16 +2741,0.0 +2742,0.0 +2743,0.0 +2744,0.0 +2745,0.0 +2746,0.0 +2747,0.0 +2748,0.0 +2749,0.0 +2750,0.0 +2751,0.0 +2752,0.0 +2753,0.0 +2754,0.0 +2755,0.0 +2756,0.0 +2757,0.0 +2758,0.0 +2759,0.0 +2760,0.0 +2761,0.0 +2762,0.0 +2763,0.0 +2764,0.0 +2765,0.0 +2766,0.0 +2767,0.0 +2768,0.0 +2769,0.0 +2770,0.0 +2771,0.0 +2772,0.0 +2773,0.0 +2774,0.0 +2775,0.0 +2776,0.0 +2777,0.0 +2778,0.0 +2779,0.0 +2780,0.0 +2781,0.0 +2782,0.0 +2783,0.0 +2784,0.0 +2785,0.0 +2786,0.0 +2787,0.0 +2788,0.0 +2789,0.0 +2790,0.0 +2791,0.0 +2792,0.0 +2793,0.0 +2794,0.0 +2795,1.1102230246251565e-16 +2796,0.0 +2797,0.0 +2798,0.0 +2799,0.0 +2800,0.0 +2801,0.0 +2802,0.0 +2803,0.0 +2804,0.0 +2805,0.0 +2806,0.0 +2807,0.0 +2808,0.0 +2809,0.0 +2810,0.0 +2811,-1.1102230246251565e-16 +2812,0.0 +2813,0.0 +2814,0.0 +2815,0.0 +2816,0.0 +2817,0.0 +2818,0.0 +2819,0.0 +2820,0.0 +2821,0.0 +2822,0.0 +2823,0.0 +2824,0.0 +2825,0.0 +2826,0.0 +2827,0.0 +2828,0.0 +2829,0.0 +2830,0.0 +2831,0.0 +2832,0.0 +2833,0.0 +2834,0.0 +2835,0.0 +2836,0.0 +2837,0.0 +2838,0.0 +2839,0.0 +2840,0.0 +2841,0.0 +2842,0.0 +2843,0.0 +2844,0.0 +2845,0.0 +2846,0.0 +2847,0.0 +2848,0.0 +2849,0.0 +2850,0.0 +2851,0.0 +2852,0.0 +2853,0.0 +2854,0.0 +2855,0.0 +2856,0.0 +2857,0.0 +2858,0.0 +2859,0.0 +2860,0.0 +2861,0.0 +2862,0.0 +2863,0.0 +2864,0.0 +2865,0.0 +2866,0.0 +2867,0.0 +2868,0.0 +2869,0.0 +2870,0.0 +2871,0.0 +2872,0.0 +2873,0.0 +2874,0.0 +2875,0.0 +2876,0.0 +2877,0.0 +2878,0.0 +2879,0.0 +2880,0.0 +2881,0.0 +2882,0.0 +2883,0.0 +2884,0.0 +2885,0.0 +2886,0.0 +2887,0.0 +2888,0.0 +2889,0.0 +2890,0.0 +2891,0.0 +2892,-1.1102230246251565e-16 +2893,0.0 +2894,0.0 +2895,0.0 +2896,0.0 +2897,0.0 +2898,0.0 +2899,0.0 +2900,0.0 +2901,-1.1102230246251565e-16 +2902,0.0 +2903,0.0 +2904,0.0 +2905,0.0 +2906,0.0 +2907,1.1102230246251565e-16 +2908,0.0 +2909,0.0 +2910,0.0 +2911,0.0 +2912,0.0 +2913,0.0 +2914,0.0 +2915,0.0 +2916,1.1102230246251565e-16 +2917,0.0 +2918,0.0 +2919,0.0 diff --git a/PyPSA/source/test/data/benchmark-sp/buses.csv b/PyPSA/source/test/data/benchmark-sp/buses.csv new file mode 100644 index 0000000000000000000000000000000000000000..01809521afce63a822b49acd8409bfecddc1f2d9 --- /dev/null +++ b/PyPSA/source/test/data/benchmark-sp/buses.csv @@ -0,0 +1,2 @@ +name +DE diff --git a/PyPSA/source/test/data/benchmark-sp/crs.json b/PyPSA/source/test/data/benchmark-sp/crs.json new file mode 100644 index 0000000000000000000000000000000000000000..1b11562e30ffd94e434bc1b28f75fe83ef1b3010 --- /dev/null +++ b/PyPSA/source/test/data/benchmark-sp/crs.json @@ -0,0 +1 @@ +{"_crs": "GEOGCRS[\"WGS 84\",ENSEMBLE[\"World Geodetic System 1984 ensemble\",MEMBER[\"World Geodetic System 1984 (Transit)\"],MEMBER[\"World Geodetic System 1984 (G730)\"],MEMBER[\"World Geodetic System 1984 (G873)\"],MEMBER[\"World Geodetic System 1984 (G1150)\"],MEMBER[\"World Geodetic System 1984 (G1674)\"],MEMBER[\"World Geodetic System 1984 (G1762)\"],MEMBER[\"World Geodetic System 1984 (G2139)\"],MEMBER[\"World Geodetic System 1984 (G2296)\"],ELLIPSOID[\"WGS 84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]],ENSEMBLEACCURACY[2.0]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],CS[ellipsoidal,2],AXIS[\"geodetic latitude (Lat)\",north,ORDER[1],ANGLEUNIT[\"degree\",0.0174532925199433]],AXIS[\"geodetic longitude (Lon)\",east,ORDER[2],ANGLEUNIT[\"degree\",0.0174532925199433]],USAGE[SCOPE[\"Horizontal component of 3D system.\"],AREA[\"World.\"],BBOX[-90,-180,90,180]],ID[\"EPSG\",4326]]"} diff --git a/PyPSA/source/test/data/benchmark-sp/generators-p-high.csv b/PyPSA/source/test/data/benchmark-sp/generators-p-high.csv new file mode 100644 index 0000000000000000000000000000000000000000..9df0cf1cddaa0f5ae258056cf2a763aa7594219d --- /dev/null +++ b/PyPSA/source/test/data/benchmark-sp/generators-p-high.csv @@ -0,0 +1,2921 @@ +,solar,wind,gas,lignite +0,-0.0,0.17793474918361238,0.734212311975145,0.08785293884124255 +1,-0.0,0.19827339548237777,0.7138736656763797,0.08785293884124255 +2,-0.0,0.22713382095660353,0.6850132402021539,0.08785293884124255 +3,0.1901276691724415,0.2786053671763841,0.44341402480993175,0.08785293884124255 +4,0.3122128154106443,0.27087895405730006,0.32905529169081316,0.08785293884124255 +5,0.0033118042136133915,0.4154083288731078,0.49342692807203614,0.08785293884124255 +6,-0.0,0.4972174089575273,0.4149296522012301,0.08785293884124255 +7,-0.0,0.628225560814938,0.28392150034381947,0.08785293884124255 +8,-0.0,0.7261692094715624,0.18597785168719505,0.08785293884124255 +9,-0.0,0.8436561383705761,0.06849092278818138,0.08785293884124255 +10,-0.0,0.913307480164672,-0.0,0.086692519835328 +11,0.07933276457155715,0.8883102612499884,-0.0,0.0323569741784545 +12,0.16619235690132655,0.8338076430986734,-0.0,-0.0 +13,0.003913950434270372,0.8326346373036473,0.07559847342083979,0.08785293884124255 +14,-0.0,0.8366114675855287,0.07553559357322871,0.08785293884124255 +15,-0.0,0.82774881724305,0.0843982439157074,0.08785293884124255 +16,-0.0,0.8101371402804318,0.10200992087832562,0.08785293884124255 +17,-0.0,0.6534500271743006,0.25869703398445687,0.08785293884124255 +18,-0.0,0.5806172211546994,0.33152984000405805,0.08785293884124255 +19,0.08294564189549904,0.6051599451800252,0.22404147408323316,0.08785293884124255 +20,0.10808524660792797,0.5708455810335048,0.23321623351732468,0.08785293884124255 +21,0.0024085848826279213,0.45597199774829916,0.45376647852783025,0.08785293884124255 +22,-0.0,0.4273388197187523,0.4848082414400051,0.08785293884124255 +23,-0.0,0.46438015320142007,0.4477669079573374,0.08785293884124255 +24,-0.0,0.5530066566262078,0.3591404045325496,0.08785293884124255 +25,-0.0,0.556642615741071,0.35550444541768644,0.08785293884124255 +26,-0.0,0.5122157403063375,0.3999313208524199,0.08785293884124255 +27,0.21903068776397655,0.47858311849385404,0.21453325490092684,0.08785293884124255 +28,0.2458261945832122,0.5066481779117035,0.15967268866384177,0.08785293884124255 +29,0.005569852541077068,0.48278719622041444,0.4237900123972659,0.08785293884124255 +30,-0.0,0.38643427967654265,0.5257127814822148,0.08785293884124255 +31,-0.0,0.28814975985289976,0.6239973013058577,0.08785293884124255 +32,-0.0,0.2706517066126211,0.6414953545461364,0.08785293884124255 +33,-0.0,0.31848729371753864,0.5936597674412187,0.08785293884124255 +34,-0.0,0.36643650454479565,0.5457105566139617,0.08785293884124255 +35,0.1950953754928616,0.37393567021920077,0.3431160154466951,0.08785293884124255 +36,0.27247116484728356,0.2871271463518445,0.3525487499596293,0.08785293884124255 +37,0.01821492317487365,0.18009359990806234,0.7138385380758214,0.08785293884124255 +38,-0.0,0.1085106548341953,0.8036364063245621,0.08785293884124255 +39,-0.0,0.0689695994600592,0.8431774616986982,0.08785293884124255 +40,-0.0,0.044994994046430724,0.8671520671123267,0.08785293884124255 +41,-0.0,0.03954105537413608,0.8726060057846213,0.08785293884124255 +42,-0.0,0.06874235201538026,0.8434047091433772,0.08785293884124255 +43,0.1816976220832438,0.0829453173078142,0.6475041217676994,0.08785293884124255 +44,0.27593350561606117,0.10703354644378217,0.529180009098914,0.08785293884124255 +45,0.009032193309854704,0.24281389463945063,0.660300973209452,0.08785293884124255 +46,-0.0,0.2723560624477132,0.6397909987110442,0.08785293884124255 +47,-0.0,0.26360703582757394,0.6485400253311835,0.08785293884124255 +48,-0.0,0.24031417274798225,0.6718328884107752,0.08785293884124255 +49,-0.0,0.24906319936812155,0.6630838617906358,0.08785293884124255 +50,-0.0,0.23701908480013759,0.6751279763586199,0.08785293884124255 +51,0.1353323630926563,0.19259220936540422,0.5842224887006969,0.08785293884124255 +52,0.2011168376994314,0.1965690396472857,0.5144611838120403,0.08785293884124255 +53,0.008731120199526213,0.26587951027436335,0.6375364306848678,0.08785293884124255 +54,-0.0,0.3039434572580863,0.6082036039006711,0.08785293884124255 +55,-0.0,0.33053140828552263,0.5816156528732348,0.08785293884124255 +56,-0.0,0.3956378011860398,0.5165092599727177,0.08785293884124255 +57,-0.0,0.46438015320142007,0.4477669079573374,0.08785293884124255 +58,-0.0,0.49517218195541685,0.41697487920334053,0.08785293884124255 +59,0.0578060371830701,0.49210434145225107,0.3622366825234363,0.08785293884124255 +60,0.08264456878517054,0.520510272037119,0.3089922203364679,0.08785293884124255 +61,0.0024085848826279213,0.4792648608278908,0.4304736154482386,0.08785293884124255 +62,-0.0,0.526305081876432,0.38584197928232544,0.08785293884124255 +63,-0.0,0.6064098061257595,0.305737255032998,0.08785293884124255 +64,-0.0,0.6312934013181037,0.2808536598406537,0.08785293884124255 +65,-0.0,0.7278735653066545,0.18427349585210295,0.08785293884124255 +66,-0.0,0.9706874599461051,-0.0,0.029312540053894942 +67,0.07120379059268792,0.9287962094073121,-0.0,-0.0 +68,0.07647257002343649,0.9235274299765635,-0.0,-0.0 +69,0.004064486989434617,0.9959355130105654,-0.0,-0.0 +70,-0.0,0.9478490917558714,-0.0,0.0521509082441286 +71,-0.0,0.7083302850642653,0.2038167760944921,0.08785293884124255 +72,-0.0,0.6435647633307666,0.2685822978279908,0.08785293884124255 +73,-0.0,0.7551432586681276,0.15700380249062984,0.08785293884124255 +74,-0.0,0.9465992308101371,-0.0,0.05340076918986292 +75,0.13006358366190773,0.8699364163380923,-0.0,-0.0 +76,0.18380513385554323,0.8161948661444568,-0.0,-0.0 +77,0.013247216854453566,0.9867527831455465,-0.0,-0.0 +78,-0.0,1.0,-0.0,-0.0 +79,-0.0,0.9096715210498089,0.0024755401089485846,0.08785293884124255 +80,-0.0,0.8430880197588786,0.06905904139987885,0.08785293884124255 +81,-0.0,0.7940025717082269,0.11814448945053059,0.08785293884124255 +82,-0.0,0.8470648500407602,0.0650822111179973,0.08785293884124255 +83,0.15098816482973781,0.8490118351702622,-0.0,-0.0 +84,0.2274607348531743,0.7725392651468257,-0.0,-0.0 +85,0.02408584882627921,0.9759141511737208,-0.0,-0.0 +86,-0.0,0.945122122419724,-0.0,0.05487787758027596 +87,-0.0,0.8692214758969571,0.04292558526180036,0.08785293884124255 +88,-0.0,0.7736639254094615,0.13848313574929594,0.08785293884124255 +89,-0.0,0.7138978474588995,0.19824921369985793,0.08785293884124255 +90,-0.0,0.7763908947456089,0.13575616641314858,0.08785293884124255 +91,0.07210700992367339,0.9060355619349458,-0.0,0.02185742814138078 +92,0.08971978687789006,0.9102802131221099,-0.0,-0.0 +93,0.009634339530511685,0.9586433453781211,-0.0,0.03172231509136719 +94,-0.0,0.9217156356177929,-0.0,0.0782843643822071 +95,-0.0,0.8264989562973158,0.0856481048614416,0.08785293884124255 +96,-0.0,0.733554751423628,0.1785923097351294,0.08785293884124255 +97,-0.0,0.6967406653856393,0.21540639577311815,0.08785293884124255 +98,-0.0,0.7837764366976745,0.12837062446108294,0.08785293884124255 +99,0.3659543656042798,0.6340456343957201,-0.0,-0.0 +100,0.5089640930103125,0.49103590698968747,-0.0,-0.0 +101,0.04531150310443776,0.7121934916238074,0.15464206643051226,0.08785293884124255 +102,-0.0,0.5816398346557546,0.3305072265030028,0.08785293884124255 +103,-0.0,0.5376674541103792,0.37447960704837824,0.08785293884124255 +104,-0.0,0.4890365009490854,0.4231105602096721,0.08785293884124255 +105,-0.0,0.44881370324091246,0.4633333579178449,0.08785293884124255 +106,-0.0,0.5356222271082687,0.37652483405048875,0.08785293884124255 +107,0.2157188835503632,0.7001493770558234,-0.0,0.08413173939381344 +108,0.25922394799283,0.6195901579171381,0.033332955248789334,0.08785293884124255 +109,0.018515996285202143,0.44449600179201254,0.4491350630815427,0.08785293884124255 +110,-0.0,0.4059775599189317,0.5061695012398257,0.08785293884124255 +111,-0.0,0.4174535558752183,0.49469350528353906,0.08785293884124255 +112,-0.0,0.3773443818893849,0.5348026792693725,0.08785293884124255 +113,-0.0,0.4917634702852327,0.4203835908735247,0.08785293884124255 +114,-0.0,0.6725388125273318,0.2396082486314256,0.08785293884124255 +115,0.13141841265838594,0.7830946943636377,-0.0,0.0854868929779764 +116,0.1920846443895767,0.8079153556104233,-0.0,-0.0 +117,0.021827800498815535,0.8102507640027713,0.08006849665717063,0.08785293884124255 +118,-0.0,0.8007063713262558,0.11144068983250167,0.08785293884124255 +119,-0.0,0.7897984939816666,0.12234856717709088,0.08785293884124255 +120,-0.0,0.6589039658465953,0.25324309531216216,0.08785293884124255 +121,-0.0,0.588002763106765,0.3241442980519924,0.08785293884124255 +122,-0.0,0.5066481779117035,0.40549888324705397,0.08785293884124255 +123,0.23017039284613072,0.31723743277180444,0.3647392355408223,0.08785293884124255 +124,0.32290091082730565,0.21474883522160115,0.3744973151098506,0.08785293884124255 +125,0.023483702605622228,0.1803208473527413,0.7083425112003938,0.08785293884124255 +126,-0.0,0.15441463865934177,0.7577324224994156,0.08785293884124255 +127,-0.0,0.1124874851160768,0.7996595760426807,0.08785293884124255 +128,-0.0,0.09419406581942188,0.8179529953393355,0.08785293884124255 +129,-0.0,0.12271362012662923,0.7894334410321282,0.08785293884124255 +130,-0.0,0.1487334525423682,0.7634136086163892,0.08785293884124255 +131,0.25636375344470935,0.12305449129364764,0.5327288164204005,0.08785293884124255 +132,0.37995426523455456,0.15384652004764443,0.37834627587655845,0.08785293884124255 +133,0.041999698890824375,0.1628227941124627,0.7073245681554704,0.08785293884124255 +134,-0.0,0.20429545276636976,0.7078516083923877,0.08785293884124255 +135,-0.0,0.20838590677059077,0.7037611543881667,0.08785293884124255 +136,-0.0,0.2065679272131592,0.7055791339455982,0.08785293884124255 +137,-0.0,0.18895625025054114,0.7231908109082164,0.08785293884124255 +138,-0.0,0.16702687183902312,0.7451201893197343,0.08785293884124255 +139,0.2815033581571383,0.13464411097227375,0.4959995920293454,0.08785293884124255 +140,0.43309366920753306,0.14191602920199992,0.3371373627492245,0.08785293884124255 +141,0.05208564808682879,0.14430212737112882,0.7157592857007998,0.08785293884124255 +142,-0.0,0.1711173258432441,0.7410297353155133,0.08785293884124255 +143,-0.0,0.1491879474317261,0.7629591137270313,0.08785293884124255 +144,-0.0,0.10317033988424014,0.8089767212745173,0.08785293884124255 +145,-0.0,0.04601760754748596,0.8661294536112715,0.08785293884124255 +146,-0.0,0.03647321487097035,0.8756738462877871,0.08785293884124255 +147,0.19464376582736886,0.02022502257642593,0.6972782727549627,0.08785293884124255 +148,0.27773994427803217,0.016134568572204955,0.6182725483085203,0.08785293884124255 +149,0.038085748456554,0.05681186116973576,0.8172494515324676,0.08785293884124255 +150,-0.0,0.06533364034519612,0.8468134208135614,0.08785293884124255 +151,-0.0,0.044426875434733365,0.8677201857240241,0.08785293884124255 +152,-0.0,0.031132899921015193,0.8810141612377422,0.08785293884124255 +153,-0.0,0.026133456138078447,0.8860136050206789,0.08785293884124255 +154,-0.0,0.023633734246610073,0.8885133269121473,0.08785293884124255 +155,0.1279560718896083,0.011021501066928736,0.7731694882022203,0.08785293884124255 +156,0.16393430857386287,0.008749026620139306,0.7394637259647552,0.08785293884124255 +157,0.020322434947173083,0.017043558350920727,0.8747810678606636,0.08785293884124255 +158,-0.0,0.02329286307959166,0.8888541980791658,0.08785293884124255 +159,-0.0,0.0367004623156493,0.8754465988431082,0.08785293884124255 +160,-0.0,0.03931380792945714,0.8728332532293003,0.08785293884124255 +161,-0.0,0.03340537436780462,0.8787416867909528,0.08785293884124255 +162,-0.0,0.03215551342207044,0.879991547736687,0.08785293884124255 +163,0.17989118342127286,0.02726969336147316,0.7049861843760115,0.08785293884124255 +164,0.24808424291067588,0.03363262181248357,0.630430196435598,0.08785293884124255 +165,0.02995677447768477,0.07283280601960124,0.8093574806614714,0.08785293884124255 +166,-0.0,0.12907654857763964,0.7830705125811178,0.08785293884124255 +167,-0.0,0.10623818038740586,0.8059088807713516,0.08785293884124255 +168,-0.0,0.0810137140280432,0.8311333471307143,0.08785293884124255 +169,-0.0,0.08203632752909844,0.830110733629659,0.08785293884124255 +170,-0.0,0.09317145231836664,0.8189756088403908,0.08785293884124255 +171,0.11576261092130445,0.10157960777148753,0.6948048424659654,0.08785293884124255 +172,0.13789148453044847,0.09612566909919289,0.6781299075291161,0.08785293884124255 +173,0.024687995046936192,0.11782780006603195,0.7696312660457892,0.08785293884124255 +174,-0.0,0.1072607938884611,0.8048862672702963,0.08785293884124255 +175,-0.0,0.07601427024510644,0.836132790913651,0.08785293884124255 +176,-0.0,0.07260555857492229,0.8395415025838351,0.08785293884124255 +177,-0.0,0.06487914545583823,0.8472679157029193,0.08785293884124255 +178,-0.0,0.06056144400693832,0.8515856171518191,0.08785293884124255 +179,0.11546153781097597,0.0454494889357886,0.7512360344119928,0.08785293884124255 +180,0.11395617225933352,0.027496940806152104,0.7706939480932719,0.08785293884124255 +181,0.015505265181917242,0.030337533864638895,0.8663042621122014,0.08785293884124255 +182,-0.0,0.04919907177299116,0.8629479893857662,0.08785293884124255 +183,-0.0,0.08987636437052197,0.8222706967882355,0.08785293884124255 +184,-0.0,0.1671404955613626,0.7450065655973949,0.08785293884124255 +185,-0.0,0.19168321958668844,0.720463841572069,0.08785293884124255 +186,-0.0,0.22031639761623525,0.6918306635425222,0.08785293884124255 +187,0.10070895540487995,0.18838813163884377,0.6230499741150337,0.08785293884124255 +188,0.12268729245885973,0.1406661682562657,0.6487936004436319,0.08785293884124255 +189,0.01896760595069488,0.17452603751342824,0.7186534176946343,0.08785293884124255 +190,-0.0,0.1994096327057725,0.7127374284529849,0.08785293884124255 +191,-0.0,0.18520666741333858,0.7269403937454189,0.08785293884124255 +192,-0.0,0.2006594936515067,0.7114875675072507,0.08785293884124255 +193,-0.0,0.20327283926531453,0.708874221893443,0.08785293884124255 +194,-0.0,0.17475328495810719,0.7373937762006503,0.08785293884124255 +195,0.1266012428931301,0.12066839312451874,0.6648774251411086,0.08785293884124255 +196,0.1539988959330227,0.09930713332469811,0.6588410319010366,0.08785293884124255 +197,0.03281696902580542,0.1088515260012137,0.7704785661317383,0.08785293884124255 +198,-0.0,0.09328507604070611,0.8188619851180513,0.08785293884124255 +199,-0.0,0.07419629068767489,0.8379507704710826,0.08785293884124255 +200,-0.0,0.0849905443099247,0.8271565168488327,0.08785293884124255 +201,-0.0,0.1191912847341056,0.7929557764246519,0.08785293884124255 +202,-0.0,0.1802072236304018,0.7319398375283557,0.08785293884124255 +203,0.09619285874995259,0.2133853505535275,0.6025688518552774,0.08785293884124255 +204,0.11982709791073907,0.3234867375004754,0.46883322574754294,0.08785293884124255 +205,0.02815033581571383,0.40211435335938966,0.481882371983654,0.08785293884124255 +206,-0.0,0.4255208401613208,0.4866262209974366,0.08785293884124255 +207,-0.0,0.3991601365785634,0.512986924580194,0.08785293884124255 +208,-0.0,0.45483576052490443,0.45731130063385295,0.08785293884124255 +209,-0.0,0.42233937593581555,0.4898076852229418,0.08785293884124255 +210,-0.0,0.43881481567503894,0.47333224548371855,0.08785293884124255 +211,0.18786962084497782,0.3516654206406643,0.3726120196731153,0.08785293884124255 +212,0.21827800498815533,0.3476885903587828,0.3461804658118194,0.08785293884124255 +213,0.05359101363847124,0.336894336736533,0.5216617107837531,0.08785293884124255 +214,-0.0,0.24656347747665316,0.6655835836821042,0.08785293884124255 +215,-0.0,0.15055143209979976,0.7615956290589576,0.08785293884124255 +216,-0.0,0.15043780837746026,0.7617092527812972,0.08785293884124255 +217,-0.0,0.20554531371210397,0.7066017474466535,0.08785293884124255 +218,-0.0,0.30837478242932564,0.6037722787294317,0.08785293884124255 +219,0.11546153781097597,0.44983631674196767,0.3468492066058138,0.08785293884124255 +220,0.15143977449523052,0.7344637412023438,0.026243545461183113,0.08785293884124255 +221,0.027397653039892604,0.9096715210498089,-0.0,0.06293082591029853 +222,-0.0,0.9061491856572853,0.005997875501472141,0.08785293884124255 +223,-0.0,0.8259308376856184,0.08621622347313908,0.08785293884124255 +224,-0.0,0.7278735653066545,0.18427349585210295,0.08785293884124255 +225,-0.0,0.6403832991052614,0.27176376205349606,0.08785293884124255 +226,-0.0,0.6232261170320013,0.2889209441267562,0.08785293884124255 +227,0.2539551685620814,0.6870826489867843,-0.0,0.058962182451134326 +228,0.2298693197358022,0.6857191643187106,-0.0,0.08441151594548718 +229,0.06292428005865443,0.46631175648119105,0.38291102461891197,0.08785293884124255 +230,-0.0,0.3524607866970406,0.5596862744617168,0.08785293884124255 +231,-0.0,0.287013522629505,0.6251335385292525,0.08785293884124255 +232,-0.0,0.21247636077481172,0.6996707003839457,0.08785293884124255 +233,-0.0,0.1646407736698942,0.7475062874888632,0.08785293884124255 +234,-0.0,0.19213771447604633,0.7200093466827111,0.08785293884124255 +235,0.15595587115015788,0.1528239065465892,0.6033672834620104,0.08785293884124255 +236,0.1803427930867656,0.14532474087218408,0.5864795271998078,0.08785293884124255 +237,0.055246915745277944,0.10317033988424014,0.7537298055292393,0.08785293884124255 +238,-0.0,0.13975717847754995,0.7723898826812075,0.08785293884124255 +239,-0.0,0.14055254453392627,0.7715945166248311,0.08785293884124255 +240,-0.0,0.1568007368284707,0.7553463243302867,0.08785293884124255 +241,-0.0,0.17429879006874932,0.7378482710900081,0.08785293884124255 +242,-0.0,0.1730489291230151,0.7390981320357424,0.08785293884124255 +243,0.19088035194826275,0.13964355475521048,0.5816231544552841,0.08785293884124255 +244,0.20563293435435875,0.16770861417305996,0.5388055126313387,0.08785293884124255 +245,0.08189188600934931,0.1579369740518654,0.6723182010975427,0.08785293884124255 +246,-0.0,0.19088785353031215,0.7212592076284453,0.08785293884124255 +247,-0.0,0.17861649151764922,0.7335305696411082,0.08785293884124255 +248,-0.0,0.1439612562041104,0.7681858049546471,0.08785293884124255 +249,-0.0,0.11464633584052676,0.7975007253182307,0.08785293884124255 +250,-0.0,0.11521445445222411,0.7969326067065333,0.08785293884124255 +251,0.1947943023825331,0.08362705964185103,0.6337256991343734,0.08785293884124255 +252,0.20412756880271632,0.13964355475521048,0.5683759376008306,0.08785293884124255 +253,0.04455882032861654,0.17020833606452831,0.6973799047656125,0.08785293884124255 +254,-0.0,0.2423593997500927,0.6697876614086647,0.08785293884124255 +255,-0.0,0.23940518296926647,0.6727418781894909,0.08785293884124255 +256,-0.0,0.2397460541362849,0.6724010070224725,0.08785293884124255 +257,-0.0,0.23735995596715598,0.6747871051916015,0.08785293884124255 +258,-0.0,0.24758609097770842,0.664560970181049,0.08785293884124255 +259,0.19614913137901133,0.2308834037938061,0.48511452598593996,0.08785293884124255 +260,0.1959985948238471,0.30008025069854427,0.4160682156363661,0.08785293884124255 +261,0.05238672119715728,0.263379788382895,0.5963805515787052,0.08785293884124255 +262,-0.0,0.2456544876979374,0.66649257346082,0.08785293884124255 +263,-0.0,0.2270201972342641,0.6851268639244934,0.08785293884124255 +264,-0.0,0.19759165314834096,0.7145554080104165,0.08785293884124255 +265,-0.0,0.17793474918361238,0.734212311975145,0.08785293884124255 +266,-0.0,0.15407376749232338,0.7580732936664341,0.08785293884124255 +267,0.18320298763488624,0.13418961608291585,0.5947544574409553,0.08785293884124255 +268,0.2574175093308591,0.12100926429153716,0.5337202875363611,0.08785293884124255 +269,0.07526827758212254,0.09123984903859562,0.7456389345380393,0.08785293884124255 +270,-0.0,0.09703465887790867,0.8151124022808488,0.08785293884124255 +271,-0.0,0.0773777549131801,0.8347693062455773,0.08785293884124255 +272,-0.0,0.06771973851432501,0.8444273226444324,0.08785293884124255 +273,-0.0,0.05749360350377258,0.8546534576549849,0.08785293884124255 +274,-0.0,0.052494159720835834,0.8596529014379216,0.08785293884124255 +275,0.20834259234731517,0.0406772925975308,0.6631271762139115,0.08785293884124255 +276,0.24627780424870493,0.0554483765016621,0.6104208804083904,0.08785293884124255 +277,0.06834359604456726,0.06374290823244351,0.7800605568817466,0.08785293884124255 +278,-0.0,0.06453827428881982,0.8476087868699376,0.08785293884124255 +279,-0.0,0.03158739481037308,0.8805596663483843,0.08785293884124255 +280,-0.0,0.026701574749775806,0.8854454864089816,0.08785293884124255 +281,-0.0,0.03306450320078621,0.8790825579579712,0.08785293884124255 +282,-0.0,0.04942631921767011,0.8627207419410873,0.08785293884124255 +283,0.20819205579215094,0.07771862608019851,0.626236379286408,0.08785293884124255 +284,0.24462190214189825,0.1431658901477341,0.5243592688691251,0.08785293884124255 +285,0.06171998761734048,0.19600092103558833,0.6544261525058287,0.08785293884124255 +286,-0.0,0.2819004551242288,0.6302466060345286,0.08785293884124255 +287,-0.0,0.23531472896504552,0.676832332193712,0.08785293884124255 +288,-0.0,0.1873655181377885,0.724781543020969,0.08785293884124255 +289,-0.0,0.18543391485801752,0.7267131463007399,0.08785293884124255 +290,-0.0,0.19145597214200952,0.7206910890167479,0.08785293884124255 +291,0.26103038665480094,0.22940629540339297,0.4217103791005635,0.08785293884124255 +292,0.3998250905162349,0.21633956733435378,0.2959824033081688,0.08785293884124255 +293,0.11395617225933352,0.1774802542942545,0.6207106346051694,0.08785293884124255 +294,-0.0,0.15180129304553394,0.7603457681132235,0.08785293884124255 +295,-0.0,0.10407932966295591,0.8080677314958016,0.08785293884124255 +296,-0.0,0.08874012714712726,0.8234069340116301,0.08785293884124255 +297,-0.0,0.11260110883841626,0.7995459523203412,0.08785293884124255 +298,-0.0,0.15521000471571808,0.7569370564430393,0.08785293884124255 +299,0.31778266795172133,0.18759276558246746,0.4067716276245687,0.08785293884124255 +300,0.3913950434270372,0.3136014736569414,0.2071505440747788,0.08785293884124255 +301,0.08941871376756157,0.4676752411492648,0.35505310624193104,0.08785293884124255 +302,-0.0,0.6056144400693831,0.3065326210893743,0.08785293884124255 +303,-0.0,0.7404857984863358,0.17166126267242166,0.08785293884124255 +304,-0.0,0.8235447395164895,0.0886023216422679,0.08785293884124255 +305,-0.0,0.7879805144242349,0.12416654673452256,0.08785293884124255 +306,-0.0,0.6478824647796666,0.26426459637909083,0.08785293884124255 +307,0.3102558401935091,0.5598240799665761,0.04206714099867222,0.08785293884124255 +308,0.44498605706550837,0.5550139429344916,-0.0,-0.0 +309,0.16182679680156345,0.4173399321528789,0.3329803322043151,0.08785293884124255 +310,-0.0,0.3484839564151591,0.5636631047435983,0.08785293884124255 +311,-0.0,0.3760945209436507,0.5360525402151067,0.08785293884124255 +312,-0.0,0.4574491061387123,0.4546979550200452,0.08785293884124255 +313,-0.0,0.5273276953774872,0.3848193657812702,0.08785293884124255 +314,-0.0,0.5370993354986818,0.3750477256600756,0.08785293884124255 +315,0.1098916852698989,0.5105113844712454,0.2917439914176131,0.08785293884124255 +316,0.1129024163731838,0.49551305312243527,0.30373159166313846,0.08785293884124255 +317,0.031462140029327214,0.4474502185728388,0.43323470255659136,0.08785293884124255 +318,-0.0,0.3875705168999373,0.5245765442588202,0.08785293884124255 +319,-0.0,0.32337311377813593,0.5887739473806215,0.08785293884124255 +320,-0.0,0.28337756351464194,0.6287694976441155,0.08785293884124255 +321,-0.0,0.28553641423909193,0.6266106469196655,0.08785293884124255 +322,-0.0,0.24474549791922165,0.6674015632395358,0.08785293884124255 +323,0.12885929122059378,0.159527706164618,0.6237600637735456,0.08785293884124255 +324,0.136988265199463,0.179525481296365,0.5956333146629293,0.08785293884124255 +325,0.040945943004674655,0.18407043018994385,0.6871306879641389,0.08785293884124255 +326,-0.0,0.1667996243943442,0.7453474367644133,0.08785293884124255 +327,-0.0,0.11078312928098473,0.8013639318777727,0.08785293884124255 +328,-0.0,0.07453716185469332,0.8376098993040642,0.08785293884124255 +329,-0.0,0.03715495720500719,0.8749921039537503,0.08785293884124255 +330,-0.0,0.026133456138078447,0.8860136050206789,0.08785293884124255 +331,0.1175690495832754,0.013862094125415525,0.7807159174500665,0.08785293884124255 +332,0.1847083531865287,0.009317145231836665,0.7181215627403921,0.08785293884124255 +333,0.09995627262905872,0.014657460181791825,0.7975333283479069,0.08785293884124255 +334,-0.0,0.06226579984203039,0.849881261316727,0.08785293884124255 +335,-0.0,0.08839925598010882,0.8237478051786487,0.08785293884124255 +336,-0.0,0.0646518980111593,0.8474951631475981,0.08785293884124255 +337,-0.0,0.04181352982092552,0.8703335313378319,0.08785293884124255 +338,-0.0,0.043404261933678114,0.8687427992250794,0.08785293884124255 +339,0.22204141886726148,0.02090676491046276,0.6691988773810332,0.08785293884124255 +340,0.3013741834388186,0.01272585690202081,0.598047020817918,0.08785293884124255 +341,0.12584856011730886,0.016134568572204955,0.7701639324692436,0.08785293884124255 +342,-0.0,0.07760500235785904,0.8345420588008984,0.08785293884124255 +343,-0.0,0.0813545851950616,0.8307924759636959,0.08785293884124255 +344,-0.0,0.06476552173349877,0.8473815394252586,0.08785293884124255 +345,-0.0,0.07465078557703278,0.8374962755817247,0.08785293884124255 +346,-0.0,0.09226246253965086,0.8198845986191066,0.08785293884124255 +347,0.36761026771108646,0.06760611479198554,0.4769306786556855,0.08785293884124255 +348,0.5794152008271793,0.07715050746850116,0.2555813528630769,0.08785293884124255 +349,0.22685858863251732,0.13714383286374213,0.548144639662498,0.08785293884124255 +350,-0.0,0.2720151912806948,0.6401318698780627,0.08785293884124255 +351,-0.0,0.21270360821949066,0.6994434529392668,0.08785293884124255 +352,-0.0,0.16134568572204952,0.7508013754367079,0.08785293884124255 +353,-0.0,0.13532585330631056,0.7768212078524469,0.08785293884124255 +354,-0.0,0.13009916207869487,0.7820478990800626,0.08785293884124255 +355,0.49451258371454504,0.052607783443175314,0.3650266940010371,0.08785293884124255 +356,0.7008982008447251,0.04272251959964129,0.1685263407143911,0.08785293884124255 +357,0.24928853535198983,0.0972619063225876,0.56559661948418,0.08785293884124255 +358,-0.0,0.19213771447604633,0.7200093466827111,0.08785293884124255 +359,-0.0,0.14657460181791826,0.7655724593408392,0.08785293884124255 +360,-0.0,0.13055365696805277,0.7815934041907047,0.08785293884124255 +361,-0.0,0.13828007008713683,0.7738669910716206,0.08785293884124255 +362,0.00015053655516424508,0.1650952685592521,0.746901256044341,0.08785293884124255 +363,0.38853484887891654,0.1774802542942545,0.3461319579855864,0.08785293884124255 +364,0.588447394137034,0.2045227002110487,0.11917696681067481,0.08785293884124255 +365,0.20758990957149395,0.2600847004350503,0.44447245115221323,0.08785293884124255 +366,-0.0,0.36711824687883243,0.545028814279925,0.08785293884124255 +367,-0.0,0.25065393148087417,0.6614931296778832,0.08785293884124255 +368,-0.0,0.17407154262407035,0.7380755185346871,0.08785293884124255 +369,-0.0,0.16305004155714162,0.7490970196016158,0.08785293884124255 +370,0.00015053655516424508,0.20361371043233295,0.7083828141712601,0.08785293884124255 +371,0.35827700129090323,0.24179128113839538,0.3120787787294589,0.08785293884124255 +372,0.5333510149469203,0.2240659804534378,0.15473006575839932,0.08785293884124255 +373,0.3099547670831806,0.2105447574950407,0.3916475365805361,0.08785293884124255 +374,-0.0,0.26326616466055547,0.648880896498202,0.08785293884124255 +375,-0.0,0.15577812332741545,0.756368937831342,0.08785293884124255 +376,-0.0,0.0865812764226773,0.8255657847360801,0.08785293884124255 +377,-0.0,0.05328952577721213,0.8588575353815453,0.08785293884124255 +378,0.00030107311032849017,0.043404261933678114,0.8684417261147509,0.08785293884124255 +379,0.3522555390843335,0.03579147253693352,0.5241000495374905,0.08785293884124255 +380,0.3462340768777637,0.038518441873080844,0.527394542407913,0.08785293884124255 +381,0.10928953904924192,0.06090231517395673,0.7419552069355588,0.08785293884124255 +382,-0.0,0.1128283562830952,0.7993187048756623,0.08785293884124255 +383,-0.0,0.09646654026621132,0.8156805208925462,0.08785293884124255 +384,-0.0,0.07215106368556441,0.839995997473193,0.08785293884124255 +385,-0.0,0.065788135234554,0.8463589259242035,0.08785293884124255 +386,-0.0,0.07442353813235385,0.8377235230264036,0.08785293884124255 +387,0.21767585876749837,0.1085106548341953,0.5859605475570637,0.08785293884124255 +388,0.4249646952286638,0.16907209884113358,0.31811026708896,0.08785293884124255 +389,0.1803427930867656,0.17259443423365722,0.5592098338383347,0.08785293884124255 +390,-0.0,0.2914448478007444,0.620702213358013,0.08785293884124255 +391,-0.0,0.30905652476336254,0.6030905363953949,0.08785293884124255 +392,-0.0,0.28462742446037614,0.6275196366983813,0.08785293884124255 +393,-0.0,0.2277019395683009,0.6844451215904566,0.08785293884124255 +394,0.0007526827758212254,0.22588396001086936,0.6855104183720669,0.08785293884124255 +395,0.41563142880848064,0.30837478242932564,0.1881408499209511,0.08785293884124255 +396,0.6015440744363233,0.30826115870698617,0.002341828015448036,0.08785293884124255 +397,0.2625357522064434,0.2819004551242288,0.36771085382808527,0.08785293884124255 +398,-0.0,0.3655275147660798,0.5466195463926776,0.08785293884124255 +399,-0.0,0.3279180626717148,0.5842289984870426,0.08785293884124255 +400,-0.0,0.30292084375703104,0.6092262174017264,0.08785293884124255 +401,-0.0,0.3155330769367124,0.596613984222045,0.08785293884124255 +402,0.0010537558861497154,0.3729130567181455,0.5381802485544621,0.08785293884124255 +403,0.4446849839551799,0.5409625420582239,-0.0,0.01435247398659617 +404,0.5761033966135658,0.4238966033864342,-0.0,-0.0 +405,0.21707371254684138,0.3987056416892055,0.2963677069227105,0.08785293884124255 +406,-0.0,0.39666041468709506,0.5154866464716623,0.08785293884124255 +407,-0.0,0.3810939647265874,0.53105309643217,0.08785293884124255 +408,-0.0,0.41086337997952893,0.5012836811792285,0.08785293884124255 +409,-0.0,0.39825114679984763,0.5138959143589098,0.08785293884124255 +410,0.002709657992956411,0.3895021201797083,0.5199352829860927,0.08785293884124255 +411,0.3027290124352968,0.3239412323898333,0.2854768163336273,0.08785293884124255 +412,0.3346427621301168,0.4690387258173384,0.10846557321130225,0.08785293884124255 +413,0.1542999690433512,0.2880361361305603,0.4698109559848459,0.08785293884124255 +414,-0.0,0.1927058330877437,0.7194412280710137,0.08785293884124255 +415,-0.0,0.15895958755292064,0.7531874736058368,0.08785293884124255 +416,-0.0,0.10794253622249794,0.8042045249362595,0.08785293884124255 +417,-0.0,0.08885375086946673,0.8232933102892908,0.08785293884124255 +418,0.0018064386619709406,0.09351232348538505,0.8168282990114014,0.08785293884124255 +419,0.40494333339181926,0.09805727237896392,0.4091464553879743,0.08785293884124255 +420,0.33313739657847435,0.12430435223938183,0.4547053123409013,0.08785293884124255 +421,0.199009325927132,0.10112511288212964,0.6120126223494957,0.08785293884124255 +422,-0.0,0.23008803773742983,0.6820590234213276,0.08785293884124255 +423,-0.0,0.37552640233195334,0.5366206588268041,0.08785293884124255 +424,-0.0,0.47551527799068827,0.43663178316806917,0.08785293884124255 +425,-0.0,0.5619829306910261,0.3501641304677313,0.08785293884124255 +426,0.0006021462206569803,0.5580061004091446,0.3535388145289559,0.08785293884124255 +427,0.15023548205391657,0.49028636189481956,0.2716252172100213,0.08785293884124255 +428,0.18229976830390077,0.32905429989510954,0.4007929929597471,0.08785293884124255 +429,0.07662310657860075,0.2198619027268774,0.6156620518532794,0.08785293884124255 +430,-0.0,0.29212659013478126,0.6200204710239762,0.08785293884124255 +431,-0.0,0.2883770072975787,0.6237700538611788,0.08785293884124255 +432,-0.0,0.29803502369643375,0.6141120374623237,0.08785293884124255 +433,-0.0,0.32428210355685166,0.5878649576019057,0.08785293884124255 +434,0.0030107311032849015,0.36234605054057467,0.5467902795148979,0.08785293884124255 +435,0.41472820947749517,0.48562778927890127,0.01179106240236101,0.08785293884124255 +436,0.37001885259371436,0.47278830865454097,0.06933989991050206,0.08785293884124255 +437,0.15746123670180034,0.3179191751058413,0.43676664935111587,0.08785293884124255 +438,-0.0,0.33814419768226717,0.5740028634764902,0.08785293884124255 +439,-0.0,0.25974382926803186,0.6524032318907256,0.08785293884124255 +440,-0.0,0.15975495360929695,0.7523921075494605,0.08785293884124255 +441,-0.0,0.12339536246066607,0.7887516986980914,0.08785293884124255 +442,0.0024085848826279213,0.1085106548341953,0.8012278214419342,0.08785293884124255 +443,0.5217597001992734,0.10760166505547952,0.2827856959040045,0.08785293884124255 +444,0.49963082659012936,0.11362372233947152,0.2988925122291566,0.08785293884124255 +445,0.2798474560503316,0.052039664831477955,0.5802599402769479,0.08785293884124255 +446,-0.0,0.058970711894185714,0.8531763492645718,0.08785293884124255 +447,-0.0,0.07362817207597754,0.8385188890827799,0.08785293884124255 +448,-0.0,0.06953771807175656,0.8426093430870009,0.08785293884124255 +449,-0.0,0.08783113736841147,0.8243159237903459,0.08785293884124255 +450,0.008128973978869233,0.10351121105125854,0.8005068761286296,0.08785293884124255 +451,0.6721457188083543,0.13555310075098953,0.10444824159941359,0.08785293884124255 +452,0.7892631587261368,0.21073684127386316,-0.0,-0.0 +453,0.3605350496183669,0.24554086397559793,0.3060711475647926,0.08785293884124255 +454,-0.0,0.4113178748688869,0.5008291862898706,0.08785293884124255 +455,-0.0,0.36666375198947454,0.5454833091692829,0.08785293884124255 +456,-0.0,0.2563351175978477,0.6558119435609098,0.08785293884124255 +457,-0.0,0.18975161630691745,0.7223954448518399,0.08785293884124255 +458,0.00797843742370499,0.13543947702865003,0.7687291467064025,0.08785293884124255 +459,0.4338463519833543,0.10044337054809283,0.3778573386273103,0.08785293884124255 +460,0.4990286803694724,0.13668933797438423,0.2764290428149008,0.08785293884124255 +461,0.20096630114426717,0.12703132157552915,0.584149438438961,0.08785293884124255 +462,-0.0,0.15566449960507597,0.7564825615536814,0.08785293884124255 +463,-0.0,0.12691769785318968,0.7852293633055678,0.08785293884124255 +464,-0.0,0.09907988588001916,0.8130671752787383,0.08785293884124255 +465,-0.0,0.08749026620139307,0.8246567949573643,0.08785293884124255 +466,0.002709657992956411,0.08669490014501677,0.8227425030207842,0.08785293884124255 +467,0.5896516865783479,0.13134902302442905,0.19114635155598048,0.08785293884124255 +468,0.5795657373823435,0.18213882691017283,0.15044249686624112,0.08785293884124255 +469,0.38326606944816793,0.18077534224209915,0.3481056494684904,0.08785293884124255 +470,-0.0,0.35223353925236167,0.5599135219063958,0.08785293884124255 +471,-0.0,0.4657436378694937,0.44640342328926375,0.08785293884124255 +472,-0.0,0.47483353565665143,0.437313525502106,0.08785293884124255 +473,-0.0,0.5439167588390501,0.3682303023197073,0.08785293884124255 +474,0.0007526827758212254,0.6145907141342014,0.2968036642487348,0.08785293884124255 +475,0.1457193853989892,0.6703799618028818,0.0960477139568864,0.08785293884124255 +476,0.24100902481795633,0.6656077654646241,0.0055302708761770375,0.08785293884124255 +477,0.14150436185439036,0.49596754801179316,0.27467515129257397,0.08785293884124255 +478,-0.0,0.4357469751718733,0.4764000859868842,0.08785293884124255 +479,-0.0,0.378821490279798,0.5333255708789595,0.08785293884124255 +480,-0.0,0.39950100774558184,0.5126460534131756,0.08785293884124255 +481,-0.0,0.5041484560202351,0.4079986051385224,0.08785293884124255 +482,0.005118242875584332,0.6317478962074616,0.2752809220757115,0.08785293884124255 +483,0.3573737819599178,0.6426262180400821,-0.0,-0.0 +484,0.43505064442466823,0.5649493555753318,-0.0,-0.0 +485,0.2312241487322804,0.6365200925457194,0.04440281988075766,0.08785293884124255 +486,-0.0,0.5731180554802943,0.33902900567846317,0.08785293884124255 +487,-0.0,0.594479315280115,0.31766774587864244,0.08785293884124255 +488,-0.0,0.6411786651616378,0.27096839599711964,0.08785293884124255 +489,-0.0,0.5911842273322703,0.32096283382648716,0.08785293884124255 +490,0.01399989963027479,0.48449155205550654,0.4136556094729761,0.08785293884124255 +491,0.5789635911616865,0.4210364088383135,-0.0,-0.0 +492,0.5499100360149872,0.4500899639850128,-0.0,-0.0 +493,0.21165439656092858,0.32689544917065955,0.3735972154271694,0.08785293884124255 +494,-0.0,0.36223242681823514,0.5499146343405223,0.08785293884124255 +495,-0.0,0.3379169502375883,0.5742301109211692,0.08785293884124255 +496,-0.0,0.3902974862360847,0.5218495749226728,0.08785293884124255 +497,-0.0,0.35836922025869317,0.5537778409000642,0.08785293884124255 +498,0.01159131474764687,0.33837144512694617,0.5621843012841644,0.08785293884124255 +499,0.4094594300467466,0.5576652292421262,-0.0,0.03287534071112713 +500,0.2846646258155874,0.6035692130672726,0.02391322227589744,0.08785293884124255 +501,0.1833535241900505,0.43006578905489967,0.2987277479138073,0.08785293884124255 +502,-0.0,0.44029192406545214,0.4718551370933053,0.08785293884124255 +503,-0.0,0.4001827500796187,0.5119643110791388,0.08785293884124255 +504,-0.0,0.3097382670973993,0.602408794061358,0.08785293884124255 +505,-0.0,0.24463187419688215,0.6675151869618753,0.08785293884124255 +506,0.007827900868540743,0.16770861417305996,0.7366105461171567,0.08785293884124255 +507,0.32786861714772575,0.17009471234218884,0.4141837316688428,0.08785293884124255 +508,0.37859943623807635,0.2238387330087589,0.3097088919119222,0.08785293884124255 +509,0.2151167373297062,0.1320307653584659,0.5649995584705854,0.08785293884124255 +510,-0.0,0.1468018492625972,0.7653452118961602,0.08785293884124255 +511,-0.0,0.145551988316863,0.7665950728418944,0.08785293884124255 +512,-0.0,0.1339623686382369,0.7781846925205205,0.08785293884124255 +513,-0.0,0.133507873748879,0.7786391874098784,0.08785293884124255 +514,0.011440778192482626,0.13134902302442905,0.7693572599418457,0.08785293884124255 +515,0.32515895915476933,0.1758895221815019,0.4110985798224862,0.08785293884124255 +516,0.30287954899046104,0.20929489654930652,0.39997261561898984,0.08785293884124255 +517,0.1660418203461623,0.10396570594061644,0.6421395348719786,0.08785293884124255 +518,-0.0,0.16929934628581256,0.7428477148729449,0.08785293884124255 +519,-0.0,0.21009026260568284,0.7020567985530746,0.08785293884124255 +520,-0.0,0.22588396001086936,0.6862631011478881,0.08785293884124255 +521,-0.0,0.2325877596288982,0.6795593015298592,0.08785293884124255 +522,0.022429946719472516,0.24179128113839538,0.6479258333008896,0.08785293884124255 +523,0.5347058439433985,0.30905652476336254,0.06838469245199642,0.08785293884124255 +524,0.7751127225406979,0.22488727745930215,-0.0,-0.0 +525,0.31416979062777944,0.3098518908197388,0.28812537971123925,0.08785293884124255 +526,-0.0,0.4268843248293944,0.485262736329363,0.08785293884124255 +527,-0.0,0.4014326110253528,0.5107144501334047,0.08785293884124255 +528,-0.0,0.36836810782456664,0.5437789533341908,0.08785293884124255 +529,-0.0,0.31882816488455706,0.5933188962742004,0.08785293884124255 +530,0.03522555390843335,0.2626980460488581,0.6142234612014659,0.08785293884124255 +531,0.767134285116993,0.18407043018994385,-0.0,0.04879528469306316 +532,0.91526225539861,0.08473774460139005,-0.0,-0.0 +533,0.3590296840667245,0.18520666741333858,0.3679107096786944,0.08785293884124255 +534,-0.0,0.28883150218693654,0.6233155589718209,0.08785293884124255 +535,-0.0,0.25622149387550824,0.6559255672832491,0.08785293884124255 +536,-0.0,0.19634179220260678,0.7158052689561507,0.08785293884124255 +537,-0.0,0.1252133420180976,0.7869337191406598,0.08785293884124255 +538,0.0320642862499842,0.042608895877301815,0.8374738790314715,0.08785293884124255 +539,0.5905549059093333,0.01363484668073658,0.3079573085686875,0.08785293884124255 +540,0.7781234536439828,0.005113067505276217,0.12891054000949842,0.08785293884124255 +541,0.4088572838260896,0.010567006177570849,0.492722771155097,0.08785293884124255 +542,-0.0,0.07226468740790389,0.8398823737508535,0.08785293884124255 +543,-0.0,0.22202075345132732,0.6901263077074301,0.08785293884124255 +544,-0.0,0.2197482790045379,0.6923987821542196,0.08785293884124255 +545,-0.0,0.12657682668617126,0.7855702344725861,0.08785293884124255 +546,0.034773944242940606,0.09476218443111924,0.7826109324846976,0.08785293884124255 +547,0.5170930669891819,0.08044559541634583,0.31460839875322966,0.08785293884124255 +548,0.51844789598566,0.10930602089057159,0.2843931442825258,0.08785293884124255 +549,0.2491379987968256,0.14646097809557876,0.516548084266353,0.08785293884124255 +550,-0.0,0.23951880669160594,0.6726282544671515,0.08785293884124255 +551,-0.0,0.22270249578536416,0.6894445653733933,0.08785293884124255 +552,-0.0,0.14566561203920247,0.766481449119555,0.08785293884124255 +553,-0.0,0.13248526024782378,0.7796618009109336,0.08785293884124255 +554,0.026494433708907132,0.12657682668617126,0.759075800763679,0.08785293884124255 +555,0.5002329728107864,0.16634512950498628,0.2455689588429848,0.08785293884124255 +556,0.7273926345536322,0.15021056093278135,0.034543865672343924,0.08785293884124255 +557,0.3813090942310328,0.10703354644378217,0.4238044204839424,0.08785293884124255 +558,-0.0,0.1332806263042001,0.7788664348545573,0.08785293884124255 +559,-0.0,0.09987525193639546,0.812271809222362,0.08785293884124255 +560,-0.0,0.05215328855381743,0.85999377260494,0.08785293884124255 +561,-0.0,0.01886153790835227,0.8932855232504051,0.08785293884124255 +562,0.03778467534622551,0.011248748511607681,0.8631136373009242,0.08785293884124255 +563,0.5453939393600599,0.026133456138078447,0.3406196656606191,0.08785293884124255 +564,0.5777592987203726,0.0678333622366645,0.2665544002017204,0.08785293884124255 +565,0.32922344614420396,0.10998776322460842,0.472935851789945,0.08785293884124255 +566,-0.0,0.21213548960779333,0.700011571550964,0.08785293884124255 +567,-0.0,0.18827450791650427,0.7238725532422532,0.08785293884124255 +568,-0.0,0.12782668763190547,0.784320373526852,0.08785293884124255 +569,-0.0,0.1072607938884611,0.8048862672702963,0.08785293884124255 +570,0.04169862578049588,0.10669267527676375,0.7637557601014978,0.08785293884124255 +571,0.5017383383624288,0.12430435223938183,0.2861043705569468,0.08785293884124255 +572,0.4499537633859285,0.12794031135424494,0.3342529864185839,0.08785293884124255 +573,0.23107361217711617,0.1650952685592521,0.5159781804223891,0.08785293884124255 +574,-0.0,0.18725189441544907,0.7248951667433083,0.08785293884124255 +575,-0.0,0.15645986566145226,0.7556871954973052,0.08785293884124255 +576,-0.0,0.1423705240913578,0.7697765370673997,0.08785293884124255 +577,-0.0,0.11112400044800314,0.8010230607107542,0.08785293884124255 +578,0.018666532840366387,0.09896626215767969,0.7945142661607114,0.08785293884124255 +579,0.23920258615598544,0.16657237694966526,0.5063720980531068,0.08785293884124255 +580,0.22550375963603908,0.1739579189017309,0.5126853826209875,0.08785293884124255 +581,0.12690231600345858,0.1746396612357677,0.6106050839195312,0.08785293884124255 +582,-0.0,0.21361259799820645,0.698534463160551,0.08785293884124255 +583,-0.0,0.17225356306663883,0.7398934980921186,0.08785293884124255 +584,-0.0,0.1427113952583762,0.7694356659003813,0.08785293884124255 +585,-0.0,0.12714494529786863,0.7850021158608889,0.08785293884124255 +586,0.02529014126759317,0.13975717847754995,0.7470997414136142,0.08785293884124255 +587,0.26539594675456407,0.2901949868550103,0.35655612754918314,0.08785293884124255 +588,0.3012236468836544,0.2950808069156075,0.3158426073594956,0.08785293884124255 +589,0.19855771626163923,0.2826958211806051,0.4308935237165131,0.08785293884124255 +590,-0.0,0.42620258249535764,0.48594447866339974,0.08785293884124255 +591,-0.0,0.3948424351296635,0.5173046260290939,0.08785293884124255 +592,-0.0,0.3378033265152488,0.5743437346435086,0.08785293884124255 +593,-0.0,0.2799688518444578,0.6321782093142996,0.08785293884124255 +594,0.06337588972414718,0.29258108502413915,0.5561900864104711,0.08785293884124255 +595,0.6718446456980257,0.3281553543019743,-0.0,-0.0 +596,0.8059727163493681,0.19402728365063193,-0.0,-0.0 +597,0.36625543871460825,0.42404373177090765,0.1218478906732416,0.08785293884124255 +598,-0.0,0.4419962799005442,0.4701507812582133,0.08785293884124255 +599,-0.0,0.35871009142571153,0.5534369697330459,0.08785293884124255 +600,-0.0,0.27440128944982367,0.6377457717089338,0.08785293884124255 +601,-0.0,0.18282056924420964,0.7293264919145478,0.08785293884124255 +602,0.07903169146122865,0.1264632029638318,0.706652166733697,0.08785293884124255 +603,0.7505752640489259,0.187479141860128,-0.0,0.06194559409094602 +604,0.9534985404103282,0.04650145958967178,-0.0,-0.0 +605,0.42887864566293415,0.17145819701026252,0.31181021848556073,0.08785293884124255 +606,-0.0,0.3298496659514858,0.5822973952072716,0.08785293884124255 +607,-0.0,0.263152540938216,0.6489945202205414,0.08785293884124255 +608,-0.0,0.19554642614623047,0.7166006350125269,0.08785293884124255 +609,-0.0,0.14032529708924732,0.7718217640695101,0.08785293884124255 +610,0.10913900249407767,0.07851399213657481,0.7244940665281049,0.08785293884124255 +611,0.8619723148704672,0.09635291654387185,-0.0,0.04167476858566088 +612,1.0,-0.0,-0.0,-0.0 +613,0.49932975347980085,0.08862650342478778,0.32419080425416885,0.08785293884124255 +614,-0.0,0.24042779647032173,0.6717192646884357,0.08785293884124255 +615,-0.0,0.17532140356980452,0.7368256575889529,0.08785293884124255 +616,-0.0,0.09442131326410082,0.8177257478946566,0.08785293884124255 +617,-0.0,0.06487914545583823,0.8472679157029193,0.08785293884124255 +618,0.10522505205980731,0.03738220464968613,0.7695398044492641,0.08785293884124255 +619,0.7371775106393081,0.04158628237624657,0.13338326814320267,0.08785293884124255 +620,0.9915842888668822,0.008415711133117787,-0.0,-0.0 +621,0.5012867286969361,0.014089341570094466,0.39677099089172685,0.08785293884124255 +622,-0.0,0.0897627406481825,0.822384320510575,0.08785293884124255 +623,-0.0,0.05238053599849637,0.8597665251602611,0.08785293884124255 +624,-0.0,0.018975161630691743,0.8931718995280656,0.08785293884124255 +625,-0.0,0.028178683140188933,0.8839683780185685,0.08785293884124255 +626,0.12118192690721728,0.0203386462987654,0.7706264879527748,0.08785293884124255 +627,0.9265524970359285,0.011135124789268208,-0.0,0.062312378174803285 +628,0.9522942479690144,0.019884151409407518,-0.0,0.027821600621578124 +629,0.46741600378498094,0.03772307581670454,0.40700798155707196,0.08785293884124255 +630,-0.0,0.05294865461019373,0.8591984065485637,0.08785293884124255 +631,-0.0,0.07590064652276697,0.8362464146359905,0.08785293884124255 +632,-0.0,0.10555643805336903,0.8065906231053884,0.08785293884124255 +633,-0.0,0.08635402897799835,0.8257930321807591,0.08785293884124255 +634,0.08309617845066328,0.06953771807175656,0.7595131646363376,0.08785293884124255 +635,0.43505064442466823,0.07874123958125376,0.3983551771528354,0.08785293884124255 +636,0.3721263643660138,0.10976051577992949,0.43026018101281416,0.08785293884124255 +637,0.16107411402574223,0.18850175536118322,0.562571191771832,0.08785293884124255 +638,-0.0,0.32519109333556745,0.5869559678231899,0.08785293884124255 +639,-0.0,0.4050685701402159,0.5070784910185415,0.08785293884124255 +640,-0.0,0.3535970239204353,0.5585500372383221,0.08785293884124255 +641,-0.0,0.27031083544560275,0.6418362257131547,0.08785293884124255 +642,0.0775263259095862,0.1906606060856332,0.6439601291635381,0.08785293884124255 +643,0.4564268352579911,0.1830478166888886,0.27267240921187774,0.08785293884124255 +644,0.6287911909210516,0.06294754217606721,0.22040832806163857,0.08785293884124255 +645,0.4037390409505053,0.03658683859330983,0.47182118161494235,0.08785293884124255 +646,-0.0,0.07260555857492229,0.8395415025838351,0.08785293884124255 +647,-0.0,0.10544281433102956,0.8067042468277279,0.08785293884124255 +648,-0.0,0.11305560372777416,0.7990914574309833,0.08785293884124255 +649,-0.0,0.09430768954176136,0.817839371616996,0.08785293884124255 +650,0.11049383149055589,0.04363150937835706,0.7580217202898445,0.08785293884124255 +651,0.6674790855982626,0.05056255644106482,0.19410541911943002,0.08785293884124255 +652,0.879585091824684,0.07635514141212485,-0.0,0.04405976676319112 +653,0.4260184511148135,0.06385653195478298,0.4222720780891609,0.08785293884124255 +654,-0.0,0.05419851555592791,0.8579485456028295,0.08785293884124255 +655,-0.0,0.056698237447396285,0.8554488237113611,0.08785293884124255 +656,-0.0,0.0398819265411545,0.8722651346176029,0.08785293884124255 +657,-0.0,0.028292306862528406,0.8838547542962291,0.08785293884124255 +658,0.1038702230633291,0.01363484668073658,0.7946419914146918,0.08785293884124255 +659,0.656640453626437,0.007726413119084063,0.2477801944132364,0.08785293884124255 +660,0.7129411252578647,0.008408155453120892,0.19079778044777185,0.08785293884124255 +661,0.3161267658449146,0.005340314949955161,0.5906799803638877,0.08785293884124255 +662,-0.0,0.0179525481296365,0.8941945130291209,0.08785293884124255 +663,-0.0,0.06919684690473815,0.8429502142540193,0.08785293884124255 +664,-0.0,0.09192159137263245,0.820225469786125,0.08785293884124255 +665,-0.0,0.1032839636065796,0.8088630975521778,0.08785293884124255 +666,0.13051519332740047,0.0813545851950616,0.7002772826362954,0.08785293884124255 +667,0.7585537014726309,0.1488470762647077,0.00474628342141882,0.08785293884124255 +668,0.8809399208211622,0.11906007917883776,-0.0,-0.0 +669,0.3162773024000789,0.1388481886988342,0.45702157005984434,0.08785293884124255 +670,-0.0,0.27224243872537374,0.6399046224333838,0.08785293884124255 +671,-0.0,0.19975050387279092,0.7123965572859665,0.08785293884124255 +672,-0.0,0.16145930944438902,0.7506877517143684,0.08785293884124255 +673,-0.0,0.12612233179681337,0.786024729361944,0.08785293884124255 +674,0.04892438042837965,0.0805592191386853,0.7826634615916925,0.08785293884124255 +675,0.20864366545764365,0.08362705964185103,0.6198763360592627,0.08785293884124255 +676,0.2756324325057327,0.10317033988424014,0.5333442887687845,0.08785293884124255 +677,0.14150436185439036,0.0869221475896957,0.6837205517146714,0.08785293884124255 +678,-0.0,0.07851399213657481,0.8336330690221826,0.08785293884124255 +679,-0.0,0.1466882255402577,0.7654588356184997,0.08785293884124255 +680,-0.0,0.17861649151764922,0.7335305696411082,0.08785293884124255 +681,-0.0,0.1902061111962753,0.7219409499624821,0.08785293884124255 +682,0.07707471624409348,0.16043669594333376,0.6746356489713302,0.08785293884124255 +683,0.3605350496183669,0.2958761729719838,0.25573583856840665,0.08785293884124255 +684,0.33765349323340166,0.36405040637566677,0.210443161549689,0.08785293884124255 +685,0.18847176706563484,0.3654138910437404,0.35826140304938225,0.08785293884124255 +686,-0.0,0.2647432730509686,0.6474037881077888,0.08785293884124255 +687,-0.0,0.22997441401509033,0.6821726471436671,0.08785293884124255 +688,-0.0,0.16032307222099432,0.7518239889377631,0.08785293884124255 +689,-0.0,0.1001024993810744,0.812044561777683,0.08785293884124255 +690,0.11500992814548322,0.043177014488999174,0.753960118524275,0.08785293884124255 +691,0.6399308960032057,0.08237719869611684,0.18983896645943488,0.08785293884124255 +692,0.7391344858564433,0.18838813163884377,-0.0,0.07247738250471292 +693,0.3123633519658085,0.23645096618844022,0.36333274300450874,0.08785293884124255 +694,-0.0,0.3834800628957164,0.528666998263041,0.08785293884124255 +695,-0.0,0.48449155205550654,0.4276555091032509,0.08785293884124255 +696,-0.0,0.5447121248954264,0.367434936263331,0.08785293884124255 +697,-0.0,0.6045918265683279,0.30755523459042955,0.08785293884124255 +698,0.039591114008196454,0.566300632139926,0.30625531501063497,0.08785293884124255 +699,0.2214392726466045,0.6878780150431606,0.0028297734689923387,0.08785293884124255 +700,0.25260033956560324,0.6159541988022751,0.04359252279087912,0.08785293884124255 +701,0.11741851302811115,0.5334633763838187,0.2612651717468276,0.08785293884124255 +702,-0.0,0.7513936758309251,0.16075338532783234,0.08785293884124255 +703,-0.0,0.8458149890950258,0.06633207206373161,0.08785293884124255 +704,-0.0,0.8800157295192068,0.032131331639550634,0.08785293884124255 +705,-0.0,0.8327482610259866,0.0793988001327708,0.08785293884124255 +706,0.1008594919600442,0.873311929901178,-0.0,0.025828578138777786 +707,0.4016315291782058,0.5983684708217942,-0.0,-0.0 +708,0.5849850533682563,0.41501494663174365,-0.0,-0.0 +709,0.3715242181453568,0.6284757818546431,-0.0,-0.0 +710,-0.0,0.6811742154251318,0.23097284573362564,0.08785293884124255 +711,-0.0,0.4098407664784738,0.5023062946802836,0.08785293884124255 +712,-0.0,0.4342698667814601,0.47787719437729725,0.08785293884124255 +713,-0.0,0.6631080435731558,0.24903901758560165,0.08785293884124255 +714,0.05434369641429247,0.9027404739871011,-0.0,0.04291582959860642 +715,0.3063418897592387,0.6936581102407613,-0.0,-0.0 +716,0.4172873309152873,0.5827126690847126,-0.0,-0.0 +717,0.25621321688954507,0.743786783110455,-0.0,-0.0 +718,-0.0,1.0,-0.0,-0.0 +719,-0.0,0.9230791202858666,-0.0,0.07692087971413342 +720,-0.0,0.8411564164791075,0.0709906446796499,0.08785293884124255 +721,-0.0,0.7833219418083166,0.12882511935044083,0.08785293884124255 +722,0.10070895540487995,0.7917300972614375,0.01970800849243999,0.08785293884124255 +723,0.4234593296770214,0.5765406703229786,-0.0,-0.0 +724,0.5765550062790586,0.42344499372094135,-0.0,-0.0 +725,0.29911613511135493,0.7008838648886451,-0.0,-0.0 +726,-0.0,0.5859575361046546,0.3261895250541028,0.08785293884124255 +727,-0.0,0.4476774660175178,0.46446959514123964,0.08785293884124255 +728,-0.0,0.4509725539653624,0.46117450719339503,0.08785293884124255 +729,-0.0,0.39597867235305817,0.5161683888056993,0.08785293884124255 +730,0.05238672119715728,0.3401894246843777,0.5195709152772224,0.08785293884124255 +731,0.3468362230984206,0.5584605952985024,0.006850242761834394,0.08785293884124255 +732,0.5527702305631079,0.4472297694368921,-0.0,-0.0 +733,0.35662109918409657,0.6433789008159034,-0.0,-0.0 +734,-0.0,0.48278719622041444,0.429359864938343,0.08785293884124255 +735,-0.0,0.3895021201797083,0.5226449409790491,0.08785293884124255 +736,-0.0,0.30030749814322316,0.6118395630155342,0.08785293884124255 +737,-0.0,0.23633734246610072,0.6758097186926567,0.08785293884124255 +738,0.1496333358332596,0.18838813163884377,0.5741255936866541,0.08785293884124255 +739,0.6086192925290428,0.21861204178114318,0.08491572684857142,0.08785293884124255 +740,0.7356721450876657,0.19043335864095426,-0.0,0.07389449627138012 +741,0.4302334746594124,0.13418961608291585,0.3477239704164291,0.08785293884124255 +742,-0.0,0.11839591867772932,0.7937511424810281,0.08785293884124255 +743,-0.0,0.11794142378837143,0.794205637370386,0.08785293884124255 +744,-0.0,0.08567228664396151,0.8264747745147959,0.08785293884124255 +745,-0.0,0.04613123126982543,0.866015829888932,0.08785293884124255 +746,0.11124651426637709,0.026133456138078447,0.7747670907543018,0.08785293884124255 +747,0.5642110087555905,0.038518441873080844,0.3094176105300861,0.08785293884124255 +748,0.5425337448119392,0.07669601257914327,0.292917303767675,0.08785293884124255 +749,0.3358470545714307,0.1316898941914475,0.4446101123958792,0.08785293884124255 +750,0.00015053655516424508,0.17179906817728094,0.7401974564263122,0.08785293884124255 +751,-0.0,0.1488470762647077,0.7632999848940497,0.08785293884124255 +752,-0.0,0.08919462203648514,0.8229524391222723,0.08785293884124255 +753,-0.0,0.0710148264621697,0.8411322346965877,0.08785293884124255 +754,0.24763263324518314,0.016248192294544425,0.6482662356190299,0.08785293884124255 +755,0.8967462591134079,0.04772196338257804,-0.0,0.05553177750401406 +756,0.7173066853576278,0.0749916567440512,0.11984871905707851,0.08785293884124255 +757,0.37859943623807635,0.11510083072988464,0.41844679419079645,0.08785293884124255 +758,0.00015053655516424508,0.1379391989201184,0.7740573256834747,0.08785293884124255 +759,-0.0,0.12169100662557399,0.7904560545331835,0.08785293884124255 +760,-0.0,0.09794364865662444,0.814203412502133,0.08785293884124255 +761,-0.0,0.08510416803226416,0.8270428931264933,0.08785293884124255 +762,0.15143977449523052,0.04590398382514649,0.7148033028383804,0.08785293884124255 +763,0.5536734498940934,0.11635069167561883,0.2421229195890452,0.08785293884124255 +764,0.6215654362731678,0.13191714163612642,0.15866448324946314,0.08785293884124255 +765,0.44859893438945025,0.12373623362768447,0.3398118931416227,0.08785293884124255 +766,0.00030107311032849017,0.11271473256075573,0.7991312554876733,0.08785293884124255 +767,-0.0,0.054653010445285796,0.8574940507134716,0.08785293884124255 +768,-0.0,0.022383873300875885,0.8897631878578816,0.08785293884124255 +769,-0.0,0.0694240943494171,0.8427229668093403,0.08785293884124255 +770,0.220536053315619,0.06840148084836184,0.6232095269947766,0.08785293884124255 +771,0.7639730174585436,0.1887290028058622,-0.0,0.04729797973559424 +772,0.7611128229104231,0.23888717708957685,-0.0,-0.0 +773,0.40554547961247617,0.2856500379614314,0.22095154358484986,0.08785293884124255 +774,0.00030107311032849017,0.2338376205746324,0.6780083674737966,0.08785293884124255 +775,-0.0,0.20247747320893822,0.7096695879498192,0.08785293884124255 +776,-0.0,0.16452714994755477,0.7476199112112026,0.08785293884124255 +777,-0.0,0.1487334525423682,0.7634136086163892,0.08785293884124255 +778,0.11937548824524633,0.14532474087218408,0.647446832041327,0.08785293884124255 +779,0.49556633960069474,0.12987191463401593,0.28670880692404677,0.08785293884124255 +780,0.6846402528869865,0.08374068336419051,0.14376612490758045,0.08785293884124255 +781,0.4479967881687933,0.04045004515285186,0.42370022783711225,0.08785293884124255 +782,0.00045160966549273514,0.03545060136991511,0.8762448501233496,0.08785293884124255 +783,-0.0,0.034087116701841454,0.8780599444569159,0.08785293884124255 +784,-0.0,0.03635959114863088,0.8757874700101266,0.08785293884124255 +785,-0.0,0.047040221048541206,0.8651068401102162,0.08785293884124255 +786,0.21406298144355648,0.027383317083812635,0.6707007626313883,0.08785293884124255 +787,0.8881656754690459,0.025678961248720558,-0.0,0.0861553632822335 +788,1.0,-0.0,-0.0,-0.0 +789,0.573694811730938,0.0019316032797710157,0.33652064614804844,0.08785293884124255 +790,0.0007526827758212254,0.00977164012119455,0.9016227382617417,0.08785293884124255 +791,-0.0,0.030451157586978365,0.8816959035717791,0.08785293884124255 +792,-0.0,0.03249638458908885,0.8796506765696686,0.08785293884124255 +793,-0.0,0.02726969336147316,0.8848773677972843,0.08785293884124255 +794,0.2644927274235786,0.011021501066928736,0.63663283266825,0.08785293884124255 +795,0.9840574611086699,0.004204077726560446,-0.0,0.011738461164769731 +796,1.0,-0.0,-0.0,-0.0 +797,0.5426842813671034,0.007840036841423535,0.36162274295023045,0.08785293884124255 +798,0.0007526827758212254,0.06681074873560924,0.844583629647327,0.08785293884124255 +799,-0.0,0.20520444254508557,0.7069426186136718,0.08785293884124255 +800,-0.0,0.26417515443927125,0.6479719067194862,0.08785293884124255 +801,-0.0,0.2786053671763841,0.6335416939823733,0.08785293884124255 +802,0.19133196161375546,0.14441575109346827,0.5763993484515337,0.08785293884124255 +803,0.7352205354221729,0.2647794645778271,-0.0,-0.0 +804,0.6080171463083858,0.39198285369161423,-0.0,-0.0 +805,0.23333166050457985,0.5633464153590998,0.1154689852950778,0.08785293884124255 +806,0.00030107311032849017,0.512556611473356,0.39928937657507296,0.08785293884124255 +807,-0.0,0.43199739233467066,0.4801496688240867,0.08785293884124255 +808,-0.0,0.3729130567181455,0.5392340044406119,0.08785293884124255 +809,-0.0,0.22747469212362195,0.6846723690351355,0.08785293884124255 +810,0.1789879640902874,0.14816533393067083,0.5849937631377993,0.08785293884124255 +811,0.8484240249056851,0.15157597509431486,-0.0,-0.0 +812,0.8850044078105967,0.11499559218940325,-0.0,-0.0 +813,0.5187489690959886,0.28496829562739456,0.10842979643537433,0.08785293884124255 +814,0.0013548289964782055,0.4040459566391607,0.5067462755231185,0.08785293884124255 +815,-0.0,0.590161613831215,0.3219854473275424,0.08785293884124255 +816,-0.0,0.7207152707992678,0.19143179035948965,0.08785293884124255 +817,-0.0,0.6368609637127378,0.2752860974460196,0.08785293884124255 +818,0.21135332345060007,0.4908544805065169,0.20993925720164042,0.08785293884124255 +819,0.7167045391369709,0.28329546086302915,-0.0,-0.0 +820,0.7356721450876657,0.26432785491233435,-0.0,-0.0 +821,0.43173884021105485,0.42040777265604456,0.06000044829165803,0.08785293884124255 +822,0.002107511772299431,0.25179016870426885,0.6582493806821892,0.08785293884124255 +823,-0.0,0.14180240547966044,0.7703446556790969,0.08785293884124255 +824,-0.0,0.09476218443111924,0.8173848767276382,0.08785293884124255 +825,-0.0,0.12964466718933698,0.7825023939694205,0.08785293884124255 +826,0.2595250211031585,0.11078312928098473,0.5418389107746142,0.08785293884124255 +827,0.8822947498176402,0.11770525018235978,-0.0,-0.0 +828,0.8320155403927825,0.16798445960721753,-0.0,-0.0 +829,0.39350255519933663,0.4227938708251735,0.09585063513424731,0.08785293884124255 +830,0.0013548289964782055,0.39268358440521356,0.5181086477570657,0.08785293884124255 +831,-0.0,0.2926947087464786,0.6194523524122788,0.08785293884124255 +832,-0.0,0.22247524834068522,0.6896718128180722,0.08785293884124255 +833,-0.0,0.23929155924692702,0.6728555019118304,0.08785293884124255 +834,0.2929441363496209,0.13850731753181578,0.48069560727732075,0.08785293884124255 +835,1.0,-0.0,-0.0,-0.0 +836,1.0,-0.0,-0.0,-0.0 +837,0.6047053420947724,0.39529465790522755,-0.0,-0.0 +838,0.0018064386619709406,0.42097589126774193,0.48936473122904456,0.08785293884124255 +839,-0.0,0.3232594900557964,0.5888875711029611,0.08785293884124255 +840,-0.0,0.21997552644921683,0.6921715347095406,0.08785293884124255 +841,-0.0,0.14566561203920247,0.766481449119555,0.08785293884124255 +842,0.25500892444823114,0.09794364865662444,0.5591944880539019,0.08785293884124255 +843,0.8232844201932563,0.13305337885952115,-0.0,0.04366220094722251 +844,0.8928323086791374,0.10716769132086257,-0.0,-0.0 +845,0.4546203965960201,0.21474883522160115,0.24277782934113618,0.08785293884124255 +846,0.0018064386619709406,0.18497941996865963,0.7253612025281269,0.08785293884124255 +847,-0.0,0.1560053707720944,0.756141690386663,0.08785293884124255 +848,-0.0,0.12464522340640026,0.7875018377523572,0.08785293884124255 +849,-0.0,0.10271584499488223,0.8094312161638753,0.08785293884124255 +850,0.19675127759966832,0.09283058115134822,0.6225652024077408,0.08785293884124255 +851,0.5870925651405557,0.1252133420180976,0.19984115400010405,0.08785293884124255 +852,0.592812954236797,0.17361704773471245,0.14571705918724798,0.08785293884124255 +853,0.33118042136133913,0.26769748983179487,0.31326914996562344,0.08785293884124255 +854,0.002559121437792166,0.2337239968522929,0.6758639428686725,0.08785293884124255 +855,-0.0,0.2206572687832537,0.6914897923755037,0.08785293884124255 +856,-0.0,0.16702687183902312,0.7451201893197343,0.08785293884124255 +857,-0.0,0.0897627406481825,0.822384320510575,0.08785293884124255 +858,0.3349438352404453,0.04033642143051238,0.5368668044877998,0.08785293884124255 +859,0.8195210063141501,0.09817089610130339,-0.0,0.08230809758454649 +860,0.9006602095476783,0.0901036118152009,-0.0,0.009236178637120784 +861,0.5867914920302273,0.10748804133314005,0.2178675277953901,0.08785293884124255 +862,0.004516096654927352,0.09601204537685343,0.8116189191269767,0.08785293884124255 +863,-0.0,0.08158183263974055,0.8305652285190169,0.08785293884124255 +864,-0.0,0.06760611479198554,0.8445409463667719,0.08785293884124255 +865,-0.0,0.051471546219780596,0.8606755149389769,0.08785293884124255 +866,0.3296750558096967,0.0060220572839919904,0.5764499480650688,0.08785293884124255 +867,1.0,-0.0,-0.0,-0.0 +868,1.0,-0.0,-0.0,-0.0 +869,0.6345115800172929,0.03647321487097035,0.2411622662704942,0.08785293884124255 +870,0.005268779430748578,0.09953438076937704,0.8073439009586318,0.08785293884124255 +871,-0.0,0.1256678369074555,0.7864792242513019,0.08785293884124255 +872,-0.0,0.09976162821405599,0.8123854329447014,0.08785293884124255 +873,-0.0,0.1240771047947029,0.7880699563640545,0.08785293884124255 +874,0.3688145601524004,0.056698237447396285,0.48663426355896067,0.08785293884124255 +875,1.0,-0.0,-0.0,-0.0 +876,1.0,-0.0,-0.0,-0.0 +877,0.6277374350349019,0.1965690396472857,0.0878405864765699,0.08785293884124255 +878,0.005419315985912822,0.21020388632802228,0.6965238588448224,0.08785293884124255 +879,-0.0,0.13418961608291585,0.7779574450758415,0.08785293884124255 +880,-0.0,0.08158183263974055,0.8305652285190169,0.08785293884124255 +881,-0.0,0.17645764079319926,0.7356894203655582,0.08785293884124255 +882,0.3841692887791534,0.1128283562830952,0.4151494160965088,0.08785293884124255 +883,1.0,-0.0,-0.0,-0.0 +884,1.0,-0.0,-0.0,-0.0 +885,0.6393287497825488,0.36067125021745117,-0.0,-0.0 +886,0.005870925651405557,0.34837033269281964,0.5579058028145323,0.08785293884124255 +887,-0.0,0.28553641423909193,0.6266106469196655,0.08785293884124255 +888,-0.0,0.22599758373320883,0.6861494774255485,0.08785293884124255 +889,-0.0,0.18134346085379652,0.7308036003049609,0.08785293884124255 +890,0.24206278070410606,0.12384985735002395,0.5462344231046274,0.08785293884124255 +891,0.7061669802754736,0.08078646658336425,0.1251936142999196,0.08785293884124255 +892,0.801607156249605,0.056130118835698926,0.054409786073453495,0.08785293884124255 +893,0.5404262330396398,0.04999443782936746,0.32172639028975025,0.08785293884124255 +894,0.006924681537555273,0.05999332539524096,0.8452290542259612,0.08785293884124255 +895,-0.0,0.06771973851432501,0.8444273226444324,0.08785293884124255 +896,-0.0,0.08010472424932741,0.8320423369094301,0.08785293884124255 +897,-0.0,0.07260555857492229,0.8395415025838351,0.08785293884124255 +898,0.3003204275526689,0.03635959114863088,0.5754670424574577,0.08785293884124255 +899,0.8974989418892291,0.05556200022400157,-0.0,0.04693905788676933 +900,1.0,-0.0,-0.0,-0.0 +901,0.6108773408565065,0.05363039694423055,0.2476393233580204,0.08785293884124255 +902,0.008430047089197723,0.0586298407271673,0.8450871733423925,0.08785293884124255 +903,-0.0,0.06033419656225937,0.8518128645964981,0.08785293884124255 +904,-0.0,0.049085448050651695,0.8630616131081057,0.08785293884124255 +905,-0.0,0.06851510457070133,0.8436319565880561,0.08785293884124255 +906,0.33885778567471564,0.019884151409407518,0.5534051240746343,0.08785293884124255 +907,1.0,-0.0,-0.0,-0.0 +908,1.0,-0.0,-0.0,-0.0 +909,0.6319524585795008,0.09703465887790867,0.18315994370134803,0.08785293884124255 +910,0.007225754647883762,0.060788691451617256,0.8441326150592564,0.08785293884124255 +911,-0.0,0.1336214974712185,0.778525563687539,0.08785293884124255 +912,-0.0,0.2408822913596796,0.6712647697990779,0.08785293884124255 +913,-0.0,0.24156403369371646,0.670583027465041,0.08785293884124255 +914,0.1704073804459254,0.09896626215767969,0.6427734185551524,0.08785293884124255 +915,0.5136307262204042,0.11123762417034261,0.2872787107680107,0.08785293884124255 +916,0.4833728786323909,0.19827339548237777,0.23050078704398874,0.08785293884124255 +917,0.191030888503427,0.17816199662829132,0.5429541760270391,0.08785293884124255 +918,0.0016559021068066958,0.15770972660718646,0.7527814324447643,0.08785293884124255 +919,-0.0,0.1766848882378782,0.7354621729208792,0.08785293884124255 +920,-0.0,0.17793474918361238,0.734212311975145,0.08785293884124255 +921,-0.0,0.13703020914140265,0.7751168520173548,0.08785293884124255 +922,0.20924581167830067,0.03465523531353881,0.668246014166918,0.08785293884124255 +923,0.579866810492672,0.10748804133314005,0.22479220933294541,0.08785293884124255 +924,0.617350412728569,0.10680629899910322,0.18799034943108517,0.08785293884124255 +925,0.397416505633607,0.052039664831477955,0.46269089069367253,0.08785293884124255 +926,0.004666633210091597,0.022270249578536416,0.8852101783701294,0.08785293884124255 +927,-0.0,0.04090454004220974,0.8712425211165477,0.08785293884124255 +928,-0.0,0.06715161990262766,0.8449954412561298,0.08785293884124255 +929,-0.0,0.0977164012119455,0.814430659946812,0.08785293884124255 +930,0.16649343001165506,0.08419517825354839,0.661458452893554,0.08785293884124255 +931,0.5079103371241628,0.13555310075098953,0.26868362328360507,0.08785293884124255 +932,0.5342542342779057,0.18509304369099908,0.19279978318985258,0.08785293884124255 +933,0.29715915989421976,0.24417737930752428,0.3708105219570134,0.08785293884124255 +934,0.003913950434270372,0.19850064292705671,0.7097324677974304,0.08785293884124255 +935,-0.0,0.18122983713145707,0.7309172240273003,0.08785293884124255 +936,-0.0,0.15702798427314962,0.7551190768856078,0.08785293884124255 +937,-0.0,0.10294309243956119,0.8092039687191962,0.08785293884124255 +938,0.19494483893769737,0.06567451151221453,0.6515277107088455,0.08785293884124255 +939,0.5563831078870497,0.10362483477359802,0.25213911849810977,0.08785293884124255 +940,0.6119310967426561,0.20031862248448828,0.09989734193161305,0.08785293884124255 +941,0.36475007316296576,0.2178166757247669,0.32958031227102474,0.08785293884124255 +942,0.00978487608567593,0.1488470762647077,0.7535151088083738,0.08785293884124255 +943,-0.0,0.17498053240278613,0.7371665287559713,0.08785293884124255 +944,-0.0,0.15748247916250752,0.7546645819962499,0.08785293884124255 +945,-0.0,0.1420296529243394,0.770117408234418,0.08785293884124255 +946,0.3984702615197567,0.08033197169400635,0.4333448279449945,0.08785293884124255 +947,1.0,-0.0,-0.0,-0.0 +948,1.0,-0.0,-0.0,-0.0 +949,0.5133296531100757,0.10794253622249794,0.29087487182618377,0.08785293884124255 +950,0.005870925651405557,0.14748359159663402,0.7587925439107178,0.08785293884124255 +951,-0.0,0.24270027091711116,0.6694467902416463,0.08785293884124255 +952,-0.0,0.22145263483962996,0.6906944263191275,0.08785293884124255 +953,-0.0,0.16748136672838101,0.7446656944303764,0.08785293884124255 +954,0.23378327017007258,0.09601204537685343,0.5823517456118315,0.08785293884124255 +955,0.6795220100114022,0.18532029113567802,0.047304760011677174,0.08785293884124255 +956,0.6209632900525108,0.2208845162279326,0.07029925487831402,0.08785293884124255 +957,0.38326606944816793,0.14032529708924732,0.38855569462134215,0.08785293884124255 +958,0.006171998761734048,0.06362928451010405,0.8423457778869193,0.08785293884124255 +959,-0.0,0.10669267527676375,0.8054543858819937,0.08785293884124255 +960,-0.0,0.1288493011329607,0.7832977600257968,0.08785293884124255 +961,-0.0,0.09385319465240348,0.8182938665063539,0.08785293884124255 +962,0.21827800498815533,0.059879701672901485,0.6339893544977007,0.08785293884124255 +963,0.496319022376516,0.0626066710090488,0.35322136777319263,0.08785293884124255 +964,0.5670712033037112,0.06749249106964608,0.2775833667854002,0.08785293884124255 +965,0.3527071487498262,0.05385764438890949,0.5055822680200217,0.08785293884124255 +966,0.01008594919600442,0.05965245422822254,0.8424086577345304,0.08785293884124255 +967,-0.0,0.06976496551643552,0.8423820956423219,0.08785293884124255 +968,-0.0,0.042381648432622876,0.8697654127261345,0.08785293884124255 +969,-0.0,0.027156069639133692,0.8849909915196238,0.08785293884124255 +970,0.31161066918998725,0.01647543973922337,0.5840609522295468,0.08785293884124255 +971,0.7553924338141818,0.02488359519234426,0.1318710321522314,0.08785293884124255 +972,0.7480161426111338,0.02874680175188629,0.13538411679573736,0.08785293884124255 +973,0.43459903475917555,0.017043558350920727,0.4605044680486612,0.08785293884124255 +974,0.014903118961260262,0.026247079860417917,0.8709968623370792,0.08785293884124255 +975,-0.0,0.08828563225776936,0.823861428900988,0.08785293884124255 +976,-0.0,0.12828118252126333,0.7838658786374941,0.08785293884124255 +977,-0.0,0.16134568572204952,0.7508013754367079,0.08785293884124255 +978,0.22565429619120336,0.06987858923877498,0.6166141757287791,0.08785293884124255 +979,0.6215654362731678,0.2238387330087589,0.0667428918768307,0.08785293884124255 +980,0.5182973594304957,0.3036025860910679,0.09024711563719391,0.08785293884124255 +981,0.16724611278747628,0.31166987037717037,0.43323107799411087,0.08785293884124255 +982,0.002258048327463676,0.3672318706011719,0.5426571422301218,0.08785293884124255 +983,-0.0,0.35473326114383,0.5574138000149274,0.08785293884124255 +984,-0.0,0.33041778456318316,0.5817292765955743,0.08785293884124255 +985,-0.0,0.28383205840399983,0.6283150027547576,0.08785293884124255 +986,0.24221331725927028,0.18372955902292545,0.4862041848765617,0.08785293884124255 +987,0.7090271748235942,0.289172373353955,-0.0,0.0018004518224506993 +988,0.7871556469538376,0.21284435304616245,-0.0,-0.0 +989,0.4794589281981205,0.0710148264621697,0.3616733064984672,0.08785293884124255 +990,0.01339775340961781,0.024542724025325847,0.8742065837238138,0.08785293884124255 +991,-0.0,0.24894957564578207,0.6631974855129754,0.08785293884124255 +992,-0.0,0.4010917398583344,0.511055321300423,0.08785293884124255 +993,-0.0,0.43086115511127593,0.48128590604748145,0.08785293884124255 +994,0.1586655291431143,0.3462114819683697,0.40727005004727346,0.08785293884124255 +995,0.48608253662534734,0.48415068088848806,-0.0,0.029766782486164545 +996,0.5328994052814275,0.46710059471857246,-0.0,-0.0 +997,0.3158256927345861,0.6020921046768596,-0.0,0.08208220258855431 +998,0.014903118961260262,0.510625008193585,0.3866189340039121,0.08785293884124255 +999,-0.0,0.3157603243813913,0.5963867367773661,0.08785293884124255 +1000,-0.0,0.197364405703662,0.7147826554550955,0.08785293884124255 +1001,-0.0,0.2065679272131592,0.7055791339455982,0.08785293884124255 +1002,0.2699120434094914,0.14521111714984458,0.4970239005994215,0.08785293884124255 +1003,0.748768825386955,0.251231174613045,-0.0,-0.0 +1004,0.784445988960881,0.21555401103911898,-0.0,-0.0 +1005,0.4755449777638502,0.48676402650229594,-0.0,0.03769099573385393 +1006,0.01956975217135186,0.36200517937355625,0.5305721296138494,0.08785293884124255 +1007,-0.0,0.3814348358936059,0.5307122252651515,0.08785293884124255 +1008,-0.0,0.42438460293792607,0.4877624582208313,0.08785293884124255 +1009,-0.0,0.41279498325930003,0.49935207789945735,0.08785293884124255 +1010,0.3337395427991313,0.49380869728734317,0.0845988210722829,0.08785293884124255 +1011,0.6573931364022582,0.34260686359774184,-0.0,-0.0 +1012,0.7508763371592544,0.2491236628407456,-0.0,-0.0 +1013,0.519652188426974,0.2559942464308293,0.13650062630095405,0.08785293884124255 +1014,0.023483702605622228,0.07090120273983022,0.8177621558133049,0.08785293884124255 +1015,-0.0,0.01942965652004963,0.8927174046387079,0.08785293884124255 +1016,-0.0,0.024542724025325847,0.8876043371334316,0.08785293884124255 +1017,-0.0,0.04090454004220974,0.8712425211165477,0.08785293884124255 +1018,0.3018257931043114,0.00818090800844195,0.602140360046004,0.08785293884124255 +1019,0.8890688948000314,0.00681742334036829,0.016260743018357796,0.08785293884124255 +1020,0.8988537708857073,0.0015907321127526012,0.011702558160297541,0.08785293884124255 +1021,0.472985856326058,0.001136237223394715,0.43802496760930465,0.08785293884124255 +1022,0.014451509295767525,0.10930602089057159,0.7883895309724183,0.08785293884124255 +1023,-0.0,0.179525481296365,0.7326215798623924,0.08785293884124255 +1024,-0.0,0.15509638099337864,0.7570506801653788,0.08785293884124255 +1025,-0.0,0.17089007839856515,0.7412569827601923,0.08785293884124255 +1026,0.19931039903746045,0.11964577962346351,0.5931908824978334,0.08785293884124255 +1027,0.48532985384952615,0.3072385452059309,0.11957866210330033,0.08785293884124255 +1028,0.5855871995889134,0.41441280041108663,-0.0,-0.0 +1029,0.3483415886500631,0.4435870120132968,0.1202184604953976,0.08785293884124255 +1030,0.0183654597300379,0.3213278867760254,0.5724537146526941,0.08785293884124255 +1031,-0.0,0.36825448410222716,0.5438925770565303,0.08785293884124255 +1032,-0.0,0.42302111826985245,0.489125942888905,0.08785293884124255 +1033,-0.0,0.45222241491109666,0.4599246462476607,0.08785293884124255 +1034,0.25621321688954507,0.504602950909593,0.1513308933596194,0.08785293884124255 +1035,0.7761664784268475,0.2238335215731525,-0.0,-0.0 +1036,0.9917348254220466,0.008265174577953394,-0.0,-0.0 +1037,0.649264162423389,0.17793474918361238,0.08494814955175602,0.08785293884124255 +1038,0.03176321313965571,0.054312139278267384,0.8260717087408344,0.08785293884124255 +1039,-0.0,0.08839925598010882,0.8237478051786487,0.08785293884124255 +1040,-0.0,0.1687312276741152,0.7434158334846422,0.08785293884124255 +1041,-0.0,0.18168433202081494,0.7304627291379425,0.08785293884124255 +1042,0.35029856386719826,0.07840036841423535,0.4834481288773238,0.08785293884124255 +1043,0.9136063532918033,0.08639364670819671,-0.0,-0.0 +1044,0.9778854623469359,0.022114537653064148,-0.0,-0.0 +1045,0.5672217398588755,0.1699810886198494,0.17494423268003256,0.08785293884124255 +1046,0.02619336059857864,0.23701908480013759,0.6489346157600412,0.08785293884124255 +1047,-0.0,0.32212325283240173,0.5900238083263557,0.08785293884124255 +1048,-0.0,0.2046363239333882,0.7075107372253693,0.08785293884124255 +1049,-0.0,0.18725189441544907,0.7248951667433083,0.08785293884124255 +1050,0.28601945481206564,0.10078424171511123,0.5253433646315806,0.08785293884124255 +1051,0.7966394499291849,0.17691213568255715,-0.0,0.026448414388257957 +1052,0.8240371029690775,0.1759628970309225,-0.0,-0.0 +1053,0.395610066971636,0.5514159245134552,-0.0,0.0529740085149088 +1054,0.022731019829801007,0.5731180554802943,0.31629798584866214,0.08785293884124255 +1055,-0.0,0.31053363315377563,0.6016134280049819,0.08785293884124255 +1056,-0.0,0.24179128113839538,0.6703557800203621,0.08785293884124255 +1057,-0.0,0.28065059417849464,0.6314964669802627,0.08785293884124255 +1058,0.4171367943601231,0.299284884642168,0.1957253821564663,0.08785293884124255 +1059,0.9617780509443618,0.03822194905563825,-0.0,-0.0 +1060,0.944165273990145,0.05583472600985495,-0.0,-0.0 +1061,0.5082114102344913,0.24417737930752428,0.15975827161674183,0.08785293884124255 +1062,0.025892287488250152,0.16770861417305996,0.7185461594974474,0.08785293884124255 +1063,-0.0,0.1488470762647077,0.7632999848940497,0.08785293884124255 +1064,-0.0,0.1307809044127317,0.7813661567460257,0.08785293884124255 +1065,-0.0,0.1260087080744739,0.7861383530842836,0.08785293884124255 +1066,0.2571164362205306,0.12487247085107919,0.5301581540871476,0.08785293884124255 +1067,0.6552856246299588,0.10021612310341388,0.1566453134253848,0.08785293884124255 +1068,0.7410914610735785,0.0602205728399199,0.11083502724525908,0.08785293884124255 +1069,0.4284270359974415,0.030678405031657308,0.45304162012965865,0.08785293884124255 +1070,0.02258048327463676,0.029769415252941536,0.8597971626311791,0.08785293884124255 +1071,-0.0,0.061015938896296196,0.8511311222624612,0.08785293884124255 +1072,-0.0,0.06806060968134343,0.844086451477414,0.08785293884124255 +1073,-0.0,0.0646518980111593,0.8474951631475981,0.08785293884124255 +1074,0.24627780424870493,0.018520666741333854,0.6473485901687186,0.08785293884124255 +1075,0.6859950818834648,0.03158739481037308,0.19456458446491953,0.08785293884124255 +1076,0.9732188291368443,0.02678117086315568,-0.0,-0.0 +1077,0.595221539119425,0.07374179579831701,0.24318372624101547,0.08785293884124255 +1078,0.04260184511148135,0.07646876513446432,0.7930764509128118,0.08785293884124255 +1079,-0.0,0.07635514141212485,0.8357919197466326,0.08785293884124255 +1080,-0.0,0.05158516994212007,0.8605618912166374,0.08785293884124255 +1081,-0.0,0.07112845018450917,0.8410186109742482,0.08785293884124255 +1082,0.3227503742721414,0.07953660563763007,0.509860081248986,0.08785293884124255 +1083,0.7213711723470624,0.20293196809829614,-0.0,0.07569685955464145 +1084,0.5720389096241313,0.31235161271120715,0.02775653882341901,0.08785293884124255 +1085,0.25139604712428926,0.3672318706011719,0.29351914343329627,0.08785293884124255 +1086,0.01776331350938092,0.3614370607618589,0.5329466868875177,0.08785293884124255 +1087,-0.0,0.32007802583029127,0.5920690353284661,0.08785293884124255 +1088,-0.0,0.26053919532440817,0.6516078658343493,0.08785293884124255 +1089,-0.0,0.2216798822843089,0.6904671788744485,0.08785293884124255 +1090,0.3159762292897504,0.26281166977119763,0.3333591620978094,0.08785293884124255 +1091,0.7304033656569171,0.2695966343430829,-0.0,-0.0 +1092,0.7016508836205463,0.2983491163794537,-0.0,-0.0 +1093,0.48487824418403336,0.38711602201057943,0.040152794964144714,0.08785293884124255 +1094,0.03627930979458306,0.18884262652820163,0.6870251248359728,0.08785293884124255 +1095,-0.0,0.1008978654374507,0.8112491957213067,0.08785293884124255 +1096,-0.0,0.09737553004492708,0.8147715311138304,0.08785293884124255 +1097,-0.0,0.08749026620139307,0.8246567949573643,0.08785293884124255 +1098,0.34156744366767205,0.035905096259273,0.5346745212318125,0.08785293884124255 +1099,0.863026070756617,0.1299855383563554,-0.0,0.006988390887027629 +1100,0.9419072256626815,0.05809277433731852,-0.0,-0.0 +1101,0.5410283792602968,0.1774802542942545,0.19363842760420613,0.08785293884124255 +1102,0.02860194548120656,0.23724633224481653,0.6462987834327344,0.08785293884124255 +1103,-0.0,0.2991712609198285,0.612975800238929,0.08785293884124255 +1104,-0.0,0.21361259799820645,0.698534463160551,0.08785293884124255 +1105,0.00015053655516424508,0.18009359990806234,0.7319029246955308,0.08785293884124255 +1106,0.23468648950105808,0.23622371874376127,0.4412368529139381,0.08785293884124255 +1107,0.6411351884445198,0.3588648115554802,-0.0,-0.0 +1108,0.7219733185677194,0.2780266814322806,-0.0,-0.0 +1109,0.44077103352090957,0.35689211186828,0.11448391576956791,0.08785293884124255 +1110,0.03522555390843335,0.1388481886988342,0.7380733185514898,0.08785293884124255 +1111,-0.0,0.12123651173621611,0.7909105494225414,0.08785293884124255 +1112,-0.0,0.12066839312451874,0.7914786680342387,0.08785293884124255 +1113,0.00030107311032849017,0.09828451982364285,0.8135614682247861,0.08785293884124255 +1114,0.21210600622642128,0.05749360350377258,0.6425474514285636,0.08785293884124255 +1115,0.4966200954868445,0.06431102684414088,0.351215938827772,0.08785293884124255 +1116,0.5363617460502051,0.0654472640675356,0.31033805104101664,0.08785293884124255 +1117,0.3505996369775268,0.07442353813235385,0.4871238860488769,0.08785293884124255 +1118,0.03658038290491155,0.07749137863551957,0.7980752996183264,0.08785293884124255 +1119,-0.0,0.1431658901477341,0.7689811710110234,0.08785293884124255 +1120,-0.0,0.14577923576154195,0.7663678253972155,0.08785293884124255 +1121,0.00015053655516424508,0.09794364865662444,0.8140528759469687,0.08785293884124255 +1122,0.36399739038714457,0.05533475277932263,0.49281491799229027,0.08785293884124255 +1123,0.6960810310794692,0.07476440929937225,0.14130162077991604,0.08785293884124255 +1124,0.6409846518893555,0.09680741143322973,0.17435499783617225,0.08785293884124255 +1125,0.4618461512439039,0.09101260159391669,0.3592883083209369,0.08785293884124255 +1126,0.05344047708330699,0.05022168527404641,0.808484898801404,0.08785293884124255 +1127,-0.0,0.04999443782936746,0.86215262332939,0.08785293884124255 +1128,-0.0,0.04510861776877019,0.8670384433899873,0.08785293884124255 +1129,0.00030107311032849017,0.04306339076665971,0.8687825972817693,0.08785293884124255 +1130,0.4385129851934459,0.015339202515828654,0.45829487344948294,0.08785293884124255 +1131,0.9971541414079593,0.002845858592040673,-0.0,-0.0 +1132,0.8859076271415822,0.03931380792945714,-0.0,0.07477856492896062 +1133,0.49737277826266574,0.04806283454959644,0.3667114483464952,0.08785293884124255 +1134,0.034924480798104854,0.03215551342207044,0.8450670669385821,0.08785293884124255 +1135,-0.0,0.04635847871450438,0.865788582444253,0.08785293884124255 +1136,-0.0,0.06522001662285665,0.8469270445359007,0.08785293884124255 +1137,-0.0,0.05965245422822254,0.8524946069305349,0.08785293884124255 +1138,0.18079440275225833,0.03931380792945714,0.692038850477042,0.08785293884124255 +1139,0.6369201648999209,0.057266356059093644,0.21796054019974287,0.08785293884124255 +1140,0.5715872999586384,0.08612678153331942,0.25443297966679956,0.08785293884124255 +1141,0.33780402978856594,0.11396459350648992,0.4603784378637016,0.08785293884124255 +1142,0.03281696902580542,0.12135013545855558,0.7579799566743964,0.08785293884124255 +1143,-0.0,0.13305337885952115,0.7790936822992363,0.08785293884124255 +1144,-0.0,0.0749916567440512,0.8371554044147063,0.08785293884124255 +1145,0.00015053655516424508,0.03306450320078621,0.8789320214028069,0.08785293884124255 +1146,0.2702131165198199,0.004090454004220975,0.6378434906347166,0.08785293884124255 +1147,0.763521407793051,0.0021588507244499587,0.14646680264125644,0.08785293884124255 +1148,0.7739084300993839,0.0036359591148630885,0.13460267194451037,0.08785293884124255 +1149,0.4886416580631395,0.006249304728670932,0.4172560983669471,0.08785293884124255 +1150,0.048322234207722664,0.008749026620139306,0.8550758003308955,0.08785293884124255 +1151,-0.0,0.021361259799820644,0.8907858013589368,0.08785293884124255 +1152,-0.0,0.054084891833588444,0.858062169325169,0.08785293884124255 +1153,0.00015053655516424508,0.08328618847483263,0.8287103361287605,0.08785293884124255 +1154,0.2118049331160928,0.02886042547422576,0.6714817025684389,0.08785293884124255 +1155,0.5864904189198988,0.03624596742629141,0.2894106748125672,0.08785293884124255 +1156,0.4939104374938881,0.06328841334308563,0.3549482103217837,0.08785293884124255 +1157,0.32350305704796267,0.08624040525565888,0.5024035988551359,0.08785293884124255 +1158,0.04651579554575173,0.10078424171511123,0.7648470238978945,0.08785293884124255 +1159,-0.0,0.1360075956403474,0.7761394655184101,0.08785293884124255 +1160,-0.0,0.1348713584169527,0.7772757027418047,0.08785293884124255 +1161,0.00015053655516424508,0.10680629899910322,0.8051902256044899,0.08785293884124255 +1162,0.2706647261853126,0.0789684870259327,0.562513847947512,0.08785293884124255 +1163,0.5727915923999525,0.13373512119355796,0.20562034756524694,0.08785293884124255 +1164,0.42752381666645595,0.187479141860128,0.29714410263217345,0.08785293884124255 +1165,0.31552461962425765,0.2240659804534378,0.37255646108106194,0.08785293884124255 +1166,0.04019326022885344,0.18725189441544907,0.684701906514455,0.08785293884124255 +1167,-0.0,0.2038409578770119,0.7083061032817455,0.08785293884124255 +1168,-0.0,0.19634179220260678,0.7158052689561507,0.08785293884124255 +1169,0.00015053655516424508,0.1520285404902129,0.7599679841133803,0.08785293884124255 +1170,0.2274607348531743,0.17213993934429933,0.5125463869612839,0.08785293884124255 +1171,0.4507064461617497,0.19202409075370686,0.26941652424330087,0.08785293884124255 +1172,0.4750933680983574,0.19281945681008314,0.24423423625031687,0.08785293884124255 +1173,0.3621909517251736,0.13737108030842105,0.4125850291251627,0.08785293884124255 +1174,0.04470935688378078,0.044654122879412304,0.8227835813955644,0.08785293884124255 +1175,-0.0,0.12623595551915284,0.7859111056396046,0.08785293884124255 +1176,-0.0,0.21031751005036176,0.7018295511083956,0.08785293884124255 +1177,-0.0,0.25338090081702147,0.658766160341736,0.08785293884124255 +1178,0.2846646258155874,0.29496718319326803,0.33251525214990196,0.08785293884124255 +1179,0.7809836481921034,0.21901635180789658,-0.0,-0.0 +1180,0.8116931054456095,0.18830689455439054,-0.0,-0.0 +1181,0.42571737800448506,0.33871231629396453,0.1477173668603079,0.08785293884124255 +1182,0.03883843123237523,0.1738442951793914,0.6994643347469909,0.08785293884124255 +1183,-0.0,0.11657793912029776,0.7955691220384596,0.08785293884124255 +1184,-0.0,0.08260444614079579,0.8295426150179617,0.08785293884124255 +1185,0.0007526827758212254,0.09123984903859562,0.8201545293443406,0.08785293884124255 +1186,0.38477143499981037,0.04738109221555962,0.4799945339433874,0.08785293884124255 +1187,0.7355216085325014,0.10055699427043228,0.07606845835582378,0.08785293884124255 +1188,0.6703392801463832,0.1666860006720047,0.07512178034036955,0.08785293884124255 +1189,0.39545953041647175,0.2145215877769222,0.3021659429653635,0.08785293884124255 +1190,0.035827700129090326,0.2070224221025171,0.66929693892715,0.08785293884124255 +1191,-0.0,0.33053140828552263,0.5816156528732348,0.08785293884124255 +1192,-0.0,0.3280316863940543,0.5841153747647032,0.08785293884124255 +1193,-0.0,0.28110508906785253,0.6310419720909048,0.08785293884124255 +1194,0.27879370016418187,0.3810939647265874,0.25225939626798816,0.08785293884124255 +1195,0.6334578241311433,0.36654217586885673,-0.0,-0.0 +1196,0.6846402528869865,0.3153597471130135,-0.0,-0.0 +1197,0.47539444120868596,0.4092726478667764,0.02747997208329507,0.08785293884124255 +1198,0.05328994052814275,0.18543391485801752,0.6734232057725972,0.08785293884124255 +1199,-0.0,0.1746396612357677,0.7375073999229897,0.08785293884124255 +1200,-0.0,0.17850286779530974,0.7336441933634477,0.08785293884124255 +1201,0.00045160966549273514,0.17475328495810719,0.7369421665351575,0.08785293884124255 +1202,0.35014802731203404,0.12453159968406077,0.43746743416266254,0.08785293884124255 +1203,0.8103382764491311,0.18966172355086885,-0.0,-0.0 +1204,0.7499731178282689,0.2500268821717311,-0.0,-0.0 +1205,0.4212012813495577,0.4304066602219181,0.060539119587281656,0.08785293884124255 +1206,0.03447287113261212,0.43995105289843367,0.4377231371277117,0.08785293884124255 +1207,-0.0,0.4283614332198076,0.48378562793894986,0.08785293884124255 +1208,-0.0,0.3700724636596587,0.5420745974990988,0.08785293884124255 +1209,-0.0,0.24383650814050586,0.6683105530182516,0.08785293884124255 +1210,0.2354391722768793,0.1598685773316364,0.5168393115502418,0.08785293884124255 +1211,0.5905549059093333,0.10407932966295591,0.21751282558646823,0.08785293884124255 +1212,0.601995684101816,0.06692437245794872,0.2432270045989927,0.08785293884124255 +1213,0.402384211954027,0.03601871998161247,0.4737441292231179,0.08785293884124255 +1214,0.048021161097394176,0.01886153790835227,0.8452643621530109,0.08785293884124255 +1215,-0.0,0.13453048724993427,0.7776165739088232,0.08785293884124255 +1216,-0.0,0.24951769425747944,0.6626293669012779,0.08785293884124255 +1217,0.00045160966549273514,0.3275771915046964,0.5841182599885684,0.08785293884124255 +1218,0.28601945481206564,0.5381219489997371,0.08800565734695472,0.08785293884124255 +1219,0.6847907894421509,0.3152092105578491,-0.0,-0.0 +1220,0.7189625874644345,0.28103741253556547,-0.0,-0.0 +1221,0.49255560849740987,0.5074443915025901,-0.0,-0.0 +1222,0.04154808922533164,0.688218886210179,0.18238008572324677,0.08785293884124255 +1223,-0.0,0.6448146242765008,0.2673324368822566,0.08785293884124255 +1224,-0.0,0.5806172211546994,0.33152984000405805,0.08785293884124255 +1225,0.00030107311032849017,0.4387011919526995,0.47314479609572946,0.08785293884124255 +1226,0.36354578072165183,0.4549493842472439,0.09365189618986176,0.08785293884124255 +1227,0.7338657064256947,0.2661342935743053,-0.0,-0.0 +1228,0.8153059827695512,0.18469401723044876,-0.0,-0.0 +1229,0.5839312974821067,0.4160687025178933,-0.0,-0.0 +1230,0.06593501116193934,0.21270360821949066,0.6335084417773275,0.08785293884124255 +1231,-0.0,0.13680296169672368,0.7753440994620338,0.08785293884124255 +1232,-0.0,0.08010472424932741,0.8320423369094301,0.08785293884124255 +1233,0.0007526827758212254,0.043404261933678114,0.8679901164492582,0.08785293884124255 +1234,0.4799105378636132,0.008521779175460363,0.4237147441196838,0.08785293884124255 +1235,1.0,-0.0,-0.0,-0.0 +1236,1.0,-0.0,-0.0,-0.0 +1237,0.7096293210442512,0.004999443782936747,0.19751829633156948,0.08785293884124255 +1238,0.07722525279925772,0.06203855239735145,0.7728832559621482,0.08785293884124255 +1239,-0.0,0.25576699898615035,0.656380062172607,0.08785293884124255 +1240,-0.0,0.2545171380404162,0.6576299231183412,0.08785293884124255 +1241,0.0018064386619709406,0.21747580455774845,0.692864817939038,0.08785293884124255 +1242,0.4808137571945988,0.09748915376726656,0.3338441501968921,0.08785293884124255 +1243,1.0,-0.0,-0.0,-0.0 +1244,1.0,-0.0,-0.0,-0.0 +1245,0.6933713730865128,0.04999443782936746,0.16878125024287727,0.08785293884124255 +1246,0.07406398514080857,0.16577701089328895,0.6723060651246598,0.08785293884124255 +1247,-0.0,0.38098034100424794,0.5311667201545095,0.08785293884124255 +1248,-0.0,0.31985077838561227,0.5922962827731452,0.08785293884124255 +1249,0.00045160966549273514,0.20599980860146183,0.7056956428918029,0.08785293884124255 +1250,0.2903850149118287,0.12021389823516086,0.5015481480117678,0.08785293884124255 +1251,0.8449616841369076,0.15503831586309236,-0.0,-0.0 +1252,0.9637350261614969,0.036264973838503134,-0.0,-0.0 +1253,0.5893506134680194,0.22633845490022725,0.09645799279051083,0.08785293884124255 +1254,0.06367696283447566,0.16941297000815203,0.6790571283161297,0.08785293884124255 +1255,-0.0,0.17486690868044666,0.7372801524783108,0.08785293884124255 +1256,-0.0,0.11078312928098473,0.8013639318777727,0.08785293884124255 +1257,0.00030107311032849017,0.09703465887790867,0.8148113291705203,0.08785293884124255 +1258,0.34502978443644966,0.06408377939946193,0.5030334973228459,0.08785293884124255 +1259,0.8810904573763264,0.06806060968134343,-0.0,0.05084893294233017 +1260,0.9953477027459884,0.004652297254011617,-0.0,-0.0 +1261,0.6397803594480416,0.08635402897799835,0.1860126727327175,0.08785293884124255 +1262,0.0671393036032533,0.1391890598658526,0.7058186976896516,0.08785293884124255 +1263,-0.0,0.217362180835409,0.6947848803233484,0.08785293884124255 +1264,-0.0,0.14191602920199992,0.7702310319567576,0.08785293884124255 +1265,0.0006021462206569803,0.10748804133314005,0.8040568736049604,0.08785293884124255 +1266,0.3037827683214466,0.12657682668617126,0.48178746615113954,0.08785293884124255 +1267,0.6322535316898292,0.11430546467350833,0.16558806479541988,0.08785293884124255 +1268,0.6336083606863074,0.13691658541906318,0.14162211505338684,0.08785293884124255 +1269,0.36234148828033785,0.18361593530058595,0.3661896375778336,0.08785293884124255 +1270,0.04636525899058748,0.21293085566416964,0.6528509465040003,0.08785293884124255 +1271,-0.0,0.21134012355141701,0.7008069376073405,0.08785293884124255 +1272,-0.0,0.16429990250287582,0.7478471586558816,0.08785293884124255 +1273,0.0006021462206569803,0.12339536246066607,0.7881495524774343,0.08785293884124255 +1274,0.24733156013485466,0.11680518656497671,0.548010314458926,0.08785293884124255 +1275,0.4940609740490523,0.09158072020561404,0.3265053669040911,0.08785293884124255 +1276,0.45853434703029045,0.08385430708652998,0.369758407041937,0.08785293884124255 +1277,0.3632447076113233,0.08124096147272213,0.467661392074712,0.08785293884124255 +1278,0.052687794307485776,0.0965801639885508,0.7628791028627208,0.08785293884124255 +1279,-0.0,0.14702909670727612,0.7651179644514813,0.08785293884124255 +1280,-0.0,0.11021501066928736,0.80193205048947,0.08785293884124255 +1281,0.0010537558861497154,0.08737664247905359,0.823716662793554,0.08785293884124255 +1282,0.3320836406923246,0.05647099000271734,0.5235924304637154,0.08785293884124255 +1283,0.7767686246475045,0.0646518980111593,0.0707265385000936,0.08785293884124255 +1284,0.7060164437203094,0.040222797708172914,0.16590781973027513,0.08785293884124255 +1285,0.4893943408389607,0.042836143321980755,0.379916576997816,0.08785293884124255 +1286,0.07060164437203094,0.0965801639885508,0.7449652527981756,0.08785293884124255 +1287,-0.0,0.16498164483691263,0.7471654163218449,0.08785293884124255 +1288,-0.0,0.11703243400965566,0.7951146271491017,0.08785293884124255 +1289,0.0012042924413139607,0.07465078557703278,0.8362919831404108,0.08785293884124255 +1290,0.37016938914887865,0.02840593058486788,0.5135717414250109,0.08785293884124255 +1291,0.852036902229627,0.022838368190233774,0.03727179073889665,0.08785293884124255 +1292,0.87838079938337,0.014884707626470768,0.018881554148916693,0.08785293884124255 +1293,0.5782109083858653,0.015111955071149711,0.3188241977017424,0.08785293884124255 +1294,0.07421452169597281,0.047835587104917504,0.7900969523578671,0.08785293884124255 +1295,-0.0,0.13373512119355796,0.7784119399651994,0.08785293884124255 +1296,-0.0,0.1172596814543346,0.7948873797044228,0.08785293884124255 +1297,0.00045160966549273514,0.0921488388173114,0.8195466126759533,0.08785293884124255 +1298,0.4278248897767845,0.018293419296654914,0.4660287520853179,0.08785293884124255 +1299,1.0,-0.0,-0.0,-0.0 +1300,1.0,-0.0,-0.0,-0.0 +1301,0.5932645639022898,0.005908433561652518,0.3129740636948152,0.08785293884124255 +1302,0.0660855477171036,0.026587951027436333,0.8194735624142174,0.08785293884124255 +1303,-0.0,0.10237497382786383,0.8097720873308936,0.08785293884124255 +1304,-0.0,0.11351009861713204,0.7986369625416254,0.08785293884124255 +1305,0.00045160966549273514,0.09555755048749554,0.8161379010057692,0.08785293884124255 +1306,0.29475057501159185,0.025906208693399504,0.591490277453766,0.08785293884124255 +1307,0.6473071872062538,0.0810137140280432,0.1838261599244605,0.08785293884124255 +1308,0.6476082603165824,0.14941519487640503,0.11512360596577,0.08785293884124255 +1309,0.4171367943601231,0.1958872973132489,0.2991229694853855,0.08785293884124255 +1310,0.05705335440724889,0.18509304369099908,0.6700006630605094,0.08785293884124255 +1311,-0.0,0.18793363674948588,0.7242134244092715,0.08785293884124255 +1312,-0.0,0.18259332179953072,0.7295537393592267,0.08785293884124255 +1313,0.00045160966549273514,0.14975606604342345,0.7619393854498413,0.08785293884124255 +1314,0.32530949570993356,0.13737108030842105,0.4494664851404029,0.08785293884124255 +1315,0.7596074573587807,0.1024885975502033,0.05005100624977345,0.08785293884124255 +1316,0.7785750633094755,0.0654472640675356,0.06812473378174634,0.08785293884124255 +1317,0.49481365682487355,0.06249304728670933,0.35484035704717454,0.08785293884124255 +1318,0.060816768286355,0.12555421318511603,0.7257760796872864,0.08785293884124255 +1319,-0.0,0.17248081051131775,0.7396662506474396,0.08785293884124255 +1320,-0.0,0.149642442321084,0.7625046188376734,0.08785293884124255 +1321,0.0007526827758212254,0.12225912523727135,0.7891352531456649,0.08785293884124255 +1322,0.23498756261138654,0.09567117420983501,0.5814883243375358,0.08785293884124255 +1323,0.6543824052989733,0.07510528046639067,0.1826593753933935,0.08785293884124255 +1324,0.723027074453869,0.09396681837474294,0.09515316833014544,0.08785293884124255 +1325,0.4978243879281584,0.13009916207869487,0.28422351115190414,0.08785293884124255 +1326,0.06593501116193934,0.13805282264245788,0.7081592273543602,0.08785293884124255 +1327,-0.0,0.14691547298493665,0.7652315881738208,0.08785293884124255 +1328,-0.0,0.0921488388173114,0.819998222341446,0.08785293884124255 +1329,0.00045160966549273514,0.05772085094845152,0.8539746005448132,0.08785293884124255 +1330,0.321245008720499,0.044994994046430724,0.5459070583918277,0.08785293884124255 +1331,0.6322535316898292,0.06760611479198554,0.21228741467694268,0.08785293884124255 +1332,0.586640955475063,0.10635180410974533,0.2191543015739491,0.08785293884124255 +1333,0.4338463519833543,0.14225690036901834,0.33604380880638474,0.08785293884124255 +1334,0.06397803594480415,0.10521556688635063,0.7429534583276027,0.08785293884124255 +1335,-0.0,0.10782891250015846,0.804318148658599,0.08785293884124255 +1336,-0.0,0.07646876513446432,0.8356782960242931,0.08785293884124255 +1337,0.0009032193309854703,0.052494159720835834,0.8587496821069361,0.08785293884124255 +1338,0.44137317974156653,0.03931380792945714,0.43146007348773374,0.08785293884124255 +1339,0.9902294598704041,0.009770540129595884,-0.0,-0.0 +1340,1.0,-0.0,-0.0,-0.0 +1341,0.5332004783917561,0.13703020914140265,0.2419163736255987,0.08785293884124255 +1342,0.04651579554575173,0.13816644636479736,0.7274648192482084,0.08785293884124255 +1343,-0.0,0.1838431827452649,0.7283038784134925,0.08785293884124255 +1344,-0.0,0.189410745139899,0.7227363160188585,0.08785293884124255 +1345,-0.0,0.18009359990806234,0.732053461250695,0.08785293884124255 +1346,0.15354728626752995,0.16134568572204952,0.597254089169178,0.08785293884124255 +1347,0.4160830384739733,0.2528127822053241,0.24325124047946,0.08785293884124255 +1348,0.4412226431864023,0.34473437357795655,0.12619004439439857,0.08785293884124255 +1349,0.3102558401935091,0.3716631957724113,0.23022802519283708,0.08785293884124255 +1350,0.04169862578049588,0.2540626431510583,0.6163857922272032,0.08785293884124255 +1351,-0.0,0.21872566550348266,0.6934213956552748,0.08785293884124255 +1352,-0.0,0.22440685162045623,0.6877402095383012,0.08785293884124255 +1353,0.00015053655516424508,0.18441130135696226,0.7275852232466309,0.08785293884124255 +1354,0.1686009417839545,0.2500858128691768,0.4934603065056262,0.08785293884124255 +1355,0.5011361921417717,0.32337311377813593,0.08763775523884977,0.08785293884124255 +1356,0.4672654672298167,0.340984790740754,0.1038968031881868,0.08785293884124255 +1357,0.31552461962425765,0.30462519959212314,0.2919972419423766,0.08785293884124255 +1358,0.04711794176640871,0.15055143209979976,0.7144776872925489,0.08785293884124255 +1359,-0.0,0.15759610288484696,0.7545509582739105,0.08785293884124255 +1360,-0.0,0.1468018492625972,0.7653452118961602,0.08785293884124255 +1361,0.00015053655516424508,0.13657571425204476,0.7754208103515484,0.08785293884124255 +1362,0.16182679680156345,0.13737108030842105,0.612949184048773,0.08785293884124255 +1363,0.4746417584328647,0.143620385037092,0.29388491768880065,0.08785293884124255 +1364,0.5119748241135975,0.16623150578264684,0.23394073126251302,0.08785293884124255 +1365,0.38326606944816793,0.14293864270305515,0.3859423490075344,0.08785293884124255 +1366,0.06292428005865443,0.08033197169400635,0.7688908094060967,0.08785293884124255 +1367,-0.0,0.09578479793217448,0.816362263226583,0.08785293884124255 +1368,-0.0,0.08033197169400635,0.8318150894647511,0.08785293884124255 +1369,-0.0,0.05647099000271734,0.85567607115604,0.08785293884124255 +1370,0.20322434947173085,0.045563112658128076,0.6633595990288985,0.08785293884124255 +1371,0.49375990093872385,0.051357922497441116,0.3670292377225925,0.08785293884124255 +1372,0.46229776090939656,0.05681186116973576,0.3930374390796251,0.08785293884124255 +1373,0.39681435941295,0.07396904324299595,0.44136365850281156,0.08785293884124255 +1374,0.05193511153166455,0.0849905443099247,0.7752214053171682,0.08785293884124255 +1375,-0.0,0.15418739121466282,0.7579596699440946,0.08785293884124255 +1376,-0.0,0.17134457328792302,0.7408024878708344,0.08785293884124255 +1377,0.00015053655516424508,0.13384874491589743,0.7781477796876958,0.08785293884124255 +1378,0.19389108305154765,0.09283058115134822,0.6254253969558615,0.08785293884124255 +1379,0.4379108389727889,0.1787301152399887,0.29550610694597984,0.08785293884124255 +1380,0.3889864585444093,0.23315587824059553,0.2900047243737526,0.08785293884124255 +1381,0.19614913137901133,0.1671404955613626,0.5488574342183835,0.08785293884124255 +1382,0.033419115246462405,0.10157960777148753,0.7771483381408075,0.08785293884124255 +1383,-0.0,0.12157738290323451,0.7905696782555229,0.08785293884124255 +1384,-0.0,0.12532696574043709,0.7868200954183203,0.08785293884124255 +1385,0.0006021462206569803,0.12828118252126333,0.7832637324168371,0.08785293884124255 +1386,0.22595536930153187,0.187479141860128,0.49871254999709747,0.08785293884124255 +1387,0.509867312341298,0.2569032362095451,0.1453765126079143,0.08785293884124255 +1388,0.6102751946358494,0.2512220500925715,0.05064981643033652,0.08785293884124255 +1389,0.40449172372632647,0.22508859395449307,0.28256674347793787,0.08785293884124255 +1390,0.060515695176026514,0.1730489291230151,0.6785824368597159,0.08785293884124255 +1391,-0.0,0.2544035143180767,0.6577435468406807,0.08785293884124255 +1392,-0.0,0.26542501538500546,0.6467220457737519,0.08785293884124255 +1393,-0.0,0.24679072492133214,0.6653563362374253,0.08785293884124255 +1394,0.17326757499404608,0.28826338357523923,0.45061610258947205,0.08785293884124255 +1395,0.4171367943601231,0.2701972117232633,0.22481305507537108,0.08785293884124255 +1396,0.5896516865783479,0.2377008271341744,0.08479454744623517,0.08785293884124255 +1397,0.5097167757861338,0.17577589845916244,0.22665438691346118,0.08785293884124255 +1398,0.076171496913108,0.08521779175460363,0.7507577724910458,0.08785293884124255 +1399,-0.0,0.14725634415195507,0.7648907170068023,0.08785293884124255 +1400,-0.0,0.1703219597868678,0.7418251013718896,0.08785293884124255 +1401,0.00015053655516424508,0.12396348107236342,0.7880330435312297,0.08785293884124255 +1402,0.3346427621301168,0.034996106480557225,0.5425081925480835,0.08785293884124255 +1403,0.8506820732331489,0.04647210243684385,0.014992885488764665,0.08785293884124255 +1404,0.8570046085500472,0.04329063821133865,0.01185181439737161,0.08785293884124255 +1405,0.5390714040431616,0.03692770976032824,0.33614794735526765,0.08785293884124255 +1406,0.06533286494128236,0.013293975513718167,0.833520220703757,0.08785293884124255 +1407,-0.0,0.00908989778715772,0.9030571633715997,0.08785293884124255 +1408,-0.0,0.037950323261383485,0.874196737897374,0.08785293884124255 +1409,0.00030107311032849017,0.05760722722611206,0.8542387608223169,0.08785293884124255 +1410,0.30453545109726776,0.012044114567983981,0.5955674954935057,0.08785293884124255 +1411,0.8208758353106284,0.0351097302028967,0.05616149564523244,0.08785293884124255 +1412,0.7624676519069012,0.05272140716551478,0.09695800208634144,0.08785293884124255 +1413,0.4016315291782058,0.03624596742629141,0.4742695645542603,0.08785293884124255 +1414,0.04019326022885344,0.013066728069039224,0.8588870728608649,0.08785293884124255 +1415,-0.0,0.03340537436780462,0.8787416867909528,0.08785293884124255 +1416,-0.0,0.115668949341582,0.7964781118171754,0.08785293884124255 +1417,-0.0,0.14850620509768928,0.7636408560610681,0.08785293884124255 +1418,0.1532462131572015,0.08624040525565888,0.6726604427458971,0.08785293884124255 +1419,0.43414742509368276,0.16611788206030734,0.31188175400476736,0.08785293884124255 +1420,0.5619529604281268,0.2512220500925715,0.09897205063805915,0.08785293884124255 +1421,0.39500792075097907,0.2851955430720735,0.23194359733570485,0.08785293884124255 +1422,0.06819305948940302,0.22145263483962996,0.6225013668297245,0.08785293884124255 +1423,-0.0,0.21304447938650908,0.6991025817722484,0.08785293884124255 +1424,-0.0,0.17804837290595185,0.7340986882528056,0.08785293884124255 +1425,0.00030107311032849017,0.12066839312451874,0.7911775949239103,0.08785293884124255 +1426,0.39681435941295,0.06374290823244351,0.4515897935133639,0.08785293884124255 +1427,0.9455201029866233,0.042381648432622876,-0.0,0.012098248580753812 +1428,0.9951971661908242,0.004802833809175788,-0.0,-0.0 +1429,0.5333510149469203,0.01738442951793914,0.361411616693898,0.08785293884124255 +1430,0.050128672869693615,0.009317145231836665,0.8527012430572272,0.08785293884124255 +1431,-0.0,0.03340537436780462,0.8787416867909528,0.08785293884124255 +1432,-0.0,0.06283391845372775,0.8493131427050297,0.08785293884124255 +1433,0.0007526827758212254,0.08942186948116408,0.8219725089017722,0.08785293884124255 +1434,0.26870775096817745,0.021361259799820644,0.6220780503907594,0.08785293884124255 +1435,0.9408534697765316,0.021474883522160117,-0.0,0.03767164670130829 +1436,0.9525953210793429,0.04740467892065714,-0.0,-0.0 +1437,0.6012430013259947,0.07260555857492229,0.23829850125784036,0.08785293884124255 +1438,0.07632203346827225,0.08908099831414566,0.7467440293763395,0.08785293884124255 +1439,-0.0,0.09442131326410082,0.8177257478946566,0.08785293884124255 +1440,-0.0,0.06249304728670933,0.8496540138720481,0.08785293884124255 +1441,0.0015053655516424507,0.04306339076665971,0.8675783048404553,0.08785293884124255 +1442,0.43068508432490515,0.01499833134881024,0.46646364548504204,0.08785293884124255 +1443,1.0,-0.0,-0.0,-0.0 +1444,1.0,-0.0,-0.0,-0.0 +1445,0.7142959542543428,0.02386098169128902,0.17399012521312562,0.08785293884124255 +1446,0.09664446841544533,0.06851510457070133,0.7469874881726108,0.08785293884124255 +1447,-0.0,0.18441130135696226,0.7277357598017952,0.08785293884124255 +1448,-0.0,0.16055031966567326,0.7515967414930842,0.08785293884124255 +1449,0.0007526827758212254,0.12873567741062122,0.782658700972315,0.08785293884124255 +1450,0.4606418588025899,0.047608339660238565,0.40389686269592895,0.08785293884124255 +1451,1.0,-0.0,-0.0,-0.0 +1452,1.0,-0.0,-0.0,-0.0 +1453,0.7139948811440143,0.12225912523727135,0.07589305477747178,0.08785293884124255 +1454,0.09408534697765317,0.21202186588545383,0.6060398482956505,0.08785293884124255 +1455,-0.0,0.3759808972213113,0.5361661639374462,0.08785293884124255 +1456,-0.0,0.3159875718260703,0.5961594893326871,0.08785293884124255 +1457,0.0006021462206569803,0.257357731098903,0.6541871838391974,0.08785293884124255 +1458,0.4473946419481364,0.13248526024782378,0.33226715896279724,0.08785293884124255 +1459,1.0,-0.0,-0.0,-0.0 +1460,1.0,-0.0,-0.0,-0.0 +1461,0.7004465911792322,0.16134568572204952,0.05035478425747565,0.08785293884124255 +1462,0.08911764065723309,0.24554086397559793,0.5774885565259265,0.08785293884124255 +1463,-0.0,0.3350763571791015,0.5770707039796559,0.08785293884124255 +1464,-0.0,0.2178166757247669,0.6943303854339905,0.08785293884124255 +1465,0.00045160966549273514,0.14055254453392627,0.7711429069593384,0.08785293884124255 +1466,0.37498655891413446,0.026133456138078447,0.5110270461065445,0.08785293884124255 +1467,0.8750689951697567,0.017838924407297025,0.019239141581703714,0.08785293884124255 +1468,1.0,-0.0,-0.0,-0.0 +1469,0.633307287575979,0.009203521509497192,0.26963625207328124,0.08785293884124255 +1470,0.08174134945418507,0.07328730090895913,0.7571184107956132,0.08785293884124255 +1471,-0.0,0.14430212737112882,0.7678449337876286,0.08785293884124255 +1472,-0.0,0.12157738290323451,0.7905696782555229,0.08785293884124255 +1473,-0.0,0.11316922745011362,0.7989778337086438,0.08785293884124255 +1474,0.40358850439534105,0.022383873300875885,0.48617468346254045,0.08785293884124255 +1475,0.9833047783328488,0.016695221667151205,-0.0,-0.0 +1476,1.0,-0.0,-0.0,-0.0 +1477,0.6138880719597913,0.05385764438890949,0.2444013448100566,0.08785293884124255 +1478,0.05705335440724889,0.13975717847754995,0.7153365282739585,0.08785293884124255 +1479,-0.0,0.13248526024782378,0.7796618009109336,0.08785293884124255 +1480,-0.0,0.09248970998432982,0.8196573511744276,0.08785293884124255 +1481,0.0006021462206569803,0.06294754217606721,0.8485973727620332,0.08785293884124255 +1482,0.359330757177053,0.006476552173349876,0.5463397518083546,0.08785293884124255 +1483,0.8726604102871286,0.009203521509497192,0.030283129362131636,0.08785293884124255 +1484,0.8652841190840806,0.015339202515828654,0.03152373955884814,0.08785293884124255 +1485,0.4991792169246366,0.05851621700482783,0.354451627229293,0.08785293884124255 +1486,0.049526526649036624,0.13987080219988943,0.7227497323098313,0.08785293884124255 +1487,-0.0,0.2715606963913369,0.6405863647674206,0.08785293884124255 +1488,-0.0,0.2741740420051447,0.6379730191536127,0.08785293884124255 +1489,0.00045160966549273514,0.3164420667154282,0.5952533847778365,0.08785293884124255 +1490,0.32530949570993356,0.2813323365125314,0.30550522893629245,0.08785293884124255 +1491,0.896896795668572,0.10310320433142794,-0.0,-0.0 +1492,1.0,-0.0,-0.0,-0.0 +1493,0.6774144982391028,0.16180018061140744,0.07293238230824718,0.08785293884124255 +1494,0.0885154944365761,0.052039664831477955,0.7715919018907034,0.08785293884124255 +1495,-0.0,0.018975161630691743,0.8931718995280656,0.08785293884124255 +1496,-0.0,0.03385986925716251,0.8782871919015949,0.08785293884124255 +1497,0.00015053655516424508,0.08192270380675895,0.8300738207968342,0.08785293884124255 +1498,0.4332442057626973,0.032041889699730965,0.44686096569632916,0.08785293884124255 +1499,0.9991111166250944,0.000888883374905558,-0.0,-0.0 +1500,0.9568103446239418,0.043189655376058234,-0.0,-0.0 +1501,0.4850287807391976,0.09953438076937704,0.3275838996501828,0.08785293884124255 +1502,0.039290040897867966,0.11055588183630578,0.7623011384245837,0.08785293884124255 +1503,-0.0,0.20395458159935134,0.7081924795594061,0.08785293884124255 +1504,-0.0,0.25269915848298463,0.6594479026757728,0.08785293884124255 +1505,-0.0,0.28110508906785253,0.6310419720909048,0.08785293884124255 +1506,0.21948229742946931,0.42381648432622876,0.26884827940305933,0.08785293884124255 +1507,0.6066623173119077,0.3933376826880923,-0.0,-0.0 +1508,0.6152429009562697,0.38475709904373034,-0.0,-0.0 +1509,0.4260184511148135,0.5739815488851865,-0.0,-0.0 +1510,0.06096730484151926,0.44210990362288366,0.4090698526943545,0.08785293884124255 +1511,-0.0,0.3570057355906195,0.5551413255681379,0.08785293884124255 +1512,-0.0,0.34916569874919595,0.5629813624095614,0.08785293884124255 +1513,-0.0,0.33212214039827526,0.5800249207604822,0.08785293884124255 +1514,0.2491379987968256,0.5233508650956058,0.13965819726632606,0.08785293884124255 +1515,0.6175009492837332,0.3824990507162668,-0.0,-0.0 +1516,0.6540813321886448,0.34591866781135516,-0.0,-0.0 +1517,0.5119748241135975,0.4880251758864025,-0.0,-0.0 +1518,0.06894574226522424,0.5397126811124896,0.3034886377810436,0.08785293884124255 +1519,-0.0,0.3934789504615898,0.5186681106971676,0.08785293884124255 +1520,-0.0,0.3553013797555274,0.5568456814032301,0.08785293884124255 +1521,-0.0,0.340984790740754,0.5711622704180035,0.08785293884124255 +1522,0.3829649963378395,0.4250663452719629,0.104115719548955,0.08785293884124255 +1523,0.8646819728634237,0.13531802713657626,-0.0,-0.0 +1524,0.9875198018774477,0.0124801981225523,-0.0,-0.0 +1525,0.6768123520184458,0.27894623834340254,-0.0,0.04424140963815171 +1526,0.08881656754690459,0.1124874851160768,0.7108430084957761,0.08785293884124255 +1527,-0.0,0.06453827428881982,0.8476087868699376,0.08785293884124255 +1528,-0.0,0.07328730090895913,0.8388597602497982,0.08785293884124255 +1529,0.00015053655516424508,0.07601427024510644,0.8359822543584867,0.08785293884124255 +1530,0.3841692887791534,0.01215773829032345,0.5158200340892807,0.08785293884124255 +1531,0.8570046085500472,0.02090676491046276,0.03423568769824749,0.08785293884124255 +1532,0.9726166829161874,0.027383317083812635,-0.0,-0.0 +1533,0.6248772404867813,0.0398819265411545,0.24738789413082163,0.08785293884124255 +1534,0.07677364313376497,0.049653566662349054,0.7857198513626433,0.08785293884124255 +1535,-0.0,0.10498831944167167,0.8071587417170858,0.08785293884124255 +1536,-0.0,0.08896737459180619,0.8231796865669513,0.08785293884124255 +1537,-0.0,0.08805838481309042,0.824088676345667,0.08785293884124255 +1538,0.2962559405632343,0.049312695495330634,0.5665784251001924,0.08785293884124255 +1539,0.7167045391369709,0.10907877344589265,0.08636374857589392,0.08785293884124255 +1540,0.5944688563436037,0.17077645467622568,0.14690175013892803,0.08785293884124255 +1541,0.28225604093295953,0.197364405703662,0.4325266145221359,0.08785293884124255 +1542,0.025892287488250152,0.10101148915979018,0.7852432845107171,0.08785293884124255 +1543,-0.0,0.14259777153603675,0.7695492896227207,0.08785293884124255 +1544,-0.0,0.15816422149654433,0.7539828396622131,0.08785293884124255 +1545,-0.0,0.1293037960223186,0.7828432651364389,0.08785293884124255 +1546,0.12705285255862284,0.11146487161502155,0.6736293369851131,0.08785293884124255 +1547,0.41939484268758676,0.17486690868044666,0.31788530979072405,0.08785293884124255 +1548,0.3462340768777637,0.2388370643575691,0.3270759199234246,0.08785293884124255 +1549,0.20548239779919453,0.1846385488016412,0.5220261145579217,0.08785293884124255 +1550,0.03356965180162665,0.0649927691781777,0.813584640178953,0.08785293884124255 +1551,-0.0,0.10044337054809283,0.8117036906106646,0.08785293884124255 +1552,-0.0,0.1108967530033242,0.8012503081554332,0.08785293884124255 +1553,-0.0,0.10816978366717689,0.8039772774915805,0.08785293884124255 +1554,0.1406011425234049,0.1072607938884611,0.6642851247468915,0.08785293884124255 +1555,0.4154808922533164,0.19043335864095426,0.3062328102644868,0.08785293884124255 +1556,0.525222040968051,0.2536081482617004,0.133316871929006,0.08785293884124255 +1557,0.4032874312850126,0.2308834037938061,0.2779762260799387,0.08785293884124255 +1558,0.05328994052814275,0.11339647489479257,0.7454606457358222,0.08785293884124255 +1559,-0.0,0.10862427855653477,0.8035227826022227,0.08785293884124255 +1560,-0.0,0.11896403728942667,0.7931830238693307,0.08785293884124255 +1561,-0.0,0.09953438076937704,0.8126126803893804,0.08785293884124255 +1562,0.2965570136735628,0.047267468493220145,0.5683225789919745,0.08785293884124255 +1563,0.7773707708681615,0.06147043378565409,0.07330585650494183,0.08785293884124255 +1564,0.845864903467893,0.06567451151221453,0.0006076461786499593,0.08785293884124255 +1565,0.5395230137086543,0.042608895877301815,0.33001515157280137,0.08785293884124255 +1566,0.06111784139668349,0.01363484668073658,0.8373943730813374,0.08785293884124255 +1567,-0.0,0.02886042547422576,0.8832866356845317,0.08785293884124255 +1568,-0.0,0.05567562394634104,0.8564714372124164,0.08785293884124255 +1569,-0.0,0.056925484892075225,0.8552215762666822,0.08785293884124255 +1570,0.29444950190126334,0.014771083904131295,0.6029264753533627,0.08785293884124255 +1571,0.8265962244068698,0.01443021273711288,0.07112062401477483,0.08785293884124255 +1572,0.9032193309854704,0.020565893743444346,-0.0,0.07621477527108533 +1573,0.6415867981100125,0.0179525481296365,0.2526077149191084,0.08785293884124255 +1574,0.07301022925465886,0.03863206559542032,0.8005047663086783,0.08785293884124255 +1575,-0.0,0.11680518656497671,0.7953418745937807,0.08785293884124255 +1576,-0.0,0.1268040741308502,0.7853429870279072,0.08785293884124255 +1577,-0.0,0.10839703111185582,0.8037500300469016,0.08785293884124255 +1578,0.29173984390830693,0.07487803302171173,0.5455291842287389,0.08785293884124255 +1579,0.7517795564902399,0.18918349769522008,-0.0,0.05903694581454011 +1580,0.9261008873704356,0.07389911262956439,-0.0,-0.0 +1581,0.6047053420947724,0.26383428327225283,0.04360743579173211,0.08785293884124255 +1582,0.06894574226522424,0.1566871131061312,0.6865142057874021,0.08785293884124255 +1583,-0.0,0.11998665079048192,0.7921604103682756,0.08785293884124255 +1584,-0.0,0.11362372233947152,0.7985233388192859,0.08785293884124255 +1585,-0.0,0.1008978654374507,0.8112491957213067,0.08785293884124255 +1586,0.2827076505984522,0.10476107199699274,0.5246783385633125,0.08785293884124255 +1587,0.7352205354221729,0.19872789037173566,-0.0,0.06605157420609142 +1588,0.7209195626815696,0.2661067577190423,-0.0,0.012973679599388 +1589,0.5312435031746209,0.21315810310884853,0.16774545487528802,0.08785293884124255 +1590,0.0660855477171036,0.10180685521616648,0.7442546582254873,0.08785293884124255 +1591,-0.0,0.07680963630148273,0.8353374248572747,0.08785293884124255 +1592,-0.0,0.056698237447396285,0.8554488237113611,0.08785293884124255 +1593,-0.0,0.06646987756859084,0.8456771835901666,0.08785293884124255 +1594,0.16453645479451984,0.06067506772927779,0.6869355386349598,0.08785293884124255 +1595,0.4373086927521319,0.20543168998976447,0.26940667841686106,0.08785293884124255 +1596,0.4794589281981205,0.3141695922686388,0.1185185406919982,0.08785293884124255 +1597,0.3490942714258843,0.35280165786405904,0.2102511318688141,0.08785293884124255 +1598,0.040795406449510414,0.18520666741333858,0.6861449872959084,0.08785293884124255 +1599,-0.0,0.1803208473527413,0.7318262138060161,0.08785293884124255 +1600,-0.0,0.22861092934701666,0.6835361318117408,0.08785293884124255 +1601,-0.0,0.19088785353031215,0.7212592076284453,0.08785293884124255 +1602,0.31161066918998725,0.16520889228159158,0.43532749968717865,0.08785293884124255 +1603,0.7386828761909506,0.16975384117517045,0.003710343792636364,0.08785293884124255 +1604,0.6998444449585753,0.1399844259222289,0.07231819027795318,0.08785293884124255 +1605,0.5094157026758053,0.07430991441001437,0.32842144407293783,0.08785293884124255 +1606,0.05494584263494945,0.021588507244499587,0.8356127112793084,0.08785293884124255 +1607,-0.0,0.06510639290051717,0.8470406682582403,0.08785293884124255 +1608,-0.0,0.1838431827452649,0.7283038784134925,0.08785293884124255 +1609,-0.0,0.23542835268738496,0.6767187084713725,0.08785293884124255 +1610,0.28511623548108017,0.21713493339073006,0.40989589228694717,0.08785293884124255 +1611,0.740188241742593,0.259811758257407,-0.0,-0.0 +1612,0.8959935763375866,0.10400642366241342,-0.0,-0.0 +1613,0.6316513854691722,0.30928377220804143,-0.0,0.059064842322786304 +1614,0.07511774102695828,0.09385319465240348,0.7431761254793956,0.08785293884124255 +1615,-0.0,0.021361259799820644,0.8907858013589368,0.08785293884124255 +1616,-0.0,0.01647543973922337,0.895671621419534,0.08785293884124255 +1617,-0.0,0.04692659732620174,0.8652204638325557,0.08785293884124255 +1618,0.3156751561794219,0.02499721891468373,0.5714746860646518,0.08785293884124255 +1619,0.8434563185852652,0.0734009246312986,-0.0,0.0831427567834363 +1620,0.8679937770770371,0.12612233179681337,-0.0,0.00588389112614951 +1621,0.4708783445537586,0.13737108030842105,0.3038976362965778,0.08785293884124255 +1622,0.0427523816666456,0.11623706795327936,0.7531576115388324,0.08785293884124255 +1623,-0.0,0.10203410266084542,0.810112958497912,0.08785293884124255 +1624,-0.0,0.08601315781097994,0.8261339033477775,0.08785293884124255 +1625,-0.0,0.09146709648327457,0.8206799646754829,0.08785293884124255 +1626,0.2378477571595072,0.061129562618635676,0.6131697413806145,0.08785293884124255 +1627,0.6896079592074067,0.09430768954176136,0.12823141240958935,0.08785293884124255 +1628,0.6215654362731678,0.12691769785318968,0.16366392703239996,0.08785293884124255 +1629,0.4683192231159664,0.10317033988424014,0.34065749815855084,0.08785293884124255 +1630,0.0581071102933986,0.06067506772927779,0.7933648831360811,0.08785293884124255 +1631,-0.0,0.05749360350377258,0.8546534576549849,0.08785293884124255 +1632,-0.0,0.04624485499216491,0.8659022061665925,0.08785293884124255 +1633,-0.0,0.045563112658128076,0.8665839485006294,0.08785293884124255 +1634,0.2873742838085438,0.017498053240278613,0.607274724109935,0.08785293884124255 +1635,0.9056279158680983,0.015339202515828654,-0.0,0.07903288161607303 +1636,0.8764238241662349,0.011589619678626095,0.02413361731389653,0.08785293884124255 +1637,0.5304908203987996,0.044426875434733365,0.3372293653252245,0.08785293884124255 +1638,0.041999698890824375,0.22077089250559315,0.64937646976234,0.08785293884124255 +1639,-0.0,0.31439683971331767,0.5977502214454398,0.08785293884124255 +1640,-0.0,0.281218712790192,0.6309283483685655,0.08785293884124255 +1641,-0.0,0.27701463506363155,0.6351324260951259,0.08785293884124255 +1642,0.24010580548697089,0.25963020554569244,0.412411050126094,0.08785293884124255 +1643,0.6515222107508527,0.3484777892491473,-0.0,-0.0 +1644,0.6512211376405241,0.34877886235947586,-0.0,-0.0 +1645,0.38928753165473773,0.6107124683452623,-0.0,-0.0 +1646,0.03898896778753947,0.8013881136602926,0.07176997971092537,0.08785293884124255 +1647,-0.0,0.8047968253304768,0.10735023582828064,0.08785293884124255 +1648,-0.0,0.7580974754489539,0.15404958570980354,0.08785293884124255 +1649,-0.0,0.5931158306120413,0.3190312305467161,0.08785293884124255 +1650,0.328621299923547,0.4891501246714248,0.09437563656378563,0.08785293884124255 +1651,0.8694991426286796,0.13050085737132044,-0.0,-0.0 +1652,0.899907526771857,0.10009247322814296,-0.0,-0.0 +1653,0.5237166754164085,0.10816978366717689,0.280260602075172,0.08785293884124255 +1654,0.030257847588013257,0.03170101853271255,0.8501881950380316,0.08785293884124255 +1655,-0.0,0.1299855383563554,0.782161522802402,0.08785293884124255 +1656,-0.0,0.18247969807719125,0.7296673630815662,0.08785293884124255 +1657,-0.0,0.19952325642811197,0.7126238047306455,0.08785293884124255 +1658,0.144966702623168,0.24110953880435854,0.5260708197312309,0.08785293884124255 +1659,0.45281395793404916,0.3633686640416299,0.09596443918307847,0.08785293884124255 +1660,0.5015878018072646,0.44256439851224155,-0.0,0.055847799680493804 +1661,0.38838431232375226,0.47062945793009103,0.05313329090491416,0.08785293884124255 +1662,0.043354527887302576,0.3632550403192904,0.5055374929521644,0.08785293884124255 +1663,-0.0,0.4133631018709974,0.4987839592877601,0.08785293884124255 +1664,-0.0,0.40654567853062906,0.5056013826281284,0.08785293884124255 +1665,-0.0,0.38268469683934003,0.5294623643194174,0.08785293884124255 +1666,0.20713829990600122,0.5658461372505681,0.13916262400218815,0.08785293884124255 +1667,0.5151360917720467,0.48486390822795333,-0.0,-0.0 +1668,0.5830280781511211,0.4169719218488789,-0.0,-0.0 +1669,0.3530082218601547,0.6469917781398453,-0.0,-0.0 +1670,0.027548189595056848,0.4081364106433817,0.47646246092031885,0.08785293884124255 +1671,-0.0,0.32291861888877804,0.5892284422699794,0.08785293884124255 +1672,-0.0,0.3293951710621279,0.5827518900966295,0.08785293884124255 +1673,-0.0,0.3159875718260703,0.5961594893326871,0.08785293884124255 +1674,0.21993390709496205,0.42165763360177877,0.2705555204620166,0.08785293884124255 +1675,0.5470498414668665,0.4529501585331335,-0.0,-0.0 +1676,0.5207059443131237,0.47929405568687633,-0.0,-0.0 +1677,0.4141260632568382,0.5858739367431618,-0.0,-0.0 +1678,0.045462039659602015,0.5013078629617483,0.3653771585374071,0.08785293884124255 +1679,-0.0,0.5472118467868947,0.3649352143718627,0.08785293884124255 +1680,-0.0,0.5644826525824945,0.3476644085762629,0.08785293884124255 +1681,-0.0,0.49619479545647205,0.4159522657022854,0.08785293884124255 +1682,0.2693098971888344,0.5638009102484576,0.07903625372146546,0.08785293884124255 +1683,0.6641672813846492,0.3358327186153508,-0.0,-0.0 +1684,0.6330062144656505,0.36699378553434947,-0.0,-0.0 +1685,0.42797542633194874,0.5184650450350085,-0.0,0.05355952863304281 +1686,0.043203991332138335,0.3422346516864882,0.5267084181401308,0.08785293884124255 +1687,-0.0,0.27383317083812636,0.6383138903206311,0.08785293884124255 +1688,-0.0,0.24667710119899264,0.6654699599597648,0.08785293884124255 +1689,-0.0,0.20225022576425927,0.7098968353944981,0.08785293884124255 +1690,0.34518032099161394,0.1739579189017309,0.39300882126541264,0.08785293884124255 +1691,0.7856502814021951,0.15384652004764443,-0.0,0.06050319855016051 +1692,0.8229833470829278,0.11987302706814244,-0.0,0.05714362584892985 +1693,0.6016946109914876,0.06624263012391189,0.244209820043358,0.08785293884124255 +1694,0.05690281785208463,0.05851621700482783,0.796728026301845,0.08785293884124255 +1695,-0.0,0.11794142378837143,0.794205637370386,0.08785293884124255 +1696,-0.0,0.07805949724721692,0.8340875639115405,0.08785293884124255 +1697,-0.0,0.06794698595900396,0.8442000751997535,0.08785293884124255 +1698,0.2635895080925931,0.024088229135967962,0.6244693239301964,0.08785293884124255 +1699,0.8214779815312853,0.025678961248720558,0.06499011837875159,0.08785293884124255 +1700,0.9628318068305114,0.008067284286102478,-0.0,0.029100908883386123 +1701,0.4905986332802747,0.0036359591148630885,0.41791246876361954,0.08785293884124255 +1702,0.03853735812204674,0.061129562618635676,0.812480140418075,0.08785293884124255 +1703,-0.0,0.10782891250015846,0.804318148658599,0.08785293884124255 +1704,-0.0,0.056698237447396285,0.8554488237113611,0.08785293884124255 +1705,-0.0,0.039995550263493974,0.8721515108952634,0.08785293884124255 +1706,0.3323847138026531,0.010680629899910322,0.569081717456194,0.08785293884124255 +1707,0.9026171847648135,0.005113067505276217,0.004416808888667814,0.08785293884124255 +1708,1.0,-0.0,-0.0,-0.0 +1709,0.6187052417250472,0.015566449960507597,0.2778753694732027,0.08785293884124255 +1710,0.04139755267016739,0.09714828260024815,0.7736012258883419,0.08785293884124255 +1711,-0.0,0.17532140356980452,0.7368256575889529,0.08785293884124255 +1712,-0.0,0.14612010692856037,0.7660269542301971,0.08785293884124255 +1713,-0.0,0.1415751580349815,0.7705719031237759,0.08785293884124255 +1714,0.3782983631277479,0.05181241738679901,0.48203628064421056,0.08785293884124255 +1715,0.9953477027459884,0.004652297254011617,-0.0,-0.0 +1716,1.0,-0.0,-0.0,-0.0 +1717,0.6598017212848861,0.0482900819942754,0.20405525787959589,0.08785293884124255 +1718,0.046967405211244456,0.1069199227214427,0.7582597332260703,0.08785293884124255 +1719,-0.0,0.20236384948659877,0.7097832116721586,0.08785293884124255 +1720,-0.0,0.21315810310884853,0.6989889580499089,0.08785293884124255 +1721,-0.0,0.2237251092864194,0.688421951872338,0.08785293884124255 +1722,0.2983634523355337,0.05238053599849637,0.5614030728247275,0.08785293884124255 +1723,0.7761664784268475,0.08646765270033782,0.0495129300315721,0.08785293884124255 +1724,0.6641672813846492,0.1307809044127317,0.11719887536137653,0.08785293884124255 +1725,0.33118042136133913,0.16827673278475733,0.41268990701266106,0.08785293884124255 +1726,0.021225654278158555,0.1860020334697149,0.704919373410884,0.08785293884124255 +1727,-0.0,0.17657126451553873,0.7355757966432187,0.08785293884124255 +1728,-0.0,0.13680296169672368,0.7753440994620338,0.08785293884124255 +1729,-0.0,0.09737553004492708,0.8147715311138304,0.08785293884124255 +1730,0.2988150620010265,0.04079091631987027,0.5725410828378606,0.08785293884124255 +1731,0.942960981548831,0.04635847871450438,-0.0,0.010680539736664607 +1732,1.0,-0.0,-0.0,-0.0 +1733,0.6558877708506157,0.005567562394634104,0.2506917279135077,0.08785293884124255 +1734,0.037031992570404286,0.03488248275821776,0.8402325858301355,0.08785293884124255 +1735,-0.0,0.19520555497921208,0.7169415061795453,0.08785293884124255 +1736,-0.0,0.19475106008985416,0.7173960010689033,0.08785293884124255 +1737,-0.0,0.1711173258432441,0.7410297353155133,0.08785293884124255 +1738,0.34518032099161394,0.02954216780826259,0.5374245723588809,0.08785293884124255 +1739,0.9462727857624446,0.03454161159119934,-0.0,0.019185602646356048 +1740,1.0,-0.0,-0.0,-0.0 +1741,0.5949204660090965,0.03215551342207044,0.28507108172759044,0.08785293884124255 +1742,0.03627930979458306,0.05578924766868051,0.8200785036954938,0.08785293884124255 +1743,-0.0,0.11066950555864526,0.8014775556001121,0.08785293884124255 +1744,-0.0,0.08942186948116408,0.8227251916775934,0.08785293884124255 +1745,-0.0,0.0554483765016621,0.8566986846570953,0.08785293884124255 +1746,0.29700862333905553,0.008067284286102478,0.6070711535335994,0.08785293884124255 +1747,0.8306607113963043,0.012044114567983981,0.06944223519446925,0.08785293884124255 +1748,0.9512404920828647,0.008635402897799835,-0.0,0.0401241050193355 +1749,0.5750496407274162,0.011021501066928736,0.3260759193644125,0.08785293884124255 +1750,0.03597823668425457,0.10442020082997432,0.7717486236445286,0.08785293884124255 +1751,-0.0,0.13543947702865003,0.7767075841301074,0.08785293884124255 +1752,-0.0,0.10646542783208482,0.8056816333266726,0.08785293884124255 +1753,-0.0,0.1108967530033242,0.8012503081554332,0.08785293884124255 +1754,0.23935312271114967,0.06476552173349877,0.608028416714109,0.08785293884124255 +1755,0.7441021921768634,0.06408377939946193,0.10396108958243212,0.08785293884124255 +1756,0.7975426692601705,0.05533475277932263,0.05926963911926432,0.08785293884124255 +1757,0.4755449777638502,0.13180351791378697,0.3047985654811203,0.08785293884124255 +1758,0.026494433708907132,0.2365645899107797,0.6490880375390706,0.08785293884124255 +1759,-0.0,0.1620274280560864,0.750119633102671,0.08785293884124255 +1760,-0.0,0.09226246253965086,0.8198845986191066,0.08785293884124255 +1761,-0.0,0.07169656879620653,0.8404504923625509,0.08785293884124255 +1762,0.27879370016418187,0.05272140716551478,0.5806319538290607,0.08785293884124255 +1763,0.8484240249056851,0.02931492036358365,0.03440811588948868,0.08785293884124255 +1764,0.9851112169948197,0.014888783005180262,-0.0,-0.0 +1765,0.5770066159445513,0.02851955430720735,0.30662089090699884,0.08785293884124255 +1766,0.025741750933085908,0.1468018492625972,0.7396034609630743,0.08785293884124255 +1767,-0.0,0.16020944849865482,0.7519376126601026,0.08785293884124255 +1768,-0.0,0.09521667932047713,0.8169303818382803,0.08785293884124255 +1769,-0.0,0.0789684870259327,0.8331785741328247,0.08785293884124255 +1770,0.2348370260562223,0.02886042547422576,0.6484496096283094,0.08785293884124255 +1771,0.7793277460852968,0.040222797708172914,0.09259651736528775,0.08785293884124255 +1772,0.8889183582448672,0.025906208693399504,-0.0,0.08517543306173325 +1773,0.49210399883191713,0.03704133348266771,0.3830017288441726,0.08785293884124255 +1774,0.021225654278158555,0.17054920723154676,0.7203721996490521,0.08785293884124255 +1775,-0.0,0.1415751580349815,0.7705719031237759,0.08785293884124255 +1776,-0.0,0.0953303030428166,0.8168167581159408,0.08785293884124255 +1777,-0.0,0.07783224980253799,0.8343148113562194,0.08785293884124255 +1778,0.2425143903695988,0.03011028641995995,0.6395223843691987,0.08785293884124255 +1779,0.8177145676521792,0.02397460541362849,0.07045788809294974,0.08785293884124255 +1780,0.9217353272706725,0.042040777265604456,-0.0,0.036223895463723066 +1781,0.5539745230044218,0.08010472424932741,0.27806781390500823,0.08785293884124255 +1782,0.027397653039892604,0.15146042187851552,0.7332889862403493,0.08785293884124255 +1783,-0.0,0.1308945281350712,0.7812525330236862,0.08785293884124255 +1784,-0.0,0.08476329686524575,0.8273837642935117,0.08785293884124255 +1785,-0.0,0.0602205728399199,0.8519264883188375,0.08785293884124255 +1786,0.21707371254684138,0.042268024710283396,0.6528053239016327,0.08785293884124255 +1787,0.7132421983681931,0.07453716185469332,0.12436770093587103,0.08785293884124255 +1788,0.770295552775442,0.05510750533464368,0.0867440030486717,0.08785293884124255 +1789,0.45386771382019886,0.06272029473138828,0.3955590526071703,0.08785293884124255 +1790,0.016559021068066955,0.13930268358819206,0.7562853565024984,0.08785293884124255 +1791,-0.0,0.13680296169672368,0.7753440994620338,0.08785293884124255 +1792,-0.0,0.12271362012662923,0.7894334410321282,0.08785293884124255 +1793,-0.0,0.13259888397016326,0.7795481771885941,0.08785293884124255 +1794,0.28346033337427345,0.14168878175732097,0.48699794602716306,0.08785293884124255 +1795,0.9425093718833384,0.05749062811666161,-0.0,-0.0 +1796,1.0,-0.0,-0.0,-0.0 +1797,0.6027483668776372,0.1870246469707701,0.12237404731035008,0.08785293884124255 +1798,0.023032092940129496,0.3115562466548309,0.577558721563797,0.08785293884124255 +1799,-0.0,0.29439906458157067,0.6177479965771868,0.08785293884124255 +1800,-0.0,0.2816732076795499,0.6304738534792076,0.08785293884124255 +1801,-0.0,0.27701463506363155,0.6351324260951259,0.08785293884124255 +1802,0.2315252218426089,0.2089540253822881,0.47166781393386037,0.08785293884124255 +1803,0.7663816023411716,0.19725078198132254,-0.0,0.036367615677505905 +1804,0.8714561178458147,0.12854388215418533,-0.0,-0.0 +1805,0.4123196245948672,0.11669156284263724,0.38313587372125296,0.08785293884124255 +1806,0.0106880954166614,0.16191380433374689,0.7395451614083491,0.08785293884124255 +1807,-0.0,0.14475662226048672,0.7673904388982707,0.08785293884124255 +1808,-0.0,0.10112511288212964,0.8110219482766278,0.08785293884124255 +1809,-0.0,0.07590064652276697,0.8362464146359905,0.08785293884124255 +1810,0.19042874228277,0.04601760754748596,0.6757007113285014,0.08785293884124255 +1811,0.5852861264785848,0.04169990609858605,0.28516102858158654,0.08785293884124255 +1812,0.5837807609269423,0.03317812692312568,0.2951881733086894,0.08785293884124255 +1813,0.3518039294188407,0.02942854408592312,0.5309145876539936,0.08785293884124255 +1814,0.01249453407863234,0.04988081410702799,0.8497717129730972,0.08785293884124255 +1815,-0.0,0.05851621700482783,0.8536308441539296,0.08785293884124255 +1816,-0.0,0.05181241738679901,0.8603346437719585,0.08785293884124255 +1817,-0.0,0.053516773221891085,0.8586302879368664,0.08785293884124255 +1818,0.07858008179573593,0.04624485499216491,0.7873221243708566,0.08785293884124255 +1819,0.2938473556806064,0.06749249106964608,0.550807214408505,0.08785293884124255 +1820,0.3346427621301168,0.08408155453120891,0.49342274449743173,0.08785293884124255 +1821,0.19193410783441248,0.09839814354598232,0.6218148097783627,0.08785293884124255 +1822,0.007225754647883762,0.12191825407025293,0.7830030524406207,0.08785293884124255 +1823,-0.0,0.13032640952337382,0.7818206516353836,0.08785293884124255 +1824,-0.0,0.13737108030842105,0.7747759808503364,0.08785293884124255 +1825,-0.0,0.12816755879892386,0.7839795023598336,0.08785293884124255 +1826,0.06292428005865443,0.10635180410974533,0.7428709769903576,0.08785293884124255 +1827,0.22174034575693297,0.16634512950498628,0.5240615858968383,0.08785293884124255 +1828,0.2789442367193461,0.2025910969312777,0.4306117275081337,0.08785293884124255 +1829,0.13608504586847753,0.15441463865934177,0.621647376630938,0.08785293884124255 +1830,0.004365560099763107,0.15293753026892865,0.7548439707900657,0.08785293884124255 +1831,-0.0,0.16725411928370207,0.7448929418750554,0.08785293884124255 +1832,-0.0,0.1638454076135179,0.7483016535452396,0.08785293884124255 +1833,-0.0,0.13555310075098953,0.7765939604077678,0.08785293884124255 +1834,0.10522505205980731,0.12203187779259239,0.6848901313063578,0.08785293884124255 +1835,0.37107260847986406,0.14009804964456837,0.40097640303432497,0.08785293884124255 +1836,0.3853735812204674,0.09203521509497192,0.4347382648433181,0.08785293884124255 +1837,0.23303058739425137,0.04135903493156763,0.6377574388329383,0.08785293884124255 +1838,0.005419315985912822,0.038177570706062425,0.8685501744667822,0.08785293884124255 +1839,-0.0,0.05476663416762527,0.8573804269911321,0.08785293884124255 +1840,-0.0,0.051357922497441116,0.8607891386613163,0.08785293884124255 +1841,-0.0,0.048517329438954336,0.8636297317198031,0.08785293884124255 +1842,0.18681586495882813,0.01977052768706804,0.7055606685128613,0.08785293884124255 +1843,0.568124959189861,0.03317812692312568,0.31084397504577077,0.08785293884124255 +1844,0.5241682850819014,0.022156625856196946,0.3658221502206591,0.08785293884124255 +1845,0.2890301859153505,0.010794253622249794,0.612322621621157,0.08785293884124255 +1846,0.006774144982391027,0.02329286307959166,0.8820800530967747,0.08785293884124255 +1847,-0.0,0.022838368190233774,0.8893086929685237,0.08785293884124255 +1848,-0.0,0.023065615634912714,0.8890814455238447,0.08785293884124255 +1849,-0.0,0.02556533752638109,0.8865817236323763,0.08785293884124255 +1850,0.17582669643183824,0.00749916567440512,0.7288211990525141,0.08785293884124255 +1851,0.648662016202732,0.029087672918904708,0.23439737203712074,0.08785293884124255 +1852,0.6632640620536637,0.014884707626470768,0.23399829147862294,0.08785293884124255 +1853,0.3963627497474572,0.007840036841423535,0.5079442745698767,0.08785293884124255 +1854,0.007526827758212253,0.03158739481037308,0.8730328385901721,0.08785293884124255 +1855,-0.0,0.06147043378565409,0.8506766273731033,0.08785293884124255 +1856,-0.0,0.05647099000271734,0.85567607115604,0.08785293884124255 +1857,-0.0,0.0382911944284019,0.8738558667303555,0.08785293884124255 +1858,0.2815033581571383,0.010567006177570849,0.6200766968240483,0.08785293884124255 +1859,0.9497351265312222,0.03579147253693352,-0.0,0.014473400931844349 +1860,0.9407029332213674,0.01817979557431544,-0.0,0.041117271204317074 +1861,0.5607486679868129,0.012384985735002395,0.3390134074369422,0.08785293884124255 +1862,0.00978487608567593,0.0594252067835436,0.842936978289538,0.08785293884124255 +1863,-0.0,0.11157849533736101,0.8005685658213965,0.08785293884124255 +1864,-0.0,0.10203410266084542,0.810112958497912,0.08785293884124255 +1865,-0.0,0.07760500235785904,0.8345420588008984,0.08785293884124255 +1866,0.3135676444071225,0.0179525481296365,0.5806268686219984,0.08785293884124255 +1867,0.9611759047237047,0.03882409527629527,-0.0,-0.0 +1868,0.9973046779631235,0.002695322036876502,-0.0,-0.0 +1869,0.5765550062790586,0.09112622531625615,0.24446582956344265,0.08785293884124255 +1870,0.00797843742370499,0.22883817679169563,0.6753304469433569,0.08785293884124255 +1871,-0.0,0.27735550623065,0.6347915549281075,0.08785293884124255 +1872,-0.0,0.23190601729486135,0.680241043863896,0.08785293884124255 +1873,-0.0,0.21918016039284052,0.6929669007659169,0.08785293884124255 +1874,0.2949011115667561,0.1148735832852057,0.5023723663067956,0.08785293884124255 +1875,0.9522942479690144,0.04770575203098559,-0.0,-0.0 +1876,0.9197783520535373,0.08022164794646269,-0.0,-0.0 +1877,0.4326420595420403,0.36882260271392453,0.11068239890279259,0.08785293884124255 +1878,0.004516096654927352,0.398819265411545,0.5088116990922851,0.08785293884124255 +1879,-0.0,0.36109618959484047,0.551050871563917,0.08785293884124255 +1880,-0.0,0.2508811789255531,0.6612658822332043,0.08785293884124255 +1881,-0.0,0.17191269189962038,0.7402343692591371,0.08785293884124255 +1882,0.1592676753637713,0.11612344423093988,0.6367559415640462,0.08785293884124255 +1883,0.14481616606800374,0.2165668147790327,0.550764080311721,0.08785293884124255 +1884,0.10883792938374919,0.25974382926803186,0.5435653025069764,0.08785293884124255 +1885,-0.0,0.209408520271646,0.7027385408871114,0.08785293884124255 +1886,-0.0,0.3280316863940543,0.5841153747647032,0.08785293884124255 +1887,-0.0,0.37086782971603505,0.5412792314427224,0.08785293884124255 +1888,-0.0,0.4294976704432023,0.4826493907155551,0.08785293884124255 +1889,-0.0,0.41893066426563147,0.493216396893126,0.08785293884124255 +1890,0.1326227050996999,0.49028636189481956,0.289237994164238,0.08785293884124255 +1891,0.48246965930140545,0.5175303406985945,-0.0,-0.0 +1892,0.574296957951595,0.42570304204840503,-0.0,-0.0 +1893,0.4151798191429879,0.40609118364127117,0.09087605837449837,0.08785293884124255 +1894,0.003161267658449146,0.20929489654930652,0.6996908969510017,0.08785293884124255 +1895,-0.0,0.2555397515414714,0.656607309617286,0.08785293884124255 +1896,-0.0,0.3051933182038205,0.606953742954937,0.08785293884124255 +1897,-0.0,0.2938309459698733,0.6183161151888841,0.08785293884124255 +1898,0.23935312271114967,0.16259554666778372,0.5101983917798241,0.08785293884124255 +1899,0.8965957225582436,0.1034042774417564,-0.0,-0.0 +1900,1.0,-0.0,-0.0,-0.0 +1901,0.5420821351464464,0.21429434033224326,0.15577058568006774,0.08785293884124255 +1902,0.0028601945481206564,0.2634934121052344,0.6457934545054024,0.08785293884124255 +1903,-0.0,0.4064320548082896,0.5057150063504678,0.08785293884124255 +1904,-0.0,0.33882594001630406,0.5733211211424534,0.08785293884124255 +1905,-0.0,0.2825821974582656,0.6295648637004918,0.08785293884124255 +1906,0.1406011425234049,0.1827069455218702,0.5888389731134824,0.08785293884124255 +1907,0.5359101363847124,0.22429322789811676,0.15194369687592824,0.08785293884124255 +1908,0.5569852541077067,0.23440573918632973,0.12075606786472105,0.08785293884124255 +1909,0.2693098971888344,0.1348713584169527,0.5079658055529703,0.08785293884124255 +1910,0.00045160966549273514,0.1104422581139663,0.8012531933792983,0.08785293884124255 +1911,-0.0,0.11794142378837143,0.794205637370386,0.08785293884124255 +1912,-0.0,0.11510083072988464,0.7970462304288728,0.08785293884124255 +1913,-0.0,0.08828563225776936,0.823861428900988,0.08785293884124255 +1914,0.12208514623820275,0.06226579984203039,0.7277961150785243,0.08785293884124255 +1915,0.4391151314141029,0.06908322318239868,0.40394870656225584,0.08785293884124255 +1916,0.5083619467896556,0.0921488388173114,0.3116362755517904,0.08785293884124255 +1917,0.3540619777463044,0.08703577131203517,0.4710493121004178,0.08785293884124255 +1918,0.0012042924413139607,0.05272140716551478,0.8582213615519287,0.08785293884124255 +1919,-0.0,0.054084891833588444,0.858062169325169,0.08785293884124255 +1920,-0.0,0.05158516994212007,0.8605618912166374,0.08785293884124255 +1921,-0.0,0.038404818150741364,0.8737422430080161,0.08785293884124255 +1922,0.24401975592124125,0.01670268718390231,0.6514246180536138,0.08785293884124255 +1923,0.8696496791838437,0.01806617185197597,0.02443121012293771,0.08785293884124255 +1924,0.944165273990145,0.013748470403076052,-0.0,0.04208625560677892 +1925,0.5374155019363549,0.003976830281881503,0.3707547289405211,0.08785293884124255 +1926,0.001956975217135186,0.019202409075370682,0.8909876768662516,0.08785293884124255 +1927,-0.0,0.09123984903859562,0.8209072121201618,0.08785293884124255 +1928,-0.0,0.08237719869611684,0.8297698624626406,0.08785293884124255 +1929,-0.0,0.06442465056648035,0.847722410592277,0.08785293884124255 +1930,0.24146063448344907,0.026928822194454746,0.6437576044808537,0.08785293884124255 +1931,0.7820374040782531,0.031473771088033606,0.09863588599247076,0.08785293884124255 +1932,0.9187245961673876,0.03170101853271255,-0.0,0.04957438529989988 +1933,0.49451258371454504,0.02545171380404162,0.39218276364017074,0.08785293884124255 +1934,0.0010537558861497154,0.06999221296111445,0.8411010923114932,0.08785293884124255 +1935,-0.0,0.09044448298221933,0.8217025781765381,0.08785293884124255 +1936,-0.0,0.06840148084836184,0.8437455803103956,0.08785293884124255 +1937,-0.0,0.06522001662285665,0.8469270445359007,0.08785293884124255 +1938,0.2241489306395609,0.038745689317759784,0.6492524412014368,0.08785293884124255 +1939,0.8576067547707041,0.0634020370654251,-0.0,0.07899120816387073 +1940,0.9646382454924824,0.03536175450751755,-0.0,-0.0 +1941,0.5271790161851863,0.10737441761080058,0.2775936273627706,0.08785293884124255 +1942,0.0012042924413139607,0.13589397191800792,0.7750487967994356,0.08785293884124255 +1943,-0.0,0.19088785353031215,0.7212592076284453,0.08785293884124255 +1944,-0.0,0.2116809947184354,0.700466066440322,0.08785293884124255 +1945,-0.0,0.2027047206536172,0.7094423405051402,0.08785293884124255 +1946,0.20337488602689507,0.11544170189690305,0.5933304732349594,0.08785293884124255 +1947,0.581673249154643,0.15805059777420488,0.17242321422990958,0.08785293884124255 +1948,0.4743406853225362,0.2445182504745427,0.19328812536167855,0.08785293884124255 +1949,0.2099984944541219,0.2701972117232633,0.43195135498137227,0.08785293884124255 +1950,-0.0,0.2912176003560655,0.620929460802692,0.08785293884124255 +1951,-0.0,0.28053697045615517,0.6316100907026023,0.08785293884124255 +1952,-0.0,0.23440573918632973,0.6777413219724278,0.08785293884124255 +1953,-0.0,0.20190935459724088,0.7102377065615165,0.08785293884124255 +1954,0.2288155638496525,0.19634179220260678,0.4869897051064982,0.08785293884124255 +1955,0.7442527287320276,0.25574727126797236,-0.0,-0.0 +1956,0.6742532305806537,0.22361148556407995,0.014282345014023826,0.08785293884124255 +1957,0.3527071487498262,0.1463473543732393,0.4130925580356919,0.08785293884124255 +1958,-0.0,0.0829453173078142,0.8292017438509433,0.08785293884124255 +1959,-0.0,0.07567339907808804,0.8364736620806694,0.08785293884124255 +1960,-0.0,0.07487803302171173,0.8372690281370457,0.08785293884124255 +1961,-0.0,0.06385653195478298,0.8482905292039744,0.08785293884124255 +1962,0.15475157870884393,0.03226913714440991,0.7251263453055036,0.08785293884124255 +1963,0.547953060797852,0.06010694911758043,0.304087051243325,0.08785293884124255 +1964,0.5475014511323594,0.061356810063314615,0.30328879996308344,0.08785293884124255 +1965,0.3460835403225994,0.049653566662349054,0.516409954173809,0.08785293884124255 +1966,-0.0,0.047267468493220145,0.8648795926655373,0.08785293884124255 +1967,-0.0,0.05851621700482783,0.8536308441539296,0.08785293884124255 +1968,-0.0,0.09839814354598232,0.8137489176127751,0.08785293884124255 +1969,-0.0,0.14123428686796308,0.7709127742907944,0.08785293884124255 +1970,0.19765449693065376,0.10555643805336903,0.6089361261747347,0.08785293884124255 +1971,0.6987906890724256,0.2229297432300431,-0.0,0.0782795676975313 +1972,0.6062107076464149,0.30462519959212314,0.0013111539202193129,0.08785293884124255 +1973,0.3630941710561591,0.32212325283240173,0.2269296372701966,0.08785293884124255 +1974,-0.0,0.27224243872537374,0.6399046224333838,0.08785293884124255 +1975,-0.0,0.2995121320868469,0.6126349290719105,0.08785293884124255 +1976,-0.0,0.33632621812483565,0.5758208430339218,0.08785293884124255 +1977,-0.0,0.35871009142571153,0.5534369697330459,0.08785293884124255 +1978,0.1679987955632975,0.37279943299580603,0.37134883259965396,0.08785293884124255 +1979,0.510469458561955,0.489530541438045,-0.0,-0.0 +1980,0.5052006791312065,0.4947993208687935,-0.0,-0.0 +1981,0.2741270669540903,0.6741295446400846,-0.0,0.05174338840582515 +1982,-0.0,0.5081252863021166,0.4040217748566408,0.08785293884124255 +1983,-0.0,0.5055119406883087,0.4066351204704487,0.08785293884124255 +1984,-0.0,0.5532339040708868,0.35891315708787064,0.08785293884124255 +1985,-0.0,0.5911842273322703,0.32096283382648716,0.08785293884124255 +1986,0.1482785068367814,0.6306116589840669,0.13325689533790913,0.08785293884124255 +1987,0.47012566177793735,0.5298743382220626,-0.0,-0.0 +1988,0.4836739517427194,0.5163260482572807,-0.0,-0.0 +1989,0.3350943717956095,0.6525410373955849,-0.0,0.012364590808805587 +1990,-0.0,0.49005911445014066,0.4220879467086167,0.08785293884124255 +1991,-0.0,0.3458706108013513,0.5662764503574061,0.08785293884124255 +1992,-0.0,0.24338201325114797,0.6687650479076095,0.08785293884124255 +1993,-0.0,0.2078177881588934,0.704329272999864,0.08785293884124255 +1994,0.14300972740603282,0.14850620509768928,0.6206311286550353,0.08785293884124255 +1995,0.5217597001992734,0.18009359990806234,0.2102937610514216,0.08785293884124255 +1996,0.5910065155748261,0.1760031459038414,0.14513739968008998,0.08785293884124255 +1997,0.33539544490593803,0.1731625528453546,0.40358906340746475,0.08785293884124255 +1998,-0.0,0.15521000471571808,0.7569370564430393,0.08785293884124255 +1999,-0.0,0.10510194316401114,0.8070451179947463,0.08785293884124255 +2000,-0.0,0.08589953408864047,0.826247527070117,0.08785293884124255 +2001,-0.0,0.09362594720772453,0.8185211139510329,0.08785293884124255 +2002,0.18485888974169296,0.050789803885743764,0.6764983675313208,0.08785293884124255 +2003,0.6647694276053062,0.07442353813235385,0.1729540954210974,0.08785293884124255 +2004,0.6281890447003947,0.07465078557703278,0.20930723088132996,0.08785293884124255 +2005,0.36264256139066636,0.08237719869611684,0.4671273010719743,0.08785293884124255 +2006,-0.0,0.0977164012119455,0.814430659946812,0.08785293884124255 +2007,-0.0,0.08374068336419051,0.8284063777945669,0.08785293884124255 +2008,-0.0,0.06476552173349877,0.8473815394252586,0.08785293884124255 +2009,-0.0,0.0602205728399199,0.8519264883188375,0.08785293884124255 +2010,0.23468648950105808,0.023520110524270603,0.6539404611334287,0.08785293884124255 +2011,0.7695428699996207,0.07056033157281182,0.07204385958632488,0.08785293884124255 +2012,0.7537365317073751,0.06987858923877498,0.08853194021260737,0.08785293884124255 +2013,0.43505064442466823,0.09317145231836664,0.38392496441572255,0.08785293884124255 +2014,-0.0,0.20134123598554352,0.7108058251732139,0.08785293884124255 +2015,-0.0,0.19225133819838577,0.7198957229603716,0.08785293884124255 +2016,-0.0,0.13737108030842105,0.7747759808503364,0.08785293884124255 +2017,-0.0,0.11680518656497671,0.7953418745937807,0.08785293884124255 +2018,0.2258048327463676,0.044086004267714946,0.642256224144675,0.08785293884124255 +2019,0.8311123210617971,0.12919017229997912,-0.0,0.0396975066382238 +2020,0.733414096760202,0.1323716365254843,0.04636132787307114,0.08785293884124255 +2021,0.4016315291782058,0.1830478166888886,0.327467715291663,0.08785293884124255 +2022,-0.0,0.24429100302986376,0.6678560581288937,0.08785293884124255 +2023,-0.0,0.2245204753427957,0.6876265858159617,0.08785293884124255 +2024,-0.0,0.189410745139899,0.7227363160188585,0.08785293884124255 +2025,-0.0,0.1568007368284707,0.7553463243302867,0.08785293884124255 +2026,0.18681586495882813,0.09112622531625615,0.6342049708836732,0.08785293884124255 +2027,0.6914143978693775,0.13316700258186062,0.08756566070751927,0.08785293884124255 +2028,0.7483172157214623,0.12862205368828175,0.035207791749013465,0.08785293884124255 +2029,0.38462089844464614,0.10771528877781898,0.41981087393629224,0.08785293884124255 +2030,-0.0,0.1771393831272361,0.7350076780315213,0.08785293884124255 +2031,-0.0,0.18906987397288058,0.7230771871858769,0.08785293884124255 +2032,-0.0,0.14464299853814722,0.7675040626206102,0.08785293884124255 +2033,-0.0,0.10987413950226894,0.8022729216564884,0.08785293884124255 +2034,0.1309668029928932,0.05056255644106482,0.7306177017247995,0.08785293884124255 +2035,0.6915649344245418,0.09839814354598232,0.12218398318823333,0.08785293884124255 +2036,0.9253482045946144,0.07465179540538558,-0.0,-0.0 +2037,0.3737822664728205,0.08192270380675895,0.456442090879178,0.08785293884124255 +2038,-0.0,0.2946263120262496,0.6175207491325079,0.08785293884124255 +2039,-0.0,0.24417737930752428,0.6679696818512332,0.08785293884124255 +2040,-0.0,0.15498275727103913,0.7571643038877183,0.08785293884124255 +2041,-0.0,0.115668949341582,0.7964781118171754,0.08785293884124255 +2042,0.13277324165486415,0.05953883050588307,0.7198349889980102,0.08785293884124255 +2043,0.5619529604281268,0.06226579984203039,0.2879283008886002,0.08785293884124255 +2044,0.5568347175525425,0.07226468740790389,0.28304765619831096,0.08785293884124255 +2045,0.2267080520773531,0.10112511288212964,0.5843138961992747,0.08785293884124255 +2046,-0.0,0.2537217719840399,0.6584252891747175,0.08785293884124255 +2047,-0.0,0.27269693361473163,0.6394501275440259,0.08785293884124255 +2048,-0.0,0.2728105573370711,0.6393365038216863,0.08785293884124255 +2049,-0.0,0.21531695383329852,0.6968301073254589,0.08785293884124255 +2050,0.09333266420183194,0.13021278580103435,0.6886016111558911,0.08785293884124255 +2051,0.3463846134329279,0.13987080219988943,0.42589164552594005,0.08785293884124255 +2052,0.37799729001741933,0.2106583812173802,0.3234913899239579,0.08785293884124255 +2053,0.24733156013485466,0.23190601729486135,0.43290948372904137,0.08785293884124255 +2054,-0.0,0.33632621812483565,0.5758208430339218,0.08785293884124255 +2055,-0.0,0.38098034100424794,0.5311667201545095,0.08785293884124255 +2056,-0.0,0.3434845126322224,0.5686625485265351,0.08785293884124255 +2057,-0.0,0.31882816488455706,0.5933188962742004,0.08785293884124255 +2058,0.14045060596824063,0.23417849174165078,0.537517963448866,0.08785293884124255 +2059,0.49737277826266574,0.5026272217373342,-0.0,-0.0 +2060,0.506706044682849,0.49329395531715103,-0.0,-0.0 +2061,0.2644927274235786,0.5001716257383536,0.14748270799682522,0.08785293884124255 +2062,-0.0,0.3850707950084689,0.5270762661502886,0.08785293884124255 +2063,-0.0,0.30507969448148103,0.6070673666772763,0.08785293884124255 +2064,-0.0,0.21315810310884853,0.6989889580499089,0.08785293884124255 +2065,-0.0,0.18054809479742023,0.7315989663613371,0.08785293884124255 +2066,0.06352642627931142,0.18486579624632016,0.6637548386331259,0.08785293884124255 +2067,0.321245008720499,0.33450823856740414,0.25639381387085436,0.08785293884124255 +2068,0.3447287113261212,0.4139312204826947,0.15348712934994152,0.08785293884124255 +2069,0.17386972121470307,0.35018831225025115,0.3880890276938032,0.08785293884124255 +2070,-0.0,0.4435870120132968,0.4685600491454607,0.08785293884124255 +2071,-0.0,0.44847283207389405,0.46367422908486344,0.08785293884124255 +2072,-0.0,0.47835587104917504,0.43379119010958234,0.08785293884124255 +2073,-0.0,0.42847505694214705,0.4836720042166104,0.08785293884124255 +2074,0.11982709791073907,0.394956058852003,0.3973639043960153,0.08785293884124255 +2075,0.4213518179047219,0.578648182095278,-0.0,-0.0 +2076,0.342169589888329,0.4784694947715145,0.09150797649891397,0.08785293884124255 +2077,0.1019132478461939,0.4357469751718733,0.3744868381406903,0.08785293884124255 +2078,-0.0,0.43052028394425756,0.48162677721449987,0.08785293884124255 +2079,-0.0,0.4363150937835706,0.47583196737518685,0.08785293884124255 +2080,-0.0,0.36257329798525356,0.5495737631735038,0.08785293884124255 +2081,-0.0,0.26497052049564757,0.6471765406631098,0.08785293884124255 +2082,0.15159031105039478,0.17168544445494147,0.5888713056534212,0.08785293884124255 +2083,0.6545329418541376,0.2217935060066484,0.035820613297971496,0.08785293884124255 +2084,0.5992860261088596,0.17338980029003354,0.13947123475986423,0.08785293884124255 +2085,0.29113769768764997,0.1188504135670872,0.5021589499040203,0.08785293884124255 +2086,-0.0,0.10021612310341388,0.8119309380553436,0.08785293884124255 +2087,-0.0,0.1468018492625972,0.7653452118961602,0.08785293884124255 +2088,-0.0,0.15407376749232338,0.7580732936664341,0.08785293884124255 +2089,-0.0,0.13259888397016326,0.7795481771885941,0.08785293884124255 +2090,0.14346133707152553,0.039995550263493974,0.7286901738237379,0.08785293884124255 +2091,0.5301897472884711,0.04169990609858605,0.3402574077717002,0.08785293884124255 +2092,0.4455882032861654,0.06237942356436986,0.4041794343082222,0.08785293884124255 +2093,0.2519981933449462,0.0686287282930408,0.5915201395207704,0.08785293884124255 +2094,-0.0,0.12373623362768447,0.7884108275310729,0.08785293884124255 +2095,-0.0,0.1439612562041104,0.7681858049546471,0.08785293884124255 +2096,-0.0,0.15373289632530496,0.7584141648334525,0.08785293884124255 +2097,-0.0,0.16929934628581256,0.7428477148729449,0.08785293884124255 +2098,0.10447236928398608,0.12100926429153716,0.6866654275832342,0.08785293884124255 +2099,0.43896459485893863,0.2146352114992617,0.2585472548005571,0.08785293884124255 +2100,0.4719321004399083,0.297126033917718,0.14308892680113117,0.08785293884124255 +2101,0.26705184886137073,0.22963354284807191,0.41546166944931473,0.08785293884124255 +2102,-0.0,0.19850064292705671,0.7136464182317007,0.08785293884124255 +2103,-0.0,0.16748136672838101,0.7446656944303764,0.08785293884124255 +2104,-0.0,0.1304400332457133,0.7817070279130441,0.08785293884124255 +2105,-0.0,0.10192047893850595,0.8102265822202515,0.08785293884124255 +2106,0.14511723917833225,0.06737886734730661,0.6996509546331187,0.08785293884124255 +2107,0.6635651351639923,0.0853314154769431,0.16325051051782202,0.08785293884124255 +2108,0.5977806605572171,0.0810137140280432,0.23335268657349717,0.08785293884124255 +2109,0.29475057501159185,0.03692770976032824,0.5804687763868374,0.08785293884124255 +2110,-0.0,0.06522001662285665,0.8469270445359007,0.08785293884124255 +2111,-0.0,0.19145597214200952,0.7206910890167479,0.08785293884124255 +2112,-0.0,0.18418405391228332,0.7279630072464741,0.08785293884124255 +2113,-0.0,0.19566004986856994,0.7164870112901875,0.08785293884124255 +2114,0.08369832467132025,0.17441241379108877,0.6540363226963485,0.08785293884124255 +2115,0.34758890587424185,0.2149760826662801,0.3495820726182355,0.08785293884124255 +2116,0.34427710166062847,0.21588507244499588,0.3519848870531331,0.08785293884124255 +2117,0.1326227050996999,0.1169188102873162,0.6626055457717414,0.08785293884124255 +2118,-0.0,0.15441463865934177,0.7577324224994156,0.08785293884124255 +2119,-0.0,0.12953104346699754,0.7826160176917599,0.08785293884124255 +2120,-0.0,0.10669267527676375,0.8054543858819937,0.08785293884124255 +2121,-0.0,0.09794364865662444,0.814203412502133,0.08785293884124255 +2122,0.06683823049292481,0.06306116589840668,0.7822476647674259,0.08785293884124255 +2123,0.2792453098296746,0.061129562618635676,0.5717721887104472,0.08785293884124255 +2124,0.3817607038965255,0.05294865461019373,0.4774377026520382,0.08785293884124255 +2125,0.1847083531865287,0.051357922497441116,0.6760807854747877,0.08785293884124255 +2126,-0.0,0.0702194604057934,0.841927600752964,0.08785293884124255 +2127,-0.0,0.131576270469108,0.7805707906896494,0.08785293884124255 +2128,-0.0,0.15725523171782857,0.7548918294409288,0.08785293884124255 +2129,-0.0,0.17020833606452831,0.7419387250942291,0.08785293884124255 +2130,0.11937548824524633,0.12271362012662923,0.6700579527868818,0.08785293884124255 +2131,0.5378671116018476,0.2037273341546724,0.17055261540223743,0.08785293884124255 +2132,0.5562325713318855,0.2665612526084002,0.08935323721847167,0.08785293884124255 +2133,0.26660023919587805,0.2396324304139454,0.40591439154893394,0.08785293884124255 +2134,-0.0,0.21134012355141701,0.7008069376073405,0.08785293884124255 +2135,-0.0,0.18861537908352272,0.7235316820752347,0.08785293884124255 +2136,-0.0,0.1520285404902129,0.7601185206685446,0.08785293884124255 +2137,-0.0,0.11532807817456359,0.7968189829841938,0.08785293884124255 +2138,0.11124651426637709,0.05851621700482783,0.7423843298875525,0.08785293884124255 +2139,0.6004903185501735,0.037609452094365066,0.27404729051421883,0.08785293884124255 +2140,0.4907491698354389,0.052039664831477955,0.3693582264918406,0.08785293884124255 +2141,0.21857907809848384,0.057266356059093644,0.6363016270011799,0.08785293884124255 +2142,-0.0,0.07749137863551957,0.8346556825232379,0.08785293884124255 +2143,-0.0,0.06794698595900396,0.8442000751997535,0.08785293884124255 +2144,-0.0,0.07067395529515128,0.8414731058636061,0.08785293884124255 +2145,-0.0,0.06760611479198554,0.8445409463667719,0.08785293884124255 +2146,0.11305295292834805,0.026474327305096863,0.7726197809253125,0.08785293884124255 +2147,0.5863398823647346,0.029769415252941536,0.29603776354108136,0.08785293884124255 +2148,0.39410470141999354,0.042268024710283396,0.47577433502848054,0.08785293884124255 +2149,0.20217059358558112,0.07567339907808804,0.6343030684950883,0.08785293884124255 +2150,-0.0,0.1499833134881024,0.7621637476706551,0.08785293884124255 +2151,-0.0,0.14464299853814722,0.7675040626206102,0.08785293884124255 +2152,-0.0,0.10407932966295591,0.8080677314958016,0.08785293884124255 +2153,-0.0,0.0853314154769431,0.8268156456818143,0.08785293884124255 +2154,0.14722475095063167,0.050789803885743764,0.714132506322382,0.08785293884124255 +2155,0.7578010186968096,0.0749916567440512,0.07935438571789667,0.08785293884124255 +2156,0.5640604722004262,0.07351454835363806,0.2745720406046932,0.08785293884124255 +2157,0.3149224734036007,0.09623929282153237,0.5009852949336244,0.08785293884124255 +2158,-0.0,0.14612010692856037,0.7660269542301971,0.08785293884124255 +2159,-0.0,0.11498720700754517,0.7971598541512123,0.08785293884124255 +2160,-0.0,0.07919573447061164,0.8329513266881458,0.08785293884124255 +2161,-0.0,0.058402593282488355,0.8537444678762691,0.08785293884124255 +2162,0.14586992195415346,0.031814642255052025,0.7344624969495519,0.08785293884124255 +2163,0.7466613136146555,0.05044893271872535,0.11503681482537666,0.08785293884124255 +2164,0.6656726469362917,0.05794809839313047,0.18852631582933532,0.08785293884124255 +2165,0.3269653978167403,0.10192047893850595,0.4832611844035112,0.08785293884124255 +2166,-0.0,0.2240659804534378,0.6880810807053196,0.08785293884124255 +2167,-0.0,0.1706628309538862,0.7414842302048712,0.08785293884124255 +2168,-0.0,0.13384874491589743,0.77829831624286,0.08785293884124255 +2169,-0.0,0.11907766101176614,0.7930694001469913,0.08785293884124255 +2170,0.15159031105039478,0.06624263012391189,0.6943141199844508,0.08785293884124255 +2171,0.8034135949115758,0.16020944849865482,-0.0,0.03637695658976936 +2172,0.622318119048989,0.1730489291230151,0.11678001298675335,0.08785293884124255 +2173,0.3072451090902242,0.19804614803769885,0.40685580403083443,0.08785293884124255 +2174,-0.0,0.30769304009528886,0.6044540210634686,0.08785293884124255 +2175,-0.0,0.25428989059573726,0.6578571705630202,0.08785293884124255 +2176,-0.0,0.18554753858035697,0.7265995225784004,0.08785293884124255 +2177,-0.0,0.1312353993020896,0.7809116618566678,0.08785293884124255 +2178,0.14737528750579593,0.06249304728670933,0.7022787263662522,0.08785293884124255 +2179,0.8348757349409032,0.13691658541906318,-0.0,0.028207679640033678 +2180,0.8398434412613232,0.13180351791378697,-0.0,0.028353040824889875 +2181,0.35421251430146866,0.15225578793489183,0.40567875892239696,0.08785293884124255 +2182,-0.0,0.2025910969312777,0.7095559642274798,0.08785293884124255 +2183,-0.0,0.15032418465512082,0.7618228765036366,0.08785293884124255 +2184,-0.0,0.10510194316401114,0.8070451179947463,0.08785293884124255 +2185,-0.0,0.08874012714712726,0.8234069340116301,0.08785293884124255 +2186,0.17462240399052428,0.05976607795056202,0.6777585792176711,0.08785293884124255 +2187,0.928810545363392,0.07118945463660797,-0.0,-0.0 +2188,0.9948960930804958,0.005103906919504242,-0.0,-0.0 +2189,0.40825513760543264,0.06658350129093031,0.4373084222623945,0.08785293884124255 +2190,-0.0,0.09305782859602717,0.8190892325627303,0.08785293884124255 +2191,-0.0,0.07760500235785904,0.8345420588008984,0.08785293884124255 +2192,-0.0,0.061356810063314615,0.8507902510954428,0.08785293884124255 +2193,-0.0,0.0634020370654251,0.8487450240933323,0.08785293884124255 +2194,0.15008494549875231,0.0351097302028967,0.7269523854571085,0.08785293884124255 +2195,0.8500799270124919,0.02851955430720735,0.03354757983905822,0.08785293884124255 +2196,0.9697564883680667,0.030243511631933284,-0.0,-0.0 +2197,0.38522304466530316,0.044994994046430724,0.4819290224470236,0.08785293884124255 +2198,-0.0,0.13509860586163164,0.7770484552971257,0.08785293884124255 +2199,-0.0,0.14339313759241307,0.7687539235663443,0.08785293884124255 +2200,-0.0,0.11816867123305036,0.7939783899257071,0.08785293884124255 +2201,-0.0,0.08680852386735623,0.8253385372914012,0.08785293884124255 +2202,0.13412807065134236,0.05499388161230421,0.7230251088951108,0.08785293884124255 +2203,0.8014566196944407,0.02999666269762048,0.08069377876669627,0.08785293884124255 +2204,0.8643808997530953,0.04010917398583344,0.00765698741982869,0.08785293884124255 +2205,0.289331259025679,0.023633734246610073,0.5991820678864683,0.08785293884124255 +2206,-0.0,0.037609452094365066,0.8745376090643924,0.08785293884124255 +2207,-0.0,0.05385764438890949,0.8582894167698479,0.08785293884124255 +2208,-0.0,0.07601427024510644,0.836132790913651,0.08785293884124255 +2209,-0.0,0.11180574278203997,0.8003413183767174,0.08785293884124255 +2210,0.07150486370301641,0.09419406581942188,0.7464481316363191,0.08785293884124255 +2211,0.46500741890235303,0.057834474670791,0.38930516758561334,0.08785293884124255 +2212,0.5195016518718097,0.05442576300060685,0.3382196462863408,0.08785293884124255 +2213,0.21120278689543584,0.0374958283720256,0.663448445891296,0.08785293884124255 +2214,-0.0,0.04692659732620174,0.8652204638325557,0.08785293884124255 +2215,-0.0,0.04999443782936746,0.86215262332939,0.08785293884124255 +2216,-0.0,0.04976719038468852,0.8623798707740689,0.08785293884124255 +2217,-0.0,0.05453938672294632,0.8576076744358111,0.08785293884124255 +2218,0.08896710410206883,0.05363039694423055,0.769549560112458,0.08785293884124255 +2219,0.6217159728283321,0.05397126811124897,0.23645982021917633,0.08785293884124255 +2220,0.718059368133449,0.07885486330359323,0.11523282972171524,0.08785293884124255 +2221,0.2294177100703095,0.08374068336419051,0.5989886677242574,0.08785293884124255 +2222,-0.0,0.26190267999248185,0.6502443811662756,0.08785293884124255 +2223,-0.0,0.263152540938216,0.6489945202205414,0.08785293884124255 +2224,-0.0,0.2613345613807845,0.650812499777973,0.08785293884124255 +2225,-0.0,0.27110620150197906,0.6410408596567784,0.08785293884124255 +2226,0.05027920942485785,0.26212992743716074,0.5997379242967389,0.08785293884124255 +2227,0.3239546667134554,0.31985077838561227,0.26834161605968976,0.08785293884124255 +2228,0.3090515477521951,0.2825821974582656,0.3205133159482967,0.08785293884124255 +2229,0.10507451550464306,0.23247413590655872,0.5745984097475556,0.08785293884124255 +2230,-0.0,0.3225777477217596,0.5895693134369978,0.08785293884124255 +2231,-0.0,0.2714470726689974,0.64069998848976,0.08785293884124255 +2232,-0.0,0.23338312568527447,0.6787639354734829,0.08785293884124255 +2233,-0.0,0.2216798822843089,0.6904671788744485,0.08785293884124255 +2234,0.03883843123237523,0.2089540253822881,0.6643546045440941,0.08785293884124255 +2235,0.22174034575693297,0.2170213096683906,0.4733854057334339,0.08785293884124255 +2236,0.19524591204802585,0.19895513781641463,0.5179460112943169,0.08785293884124255 +2237,0.08354778811615601,0.1423705240913578,0.6862287489512436,0.08785293884124255 +2238,-0.0,0.18122983713145707,0.7309172240273003,0.08785293884124255 +2239,-0.0,0.1571416079954891,0.7550054531632684,0.08785293884124255 +2240,-0.0,0.11339647489479257,0.7987505862639649,0.08785293884124255 +2241,-0.0,0.0829453173078142,0.8292017438509433,0.08785293884124255 +2242,0.0427523816666456,0.06760611479198554,0.8017885647001263,0.08785293884124255 +2243,0.23032092940129495,0.042268024710283396,0.6395581070471792,0.08785293884124255 +2244,0.1857621090726784,0.02499721891468373,0.7013877331713952,0.08785293884124255 +2245,0.09002085998821854,0.01499833134881024,0.8071278698217287,0.08785293884124255 +2246,-0.0,0.03170101853271255,0.8804460426260449,0.08785293884124255 +2247,-0.0,0.03317812692312568,0.8789689342356317,0.08785293884124255 +2248,-0.0,0.026815198472115276,0.8853318626866422,0.08785293884124255 +2249,-0.0,0.027951435695509994,0.8841956254632475,0.08785293884124255 +2250,0.0367309194600758,0.02874680175188629,0.8466693399467954,0.08785293884124255 +2251,0.28451408926042315,0.03885931304009926,0.5887736588582351,0.08785293884124255 +2252,0.3113095960796588,0.03215551342207044,0.5686819516570282,0.08785293884124255 +2253,0.1403000694130764,0.052494159720835834,0.7193528320248452,0.08785293884124255 +2254,-0.0,0.13475773469461322,0.7773893264641443,0.08785293884124255 +2255,-0.0,0.12100926429153716,0.7911377968672203,0.08785293884124255 +2256,-0.0,0.115668949341582,0.7964781118171754,0.08785293884124255 +2257,-0.0,0.12078201684685823,0.7913650443118992,0.08785293884124255 +2258,0.05554798885560643,0.10055699427043228,0.7560420780327187,0.08785293884124255 +2259,0.5029426308037428,0.1539601437699839,0.25524428658503073,0.08785293884124255 +2260,0.6196084610560327,0.18816088419416482,0.10437771590855983,0.08785293884124255 +2261,0.21466512766421347,0.23076978007146665,0.46671215342307737,0.08785293884124255 +2262,-0.0,0.34007580096203827,0.5720712601967192,0.08785293884124255 +2263,-0.0,0.2796279806774394,0.6325190804813181,0.08785293884124255 +2264,-0.0,0.24417737930752428,0.6679696818512332,0.08785293884124255 +2265,-0.0,0.22327061439706153,0.6888764467616959,0.08785293884124255 +2266,0.08791334821591912,0.18895625025054114,0.6352774626922972,0.08785293884124255 +2267,0.7642740905688723,0.2357259094311277,-0.0,-0.0 +2268,0.8457143669127287,0.15428563308727128,-0.0,-0.0 +2269,0.2888796493601863,0.336894336736533,0.28637307506203813,0.08785293884124255 +2270,-0.0,0.3591645863150694,0.552982474843688,0.08785293884124255 +2271,-0.0,0.27269693361473163,0.6394501275440259,0.08785293884124255 +2272,-0.0,0.2146352114992617,0.6975118496594958,0.08785293884124255 +2273,-0.0,0.17850286779530974,0.7336441933634477,0.08785293884124255 +2274,0.0931821276466677,0.13259888397016326,0.6863660495419265,0.08785293884124255 +2275,0.8281015899585121,0.17189841004148787,-0.0,-0.0 +2276,0.8461659765782216,0.15146042187851552,-0.0,0.0023736015432629376 +2277,0.21556834699519892,0.14657460181791826,0.5500041123456403,0.08785293884124255 +2278,-0.0,0.253267277094682,0.6588797840640754,0.08785293884124255 +2279,-0.0,0.17634401707085978,0.7358030440878977,0.08785293884124255 +2280,-0.0,0.13214438908080536,0.780002672077952,0.08785293884124255 +2281,-0.0,0.13305337885952115,0.7790936822992363,0.08785293884124255 +2282,0.04004272367368918,0.11078312928098473,0.7613212082040834,0.08785293884124255 +2283,0.2762345787263897,0.12146375918089504,0.5144487232514727,0.08785293884124255 +2284,0.1950953754928616,0.07544615163340908,0.6416055340324868,0.08785293884124255 +2285,0.06427910905513265,0.08953549320350354,0.7583324589001212,0.08785293884124255 +2286,-0.0,0.0901036118152009,0.8220434493435566,0.08785293884124255 +2287,-0.0,0.06351566078776458,0.8486314003709928,0.08785293884124255 +2288,-0.0,0.06817423340368291,0.8439728277550745,0.08785293884124255 +2289,-0.0,0.08374068336419051,0.8284063777945669,0.08785293884124255 +2290,0.02002136183684459,0.09635291654387185,0.795772782778041,0.08785293884124255 +2291,0.13171948576871442,0.15725523171782857,0.6231723436722144,0.08785293884124255 +2292,0.09845090707741627,0.15361927260296546,0.6600768814783757,0.08785293884124255 +2293,0.047268478321572945,0.1048746957193322,0.7600038871178523,0.08785293884124255 +2294,-0.0,0.12691769785318968,0.7852293633055678,0.08785293884124255 +2295,-0.0,0.09748915376726656,0.8146579073914909,0.08785293884124255 +2296,-0.0,0.08794476109075094,0.8242023000680065,0.08785293884124255 +2297,-0.0,0.11419184095116887,0.7979552202075886,0.08785293884124255 +2298,0.01098916852698989,0.14725634415195507,0.7539015484798124,0.08785293884124255 +2299,0.08610690955394817,0.1860020334697149,0.6400381181350944,0.08785293884124255 +2300,0.06744037671358179,0.18975161630691745,0.6549550681382581,0.08785293884124255 +2301,0.04470935688378078,0.16657237694966526,0.7008653273253115,0.08785293884124255 +2302,-0.0,0.159527706164618,0.7526193549941395,0.08785293884124255 +2303,-0.0,0.08885375086946673,0.8232933102892908,0.08785293884124255 +2304,-0.0,0.06646987756859084,0.8456771835901666,0.08785293884124255 +2305,-0.0,0.054312139278267384,0.85783492188049,0.08785293884124255 +2306,0.010537558861497156,0.042836143321980755,0.8587733589752795,0.08785293884124255 +2307,0.08911764065723309,0.032382760866749384,0.790646659634775,0.08785293884124255 +2308,0.0852036902229627,0.028292306862528406,0.7986510640732664,0.08785293884124255 +2309,0.05344047708330699,0.03158739481037308,0.8271191892650773,0.08785293884124255 +2310,-0.0,0.035905096259273,0.8762419648994845,0.08785293884124255 +2311,-0.0,0.02999666269762048,0.882150398461137,0.08785293884124255 +2312,-0.0,0.022497497023215362,0.8896495641355421,0.08785293884124255 +2313,-0.0,0.0171571820732602,0.8949898790854972,0.08785293884124255 +2314,0.01159131474764687,0.01806617185197597,0.8824895745591346,0.08785293884124255 +2315,0.15249353038138025,0.008862650342478778,0.7507908804348984,0.08785293884124255 +2316,0.11666583025228992,0.005113067505276217,0.7903681634011913,0.08785293884124255 +2317,0.051634038421336056,0.005908433561652518,0.8546045891757689,0.08785293884124255 +2318,-0.0,0.01738442951793914,0.8947626316408183,0.08785293884124255 +2319,-0.0,0.022838368190233774,0.8893086929685237,0.08785293884124255 +2320,-0.0,0.030337533864638895,0.8818095272941185,0.08785293884124255 +2321,-0.0,0.038404818150741364,0.8737422430080161,0.08785293884124255 +2322,0.01159131474764687,0.033291750645465155,0.8672639957656454,0.08785293884124255 +2323,0.183052451079722,0.02090676491046276,0.7081878451685727,0.08785293884124255 +2324,0.15460104215367967,0.015339202515828654,0.7422068164892491,0.08785293884124255 +2325,0.05103189220067908,0.013521222958397111,0.8475939459996813,0.08785293884124255 +2326,-0.0,0.025906208693399504,0.8862408524653579,0.08785293884124255 +2327,-0.0,0.02988303897528101,0.8822640221834764,0.08785293884124255 +2328,-0.0,0.03681408603798877,0.8753329751207687,0.08785293884124255 +2329,-0.0,0.04692659732620174,0.8652204638325557,0.08785293884124255 +2330,0.015354728626752998,0.04715384477088068,0.8496384877611237,0.08785293884124255 +2331,0.21195546967125706,0.02045227002110487,0.6797393214663956,0.08785293884124255 +2332,0.1871169380691566,0.01670268718390231,0.7083274359056985,0.08785293884124255 +2333,0.07541881413728678,0.013748470403076052,0.8229797766183946,0.08785293884124255 +2334,-0.0,0.02488359519234426,0.8872634659664131,0.08785293884124255 +2335,-0.0,0.014089341570094466,0.898057719588663,0.08785293884124255 +2336,-0.0,0.008521779175460363,0.9036252819832971,0.08785293884124255 +2337,-0.0,0.011021501066928736,0.9011255600918286,0.08785293884124255 +2338,0.01129024163731838,0.022838368190233774,0.8780184513312053,0.08785293884124255 +2339,0.19554698515835434,0.015566449960507597,0.7010336260398955,0.08785293884124255 +2340,0.1539988959330227,0.03931380792945714,0.7188343572962775,0.08785293884124255 +2341,0.05359101363847124,0.03931380792945714,0.819242239590829,0.08785293884124255 +2342,-0.0,0.06726524362496714,0.8448818175337903,0.08785293884124255 +2343,-0.0,0.05851621700482783,0.8536308441539296,0.08785293884124255 +2344,-0.0,0.052266912276156895,0.8598801488826006,0.08785293884124255 +2345,-0.0,0.039768302818815035,0.8723787583399424,0.08785293884124255 +2346,0.009333266420183194,0.05294865461019373,0.8498651401283805,0.08785293884124255 +2347,0.22836395418415978,0.06215217611969091,0.6216309308549067,0.08785293884124255 +2348,0.27969691949516734,0.10601093294272691,0.5264392087208631,0.08785293884124255 +2349,0.08339725156099176,0.11441908839584781,0.7143307212019179,0.08785293884124255 +2350,-0.0,0.17520777984746508,0.7369392813112924,0.08785293884124255 +2351,-0.0,0.22508859395449307,0.6870584672042643,0.08785293884124255 +2352,-0.0,0.22327061439706153,0.6888764467616959,0.08785293884124255 +2353,-0.0,0.21633956733435378,0.6958074938244037,0.08785293884124255 +2354,0.016709557623231203,0.217362180835409,0.6780753227001172,0.08785293884124255 +2355,0.23619185505270052,0.2156578250003169,0.46029738110574003,0.08785293884124255 +2356,0.19690181415483254,0.2706517066126211,0.44459354039130383,0.08785293884124255 +2357,0.04501042999410927,0.3051933182038205,0.5619433129608277,0.08785293884124255 +2358,-0.0,0.3475749666364434,0.564572094522314,0.08785293884124255 +2359,-0.0,0.30860202987400465,0.6035450312847528,0.08785293884124255 +2360,-0.0,0.2933764510805154,0.618770610078242,0.08785293884124255 +2361,-0.0,0.2787189908987236,0.6334280702600339,0.08785293884124255 +2362,0.01399989963027479,0.2645160256062897,0.6336311359221929,0.08785293884124255 +2363,0.48487824418403336,0.22724744467894303,0.2000213722957811,0.08785293884124255 +2364,0.3408147608918508,0.19009248747393584,0.3812398127929708,0.08785293884124255 +2365,0.09002085998821854,0.09078535414923773,0.7313408470213012,0.08785293884124255 +2366,-0.0,0.07692326002382222,0.8352238011349352,0.08785293884124255 +2367,-0.0,0.06601538267923295,0.8461316784795245,0.08785293884124255 +2368,-0.0,0.08930824575882461,0.8228388153999329,0.08785293884124255 +2369,-0.0,0.11839591867772932,0.7937511424810281,0.08785293884124255 +2370,0.012946143744125076,0.1172596814543346,0.7819412359602977,0.08785293884124255 +2371,0.3953089938613075,0.061129562618635676,0.4557085046788143,0.08785293884124255 +2372,0.47765248953614964,0.09544392676515608,0.3390506448574517,0.08785293884124255 +2373,0.09303159109150345,0.07908211074827216,0.7400333593189817,0.08785293884124255 +2374,-0.0,0.15725523171782857,0.7548918294409288,0.08785293884124255 +2375,-0.0,0.1639590313358574,0.7481880298229,0.08785293884124255 +2376,-0.0,0.159527706164618,0.7526193549941395,0.08785293884124255 +2377,-0.0,0.1375983277531,0.7745487334056574,0.08785293884124255 +2378,0.006322535316898292,0.14225690036901834,0.7635676254728407,0.08785293884124255 +2379,0.229116636959981,0.11316922745011362,0.5698611967486629,0.08785293884124255 +2380,0.2399552689318066,0.12316811501598711,0.5490236772109637,0.08785293884124255 +2381,0.06954788848588121,0.06567451151221453,0.7769246611606617,0.08785293884124255 +2382,-0.0,0.04431325171239389,0.8678338094463636,0.08785293884124255 +2383,-0.0,0.0367004623156493,0.8754465988431082,0.08785293884124255 +2384,-0.0,0.048517329438954336,0.8636297317198031,0.08785293884124255 +2385,-0.0,0.06374290823244351,0.8484041529263139,0.08785293884124255 +2386,0.009333266420183194,0.0713556976291881,0.8314580971093861,0.08785293884124255 +2387,0.4616956146887396,0.08010472424932741,0.3703467222206905,0.08785293884124255 +2388,0.6318019220243366,0.09828451982364285,0.182060619310778,0.08785293884124255 +2389,0.12328943867951671,0.12328173873832658,0.6655758837409141,0.08785293884124255 +2390,-0.0,0.2795143569550999,0.6326327042036575,0.08785293884124255 +2391,-0.0,0.22270249578536416,0.6894445653733933,0.08785293884124255 +2392,-0.0,0.16770861417305996,0.7444384469856975,0.08785293884124255 +2393,-0.0,0.12634957924149232,0.7857974819172651,0.08785293884124255 +2394,0.00948380297534744,0.1001024993810744,0.8025607588023356,0.08785293884124255 +2395,0.4555236159270055,0.07590064652276697,0.38072279870898496,0.08785293884124255 +2396,0.5845334437027636,0.09317145231836664,0.23444216513762717,0.08785293884124255 +2397,0.10131110162553693,0.11748692889901355,0.6933490306342069,0.08785293884124255 +2398,-0.0,0.2731514285040895,0.638995632654668,0.08785293884124255 +2399,-0.0,0.18520666741333858,0.7269403937454189,0.08785293884124255 +2400,-0.0,0.12135013545855558,0.7907969257002019,0.08785293884124255 +2401,-0.0,0.11930490845644508,0.7928421527023124,0.08785293884124255 +2402,0.006473071872062538,0.12919017229997912,0.7764838169867158,0.08785293884124255 +2403,0.3323847138026531,0.11771417634369248,0.4620481710124118,0.08785293884124255 +2404,0.43309366920753306,0.12225912523727135,0.35679426671395303,0.08785293884124255 +2405,0.07331130236498735,0.1316898941914475,0.7071458646023225,0.08785293884124255 +2406,-0.0,0.1817979557431544,0.730349105415603,0.08785293884124255 +2407,-0.0,0.12305449129364764,0.7890925698651098,0.08785293884124255 +2408,-0.0,0.07396904324299595,0.8381780179157615,0.08785293884124255 +2409,-0.0,0.04306339076665971,0.8690836703920978,0.08785293884124255 +2410,0.0033118042136133915,0.02942854408592312,0.8794067128592209,0.08785293884124255 +2411,0.24898746224166132,0.0054539386722946325,0.6577056602448015,0.08785293884124255 +2412,0.25636375344470935,0.009203521509497192,0.6465797862045508,0.08785293884124255 +2413,0.05283833086265002,0.003067840503165731,0.8562408897929417,0.08785293884124255 +2414,-0.0,0.007044670785047233,0.9051023903737102,0.08785293884124255 +2415,-0.0,0.015566449960507597,0.8965806111982498,0.08785293884124255 +2416,-0.0,0.018975161630691743,0.8931718995280656,0.08785293884124255 +2417,-0.0,0.03863206559542032,0.8735149955633371,0.08785293884124255 +2418,0.0009032193309854703,0.04522224149110966,0.8660216003366623,0.08785293884124255 +2419,0.1734181115492103,0.025906208693399504,0.7128227409161476,0.08785293884124255 +2420,0.2815033581571383,0.02931492036358365,0.6013287826380356,0.08785293884124255 +2421,0.043203991332138335,0.06658350129093031,0.8023595685356888,0.08785293884124255 +2422,-0.0,0.15918683499759959,0.7529602261611579,0.08785293884124255 +2423,-0.0,0.16418627878053632,0.7479607823782211,0.08785293884124255 +2424,-0.0,0.13816644636479736,0.7739806147939601,0.08785293884124255 +2425,-0.0,0.10839703111185582,0.8037500300469016,0.08785293884124255 +2426,0.004215023544598862,0.10714717016612163,0.8007848674480369,0.08785293884124255 +2427,0.4427280087380447,0.14293864270305515,0.3264804097176576,0.08785293884124255 +2428,0.6379739207860706,0.13259888397016326,0.14157425640252352,0.08785293884124255 +2429,0.08189188600934931,0.1787301152399887,0.6515250599094194,0.08785293884124255 +2430,-0.0,0.2975805288070759,0.6145665323516816,0.08785293884124255 +2431,-0.0,0.2278155632906404,0.684331497868117,0.08785293884124255 +2432,-0.0,0.1711173258432441,0.7410297353155133,0.08785293884124255 +2433,-0.0,0.11862316612240827,0.7935238950363491,0.08785293884124255 +2434,0.007376291203048008,0.09635291654387185,0.8084178534118375,0.08785293884124255 +2435,0.6525759666370023,0.027951435695509994,0.23161965882624513,0.08785293884124255 +2436,0.7203174164609126,0.02181575468917853,0.1700138900086663,0.08785293884124255 +2437,0.10582719828046429,0.021702130966839057,0.7846177319114541,0.08785293884124255 +2438,-0.0,0.04272251959964129,0.8694245415591162,0.08785293884124255 +2439,-0.0,0.07805949724721692,0.8340875639115405,0.08785293884124255 +2440,-0.0,0.06976496551643552,0.8423820956423219,0.08785293884124255 +2441,-0.0,0.042381648432622876,0.8697654127261345,0.08785293884124255 +2442,0.007526827758212253,0.03295087947844674,0.8716693539220984,0.08785293884124255 +2443,0.5517164746769582,0.004772196338257803,0.3556583901435415,0.08785293884124255 +2444,0.5958236853400819,0.004090454004220975,0.3122329218144546,0.08785293884124255 +2445,0.09137568898469675,0.014657460181791825,0.8061139119922689,0.08785293884124255 +2446,-0.0,0.10930602089057159,0.8028410402681858,0.08785293884124255 +2447,-0.0,0.10816978366717689,0.8039772774915805,0.08785293884124255 +2448,-0.0,0.08510416803226416,0.8270428931264933,0.08785293884124255 +2449,-0.0,0.060447820284598844,0.8516992408741586,0.08785293884124255 +2450,0.006473071872062538,0.05056255644106482,0.8551114328456301,0.08785293884124255 +2451,0.5676733495243681,0.010794253622249794,0.3336794580121395,0.08785293884124255 +2452,0.6376728476757421,0.0027269693361473162,0.271747244146868,0.08785293884124255 +2453,0.0928810545363392,0.004772196338257803,0.8144938102841605,0.08785293884124255 +2454,-0.0,0.02954216780826259,0.8826048933504949,0.08785293884124255 +2455,-0.0,0.056357366280377866,0.8557896948783795,0.08785293884124255 +2456,-0.0,0.08464967314290628,0.8274973880158512,0.08785293884124255 +2457,-0.0,0.0953303030428166,0.8168167581159408,0.08785293884124255 +2458,0.0037634138791061266,0.09487580815345872,0.8135078391261925,0.08785293884124255 +2459,0.48758790217698983,0.035564225092254584,0.38899493388951306,0.08785293884124255 +2460,0.550963791901137,0.042608895877301815,0.3185743733803187,0.08785293884124255 +2461,0.05991354895536954,0.0602205728399199,0.7920129393634681,0.08785293884124255 +2462,-0.0,0.15089230326681816,0.7612547578919393,0.08785293884124255 +2463,-0.0,0.13896181242117367,0.7731852487375838,0.08785293884124255 +2464,-0.0,0.11600982050860041,0.796137240650157,0.08785293884124255 +2465,-0.0,0.10123873660446911,0.8109083245542883,0.08785293884124255 +2466,0.0015053655516424507,0.10646542783208482,0.8041762677750302,0.08785293884124255 +2467,0.5058028253518635,0.026587951027436333,0.37975628477945755,0.08785293884124255 +2468,0.5542755961147504,0.03738220464968613,0.3204892603943209,0.08785293884124255 +2469,0.08339725156099176,0.049312695495330634,0.779437114102435,0.08785293884124255 +2470,-0.0,0.16123206199971007,0.7509149991590474,0.08785293884124255 +2471,-0.0,0.20531806626742502,0.7068289948913324,0.08785293884124255 +2472,-0.0,0.17759387801659396,0.7345531831421634,0.08785293884124255 +2473,-0.0,0.1870246469707701,0.7251224141879873,0.08785293884124255 +2474,0.002258048327463676,0.24201852858307432,0.6678704842482195,0.08785293884124255 +2475,0.23032092940129495,0.15816422149654433,0.5236619102609181,0.08785293884124255 +2476,0.3013741834388186,0.18054809479742023,0.43022478292251853,0.08785293884124255 +2477,0.04184916233566013,0.1942965652004963,0.676001333622601,0.08785293884124255 +2478,-0.0,0.28371843468166036,0.6284286264770971,0.08785293884124255 +2479,-0.0,0.3191690360515755,0.5929780251071819,0.08785293884124255 +2480,-0.0,0.35814197281401416,0.5540050883447433,0.08785293884124255 +2481,-0.0,0.3484839564151591,0.5636631047435983,0.08785293884124255 +2482,0.00045160966549273514,0.26497052049564757,0.6467249309976171,0.08785293884124255 +2483,0.16182679680156345,0.12509971829575814,0.6252205460614358,0.08785293884124255 +2484,0.22490161341538215,0.15691436055081015,0.5303310871925652,0.08785293884124255 +2485,0.027849262705385337,0.3329175064546515,0.5513802919987206,0.08785293884124255 +2486,-0.0,0.5632327916367602,0.3489142695219972,0.08785293884124255 +2487,-0.0,0.714011471181239,0.19813558997751846,0.08785293884124255 +2488,-0.0,0.755938624724504,0.15620843643425342,0.08785293884124255 +2489,-0.0,0.5661870084175865,0.3459600527411709,0.08785293884124255 +2490,0.002107511772299431,0.37859424283511905,0.531445306551339,0.08785293884124255 +2491,0.5637593990900978,0.18861537908352272,0.1597722829851369,0.08785293884124255 +2492,0.6251783135971097,0.093739570930064,0.19322917663158368,0.08785293884124255 +2493,0.04365560099763107,0.10294309243956119,0.7655483677215651,0.08785293884124255 +2494,-0.0,0.25826672087761876,0.6538803402811386,0.08785293884124255 +2495,-0.0,0.37268580927346656,0.5394612518852908,0.08785293884124255 +2496,-0.0,0.46006245175252014,0.4520846094062373,0.08785293884124255 +2497,-0.0,0.49108172795119587,0.42106533320756157,0.08785293884124255 +2498,0.0009032193309854703,0.49551305312243527,0.41573078870533675,0.08785293884124255 +2499,0.26163253287545796,0.5151699570871638,0.13534457119613563,0.08785293884124255 +2500,0.1630310892428774,0.7062850580621549,0.04283091385372513,0.08785293884124255 +2501,0.020623508057501575,0.7530980316660171,0.13842552143523879,0.08785293884124255 +2502,-0.0,0.7642331564552853,0.1479139047034721,0.08785293884124255 +2503,-0.0,0.7834355655306561,0.12871149562810136,0.08785293884124255 +2504,-0.0,0.7959341749879979,0.11621288617075955,0.08785293884124255 +2505,-0.0,0.8350207354727761,0.07712632568598134,0.08785293884124255 +2506,0.0007526827758212254,0.8307030340238762,0.08069134435905999,0.08785293884124255 +2507,0.19885878937196771,0.7637786615659276,-0.0,0.03736254906210473 +2508,0.18591264562784265,0.7218515080226625,0.004382907508252279,0.08785293884124255 +2509,0.028300872370878073,0.6105002601299804,0.273345928657899,0.08785293884124255 +2510,-0.0,0.530395535880653,0.3817515252781044,0.08785293884124255 +2511,-0.0,0.5210783906488163,0.3910686705099411,0.08785293884124255 +2512,-0.0,0.4714248239864673,0.4407222371722901,0.08785293884124255 +2513,-0.0,0.4172263084305394,0.494920752728218,0.08785293884124255 +2514,0.00045160966549273514,0.39245633696053456,0.5192391145327302,0.08785293884124255 +2515,0.21210600622642128,0.3533697764757564,0.34667127845657975,0.08785293884124255 +2516,0.31085798641416607,0.36779998921286927,0.2334890855317221,0.08785293884124255 +2517,0.039440577453032206,0.32644095428130165,0.5462655294244236,0.08785293884124255 +2518,-0.0,0.3481430852481407,0.5640039759106168,0.08785293884124255 +2519,-0.0,0.34337088890988293,0.5687761722488744,0.08785293884124255 +2520,-0.0,0.34734771919176444,0.564799341966993,0.08785293884124255 +2521,-0.0,0.3573466067576379,0.5548004544011196,0.08785293884124255 +2522,0.00030107311032849017,0.3673454943235114,0.5445004937249176,0.08785293884124255 +2523,0.2542562416724099,0.30451157586978367,0.3533792436165639,0.08785293884124255 +2524,0.3545135874117971,0.3234867375004754,0.2341467362464849,0.08785293884124255 +2525,0.03402126146711938,0.15907321127526014,0.7190525884163779,0.08785293884124255 +2526,-0.0,0.1383936938094763,0.7737533673492811,0.08785293884124255 +2527,-0.0,0.20634067976848028,0.7058063813902772,0.08785293884124255 +2528,-0.0,0.24349563697348744,0.66865142418527,0.08785293884124255 +2529,-0.0,0.2746285368945026,0.6375185242642548,0.08785293884124255 +2530,0.00015053655516424508,0.3921154657935162,0.519881058810077,0.08785293884124255 +2531,0.24296600003509153,0.4020007296370502,0.2671803314866157,0.08785293884124255 +2532,0.2655464833097283,0.5055119406883087,0.1410886371607204,0.08785293884124255 +2533,0.01339775340961781,0.5353949796635897,0.3633543280855499,0.08785293884124255 +2534,-0.0,0.5883436342737836,0.3238034268849739,0.08785293884124255 +2535,-0.0,0.7103755120663758,0.2017715490923816,0.08785293884124255 +2536,-0.0,0.7303732871981229,0.18177377396063454,0.08785293884124255 +2537,-0.0,0.7104891357887153,0.20165792537004212,0.08785293884124255 +2538,-0.0,0.7031035938366498,0.20904346732210766,0.08785293884124255 +2539,0.327417007482233,0.672582992517767,-0.0,-0.0 +2540,0.2626862887616076,0.7373137112383924,-0.0,-0.0 +2541,0.014150436185439036,0.582207953267452,0.3157886717058664,0.08785293884124255 +2542,-0.0,0.46403928203440165,0.44810777912435573,0.08785293884124255 +2543,-0.0,0.39984187891260026,0.5123051822461572,0.08785293884124255 +2544,-0.0,0.4156355763177868,0.49651148484097063,0.08785293884124255 +2545,-0.0,0.49642204290115105,0.41572501825760644,0.08785293884124255 +2546,0.006473071872062538,0.622203503530946,0.2834704857557489,0.08785293884124255 +2547,0.07571988724761526,0.7176474302961021,0.11877974361504008,0.08785293884124255 +2548,0.1496333358332596,0.763892285288267,-0.0,0.08647437887847337 +2549,0.015655801737081487,0.723896735024773,0.17259452439690293,0.08785293884124255 +2550,-0.0,0.5485753314549685,0.3635717297037889,0.08785293884124255 +2551,-0.0,0.41109062742420793,0.5010564337345496,0.08785293884124255 +2552,-0.0,0.3551877560331879,0.5569593051255695,0.08785293884124255 +2553,-0.0,0.3754127786096139,0.5367342825491435,0.08785293884124255 +2554,0.00015053655516424508,0.47437904076729354,0.4376174838362996,0.08785293884124255 +2555,0.24838531602100436,0.48880925350440646,0.1749524916333467,0.08785293884124255 +2556,0.2959548674529058,0.4914225991182143,0.12476959458763738,0.08785293884124255 +2557,0.018064386619709407,0.44233715106756255,0.45174552347148544,0.08785293884124255 +2558,-0.0,0.4552902554142623,0.45685680574449505,0.08785293884124255 +2559,-0.0,0.3840481815074137,0.5280988796513437,0.08785293884124255 +2560,-0.0,0.340984790740754,0.5711622704180035,0.08785293884124255 +2561,-0.0,0.3462114819683697,0.5659355791903877,0.08785293884124255 +2562,0.00015053655516424508,0.37984410378085326,0.5321524208227398,0.08785293884124255 +2563,0.11967656135557483,0.43052028394425756,0.36195021585892506,0.08785293884124255 +2564,0.15023548205391657,0.46119868897591487,0.300712890128926,0.08785293884124255 +2565,0.004516096654927352,0.46835698348330157,0.4392739810205285,0.08785293884124255 +2566,-0.0,0.616181446246954,0.2959656149118034,0.08785293884124255 +2567,-0.0,0.9055810670455879,0.006565994113169507,0.08785293884124255 +2568,-0.0,1.0,-0.0,-0.0 +2569,-0.0,1.0,-0.0,-0.0 +2570,-0.0,1.0,-0.0,-0.0 +2571,0.20247166669590963,0.7975283333040903,-0.0,-0.0 +2572,0.30829886497637393,0.6917011350236261,-0.0,-0.0 +2573,0.01716116728872394,0.6661758840763216,0.2288100097937119,0.08785293884124255 +2574,-0.0,0.7630969192318906,0.14905014192686683,0.08785293884124255 +2575,-0.0,0.9013769893190275,0.010770071839729903,0.08785293884124255 +2576,-0.0,0.9069445517136616,0.005202509445095829,0.08785293884124255 +2577,-0.0,0.9370548381336216,-0.0,0.06294516186637844 +2578,-0.0,0.9623929282153236,-0.0,0.03760707178467637 +2579,0.22294463819824695,0.777055361801753,-0.0,-0.0 +2580,0.22204141886726148,0.7779585811327385,-0.0,-0.0 +2581,0.01008594919600442,0.7890031279252901,0.11305798403746288,0.08785293884124255 +2582,-0.0,0.6080005382385121,0.30414652292024535,0.08785293884124255 +2583,-0.0,0.44972269301962825,0.4624243681391291,0.08785293884124255 +2584,-0.0,0.41983965404434725,0.49230740711441023,0.08785293884124255 +2585,-0.0,0.4043868278061791,0.5077602333525784,0.08785293884124255 +2586,-0.0,0.4160900712071447,0.49605698995161274,0.08785293884124255 +2587,0.13277324165486415,0.32996328967382527,0.449410529830068,0.08785293884124255 +2588,0.14120328874406188,0.28814975985289976,0.4827940125617959,0.08785293884124255 +2589,0.0024085848826279213,0.19134234841967002,0.7183961278564595,0.08785293884124255 +2590,-0.0,0.1866837758037517,0.7254632853550057,0.08785293884124255 +2591,-0.0,0.19600092103558833,0.7161461401231691,0.08785293884124255 +2592,-0.0,0.20202297831958035,0.710124082839177,0.08785293884124255 +2593,-0.0,0.19134234841967002,0.7208047127390874,0.08785293884124255 +2594,-0.0,0.15657348938379176,0.7555735717749656,0.08785293884124255 +2595,0.24507351180739098,0.10703354644378217,0.5600400029075843,0.08785293884124255 +2596,0.19946093559262473,0.09964800449171651,0.6130381210744162,0.08785293884124255 +2597,0.008731120199526213,0.12203187779259239,0.7813840631666388,0.08785293884124255 +2598,-0.0,0.16634512950498628,0.7458019316537712,0.08785293884124255 +2599,-0.0,0.16691324811668365,0.7452338130420738,0.08785293884124255 +2600,-0.0,0.1806617185197597,0.7314853426389978,0.08785293884124255 +2601,-0.0,0.21304447938650908,0.6991025817722484,0.08785293884124255 +2602,-0.0,0.2348602340756876,0.6772868270830699,0.08785293884124255 +2603,0.19328893683089066,0.2851955430720735,0.43366258125579327,0.08785293884124255 +2604,0.21436405455388496,0.33678071301419354,0.3610022935906789,0.08785293884124255 +2605,0.009935412640840174,0.3382578214046067,0.5639538271133105,0.08785293884124255 +2606,-0.0,0.3016709828112969,0.6104760783474605,0.08785293884124255 +2607,-0.0,0.24190490486073485,0.6702421562980225,0.08785293884124255 +2608,-0.0,0.1703219597868678,0.7418251013718896,0.08785293884124255 +2609,-0.0,0.14146153431264202,0.7706855268461155,0.08785293884124255 +2610,-0.0,0.10589730922038745,0.80624975193837,0.08785293884124255 +2611,0.22264356508791847,0.044654122879412304,0.6448493731914267,0.08785293884124255 +2612,0.2875248203637081,0.06306116589840668,0.5615610748966426,0.08785293884124255 +2613,0.008430047089197723,0.13691658541906318,0.7668004286504966,0.08785293884124255 +2614,-0.0,0.1706628309538862,0.7414842302048712,0.08785293884124255 +2615,-0.0,0.19770527687068043,0.714441784288077,0.08785293884124255 +2616,-0.0,0.24610898258729527,0.6660380785714621,0.08785293884124255 +2617,-0.0,0.267015747497758,0.6451313136609994,0.08785293884124255 +2618,-0.0,0.3057614368155178,0.6063856243432396,0.08785293884124255 +2619,0.24236385381443457,0.3649593961543825,0.3048238111899404,0.08785293884124255 +2620,0.3259116419305906,0.48267357249807497,0.1035618467300919,0.08785293884124255 +2621,0.006322535316898292,0.5409625420582239,0.3648619837836352,0.08785293884124255 +2622,-0.0,0.49028636189481956,0.4218606992639379,0.08785293884124255 +2623,-0.0,0.38427542895209266,0.5278716322066648,0.08785293884124255 +2624,-0.0,0.28860425474225765,0.6235428064164998,0.08785293884124255 +2625,-0.0,0.21077200493971965,0.7013750562190377,0.08785293884124255 +2626,-0.0,0.1842976776346228,0.7278493835241346,0.08785293884124255 +2627,0.0852036902229627,0.1323716365254843,0.6945717344103104,0.08785293884124255 +2628,0.09303159109150345,0.10623818038740586,0.7128772896798481,0.08785293884124255 +2629,0.001956975217135186,0.11975940334580297,0.7904306825958193,0.08785293884124255 +2630,-0.0,0.12373623362768447,0.7884108275310729,0.08785293884124255 +2631,-0.0,0.12544058946277656,0.7867064716959808,0.08785293884124255 +2632,-0.0,0.12748581646488702,0.7846612446938704,0.08785293884124255 +2633,-0.0,0.13373512119355796,0.7784119399651994,0.08785293884124255 +2634,-0.0,0.1072607938884611,0.8048862672702963,0.08785293884124255 +2635,0.18982659606211302,0.048176458271935924,0.6741440068247084,0.08785293884124255 +2636,0.2535035588965887,0.03954105537413608,0.6191024468880326,0.08785293884124255 +2637,0.004064486989434617,0.07146932135152757,0.8366132528177952,0.08785293884124255 +2638,-0.0,0.071810192518546,0.8403368686402114,0.08785293884124255 +2639,-0.0,0.06851510457070133,0.8436319565880561,0.08785293884124255 +2640,-0.0,0.08237719869611684,0.8297698624626406,0.08785293884124255 +2641,-0.0,0.079763853082309,0.8323832080764484,0.08785293884124255 +2642,-0.0,0.1108967530033242,0.8012503081554332,0.08785293884124255 +2643,0.23257897772875863,0.15532362843805755,0.5242444549919412,0.08785293884124255 +2644,0.3509007100878552,0.17441241379108877,0.38683393727981347,0.08785293884124255 +2645,0.0033118042136133915,0.23804169830119282,0.6707935586439512,0.08785293884124255 +2646,-0.0,0.33416736740038566,0.5779796937583718,0.08785293884124255 +2647,-0.0,0.36916347388094295,0.5429835872778145,0.08785293884124255 +2648,-0.0,0.3893884964573689,0.5227585647013886,0.08785293884124255 +2649,-0.0,0.39506968257434244,0.5170773785844149,0.08785293884124255 +2650,-0.0,0.4074546683093448,0.5046923928494127,0.08785293884124255 +2651,0.13202055887904293,0.39791027563282927,0.38221622664688526,0.08785293884124255 +2652,0.18982659606211302,0.42779331460811026,0.29452715048853406,0.08785293884124255 +2653,0.004967706320420087,0.4413145375665074,0.46586481727182993,0.08785293884124255 +2654,-0.0,0.4655163904248148,0.4466306707339427,0.08785293884124255 +2655,-0.0,0.5713000759228628,0.34084698523589463,0.08785293884124255 +2656,-0.0,0.7270781992502783,0.18506886190847915,0.08785293884124255 +2657,-0.0,0.8009336187709346,0.11121344238782284,0.08785293884124255 +2658,-0.0,0.7859352874221244,0.12621177373663306,0.08785293884124255 +2659,0.06171998761734048,0.7524162893319803,0.09801078420943661,0.08785293884124255 +2660,0.07602096035794377,0.7224196266343599,0.11370647416645381,0.08785293884124255 +2661,0.0009032193309854703,0.8313847763579131,0.0798590654698589,0.08785293884124255 +2662,-0.0,0.9877310182970258,-0.0,0.012268981702974191 +2663,-0.0,1.0,-0.0,-0.0 +2664,-0.0,0.9832996931257864,-0.0,0.016700306874213644 +2665,-0.0,0.9177388053359113,-0.0,0.08226119466408865 +2666,-0.0,0.8683124861182413,0.04383457504051615,0.08785293884124255 +2667,0.14240758118537583,0.7293506736970676,0.040388806276313985,0.08785293884124255 +2668,0.09408534697765317,0.6712889515815976,0.14677276259950667,0.08785293884124255 +2669,0.0009032193309854703,0.48608228416825916,0.4251615576595128,0.08785293884124255 +2670,-0.0,0.5590287139101998,0.3531183472485576,0.08785293884124255 +2671,-0.0,0.6524274136732454,0.259719647485512,0.08785293884124255 +2672,-0.0,0.6843556796506368,0.22779138150812062,0.08785293884124255 +2673,-0.0,0.5856166649376361,0.32653039622112134,0.08785293884124255 +2674,-0.0,0.4334745007250838,0.4786725604336737,0.08785293884124255 +2675,0.19298786372056217,0.34552973963433287,0.37362945780386236,0.08785293884124255 +2676,0.19825664315131078,0.33655346556951465,0.3773369524379321,0.08785293884124255 +2677,0.0033118042136133915,0.2796279806774394,0.6292072762677047,0.08785293884124255 +2678,-0.0,0.23951880669160594,0.6726282544671515,0.08785293884124255 +2679,-0.0,0.28053697045615517,0.6316100907026023,0.08785293884124255 +2680,-0.0,0.4285886806644865,0.4835583804942709,0.08785293884124255 +2681,-0.0,0.4669934988152279,0.44515356234352954,0.08785293884124255 +2682,-0.0,0.44608673390476516,0.46606032725399227,0.08785293884124255 +2683,0.0627737435034902,0.3335992487886884,0.5157740688665788,0.08785293884124255 +2684,0.07827900868540744,0.2580394734329398,0.5758285790404101,0.08785293884124255 +2685,0.0018064386619709406,0.3654138910437404,0.5449267314530462,0.08785293884124255 +2686,-0.0,0.3692770976032824,0.542869963555475,0.08785293884124255 +2687,-0.0,0.36382315893098777,0.5483239022277697,0.08785293884124255 +2688,-0.0,0.3362125944024962,0.5759344667562613,0.08785293884124255 +2689,-0.0,0.30076199303258105,0.6113850681261763,0.08785293884124255 +2690,-0.0,0.2700835880009238,0.6420634731578336,0.08785293884124255 +2691,0.25380463200691716,0.1599822010539759,0.4983602280978644,0.08785293884124255 +2692,0.388835921989245,0.1404389208115868,0.3828722183579256,0.08785293884124255 +2693,0.01309668029928932,0.25474438548509515,0.6443059953743729,0.08785293884124255 +2694,-0.0,0.2720151912806948,0.6401318698780627,0.08785293884124255 +2695,-0.0,0.2861045328507893,0.6260425283079681,0.08785293884124255 +2696,-0.0,0.3234867375004754,0.588660323658282,0.08785293884124255 +2697,-0.0,0.47017496304073314,0.4419720981180243,0.08785293884124255 +2698,-0.0,0.6551543830093928,0.25699267814936466,0.08785293884124255 +2699,0.1600203581395925,0.636179221378701,0.11594748164046396,0.08785293884124255 +2700,0.1902782057276058,0.5359630982752871,0.18590575715586455,0.08785293884124255 +2701,0.0037634138791061266,0.4927860837862879,0.4155975634933634,0.08785293884124255 +2702,-0.0,0.47812862360449615,0.4340184375542613,0.08785293884124255 +2703,-0.0,0.46881147837265946,0.44333558278609797,0.08785293884124255 +2704,-0.0,0.4636984108673833,0.44844865029137415,0.08785293884124255 +2705,-0.0,0.4780149998821567,0.43413206127660076,0.08785293884124255 +2706,-0.0,0.5394854336678108,0.37266162749094667,0.08785293884124255 +2707,0.27849262705385336,0.6141362192448435,0.01951821486006057,0.08785293884124255 +2708,0.3012236468836544,0.6987763531163456,-0.0,-0.0 +2709,0.006774144982391027,0.7426446492107858,0.16272826696558063,0.08785293884124255 +2710,-0.0,0.771391450962672,0.1407556101960854,0.08785293884124255 +2711,-0.0,0.7682099867371669,0.14393707442159054,0.08785293884124255 +2712,-0.0,0.7545751400564303,0.1575719211023271,0.08785293884124255 +2713,-0.0,0.7492348251064751,0.16291223605228233,0.08785293884124255 +2714,-0.0,0.7418492831544096,0.17029777800434787,0.08785293884124255 +2715,0.2939978922357706,0.7060021077642293,-0.0,-0.0 +2716,0.3310298848061749,0.6689701151938251,-0.0,-0.0 +2717,0.008128973978869233,0.777868003136022,0.12615008404386618,0.08785293884124255 +2718,-0.0,0.6988995161100893,0.21324754504866816,0.08785293884124255 +2719,-0.0,0.5433486402273527,0.3687984209314047,0.08785293884124255 +2720,-0.0,0.34439350241093813,0.5677535587478193,0.08785293884124255 +2721,-0.0,0.19793252431535938,0.7142145368433981,0.08785293884124255 +2722,-0.0,0.11873678984474773,0.7934102713140097,0.08785293884124255 +2723,0.2679550681923562,0.05453938672294632,0.5896526062434548,0.08785293884124255 +2724,0.38868538543408077,0.02090676491046276,0.502554910814214,0.08785293884124255 +2725,0.013247216854453566,0.044086004267714946,0.854813840036589,0.08785293884124255 +2726,-0.0,0.09430768954176136,0.817839371616996,0.08785293884124255 +2727,-0.0,0.12987191463401593,0.7822751465247415,0.08785293884124255 +2728,-0.0,0.14100703942328416,0.7711400217354732,0.08785293884124255 +2729,-0.0,0.13021278580103435,0.7819342753577231,0.08785293884124255 +2730,-0.0,0.131576270469108,0.7805707906896494,0.08785293884124255 +2731,0.4272227435561275,0.12873567741062122,0.35618864019200874,0.08785293884124255 +2732,0.5703830075173246,0.11839591867772932,0.22336813496370345,0.08785293884124255 +2733,0.010537558861497156,0.2941718171368918,0.6074376851603684,0.08785293884124255 +2734,-0.0,0.32734994406001744,0.58479711709874,0.08785293884124255 +2735,-0.0,0.25894846321165554,0.6531985979471019,0.08785293884124255 +2736,-0.0,0.23520110524270602,0.6769459559160514,0.08785293884124255 +2737,-0.0,0.21122649982907754,0.7009205613296798,0.08785293884124255 +2738,-0.0,0.24406375558518478,0.6680833055735727,0.08785293884124255 +2739,0.18591264562784265,0.23895068807990857,0.48728372745100623,0.08785293884124255 +2740,0.24236385381443457,0.2508811789255531,0.41890202841876967,0.08785293884124255 +2741,0.004967706320420087,0.2673566186647765,0.6398227361735609,0.08785293884124255 +2742,-0.0,0.28099146534551306,0.6311555958132444,0.08785293884124255 +2743,-0.0,0.2345193629086692,0.6776276982500882,0.08785293884124255 +2744,-0.0,0.23951880669160594,0.6726282544671515,0.08785293884124255 +2745,-0.0,0.28996773941033127,0.6221793217484262,0.08785293884124255 +2746,-0.0,0.32598645939194376,0.5861606017668137,0.08785293884124255 +2747,0.34186851677800056,0.3977966519104898,0.17248189247026702,0.08785293884124255 +2748,0.42180342757021466,0.39938738402324236,0.09095624956530042,0.08785293884124255 +2749,0.005870925651405557,0.4631302922556859,0.44314584325166595,0.08785293884124255 +2750,-0.0,0.4651755192577964,0.446971541900961,0.08785293884124255 +2751,-0.0,0.4513134251323808,0.4608336360263766,0.08785293884124255 +2752,-0.0,0.4199532777666867,0.49219378339207076,0.08785293884124255 +2753,-0.0,0.40665930225296854,0.5054877589057889,0.08785293884124255 +2754,-0.0,0.4375649547293048,0.47458210642945264,0.08785293884124255 +2755,0.13849363075110546,0.47835587104917504,0.2952975593584769,0.08785293884124255 +2756,0.13171948576871442,0.55255216173685,0.22787541365319308,0.08785293884124255 +2757,0.0013548289964782055,0.5799354788206625,0.3308567533416167,0.08785293884124255 +2758,-0.0,0.5174424315339533,0.3947046296248041,0.08785293884124255 +2759,-0.0,0.5514159245134552,0.3607311366453022,0.08785293884124255 +2760,-0.0,0.5452802435071238,0.36686681765163365,0.08785293884124255 +2761,-0.0,0.46665262764820953,0.44549443351054796,0.08785293884124255 +2762,-0.0,0.424611850382605,0.48753521077615236,0.08785293884124255 +2763,0.2274607348531743,0.3359853469578173,0.3487009793477658,0.08785293884124255 +2764,0.267804531637192,0.30417070470276525,0.34017182481880015,0.08785293884124255 +2765,0.0034623407687776366,0.22827005817999826,0.6804146622099816,0.08785293884124255 +2766,-0.0,0.24940407053513997,0.6627429906236175,0.08785293884124255 +2767,-0.0,0.42847505694214705,0.4836720042166104,0.08785293884124255 +2768,-0.0,0.6767428902538923,0.2354041709048651,0.08785293884124255 +2769,-0.0,0.8327482610259866,0.0793988001327708,0.08785293884124255 +2770,-0.0,0.6586767184019163,0.2534703427568411,0.08785293884124255 +2771,0.15595587115015788,0.41620369492948417,0.3399874950791154,0.08785293884124255 +2772,0.2452240483625552,0.22395235673109834,0.4429706560651039,0.08785293884124255 +2773,0.004365560099763107,0.13873456497649472,0.7690469360824996,0.08785293884124255 +2774,-0.0,0.07760500235785904,0.8345420588008984,0.08785293884124255 +2775,-0.0,0.0390865604847782,0.8730605006739792,0.08785293884124255 +2776,-0.0,0.02397460541362849,0.8881724557451289,0.08785293884124255 +2777,-0.0,0.02840593058486788,0.8837411305738896,0.08785293884124255 +2778,-0.0,0.058970711894185714,0.8531763492645718,0.08785293884124255 +2779,0.19328893683089066,0.061015938896296196,0.6578421854315706,0.08785293884124255 +2780,0.24010580548697089,0.05908433561652518,0.6129569200552614,0.08785293884124255 +2781,0.004967706320420087,0.14839258137534977,0.7587867734629876,0.08785293884124255 +2782,-0.0,0.177366630571915,0.7347804305868424,0.08785293884124255 +2783,-0.0,0.14464299853814722,0.7675040626206102,0.08785293884124255 +2784,-0.0,0.11794142378837143,0.794205637370386,0.08785293884124255 +2785,-0.0,0.07919573447061164,0.8329513266881458,0.08785293884124255 +2786,-0.0,0.05965245422822254,0.8524946069305349,0.08785293884124255 +2787,0.15610640770532214,0.028065059417849463,0.7279755940355859,0.08785293884124255 +2788,0.24010580548697089,0.012612233179681338,0.6594290224921052,0.08785293884124255 +2789,0.008279510534033478,0.04124541120922816,0.8626221394154957,0.08785293884124255 +2790,-0.0,0.08090009030570372,0.8312469708530537,0.08785293884124255 +2791,-0.0,0.09396681837474294,0.8181802427840145,0.08785293884124255 +2792,-0.0,0.1124874851160768,0.7996595760426807,0.08785293884124255 +2793,-0.0,0.1463473543732393,0.7657997067855181,0.08785293884124255 +2794,-0.0,0.19213771447604633,0.7200093466827111,0.08785293884124255 +2795,0.10898846593891344,0.1817979557431544,0.6213606394766896,0.08785293884124255 +2796,0.18260084141422928,0.1620274280560864,0.5675187916884418,0.08785293884124255 +2797,0.0013548289964782055,0.2759920215625763,0.6348002105997029,0.08785293884124255 +2798,-0.0,0.29303557991349705,0.6191114812452604,0.08785293884124255 +2799,-0.0,0.30485244703680203,0.6072946141219554,0.08785293884124255 +2800,-0.0,0.3136014736569414,0.598545587501816,0.08785293884124255 +2801,-0.0,0.31166987037717037,0.6004771907815871,0.08785293884124255 +2802,-0.0,0.30826115870698617,0.6038859024517713,0.08785293884124255 +2803,0.09408534697765317,0.2816732076795499,0.5363885065015545,0.08785293884124255 +2804,0.16543967412550534,0.2780372485646868,0.4686701384685653,0.08785293884124255 +2805,0.004064486989434617,0.36291416915227204,0.5451684050170508,0.08785293884124255 +2806,-0.0,0.3856389136201663,0.5265081475385911,0.08785293884124255 +2807,-0.0,0.37109507716071394,0.5410519839980434,0.08785293884124255 +2808,-0.0,0.3551877560331879,0.5569593051255695,0.08785293884124255 +2809,-0.0,0.34314364146520393,0.5690034196935535,0.08785293884124255 +2810,-0.0,0.34643872941304865,0.5657083317457088,0.08785293884124255 +2811,0.11169812393186984,0.29451268830391014,0.5059362489229774,0.08785293884124255 +2812,0.14842904339194563,0.26019832415738975,0.5035196936094221,0.08785293884124255 +2813,0.004967706320420087,0.2883770072975787,0.6188023475407587,0.08785293884124255 +2814,-0.0,0.2941718171368918,0.6179752440218657,0.08785293884124255 +2815,-0.0,0.3071249214835915,0.6050221396751659,0.08785293884124255 +2816,-0.0,0.2856500379614314,0.626497023197326,0.08785293884124255 +2817,-0.0,0.25428989059573726,0.6578571705630202,0.08785293884124255 +2818,-0.0,0.24383650814050586,0.6683105530182516,0.08785293884124255 +2819,0.18651479184849964,0.2116809947184354,0.5139512745918224,0.08785293884124255 +2820,0.36505114627329427,0.20520444254508557,0.34189147234037753,0.08785293884124255 +2821,0.006473071872062538,0.3186009174398781,0.5870730718468168,0.08785293884124255 +2822,-0.0,0.33064503200786205,0.5815020291508954,0.08785293884124255 +2823,-0.0,0.29212659013478126,0.6200204710239762,0.08785293884124255 +2824,-0.0,0.267015747497758,0.6451313136609994,0.08785293884124255 +2825,-0.0,0.263379788382895,0.6487672727758624,0.08785293884124255 +2826,-0.0,0.30314809120171,0.6089989699570475,0.08785293884124255 +2827,0.2441702924764055,0.3098518908197388,0.35812487786261316,0.08785293884124255 +2828,0.289933405246336,0.2931492036358365,0.32906445227658493,0.08785293884124255 +2829,0.005268779430748578,0.34564336335667234,0.5612349183713365,0.08785293884124255 +2830,-0.0,0.3533697764757564,0.558777284683001,0.08785293884124255 +2831,-0.0,0.3320085166759358,0.5801385444828216,0.08785293884124255 +2832,-0.0,0.3079202875399678,0.6042267736187896,0.08785293884124255 +2833,-0.0,0.34427987868859866,0.5678671824701588,0.08785293884124255 +2834,-0.0,0.3754127786096139,0.5367342825491435,0.08785293884124255 +2835,0.144966702623168,0.4131358544263184,0.354044504109271,0.08785293884124255 +2836,0.1847083531865287,0.5961836711152071,0.13125503685702167,0.08785293884124255 +2837,0.0030107311032849015,0.7163975693503678,0.1927387607051047,0.08785293884124255 +2838,-0.0,0.8357024778068131,0.07644458335194439,0.08785293884124255 +2839,-0.0,0.9433041428622926,-0.0,0.05669585713770742 +2840,-0.0,0.8903554882520988,0.021791572906658688,0.08785293884124255 +2841,-0.0,0.6632216672954953,0.24892539386326218,0.08785293884124255 +2842,-0.0,0.4952858056777563,0.41686125548100117,0.08785293884124255 +2843,0.15550426148466515,0.6099321415182831,0.1467106581558092,0.08785293884124255 +2844,0.23032092940129495,0.7696790705987051,-0.0,-0.0 +2845,0.00797843742370499,0.8024107271613479,0.10175789657370458,0.08785293884124255 +2846,-0.0,0.7674146206807906,0.14473244047796685,0.08785293884124255 +2847,-0.0,0.7405994222086754,0.17154763895008207,0.08785293884124255 +2848,-0.0,0.7478713404384015,0.1642757207203559,0.08785293884124255 +2849,-0.0,0.7694598476829011,0.14268721347585633,0.08785293884124255 +2850,-0.0,0.7649148987893223,0.14723216236943515,0.08785293884124255 +2851,0.2298693197358022,0.7429855203778043,-0.0,0.02714515988639349 +2852,0.2705141896301484,0.7294858103698516,-0.0,-0.0 +2853,0.0030107311032849015,0.5540292701272631,0.3551070599282094,0.08785293884124255 +2854,-0.0,0.38666152712122154,0.525485534037536,0.08785293884124255 +2855,-0.0,0.2365645899107797,0.6755824712479778,0.08785293884124255 +2856,-0.0,0.13305337885952115,0.7790936822992363,0.08785293884124255 +2857,-0.0,0.14532474087218408,0.7668223202865734,0.08785293884124255 +2858,-0.0,0.2084995304929302,0.7036475306658272,0.08785293884124255 +2859,0.29339574601511365,0.27485578433918156,0.3438955308044622,0.08785293884124255 +2860,0.4239109393425141,0.3614370607618589,0.12679906105438443,0.08785293884124255 +2861,0.010236485751168664,0.5333497526614792,0.3685608227461096,0.08785293884124255 +2862,-0.0,0.6201582765288355,0.29198878462992195,0.08785293884124255 +2863,-0.0,0.6545862643976953,0.25756079676106214,0.08785293884124255 +2864,-0.0,0.5018759815734456,0.41027107958531184,0.08785293884124255 +2865,-0.0,0.3859797847871847,0.5261672763715728,0.08785293884124255 +2866,-0.0,0.3357580995131383,0.5763889616456191,0.08785293884124255 +2867,0.2712668724059696,0.2688337270551896,0.37204646169759825,0.08785293884124255 +2868,0.335997591126595,0.27815087228702623,0.29799859774513615,0.08785293884124255 +2869,0.008279510534033478,0.30598868426019676,0.5978788663645271,0.08785293884124255 +2870,-0.0,0.3509836783066275,0.5611633828521299,0.08785293884124255 +2871,-0.0,0.4990353885149589,0.41311167264379856,0.08785293884124255 +2872,-0.0,0.7048079496717418,0.20733911148701567,0.08785293884124255 +2873,-0.0,0.7865034060338219,0.12564365512493558,0.08785293884124255 +2874,-0.0,0.7803677250274903,0.13177933613126713,0.08785293884124255 +2875,0.3140192540726152,0.6859807459273848,-0.0,-0.0 +2876,0.46560956512301005,0.5343904348769899,-0.0,-0.0 +2877,0.013548289964782054,0.6875371438761421,0.2110616273178333,0.08785293884124255 +2878,-0.0,0.6195901579171381,0.2925569032416193,0.08785293884124255 +2879,-0.0,0.5305091596029925,0.38163790155576494,0.08785293884124255 +2880,-0.0,0.4435870120132968,0.4685600491454607,0.08785293884124255 +2881,-0.0,0.4239301080485682,0.4882169531101892,0.08785293884124255 +2882,-0.0,0.4575627298610518,0.4545843312977056,0.08785293884124255 +2883,0.28707321069821534,0.47210656632050413,0.1529672841400379,0.08785293884124255 +2884,0.3658038290491155,0.47460628821197254,0.07173694389766934,0.08785293884124255 +2885,0.01309668029928932,0.48835475861504857,0.4106956222444196,0.08785293884124255 +2886,-0.0,0.4215440098794393,0.49060305127931814,0.08785293884124255 +2887,-0.0,0.3039434572580863,0.6082036039006711,0.08785293884124255 +2888,-0.0,0.17498053240278613,0.7371665287559713,0.08785293884124255 +2889,-0.0,0.09680741143322973,0.8153396497255277,0.08785293884124255 +2890,-0.0,0.05976607795056202,0.8523809832081954,0.08785293884124255 +2891,0.2998688178871762,0.04181352982092552,0.5704647134506557,0.08785293884124255 +2892,0.43821191208311744,0.05681186116973576,0.41712328790590414,0.08785293884124255 +2893,0.017462240399052427,0.22429322789811676,0.6703915928615882,0.08785293884124255 +2894,-0.0,0.3138287211016203,0.5983183400571371,0.08785293884124255 +2895,-0.0,0.31882816488455706,0.5933188962742004,0.08785293884124255 +2896,-0.0,0.30269359631235215,0.6094534648464053,0.08785293884124255 +2897,-0.0,0.3093973959303809,0.6027496652283766,0.08785293884124255 +2898,-0.0,0.31837366999519917,0.5937733911635583,0.08785293884124255 +2899,0.2504928277933038,0.31530582949203345,0.3463484038734202,0.08785293884124255 +2900,0.3731801202521635,0.23474661035334815,0.3042203305532457,0.08785293884124255 +2901,0.011440778192482626,0.22315699067472203,0.6775492922915527,0.08785293884124255 +2902,-0.0,0.2216798822843089,0.6904671788744485,0.08785293884124255 +2903,-0.0,0.1845249250793017,0.7276221360794557,0.08785293884124255 +2904,-0.0,0.14146153431264202,0.7706855268461155,0.08785293884124255 +2905,-0.0,0.1507786795444787,0.7613683816142787,0.08785293884124255 +2906,-0.0,0.21804392316944582,0.6941031379893117,0.08785293884124255 +2907,0.19855771626163923,0.23554197640972446,0.4780473684873938,0.08785293884124255 +2908,0.30077203721816165,0.3075794163729494,0.3037956075676464,0.08785293884124255 +2909,0.007677364313376499,0.42938404672086283,0.4750856501245181,0.08785293884124255 +2910,-0.0,0.4899454907278012,0.4222015704309563,0.08785293884124255 +2911,-0.0,0.47244743748752255,0.43969962367123494,0.08785293884124255 +2912,-0.0,0.4709703290971094,0.441176732061648,0.08785293884124255 +2913,-0.0,0.45903983825146494,0.45310722290729255,0.08785293884124255 +2914,-0.0,0.44029192406545214,0.4718551370933053,0.08785293884124255 +2915,0.1606225043602495,0.3601871998161247,0.39133735698238326,0.08785293884124255 +2916,0.22715966174284583,0.2634934121052344,0.4214939873106772,0.08785293884124255 +2917,0.009032193309854704,0.231337898683164,0.6717769691657387,0.08785293884124255 +2918,-0.0,0.17532140356980452,0.7368256575889529,0.08785293884124255 +2919,-0.0,0.12294086757130818,0.7892061935874493,0.08785293884124255 diff --git a/PyPSA/source/test/data/benchmark-sp/generators-p-med.csv b/PyPSA/source/test/data/benchmark-sp/generators-p-med.csv new file mode 100644 index 0000000000000000000000000000000000000000..47eb658ef9800545eb59815c8299e32843d0560f --- /dev/null +++ b/PyPSA/source/test/data/benchmark-sp/generators-p-med.csv @@ -0,0 +1,2921 @@ +,solar,wind,gas,lignite +0,-0.0,0.17793474918361238,0.8220652508163876,-0.0 +1,-0.0,0.19827339548237777,0.8017266045176222,-0.0 +2,-0.0,0.22713382095660353,0.7728661790433965,-0.0 +3,0.1901276691724415,0.2786053671763841,0.5312669636511743,-0.0 +4,0.3122128154106443,0.27087895405730006,0.4169082305320557,-0.0 +5,0.0033118042136133915,0.4154083288731078,0.5812798669132787,-0.0 +6,-0.0,0.4972174089575273,0.5027825910424727,-0.0 +7,-0.0,0.628225560814938,0.37177443918506203,-0.0 +8,-0.0,0.7261692094715624,0.2738307905284376,-0.0 +9,-0.0,0.8436561383705761,0.15634386162942393,-0.0 +10,-0.0,0.913307480164672,0.086692519835328,-0.0 +11,0.07933276457155715,0.8883102612499884,0.0323569741784545,-0.0 +12,0.16619235690132655,0.8338076430986734,-0.0,-0.0 +13,0.003913950434270372,0.8326346373036473,0.16345141226208235,-0.0 +14,-0.0,0.8366114675855287,0.16338853241447127,-0.0 +15,-0.0,0.82774881724305,0.17225118275694995,-0.0 +16,-0.0,0.8101371402804318,0.18986285971956818,-0.0 +17,-0.0,0.6534500271743006,0.34654997282569944,-0.0 +18,-0.0,0.5806172211546994,0.4193827788453006,-0.0 +19,0.08294564189549904,0.6051599451800252,0.31189441292447573,-0.0 +20,0.10808524660792797,0.5708455810335048,0.32106917235856725,-0.0 +21,0.0024085848826279213,0.45597199774829916,0.5416194173690728,-0.0 +22,-0.0,0.4273388197187523,0.5726611802812477,-0.0 +23,-0.0,0.46438015320142007,0.53561984679858,-0.0 +24,-0.0,0.5530066566262078,0.44699334337379215,-0.0 +25,-0.0,0.556642615741071,0.443357384258929,-0.0 +26,-0.0,0.5122157403063375,0.48778425969366246,-0.0 +27,0.21903068776397655,0.47858311849385404,0.3023861937421694,-0.0 +28,0.2458261945832122,0.5066481779117035,0.24752562750508433,-0.0 +29,0.005569852541077068,0.48278719622041444,0.5116429512385084,-0.0 +30,-0.0,0.38643427967654265,0.6135657203234574,-0.0 +31,-0.0,0.28814975985289976,0.7118502401471003,-0.0 +32,-0.0,0.2706517066126211,0.7293482933873789,-0.0 +33,-0.0,0.31848729371753864,0.6815127062824613,-0.0 +34,-0.0,0.36643650454479565,0.6335634954552043,-0.0 +35,0.1950953754928616,0.37393567021920077,0.4309689542879377,-0.0 +36,0.27247116484728356,0.2871271463518445,0.4404016888008719,-0.0 +37,0.01821492317487365,0.18009359990806234,0.801691476917064,-0.0 +38,-0.0,0.1085106548341953,0.8914893451658047,-0.0 +39,-0.0,0.0689695994600592,0.9051023903737102,0.025928010166230586 +40,-0.0,0.044994994046430724,0.9051023903737102,0.04990261557985907 +41,-0.0,0.03954105537413608,0.9051023903737102,0.05535655425215371 +42,-0.0,0.06874235201538026,0.9051023903737102,0.026155257610909532 +43,0.1816976220832438,0.0829453173078142,0.735357060608942,-0.0 +44,0.27593350561606117,0.10703354644378217,0.6170329479401566,-0.0 +45,0.009032193309854704,0.24281389463945063,0.7481539120506946,-0.0 +46,-0.0,0.2723560624477132,0.7276439375522867,-0.0 +47,-0.0,0.26360703582757394,0.7363929641724261,-0.0 +48,-0.0,0.24031417274798225,0.7596858272520177,-0.0 +49,-0.0,0.24906319936812155,0.7509368006318784,-0.0 +50,-0.0,0.23701908480013759,0.7629809151998624,-0.0 +51,0.1353323630926563,0.19259220936540422,0.6720754275419395,-0.0 +52,0.2011168376994314,0.1965690396472857,0.6023141226532829,-0.0 +53,0.008731120199526213,0.26587951027436335,0.7253893695261103,-0.0 +54,-0.0,0.3039434572580863,0.6960565427419136,-0.0 +55,-0.0,0.33053140828552263,0.6694685917144774,-0.0 +56,-0.0,0.3956378011860398,0.6043621988139602,-0.0 +57,-0.0,0.46438015320142007,0.53561984679858,-0.0 +58,-0.0,0.49517218195541685,0.5048278180445831,-0.0 +59,0.0578060371830701,0.49210434145225107,0.4500896213646789,-0.0 +60,0.08264456878517054,0.520510272037119,0.39684515917771046,-0.0 +61,0.0024085848826279213,0.4792648608278908,0.5183265542894812,-0.0 +62,-0.0,0.526305081876432,0.473694918123568,-0.0 +63,-0.0,0.6064098061257595,0.39359019387424055,-0.0 +64,-0.0,0.6312934013181037,0.36870659868189626,-0.0 +65,-0.0,0.7278735653066545,0.2721264346933455,-0.0 +66,-0.0,0.9706874599461051,0.029312540053894942,-0.0 +67,0.07120379059268792,0.9287962094073121,-0.0,-0.0 +68,0.07647257002343649,0.9235274299765635,-0.0,-0.0 +69,0.004064486989434617,0.9959355130105654,-0.0,-0.0 +70,-0.0,0.9478490917558714,0.0521509082441286,-0.0 +71,-0.0,0.7083302850642653,0.2916697149357347,-0.0 +72,-0.0,0.6435647633307666,0.3564352366692334,-0.0 +73,-0.0,0.7551432586681276,0.2448567413318724,-0.0 +74,-0.0,0.9465992308101371,0.05340076918986292,-0.0 +75,0.13006358366190773,0.8699364163380923,-0.0,-0.0 +76,0.18380513385554323,0.8161948661444568,-0.0,-0.0 +77,0.013247216854453566,0.9867527831455465,-0.0,-0.0 +78,-0.0,1.0,-0.0,-0.0 +79,-0.0,0.9096715210498089,0.09032847895019114,-0.0 +80,-0.0,0.8430880197588786,0.1569119802411214,-0.0 +81,-0.0,0.7940025717082269,0.20599742829177314,-0.0 +82,-0.0,0.8470648500407602,0.15293514995923985,-0.0 +83,0.15098816482973781,0.8490118351702622,-0.0,-0.0 +84,0.2274607348531743,0.7725392651468257,-0.0,-0.0 +85,0.02408584882627921,0.9759141511737208,-0.0,-0.0 +86,-0.0,0.945122122419724,0.05487787758027596,-0.0 +87,-0.0,0.8692214758969571,0.13077852410304291,-0.0 +88,-0.0,0.7736639254094615,0.2263360745905385,-0.0 +89,-0.0,0.7138978474588995,0.2861021525411005,-0.0 +90,-0.0,0.7763908947456089,0.22360910525439115,-0.0 +91,0.07210700992367339,0.9060355619349458,0.02185742814138078,-0.0 +92,0.08971978687789006,0.9102802131221099,-0.0,-0.0 +93,0.009634339530511685,0.9586433453781211,0.03172231509136719,-0.0 +94,-0.0,0.9217156356177929,0.0782843643822071,-0.0 +95,-0.0,0.8264989562973158,0.17350104370268415,-0.0 +96,-0.0,0.733554751423628,0.266445248576372,-0.0 +97,-0.0,0.6967406653856393,0.3032593346143607,-0.0 +98,-0.0,0.7837764366976745,0.2162235633023255,-0.0 +99,0.3659543656042798,0.6340456343957201,-0.0,-0.0 +100,0.5089640930103125,0.49103590698968747,-0.0,-0.0 +101,0.04531150310443776,0.7121934916238074,0.24249500527175483,-0.0 +102,-0.0,0.5816398346557546,0.41836016534424536,-0.0 +103,-0.0,0.5376674541103792,0.4623325458896208,-0.0 +104,-0.0,0.4890365009490854,0.5109634990509146,-0.0 +105,-0.0,0.44881370324091246,0.5511862967590875,-0.0 +106,-0.0,0.5356222271082687,0.4643777728917313,-0.0 +107,0.2157188835503632,0.7001493770558234,0.08413173939381344,-0.0 +108,0.25922394799283,0.6195901579171381,0.12118589409003189,-0.0 +109,0.018515996285202143,0.44449600179201254,0.5369880019227853,-0.0 +110,-0.0,0.4059775599189317,0.5940224400810683,-0.0 +111,-0.0,0.4174535558752183,0.5825464441247816,-0.0 +112,-0.0,0.3773443818893849,0.6226556181106151,-0.0 +113,-0.0,0.4917634702852327,0.5082365297147673,-0.0 +114,-0.0,0.6725388125273318,0.32746118747266817,-0.0 +115,0.13141841265838594,0.7830946943636377,0.0854868929779764,-0.0 +116,0.1920846443895767,0.8079153556104233,-0.0,-0.0 +117,0.021827800498815535,0.8102507640027713,0.16792143549841318,-0.0 +118,-0.0,0.8007063713262558,0.19929362867374423,-0.0 +119,-0.0,0.7897984939816666,0.21020150601833343,-0.0 +120,-0.0,0.6589039658465953,0.3410960341534047,-0.0 +121,-0.0,0.588002763106765,0.411997236893235,-0.0 +122,-0.0,0.5066481779117035,0.49335182208829653,-0.0 +123,0.23017039284613072,0.31723743277180444,0.4525921743820649,-0.0 +124,0.32290091082730565,0.21474883522160115,0.4623502539510932,-0.0 +125,0.023483702605622228,0.1803208473527413,0.7961954500416364,-0.0 +126,-0.0,0.15441463865934177,0.8455853613406582,-0.0 +127,-0.0,0.1124874851160768,0.8875125148839232,-0.0 +128,-0.0,0.09419406581942188,0.9051023903737102,0.0007035438068679067 +129,-0.0,0.12271362012662923,0.8772863798733708,-0.0 +130,-0.0,0.1487334525423682,0.8512665474576318,-0.0 +131,0.25636375344470935,0.12305449129364764,0.620581755261643,-0.0 +132,0.37995426523455456,0.15384652004764443,0.466199214717801,-0.0 +133,0.041999698890824375,0.1628227941124627,0.795177506996713,-0.0 +134,-0.0,0.20429545276636976,0.7957045472336303,-0.0 +135,-0.0,0.20838590677059077,0.7916140932294092,-0.0 +136,-0.0,0.2065679272131592,0.7934320727868408,-0.0 +137,-0.0,0.18895625025054114,0.8110437497494589,-0.0 +138,-0.0,0.16702687183902312,0.8329731281609769,-0.0 +139,0.2815033581571383,0.13464411097227375,0.583852530870588,-0.0 +140,0.43309366920753306,0.14191602920199992,0.4249903015904671,-0.0 +141,0.05208564808682879,0.14430212737112882,0.8036122245420424,-0.0 +142,-0.0,0.1711173258432441,0.8288826741567559,-0.0 +143,-0.0,0.1491879474317261,0.8508120525682739,-0.0 +144,-0.0,0.10317033988424014,0.8968296601157598,-0.0 +145,-0.0,0.04601760754748596,0.9051023903737102,0.04888000207880383 +146,-0.0,0.03647321487097035,0.9051023903737102,0.05842439475531944 +147,0.19464376582736886,0.02022502257642593,0.7851312115962052,-0.0 +148,0.27773994427803217,0.016134568572204955,0.7061254871497629,-0.0 +149,0.038085748456554,0.05681186116973576,0.9051023903737102,-0.0 +150,-0.0,0.06533364034519612,0.9051023903737102,0.02956396928109367 +151,-0.0,0.044426875434733365,0.9051023903737102,0.05047073419155643 +152,-0.0,0.031132899921015193,0.9051023903737102,0.06376470970527459 +153,-0.0,0.026133456138078447,0.9051023903737102,0.06876415348821134 +154,-0.0,0.023633734246610073,0.9051023903737102,0.07126387537967972 +155,0.1279560718896083,0.011021501066928736,0.8610224270434629,-0.0 +156,0.16393430857386287,0.008749026620139306,0.8273166648059977,-0.0 +157,0.020322434947173083,0.017043558350920727,0.9051023903737102,0.057531616328195985 +158,-0.0,0.02329286307959166,0.9051023903737102,0.07160474654669813 +159,-0.0,0.0367004623156493,0.9051023903737102,0.05819714731064049 +160,-0.0,0.03931380792945714,0.9051023903737102,0.05558380169683265 +161,-0.0,0.03340537436780462,0.9051023903737102,0.06149223525848517 +162,-0.0,0.03215551342207044,0.9051023903737102,0.06274209620421936 +163,0.17989118342127286,0.02726969336147316,0.792839123217254,-0.0 +164,0.24808424291067588,0.03363262181248357,0.7182831352768405,-0.0 +165,0.02995677447768477,0.07283280601960124,0.897210419502714,-0.0 +166,-0.0,0.12907654857763964,0.8709234514223604,-0.0 +167,-0.0,0.10623818038740586,0.8937618196125942,-0.0 +168,-0.0,0.0810137140280432,0.9051023903737102,0.013883895598246598 +169,-0.0,0.08203632752909844,0.9051023903737102,0.012861282097191354 +170,-0.0,0.09317145231836664,0.9051023903737102,0.0017261573079231513 +171,0.11576261092130445,0.10157960777148753,0.782657781307208,-0.0 +172,0.13789148453044847,0.09612566909919289,0.7659828463703586,-0.0 +173,0.024687995046936192,0.11782780006603195,0.8574842048870318,-0.0 +174,-0.0,0.1072607938884611,0.8927392061115389,-0.0 +175,-0.0,0.07601427024510644,0.9051023903737102,0.018883339381183348 +176,-0.0,0.07260555857492229,0.9051023903737102,0.0222920510513675 +177,-0.0,0.06487914545583823,0.9051023903737102,0.030018464170451564 +178,-0.0,0.06056144400693832,0.9051023903737102,0.034336165619351475 +179,0.11546153781097597,0.0454494889357886,0.8390889732532354,-0.0 +180,0.11395617225933352,0.027496940806152104,0.8585468869345144,-0.0 +181,0.015505265181917242,0.030337533864638895,0.9051023903737102,0.04905481057973366 +182,-0.0,0.04919907177299116,0.9051023903737102,0.04569853785329863 +183,-0.0,0.08987636437052197,0.9051023903737102,0.005021245255767817 +184,-0.0,0.1671404955613626,0.8328595044386374,-0.0 +185,-0.0,0.19168321958668844,0.8083167804133116,-0.0 +186,-0.0,0.22031639761623525,0.7796836023837648,-0.0 +187,0.10070895540487995,0.18838813163884377,0.7109029129562763,-0.0 +188,0.12268729245885973,0.1406661682562657,0.7366465392848744,-0.0 +189,0.01896760595069488,0.17452603751342824,0.8065063565358769,-0.0 +190,-0.0,0.1994096327057725,0.8005903672942275,-0.0 +191,-0.0,0.18520666741333858,0.8147933325866614,-0.0 +192,-0.0,0.2006594936515067,0.7993405063484933,-0.0 +193,-0.0,0.20327283926531453,0.7967271607346855,-0.0 +194,-0.0,0.17475328495810719,0.8252467150418928,-0.0 +195,0.1266012428931301,0.12066839312451874,0.7527303639823512,-0.0 +196,0.1539988959330227,0.09930713332469811,0.7466939707422792,-0.0 +197,0.03281696902580542,0.1088515260012137,0.8583315049729808,-0.0 +198,-0.0,0.09328507604070611,0.9051023903737102,0.001612533585583678 +199,-0.0,0.07419629068767489,0.9051023903737102,0.020701318938614904 +200,-0.0,0.0849905443099247,0.9051023903737102,0.009907065316365093 +201,-0.0,0.1191912847341056,0.8808087152658944,-0.0 +202,-0.0,0.1802072236304018,0.8197927763695982,-0.0 +203,0.09619285874995259,0.2133853505535275,0.69042179069652,-0.0 +204,0.11982709791073907,0.3234867375004754,0.5566861645887855,-0.0 +205,0.02815033581571383,0.40211435335938966,0.5697353108248966,-0.0 +206,-0.0,0.4255208401613208,0.5744791598386791,-0.0 +207,-0.0,0.3991601365785634,0.6008398634214366,-0.0 +208,-0.0,0.45483576052490443,0.5451642394750955,-0.0 +209,-0.0,0.42233937593581555,0.5776606240641844,-0.0 +210,-0.0,0.43881481567503894,0.5611851843249611,-0.0 +211,0.18786962084497782,0.3516654206406643,0.46046495851435787,-0.0 +212,0.21827800498815533,0.3476885903587828,0.43403340465306195,-0.0 +213,0.05359101363847124,0.336894336736533,0.6095146496249957,-0.0 +214,-0.0,0.24656347747665316,0.7534365225233468,-0.0 +215,-0.0,0.15055143209979976,0.8494485679002002,-0.0 +216,-0.0,0.15043780837746026,0.8495621916225398,-0.0 +217,-0.0,0.20554531371210397,0.7944546862878961,-0.0 +218,-0.0,0.30837478242932564,0.6916252175706743,-0.0 +219,0.11546153781097597,0.44983631674196767,0.4347021454470564,-0.0 +220,0.15143977449523052,0.7344637412023438,0.11409648430242567,-0.0 +221,0.027397653039892604,0.9096715210498089,0.06293082591029853,-0.0 +222,-0.0,0.9061491856572853,0.0938508143427147,-0.0 +223,-0.0,0.8259308376856184,0.17406916231438163,-0.0 +224,-0.0,0.7278735653066545,0.2721264346933455,-0.0 +225,-0.0,0.6403832991052614,0.35961670089473863,-0.0 +226,-0.0,0.6232261170320013,0.37677388296799874,-0.0 +227,0.2539551685620814,0.6870826489867843,0.058962182451134326,-0.0 +228,0.2298693197358022,0.6857191643187106,0.08441151594548718,-0.0 +229,0.06292428005865443,0.46631175648119105,0.47076396346015453,-0.0 +230,-0.0,0.3524607866970406,0.6475392133029594,-0.0 +231,-0.0,0.287013522629505,0.712986477370495,-0.0 +232,-0.0,0.21247636077481172,0.7875236392251883,-0.0 +233,-0.0,0.1646407736698942,0.8353592263301057,-0.0 +234,-0.0,0.19213771447604633,0.8078622855239537,-0.0 +235,0.15595587115015788,0.1528239065465892,0.691220222303253,-0.0 +236,0.1803427930867656,0.14532474087218408,0.6743324660410503,-0.0 +237,0.055246915745277944,0.10317033988424014,0.8415827443704819,-0.0 +238,-0.0,0.13975717847754995,0.86024282152245,-0.0 +239,-0.0,0.14055254453392627,0.8594474554660737,-0.0 +240,-0.0,0.1568007368284707,0.8431992631715293,-0.0 +241,-0.0,0.17429879006874932,0.8257012099312506,-0.0 +242,-0.0,0.1730489291230151,0.8269510708769849,-0.0 +243,0.19088035194826275,0.13964355475521048,0.6694760932965267,-0.0 +244,0.20563293435435875,0.16770861417305996,0.6266584514725813,-0.0 +245,0.08189188600934931,0.1579369740518654,0.7601711399387853,-0.0 +246,-0.0,0.19088785353031215,0.8091121464696879,-0.0 +247,-0.0,0.17861649151764922,0.8213835084823508,-0.0 +248,-0.0,0.1439612562041104,0.8560387437958896,-0.0 +249,-0.0,0.11464633584052676,0.8853536641594733,-0.0 +250,-0.0,0.11521445445222411,0.8847855455477759,-0.0 +251,0.1947943023825331,0.08362705964185103,0.7215786379756159,-0.0 +252,0.20412756880271632,0.13964355475521048,0.6562288764420732,-0.0 +253,0.04455882032861654,0.17020833606452831,0.7852328436068551,-0.0 +254,-0.0,0.2423593997500927,0.7576406002499073,-0.0 +255,-0.0,0.23940518296926647,0.7605948170307335,-0.0 +256,-0.0,0.2397460541362849,0.7602539458637151,-0.0 +257,-0.0,0.23735995596715598,0.762640044032844,-0.0 +258,-0.0,0.24758609097770842,0.7524139090222915,-0.0 +259,0.19614913137901133,0.2308834037938061,0.5729674648271825,-0.0 +260,0.1959985948238471,0.30008025069854427,0.5039211544776087,-0.0 +261,0.05238672119715728,0.263379788382895,0.6842334904199477,-0.0 +262,-0.0,0.2456544876979374,0.7543455123020626,-0.0 +263,-0.0,0.2270201972342641,0.772979802765736,-0.0 +264,-0.0,0.19759165314834096,0.8024083468516591,-0.0 +265,-0.0,0.17793474918361238,0.8220652508163876,-0.0 +266,-0.0,0.15407376749232338,0.8459262325076766,-0.0 +267,0.18320298763488624,0.13418961608291585,0.6826073962821979,-0.0 +268,0.2574175093308591,0.12100926429153716,0.6215732263776037,-0.0 +269,0.07526827758212254,0.09123984903859562,0.8334918733792819,-0.0 +270,-0.0,0.09703465887790867,0.9029653411220914,-0.0 +271,-0.0,0.0773777549131801,0.9051023903737102,0.017519854713109698 +272,-0.0,0.06771973851432501,0.9051023903737102,0.027177871111964777 +273,-0.0,0.05749360350377258,0.9051023903737102,0.03740400612251721 +274,-0.0,0.052494159720835834,0.9051023903737102,0.04240344990545396 +275,0.20834259234731517,0.0406772925975308,0.750980115055154,-0.0 +276,0.24627780424870493,0.0554483765016621,0.6982738192496329,-0.0 +277,0.06834359604456726,0.06374290823244351,0.8679134957229891,-0.0 +278,-0.0,0.06453827428881982,0.9051023903737102,0.03035933533746997 +279,-0.0,0.03158739481037308,0.9051023903737102,0.06331021481591671 +280,-0.0,0.026701574749775806,0.9051023903737102,0.06819603487651399 +281,-0.0,0.03306450320078621,0.9051023903737102,0.06183310642550358 +282,-0.0,0.04942631921767011,0.9051023903737102,0.045471290408619684 +283,0.20819205579215094,0.07771862608019851,0.7140893181276505,-0.0 +284,0.24462190214189825,0.1431658901477341,0.6122122077103677,-0.0 +285,0.06171998761734048,0.19600092103558833,0.7422790913470713,-0.0 +286,-0.0,0.2819004551242288,0.7180995448757712,-0.0 +287,-0.0,0.23531472896504552,0.7646852710349545,-0.0 +288,-0.0,0.1873655181377885,0.8126344818622115,-0.0 +289,-0.0,0.18543391485801752,0.8145660851419825,-0.0 +290,-0.0,0.19145597214200952,0.8085440278579905,-0.0 +291,0.26103038665480094,0.22940629540339297,0.5095633179418061,-0.0 +292,0.3998250905162349,0.21633956733435378,0.38383534214941134,-0.0 +293,0.11395617225933352,0.1774802542942545,0.708563573446412,-0.0 +294,-0.0,0.15180129304553394,0.8481987069544661,-0.0 +295,-0.0,0.10407932966295591,0.8959206703370441,-0.0 +296,-0.0,0.08874012714712726,0.9051023903737102,0.006157482479162535 +297,-0.0,0.11260110883841626,0.8873988911615838,-0.0 +298,-0.0,0.15521000471571808,0.8447899952842819,-0.0 +299,0.31778266795172133,0.18759276558246746,0.49462456646581127,-0.0 +300,0.3913950434270372,0.3136014736569414,0.29500348291602135,-0.0 +301,0.08941871376756157,0.4676752411492648,0.4429060450831736,-0.0 +302,-0.0,0.6056144400693831,0.39438555993061686,-0.0 +303,-0.0,0.7404857984863358,0.2595142015136642,-0.0 +304,-0.0,0.8235447395164895,0.17645526048351046,-0.0 +305,-0.0,0.7879805144242349,0.2120194855757651,-0.0 +306,-0.0,0.6478824647796666,0.3521175352203334,-0.0 +307,0.3102558401935091,0.5598240799665761,0.12992007983991477,-0.0 +308,0.44498605706550837,0.5550139429344916,-0.0,-0.0 +309,0.16182679680156345,0.4173399321528789,0.4208332710455577,-0.0 +310,-0.0,0.3484839564151591,0.6515160435848408,-0.0 +311,-0.0,0.3760945209436507,0.6239054790563493,-0.0 +312,-0.0,0.4574491061387123,0.5425508938612877,-0.0 +313,-0.0,0.5273276953774872,0.47267230462251275,-0.0 +314,-0.0,0.5370993354986818,0.46290066450131817,-0.0 +315,0.1098916852698989,0.5105113844712454,0.37959693025885566,-0.0 +316,0.1129024163731838,0.49551305312243527,0.391584530504381,-0.0 +317,0.031462140029327214,0.4474502185728388,0.5210876413978339,-0.0 +318,-0.0,0.3875705168999373,0.6124294831000627,-0.0 +319,-0.0,0.32337311377813593,0.6766268862218641,-0.0 +320,-0.0,0.28337756351464194,0.7166224364853581,-0.0 +321,-0.0,0.28553641423909193,0.7144635857609081,-0.0 +322,-0.0,0.24474549791922165,0.7552545020807784,-0.0 +323,0.12885929122059378,0.159527706164618,0.7116130026147882,-0.0 +324,0.136988265199463,0.179525481296365,0.6834862535041719,-0.0 +325,0.040945943004674655,0.18407043018994385,0.7749836268053815,-0.0 +326,-0.0,0.1667996243943442,0.8332003756056559,-0.0 +327,-0.0,0.11078312928098473,0.8892168707190152,-0.0 +328,-0.0,0.07453716185469332,0.9051023903737102,0.02036044777159647 +329,-0.0,0.03715495720500719,0.9051023903737102,0.057742652421282605 +330,-0.0,0.026133456138078447,0.9051023903737102,0.06876415348821134 +331,0.1175690495832754,0.013862094125415525,0.8685688562913091,-0.0 +332,0.1847083531865287,0.009317145231836665,0.8059745015816346,-0.0 +333,0.09995627262905872,0.014657460181791825,0.8853862671891495,-0.0 +334,-0.0,0.06226579984203039,0.9051023903737102,0.032631809784259405 +335,-0.0,0.08839925598010882,0.9051023903737102,0.006498353646180968 +336,-0.0,0.0646518980111593,0.9051023903737102,0.030245711615130497 +337,-0.0,0.04181352982092552,0.9051023903737102,0.053084079805364275 +338,-0.0,0.043404261933678114,0.9051023903737102,0.05149334769261168 +339,0.22204141886726148,0.02090676491046276,0.7570518162222758,-0.0 +340,0.3013741834388186,0.01272585690202081,0.6858999596591606,-0.0 +341,0.12584856011730886,0.016134568572204955,0.8580168713104862,-0.0 +342,-0.0,0.07760500235785904,0.9051023903737102,0.01729260726843075 +343,-0.0,0.0813545851950616,0.9051023903737102,0.013543024431228193 +344,-0.0,0.06476552173349877,0.9051023903737102,0.030132087892791024 +345,-0.0,0.07465078557703278,0.9051023903737102,0.020246824049257012 +346,-0.0,0.09226246253965086,0.9051023903737102,0.0026351470866389365 +347,0.36761026771108646,0.06760611479198554,0.5647836174969281,-0.0 +348,0.5794152008271793,0.07715050746850116,0.3434342917043195,-0.0 +349,0.22685858863251732,0.13714383286374213,0.6359975785037406,-0.0 +350,-0.0,0.2720151912806948,0.7279848087193053,-0.0 +351,-0.0,0.21270360821949066,0.7872963917805094,-0.0 +352,-0.0,0.16134568572204952,0.8386543142779505,-0.0 +353,-0.0,0.13532585330631056,0.8646741466936895,-0.0 +354,-0.0,0.13009916207869487,0.8699008379213051,-0.0 +355,0.49451258371454504,0.052607783443175314,0.45287963284227967,-0.0 +356,0.7008982008447251,0.04272251959964129,0.25637927955563367,-0.0 +357,0.24928853535198983,0.0972619063225876,0.6534495583254226,-0.0 +358,-0.0,0.19213771447604633,0.8078622855239537,-0.0 +359,-0.0,0.14657460181791826,0.8534253981820817,-0.0 +360,-0.0,0.13055365696805277,0.8694463430319472,-0.0 +361,-0.0,0.13828007008713683,0.8617199299128632,-0.0 +362,0.00015053655516424508,0.1650952685592521,0.8347541948855836,-0.0 +363,0.38853484887891654,0.1774802542942545,0.43398489682682895,-0.0 +364,0.588447394137034,0.2045227002110487,0.20702990565191737,-0.0 +365,0.20758990957149395,0.2600847004350503,0.5323253899934558,-0.0 +366,-0.0,0.36711824687883243,0.6328817531211676,-0.0 +367,-0.0,0.25065393148087417,0.7493460685191258,-0.0 +368,-0.0,0.17407154262407035,0.8259284573759297,-0.0 +369,-0.0,0.16305004155714162,0.8369499584428584,-0.0 +370,0.00015053655516424508,0.20361371043233295,0.7962357530125027,-0.0 +371,0.35827700129090323,0.24179128113839538,0.39993171757070145,-0.0 +372,0.5333510149469203,0.2240659804534378,0.24258300459964188,-0.0 +373,0.3099547670831806,0.2105447574950407,0.47950047542177865,-0.0 +374,-0.0,0.26326616466055547,0.7367338353394446,-0.0 +375,-0.0,0.15577812332741545,0.8442218766725845,-0.0 +376,-0.0,0.0865812764226773,0.9051023903737102,0.008316333203612497 +377,-0.0,0.05328952577721213,0.9051023903737102,0.04160808384907766 +378,0.00030107311032849017,0.043404261933678114,0.9051023903737102,0.05119227458228319 +379,0.3522555390843335,0.03579147253693352,0.611952988378733,-0.0 +380,0.3462340768777637,0.038518441873080844,0.6152474812491555,-0.0 +381,0.10928953904924192,0.06090231517395673,0.8298081457768014,-0.0 +382,-0.0,0.1128283562830952,0.8871716437169048,-0.0 +383,-0.0,0.09646654026621132,0.9035334597337887,-0.0 +384,-0.0,0.07215106368556441,0.9051023903737102,0.02274654594072538 +385,-0.0,0.065788135234554,0.9051023903737102,0.029109474391735793 +386,-0.0,0.07442353813235385,0.9051023903737102,0.020474071493935944 +387,0.21767585876749837,0.1085106548341953,0.6738134863983063,-0.0 +388,0.4249646952286638,0.16907209884113358,0.4059632059302026,-0.0 +389,0.1803427930867656,0.17259443423365722,0.6470627726795772,-0.0 +390,-0.0,0.2914448478007444,0.7085551521992556,-0.0 +391,-0.0,0.30905652476336254,0.6909434752366375,-0.0 +392,-0.0,0.28462742446037614,0.7153725755396239,-0.0 +393,-0.0,0.2277019395683009,0.7722980604316991,-0.0 +394,0.0007526827758212254,0.22588396001086936,0.7733633572133095,-0.0 +395,0.41563142880848064,0.30837478242932564,0.27599378876219366,-0.0 +396,0.6015440744363233,0.30826115870698617,0.09019476685669059,-0.0 +397,0.2625357522064434,0.2819004551242288,0.45556379266932784,-0.0 +398,-0.0,0.3655275147660798,0.6344724852339202,-0.0 +399,-0.0,0.3279180626717148,0.6720819373282851,-0.0 +400,-0.0,0.30292084375703104,0.697079156242969,-0.0 +401,-0.0,0.3155330769367124,0.6844669230632876,-0.0 +402,0.0010537558861497154,0.3729130567181455,0.6260331873957047,-0.0 +403,0.4446849839551799,0.5409625420582239,0.01435247398659617,-0.0 +404,0.5761033966135658,0.4238966033864342,-0.0,-0.0 +405,0.21707371254684138,0.3987056416892055,0.3842206457639531,-0.0 +406,-0.0,0.39666041468709506,0.6033395853129049,-0.0 +407,-0.0,0.3810939647265874,0.6189060352734126,-0.0 +408,-0.0,0.41086337997952893,0.5891366200204711,-0.0 +409,-0.0,0.39825114679984763,0.6017488532001524,-0.0 +410,0.002709657992956411,0.3895021201797083,0.6077882218273353,-0.0 +411,0.3027290124352968,0.3239412323898333,0.3733297551748699,-0.0 +412,0.3346427621301168,0.4690387258173384,0.1963185120525448,-0.0 +413,0.1542999690433512,0.2880361361305603,0.5576638948260885,-0.0 +414,-0.0,0.1927058330877437,0.8072941669122563,-0.0 +415,-0.0,0.15895958755292064,0.8410404124470794,-0.0 +416,-0.0,0.10794253622249794,0.8920574637775021,-0.0 +417,-0.0,0.08885375086946673,0.9051023903737102,0.006043858756823062 +418,0.0018064386619709406,0.09351232348538505,0.904681237852644,-0.0 +419,0.40494333339181926,0.09805727237896392,0.49699939422921685,-0.0 +420,0.33313739657847435,0.12430435223938183,0.5425582511821438,-0.0 +421,0.199009325927132,0.10112511288212964,0.6998655611907383,-0.0 +422,-0.0,0.23008803773742983,0.7699119622625702,-0.0 +423,-0.0,0.37552640233195334,0.6244735976680467,-0.0 +424,-0.0,0.47551527799068827,0.5244847220093117,-0.0 +425,-0.0,0.5619829306910261,0.4380170693089739,-0.0 +426,0.0006021462206569803,0.5580061004091446,0.4413917533701985,-0.0 +427,0.15023548205391657,0.49028636189481956,0.3594781560512639,-0.0 +428,0.18229976830390077,0.32905429989510954,0.48864593180098964,-0.0 +429,0.07662310657860075,0.2198619027268774,0.703514990694522,-0.0 +430,-0.0,0.29212659013478126,0.7078734098652187,-0.0 +431,-0.0,0.2883770072975787,0.7116229927024214,-0.0 +432,-0.0,0.29803502369643375,0.7019649763035662,-0.0 +433,-0.0,0.32428210355685166,0.6757178964431483,-0.0 +434,0.0030107311032849015,0.36234605054057467,0.6346432183561405,-0.0 +435,0.41472820947749517,0.48562778927890127,0.09964400124360356,-0.0 +436,0.37001885259371436,0.47278830865454097,0.1571928387517446,-0.0 +437,0.15746123670180034,0.3179191751058413,0.5246195881923584,-0.0 +438,-0.0,0.33814419768226717,0.6618558023177328,-0.0 +439,-0.0,0.25974382926803186,0.7402561707319681,-0.0 +440,-0.0,0.15975495360929695,0.8402450463907031,-0.0 +441,-0.0,0.12339536246066607,0.8766046375393339,-0.0 +442,0.0024085848826279213,0.1085106548341953,0.8890807602831767,-0.0 +443,0.5217597001992734,0.10760166505547952,0.37063863474524705,-0.0 +444,0.49963082659012936,0.11362372233947152,0.38674545107039915,-0.0 +445,0.2798474560503316,0.052039664831477955,0.6681128791181905,-0.0 +446,-0.0,0.058970711894185714,0.9051023903737102,0.03592689773210408 +447,-0.0,0.07362817207597754,0.9051023903737102,0.021269437550312256 +448,-0.0,0.06953771807175656,0.9051023903737102,0.025359891554533234 +449,-0.0,0.08783113736841147,0.9051023903737102,0.00706647225787832 +450,0.008128973978869233,0.10351121105125854,0.8883598149698722,-0.0 +451,0.6721457188083543,0.13555310075098953,0.19230118044065614,-0.0 +452,0.7892631587261368,0.21073684127386316,-0.0,-0.0 +453,0.3605350496183669,0.24554086397559793,0.39392408640603516,-0.0 +454,-0.0,0.4113178748688869,0.5886821251311132,-0.0 +455,-0.0,0.36666375198947454,0.6333362480105255,-0.0 +456,-0.0,0.2563351175978477,0.7436648824021523,-0.0 +457,-0.0,0.18975161630691745,0.8102483836930825,-0.0 +458,0.00797843742370499,0.13543947702865003,0.856582085547645,-0.0 +459,0.4338463519833543,0.10044337054809283,0.46571027746855287,-0.0 +460,0.4990286803694724,0.13668933797438423,0.3642819816561434,-0.0 +461,0.20096630114426717,0.12703132157552915,0.6720023772802036,-0.0 +462,-0.0,0.15566449960507597,0.844335500394924,-0.0 +463,-0.0,0.12691769785318968,0.8730823021468104,-0.0 +464,-0.0,0.09907988588001916,0.9009201141199809,-0.0 +465,-0.0,0.08749026620139307,0.9051023903737102,0.007407343424896726 +466,0.002709657992956411,0.08669490014501677,0.9051023903737102,0.005493051488316613 +467,0.5896516865783479,0.13134902302442905,0.27899929039722304,-0.0 +468,0.5795657373823435,0.18213882691017283,0.23829543570748368,-0.0 +469,0.38326606944816793,0.18077534224209915,0.43595858830973294,-0.0 +470,-0.0,0.35223353925236167,0.6477664607476383,-0.0 +471,-0.0,0.4657436378694937,0.5342563621305063,-0.0 +472,-0.0,0.47483353565665143,0.5251664643433486,-0.0 +473,-0.0,0.5439167588390501,0.4560832411609499,-0.0 +474,0.0007526827758212254,0.6145907141342014,0.38465660308997734,-0.0 +475,0.1457193853989892,0.6703799618028818,0.18390065279812895,-0.0 +476,0.24100902481795633,0.6656077654646241,0.09338320971741959,-0.0 +477,0.14150436185439036,0.49596754801179316,0.36252809013381654,-0.0 +478,-0.0,0.4357469751718733,0.5642530248281268,-0.0 +479,-0.0,0.378821490279798,0.621178509720202,-0.0 +480,-0.0,0.39950100774558184,0.6004989922544182,-0.0 +481,-0.0,0.5041484560202351,0.49585154397976494,-0.0 +482,0.005118242875584332,0.6317478962074616,0.36313386091695404,-0.0 +483,0.3573737819599178,0.6426262180400821,-0.0,-0.0 +484,0.43505064442466823,0.5649493555753318,-0.0,-0.0 +485,0.2312241487322804,0.6365200925457194,0.1322557587220002,-0.0 +486,-0.0,0.5731180554802943,0.42688194451970574,-0.0 +487,-0.0,0.594479315280115,0.405520684719885,-0.0 +488,-0.0,0.6411786651616378,0.3588213348383622,-0.0 +489,-0.0,0.5911842273322703,0.4088157726677297,-0.0 +490,0.01399989963027479,0.48449155205550654,0.5015085483142186,-0.0 +491,0.5789635911616865,0.4210364088383135,-0.0,-0.0 +492,0.5499100360149872,0.4500899639850128,-0.0,-0.0 +493,0.21165439656092858,0.32689544917065955,0.46145015426841196,-0.0 +494,-0.0,0.36223242681823514,0.6377675731817649,-0.0 +495,-0.0,0.3379169502375883,0.6620830497624117,-0.0 +496,-0.0,0.3902974862360847,0.6097025137639154,-0.0 +497,-0.0,0.35836922025869317,0.6416307797413068,-0.0 +498,0.01159131474764687,0.33837144512694617,0.650037240125407,-0.0 +499,0.4094594300467466,0.5576652292421262,0.03287534071112713,-0.0 +500,0.2846646258155874,0.6035692130672726,0.11176616111714,-0.0 +501,0.1833535241900505,0.43006578905489967,0.38658068675504986,-0.0 +502,-0.0,0.44029192406545214,0.5597080759345479,-0.0 +503,-0.0,0.4001827500796187,0.5998172499203813,-0.0 +504,-0.0,0.3097382670973993,0.6902617329026006,-0.0 +505,-0.0,0.24463187419688215,0.7553681258031179,-0.0 +506,0.007827900868540743,0.16770861417305996,0.8244634849583993,-0.0 +507,0.32786861714772575,0.17009471234218884,0.5020366705100854,-0.0 +508,0.37859943623807635,0.2238387330087589,0.39756183075316476,-0.0 +509,0.2151167373297062,0.1320307653584659,0.6528524973118279,-0.0 +510,-0.0,0.1468018492625972,0.8531981507374028,-0.0 +511,-0.0,0.145551988316863,0.854448011683137,-0.0 +512,-0.0,0.1339623686382369,0.866037631361763,-0.0 +513,-0.0,0.133507873748879,0.8664921262511209,-0.0 +514,0.011440778192482626,0.13134902302442905,0.8572101987830882,-0.0 +515,0.32515895915476933,0.1758895221815019,0.4989515186637288,-0.0 +516,0.30287954899046104,0.20929489654930652,0.4878255544602324,-0.0 +517,0.1660418203461623,0.10396570594061644,0.7299924737132212,-0.0 +518,-0.0,0.16929934628581256,0.8307006537141874,-0.0 +519,-0.0,0.21009026260568284,0.7899097373943171,-0.0 +520,-0.0,0.22588396001086936,0.7741160399891307,-0.0 +521,-0.0,0.2325877596288982,0.7674122403711018,-0.0 +522,0.022429946719472516,0.24179128113839538,0.7357787721421322,-0.0 +523,0.5347058439433985,0.30905652476336254,0.15623763129323898,-0.0 +524,0.7751127225406979,0.22488727745930215,-0.0,-0.0 +525,0.31416979062777944,0.3098518908197388,0.3759783185524818,-0.0 +526,-0.0,0.4268843248293944,0.5731156751706056,-0.0 +527,-0.0,0.4014326110253528,0.5985673889746472,-0.0 +528,-0.0,0.36836810782456664,0.6316318921754334,-0.0 +529,-0.0,0.31882816488455706,0.681171835115443,-0.0 +530,0.03522555390843335,0.2626980460488581,0.7020764000427084,-0.0 +531,0.767134285116993,0.18407043018994385,0.04879528469306316,-0.0 +532,0.91526225539861,0.08473774460139005,-0.0,-0.0 +533,0.3590296840667245,0.18520666741333858,0.45576364851993695,-0.0 +534,-0.0,0.28883150218693654,0.7111684978130635,-0.0 +535,-0.0,0.25622149387550824,0.7437785061244917,-0.0 +536,-0.0,0.19634179220260678,0.8036582077973933,-0.0 +537,-0.0,0.1252133420180976,0.8747866579819024,-0.0 +538,0.0320642862499842,0.042608895877301815,0.9051023903737102,0.02022442749900378 +539,0.5905549059093333,0.01363484668073658,0.3958102474099301,-0.0 +540,0.7781234536439828,0.005113067505276217,0.216763478850741,-0.0 +541,0.4088572838260896,0.010567006177570849,0.5805757099963396,-0.0 +542,-0.0,0.07226468740790389,0.9051023903737102,0.022632922218385906 +543,-0.0,0.22202075345132732,0.7779792465486727,-0.0 +544,-0.0,0.2197482790045379,0.7802517209954621,-0.0 +545,-0.0,0.12657682668617126,0.8734231733138287,-0.0 +546,0.034773944242940606,0.09476218443111924,0.8704638713259402,-0.0 +547,0.5170930669891819,0.08044559541634583,0.4024613375944722,-0.0 +548,0.51844789598566,0.10930602089057159,0.3722460831237684,-0.0 +549,0.2491379987968256,0.14646097809557876,0.6044010231075956,-0.0 +550,-0.0,0.23951880669160594,0.760481193308394,-0.0 +551,-0.0,0.22270249578536416,0.7772975042146358,-0.0 +552,-0.0,0.14566561203920247,0.8543343879607975,-0.0 +553,-0.0,0.13248526024782378,0.8675147397521762,-0.0 +554,0.026494433708907132,0.12657682668617126,0.8469287396049215,-0.0 +555,0.5002329728107864,0.16634512950498628,0.33342189768422736,-0.0 +556,0.7273926345536322,0.15021056093278135,0.12239680451358648,-0.0 +557,0.3813090942310328,0.10703354644378217,0.511657359325185,-0.0 +558,-0.0,0.1332806263042001,0.8667193736957999,-0.0 +559,-0.0,0.09987525193639546,0.9001247480636045,-0.0 +560,-0.0,0.05215328855381743,0.9051023903737102,0.04274432107247236 +561,-0.0,0.01886153790835227,0.9051023903737102,0.07603607171793753 +562,0.03778467534622551,0.011248748511607681,0.9051023903737102,0.045864185768456606 +563,0.5453939393600599,0.026133456138078447,0.42847260450186164,-0.0 +564,0.5777592987203726,0.0678333622366645,0.35440733904296295,-0.0 +565,0.32922344614420396,0.10998776322460842,0.5607887906311876,-0.0 +566,-0.0,0.21213548960779333,0.7878645103922066,-0.0 +567,-0.0,0.18827450791650427,0.8117254920834958,-0.0 +568,-0.0,0.12782668763190547,0.8721733123680946,-0.0 +569,-0.0,0.1072607938884611,0.8927392061115389,-0.0 +570,0.04169862578049588,0.10669267527676375,0.8516086989427404,-0.0 +571,0.5017383383624288,0.12430435223938183,0.3739573093981894,-0.0 +572,0.4499537633859285,0.12794031135424494,0.4221059252598265,-0.0 +573,0.23107361217711617,0.1650952685592521,0.6038311192636316,-0.0 +574,-0.0,0.18725189441544907,0.8127481055845509,-0.0 +575,-0.0,0.15645986566145226,0.8435401343385478,-0.0 +576,-0.0,0.1423705240913578,0.8576294759086422,-0.0 +577,-0.0,0.11112400044800314,0.8888759995519968,-0.0 +578,0.018666532840366387,0.09896626215767969,0.882367205001954,-0.0 +579,0.23920258615598544,0.16657237694966526,0.5942250368943494,-0.0 +580,0.22550375963603908,0.1739579189017309,0.6005383214622301,-0.0 +581,0.12690231600345858,0.1746396612357677,0.6984580227607737,-0.0 +582,-0.0,0.21361259799820645,0.7863874020017936,-0.0 +583,-0.0,0.17225356306663883,0.8277464369333611,-0.0 +584,-0.0,0.1427113952583762,0.8572886047416238,-0.0 +585,-0.0,0.12714494529786863,0.8728550547021314,-0.0 +586,0.02529014126759317,0.13975717847754995,0.8349526802548568,-0.0 +587,0.26539594675456407,0.2901949868550103,0.4444090663904257,-0.0 +588,0.3012236468836544,0.2950808069156075,0.40369554620073816,-0.0 +589,0.19855771626163923,0.2826958211806051,0.5187464625577557,-0.0 +590,-0.0,0.42620258249535764,0.5737974175046423,-0.0 +591,-0.0,0.3948424351296635,0.6051575648703365,-0.0 +592,-0.0,0.3378033265152488,0.6621966734847512,-0.0 +593,-0.0,0.2799688518444578,0.7200311481555421,-0.0 +594,0.06337588972414718,0.29258108502413915,0.6440430252517136,-0.0 +595,0.6718446456980257,0.3281553543019743,-0.0,-0.0 +596,0.8059727163493681,0.19402728365063193,-0.0,-0.0 +597,0.36625543871460825,0.42404373177090765,0.20970082951448415,-0.0 +598,-0.0,0.4419962799005442,0.5580037200994559,-0.0 +599,-0.0,0.35871009142571153,0.6412899085742885,-0.0 +600,-0.0,0.27440128944982367,0.7255987105501763,-0.0 +601,-0.0,0.18282056924420964,0.8171794307557904,-0.0 +602,0.07903169146122865,0.1264632029638318,0.7945051055749396,-0.0 +603,0.7505752640489259,0.187479141860128,0.06194559409094602,-0.0 +604,0.9534985404103282,0.04650145958967178,-0.0,-0.0 +605,0.42887864566293415,0.17145819701026252,0.3996631573268033,-0.0 +606,-0.0,0.3298496659514858,0.6701503340485142,-0.0 +607,-0.0,0.263152540938216,0.736847459061784,-0.0 +608,-0.0,0.19554642614623047,0.8044535738537695,-0.0 +609,-0.0,0.14032529708924732,0.8596747029107527,-0.0 +610,0.10913900249407767,0.07851399213657481,0.8123470053693475,-0.0 +611,0.8619723148704672,0.09635291654387185,0.04167476858566088,-0.0 +612,1.0,-0.0,-0.0,-0.0 +613,0.49932975347980085,0.08862650342478778,0.4120437430954114,-0.0 +614,-0.0,0.24042779647032173,0.7595722035296782,-0.0 +615,-0.0,0.17532140356980452,0.8246785964301955,-0.0 +616,-0.0,0.09442131326410082,0.9051023903737102,0.0004762963621889743 +617,-0.0,0.06487914545583823,0.9051023903737102,0.030018464170451564 +618,0.10522505205980731,0.03738220464968613,0.8573927432905066,-0.0 +619,0.7371775106393081,0.04158628237624657,0.22123620698444524,-0.0 +620,0.9915842888668822,0.008415711133117787,-0.0,-0.0 +621,0.5012867286969361,0.014089341570094466,0.4846239297329694,-0.0 +622,-0.0,0.0897627406481825,0.9051023903737102,0.00513486897810729 +623,-0.0,0.05238053599849637,0.9051023903737102,0.042517073627793424 +624,-0.0,0.018975161630691743,0.9051023903737102,0.07592244799559805 +625,-0.0,0.028178683140188933,0.9051023903737102,0.06671892648610087 +626,0.12118192690721728,0.0203386462987654,0.8584794267940173,-0.0 +627,0.9265524970359285,0.011135124789268208,0.062312378174803285,-0.0 +628,0.9522942479690144,0.019884151409407518,0.027821600621578124,-0.0 +629,0.46741600378498094,0.03772307581670454,0.49486092039831453,-0.0 +630,-0.0,0.05294865461019373,0.9051023903737102,0.041948955016096065 +631,-0.0,0.07590064652276697,0.9051023903737102,0.01899696310352282 +632,-0.0,0.10555643805336903,0.894443561946631,-0.0 +633,-0.0,0.08635402897799835,0.9051023903737102,0.008543580648291443 +634,0.08309617845066328,0.06953771807175656,0.8473661034775801,-0.0 +635,0.43505064442466823,0.07874123958125376,0.486208115994078,-0.0 +636,0.3721263643660138,0.10976051577992949,0.5181131198540567,-0.0 +637,0.16107411402574223,0.18850175536118322,0.6504241306130746,-0.0 +638,-0.0,0.32519109333556745,0.6748089066644325,-0.0 +639,-0.0,0.4050685701402159,0.5949314298597841,-0.0 +640,-0.0,0.3535970239204353,0.6464029760795647,-0.0 +641,-0.0,0.27031083544560275,0.7296891645543973,-0.0 +642,0.0775263259095862,0.1906606060856332,0.7318130680047806,-0.0 +643,0.4564268352579911,0.1830478166888886,0.3605253480531203,-0.0 +644,0.6287911909210516,0.06294754217606721,0.30826126690288114,-0.0 +645,0.4037390409505053,0.03658683859330983,0.5596741204561849,-0.0 +646,-0.0,0.07260555857492229,0.9051023903737102,0.0222920510513675 +647,-0.0,0.10544281433102956,0.8945571856689705,-0.0 +648,-0.0,0.11305560372777416,0.8869443962722259,-0.0 +649,-0.0,0.09430768954176136,0.9051023903737102,0.0005899200845284336 +650,0.11049383149055589,0.04363150937835706,0.8458746591310871,-0.0 +651,0.6674790855982626,0.05056255644106482,0.2819583579606726,-0.0 +652,0.879585091824684,0.07635514141212485,0.04405976676319112,-0.0 +653,0.4260184511148135,0.06385653195478298,0.5101250169304035,-0.0 +654,-0.0,0.05419851555592791,0.9051023903737102,0.04069909407036188 +655,-0.0,0.056698237447396285,0.9051023903737102,0.038199372178893506 +656,-0.0,0.0398819265411545,0.9051023903737102,0.05501568308513529 +657,-0.0,0.028292306862528406,0.9051023903737102,0.06660530276376139 +658,0.1038702230633291,0.01363484668073658,0.8824949302559344,-0.0 +659,0.656640453626437,0.007726413119084063,0.33563313325447897,-0.0 +660,0.7129411252578647,0.008408155453120892,0.2786507192890144,-0.0 +661,0.3161267658449146,0.005340314949955161,0.6785329192051303,-0.0 +662,-0.0,0.0179525481296365,0.9051023903737102,0.07694506149665328 +663,-0.0,0.06919684690473815,0.9051023903737102,0.02570076272155164 +664,-0.0,0.09192159137263245,0.9051023903737102,0.002976018253657342 +665,-0.0,0.1032839636065796,0.8967160363934203,-0.0 +666,0.13051519332740047,0.0813545851950616,0.788130221477538,-0.0 +667,0.7585537014726309,0.1488470762647077,0.09259922226266137,-0.0 +668,0.8809399208211622,0.11906007917883776,-0.0,-0.0 +669,0.3162773024000789,0.1388481886988342,0.5448745089010869,-0.0 +670,-0.0,0.27224243872537374,0.7277575612746263,-0.0 +671,-0.0,0.19975050387279092,0.8002494961272091,-0.0 +672,-0.0,0.16145930944438902,0.838540690555611,-0.0 +673,-0.0,0.12612233179681337,0.8738776682031866,-0.0 +674,0.04892438042837965,0.0805592191386853,0.8705164004329351,-0.0 +675,0.20864366545764365,0.08362705964185103,0.7077292749005053,-0.0 +676,0.2756324325057327,0.10317033988424014,0.621197227610027,-0.0 +677,0.14150436185439036,0.0869221475896957,0.771573490555914,-0.0 +678,-0.0,0.07851399213657481,0.9051023903737102,0.01638361748971498 +679,-0.0,0.1466882255402577,0.8533117744597423,-0.0 +680,-0.0,0.17861649151764922,0.8213835084823508,-0.0 +681,-0.0,0.1902061111962753,0.8097938888037247,-0.0 +682,0.07707471624409348,0.16043669594333376,0.7624885878125728,-0.0 +683,0.3605350496183669,0.2958761729719838,0.3435887774096492,-0.0 +684,0.33765349323340166,0.36405040637566677,0.2982961003909316,-0.0 +685,0.18847176706563484,0.3654138910437404,0.4461143418906248,-0.0 +686,-0.0,0.2647432730509686,0.7352567269490313,-0.0 +687,-0.0,0.22997441401509033,0.7700255859849097,-0.0 +688,-0.0,0.16032307222099432,0.8396769277790057,-0.0 +689,-0.0,0.1001024993810744,0.8998975006189256,-0.0 +690,0.11500992814548322,0.043177014488999174,0.8418130573655176,-0.0 +691,0.6399308960032057,0.08237719869611684,0.27769190530067744,-0.0 +692,0.7391344858564433,0.18838813163884377,0.07247738250471292,-0.0 +693,0.3123633519658085,0.23645096618844022,0.4511856818457513,-0.0 +694,-0.0,0.3834800628957164,0.6165199371042835,-0.0 +695,-0.0,0.48449155205550654,0.5155084479444935,-0.0 +696,-0.0,0.5447121248954264,0.4552878751045736,-0.0 +697,-0.0,0.6045918265683279,0.3954081734316721,-0.0 +698,0.039591114008196454,0.566300632139926,0.39410825385187753,-0.0 +699,0.2214392726466045,0.6878780150431606,0.09068271231023489,-0.0 +700,0.25260033956560324,0.6159541988022751,0.13144546163212167,-0.0 +701,0.11741851302811115,0.5334633763838187,0.3491181105880702,-0.0 +702,-0.0,0.7513936758309251,0.2486063241690749,-0.0 +703,-0.0,0.8458149890950258,0.15418501090497416,-0.0 +704,-0.0,0.8800157295192068,0.11998427048079319,-0.0 +705,-0.0,0.8327482610259866,0.16725173897401335,-0.0 +706,0.1008594919600442,0.873311929901178,0.025828578138777786,-0.0 +707,0.4016315291782058,0.5983684708217942,-0.0,-0.0 +708,0.5849850533682563,0.41501494663174365,-0.0,-0.0 +709,0.3715242181453568,0.6284757818546431,-0.0,-0.0 +710,-0.0,0.6811742154251318,0.3188257845748682,-0.0 +711,-0.0,0.4098407664784738,0.5901592335215262,-0.0 +712,-0.0,0.4342698667814601,0.5657301332185398,-0.0 +713,-0.0,0.6631080435731558,0.3368919564268442,-0.0 +714,0.05434369641429247,0.9027404739871011,0.04291582959860642,-0.0 +715,0.3063418897592387,0.6936581102407613,-0.0,-0.0 +716,0.4172873309152873,0.5827126690847126,-0.0,-0.0 +717,0.25621321688954507,0.743786783110455,-0.0,-0.0 +718,-0.0,1.0,-0.0,-0.0 +719,-0.0,0.9230791202858666,0.07692087971413342,-0.0 +720,-0.0,0.8411564164791075,0.15884358352089245,-0.0 +721,-0.0,0.7833219418083166,0.2166780581916834,-0.0 +722,0.10070895540487995,0.7917300972614375,0.10756094733368254,-0.0 +723,0.4234593296770214,0.5765406703229786,-0.0,-0.0 +724,0.5765550062790586,0.42344499372094135,-0.0,-0.0 +725,0.29911613511135493,0.7008838648886451,-0.0,-0.0 +726,-0.0,0.5859575361046546,0.4140424638953454,-0.0 +727,-0.0,0.4476774660175178,0.5523225339824822,-0.0 +728,-0.0,0.4509725539653624,0.5490274460346376,-0.0 +729,-0.0,0.39597867235305817,0.6040213276469418,-0.0 +730,0.05238672119715728,0.3401894246843777,0.607423854118465,-0.0 +731,0.3468362230984206,0.5584605952985024,0.09470318160307695,-0.0 +732,0.5527702305631079,0.4472297694368921,-0.0,-0.0 +733,0.35662109918409657,0.6433789008159034,-0.0,-0.0 +734,-0.0,0.48278719622041444,0.5172128037795856,-0.0 +735,-0.0,0.3895021201797083,0.6104978798202917,-0.0 +736,-0.0,0.30030749814322316,0.6996925018567768,-0.0 +737,-0.0,0.23633734246610072,0.7636626575338993,-0.0 +738,0.1496333358332596,0.18838813163884377,0.6619785325278966,-0.0 +739,0.6086192925290428,0.21861204178114318,0.17276866568981397,-0.0 +740,0.7356721450876657,0.19043335864095426,0.07389449627138012,-0.0 +741,0.4302334746594124,0.13418961608291585,0.4355769092576717,-0.0 +742,-0.0,0.11839591867772932,0.8816040813222706,-0.0 +743,-0.0,0.11794142378837143,0.8820585762116285,-0.0 +744,-0.0,0.08567228664396151,0.9051023903737102,0.009225322982328282 +745,-0.0,0.04613123126982543,0.9051023903737102,0.048766378356464364 +746,0.11124651426637709,0.026133456138078447,0.8626200295955444,-0.0 +747,0.5642110087555905,0.038518441873080844,0.39727054937132866,-0.0 +748,0.5425337448119392,0.07669601257914327,0.38077024260891756,-0.0 +749,0.3358470545714307,0.1316898941914475,0.5324630512371218,-0.0 +750,0.00015053655516424508,0.17179906817728094,0.8280503952675548,-0.0 +751,-0.0,0.1488470762647077,0.8511529237352923,-0.0 +752,-0.0,0.08919462203648514,0.9051023903737102,0.005702987589804656 +753,-0.0,0.0710148264621697,0.9051023903737102,0.023882783164120097 +754,0.24763263324518314,0.016248192294544425,0.7361191744602724,-0.0 +755,0.8967462591134079,0.04772196338257804,0.05553177750401406,-0.0 +756,0.7173066853576278,0.0749916567440512,0.20770165789832107,-0.0 +757,0.37859943623807635,0.11510083072988464,0.506299733032039,-0.0 +758,0.00015053655516424508,0.1379391989201184,0.8619102645247173,-0.0 +759,-0.0,0.12169100662557399,0.878308993374426,-0.0 +760,-0.0,0.09794364865662444,0.9020563513433756,-0.0 +761,-0.0,0.08510416803226416,0.9051023903737102,0.009793441594025634 +762,0.15143977449523052,0.04590398382514649,0.802656241679623,-0.0 +763,0.5536734498940934,0.11635069167561883,0.32997585843028776,-0.0 +764,0.6215654362731678,0.13191714163612642,0.2465174220907057,-0.0 +765,0.44859893438945025,0.12373623362768447,0.42766483198286526,-0.0 +766,0.00030107311032849017,0.11271473256075573,0.8869841943289158,-0.0 +767,-0.0,0.054653010445285796,0.9051023903737102,0.040244599181003995 +768,-0.0,0.022383873300875885,0.9051023903737102,0.07251373632541391 +769,-0.0,0.0694240943494171,0.9051023903737102,0.025473515276872694 +770,0.220536053315619,0.06840148084836184,0.7110624658360192,-0.0 +771,0.7639730174585436,0.1887290028058622,0.04729797973559424,-0.0 +772,0.7611128229104231,0.23888717708957685,-0.0,-0.0 +773,0.40554547961247617,0.2856500379614314,0.30880448242609243,-0.0 +774,0.00030107311032849017,0.2338376205746324,0.7658613063150391,-0.0 +775,-0.0,0.20247747320893822,0.7975225267910617,-0.0 +776,-0.0,0.16452714994755477,0.8354728500524452,-0.0 +777,-0.0,0.1487334525423682,0.8512665474576318,-0.0 +778,0.11937548824524633,0.14532474087218408,0.7352997708825696,-0.0 +779,0.49556633960069474,0.12987191463401593,0.37456174576528933,-0.0 +780,0.6846402528869865,0.08374068336419051,0.23161906374882302,-0.0 +781,0.4479967881687933,0.04045004515285186,0.5115531666783548,-0.0 +782,0.00045160966549273514,0.03545060136991511,0.9051023903737102,0.058995398590881945 +783,-0.0,0.034087116701841454,0.9051023903737102,0.06081049292444834 +784,-0.0,0.03635959114863088,0.9051023903737102,0.05853801847765891 +785,-0.0,0.047040221048541206,0.9051023903737102,0.047857388577748586 +786,0.21406298144355648,0.027383317083812635,0.7585537014726309,-0.0 +787,0.8881656754690459,0.025678961248720558,0.0861553632822335,-0.0 +788,1.0,-0.0,-0.0,-0.0 +789,0.573694811730938,0.0019316032797710157,0.424373584989291,-0.0 +790,0.0007526827758212254,0.00977164012119455,0.9051023903737102,0.08437328672927401 +791,-0.0,0.030451157586978365,0.9051023903737102,0.06444645203931143 +792,-0.0,0.03249638458908885,0.9051023903737102,0.06240122503720094 +793,-0.0,0.02726969336147316,0.9051023903737102,0.06762791626481662 +794,0.2644927274235786,0.011021501066928736,0.7244857715094926,-0.0 +795,0.9840574611086699,0.004204077726560446,0.011738461164769731,-0.0 +796,1.0,-0.0,-0.0,-0.0 +797,0.5426842813671034,0.007840036841423535,0.449475681791473,-0.0 +798,0.0007526827758212254,0.06681074873560924,0.9051023903737102,0.027334178114859324 +799,-0.0,0.20520444254508557,0.7947955574549144,-0.0 +800,-0.0,0.26417515443927125,0.7358248455607288,-0.0 +801,-0.0,0.2786053671763841,0.7213946328236158,-0.0 +802,0.19133196161375546,0.14441575109346827,0.6642522872927763,-0.0 +803,0.7352205354221729,0.2647794645778271,-0.0,-0.0 +804,0.6080171463083858,0.39198285369161423,-0.0,-0.0 +805,0.23333166050457985,0.5633464153590998,0.20332192413632036,-0.0 +806,0.00030107311032849017,0.512556611473356,0.48714231541631553,-0.0 +807,-0.0,0.43199739233467066,0.5680026076653293,-0.0 +808,-0.0,0.3729130567181455,0.6270869432818544,-0.0 +809,-0.0,0.22747469212362195,0.7725253078763781,-0.0 +810,0.1789879640902874,0.14816533393067083,0.6728467019790418,-0.0 +811,0.8484240249056851,0.15157597509431486,-0.0,-0.0 +812,0.8850044078105967,0.11499559218940325,-0.0,-0.0 +813,0.5187489690959886,0.28496829562739456,0.19628273527661688,-0.0 +814,0.0013548289964782055,0.4040459566391607,0.594599214364361,-0.0 +815,-0.0,0.590161613831215,0.409838386168785,-0.0 +816,-0.0,0.7207152707992678,0.2792847292007322,-0.0 +817,-0.0,0.6368609637127378,0.3631390362872622,-0.0 +818,0.21135332345060007,0.4908544805065169,0.297792196042883,-0.0 +819,0.7167045391369709,0.28329546086302915,-0.0,-0.0 +820,0.7356721450876657,0.26432785491233435,-0.0,-0.0 +821,0.43173884021105485,0.42040777265604456,0.14785338713290058,-0.0 +822,0.002107511772299431,0.25179016870426885,0.7461023195234318,-0.0 +823,-0.0,0.14180240547966044,0.8581975945203395,-0.0 +824,-0.0,0.09476218443111924,0.9051023903737102,0.00013542519517055485 +825,-0.0,0.12964466718933698,0.870355332810663,-0.0 +826,0.2595250211031585,0.11078312928098473,0.6296918496158568,-0.0 +827,0.8822947498176402,0.11770525018235978,-0.0,-0.0 +828,0.8320155403927825,0.16798445960721753,-0.0,-0.0 +829,0.39350255519933663,0.4227938708251735,0.18370357397548986,-0.0 +830,0.0013548289964782055,0.39268358440521356,0.6059615865983082,-0.0 +831,-0.0,0.2926947087464786,0.7073052912535214,-0.0 +832,-0.0,0.22247524834068522,0.7775247516593148,-0.0 +833,-0.0,0.23929155924692702,0.760708440753073,-0.0 +834,0.2929441363496209,0.13850731753181578,0.5685485461185633,-0.0 +835,1.0,-0.0,-0.0,-0.0 +836,1.0,-0.0,-0.0,-0.0 +837,0.6047053420947724,0.39529465790522755,-0.0,-0.0 +838,0.0018064386619709406,0.42097589126774193,0.5772176700702871,-0.0 +839,-0.0,0.3232594900557964,0.6767405099442036,-0.0 +840,-0.0,0.21997552644921683,0.7800244735507832,-0.0 +841,-0.0,0.14566561203920247,0.8543343879607975,-0.0 +842,0.25500892444823114,0.09794364865662444,0.6470474268951445,-0.0 +843,0.8232844201932563,0.13305337885952115,0.04366220094722251,-0.0 +844,0.8928323086791374,0.10716769132086257,-0.0,-0.0 +845,0.4546203965960201,0.21474883522160115,0.33063076818237874,-0.0 +846,0.0018064386619709406,0.18497941996865963,0.8132141413693694,-0.0 +847,-0.0,0.1560053707720944,0.8439946292279056,-0.0 +848,-0.0,0.12464522340640026,0.8753547765935997,-0.0 +849,-0.0,0.10271584499488223,0.8972841550051178,-0.0 +850,0.19675127759966832,0.09283058115134822,0.7104181412489834,-0.0 +851,0.5870925651405557,0.1252133420180976,0.2876940928413466,-0.0 +852,0.592812954236797,0.17361704773471245,0.23356999802849054,-0.0 +853,0.33118042136133913,0.26769748983179487,0.401122088806866,-0.0 +854,0.002559121437792166,0.2337239968522929,0.763716881709915,-0.0 +855,-0.0,0.2206572687832537,0.7793427312167462,-0.0 +856,-0.0,0.16702687183902312,0.8329731281609769,-0.0 +857,-0.0,0.0897627406481825,0.9051023903737102,0.00513486897810729 +858,0.3349438352404453,0.04033642143051238,0.6247197433290423,-0.0 +859,0.8195210063141501,0.09817089610130339,0.08230809758454649,-0.0 +860,0.9006602095476783,0.0901036118152009,0.009236178637120784,-0.0 +861,0.5867914920302273,0.10748804133314005,0.30572046663663266,-0.0 +862,0.004516096654927352,0.09601204537685343,0.8994718579682193,-0.0 +863,-0.0,0.08158183263974055,0.9051023903737102,0.013315776986549246 +864,-0.0,0.06760611479198554,0.9051023903737102,0.02729149483430425 +865,-0.0,0.051471546219780596,0.9051023903737102,0.043426063406509195 +866,0.3296750558096967,0.0060220572839919904,0.6643028869063113,-0.0 +867,1.0,-0.0,-0.0,-0.0 +868,1.0,-0.0,-0.0,-0.0 +869,0.6345115800172929,0.03647321487097035,0.32901520511173676,-0.0 +870,0.005268779430748578,0.09953438076937704,0.8951968397998744,-0.0 +871,-0.0,0.1256678369074555,0.8743321630925445,-0.0 +872,-0.0,0.09976162821405599,0.900238371785944,-0.0 +873,-0.0,0.1240771047947029,0.8759228952052971,-0.0 +874,0.3688145601524004,0.056698237447396285,0.5744872024002032,-0.0 +875,1.0,-0.0,-0.0,-0.0 +876,1.0,-0.0,-0.0,-0.0 +877,0.6277374350349019,0.1965690396472857,0.17569352531781246,-0.0 +878,0.005419315985912822,0.21020388632802228,0.7843767976860649,-0.0 +879,-0.0,0.13418961608291585,0.8658103839170841,-0.0 +880,-0.0,0.08158183263974055,0.9051023903737102,0.013315776986549246 +881,-0.0,0.17645764079319926,0.8235423592068007,-0.0 +882,0.3841692887791534,0.1128283562830952,0.5030023549377514,-0.0 +883,1.0,-0.0,-0.0,-0.0 +884,1.0,-0.0,-0.0,-0.0 +885,0.6393287497825488,0.36067125021745117,-0.0,-0.0 +886,0.005870925651405557,0.34837033269281964,0.6457587416557748,-0.0 +887,-0.0,0.28553641423909193,0.7144635857609081,-0.0 +888,-0.0,0.22599758373320883,0.7740024162667911,-0.0 +889,-0.0,0.18134346085379652,0.8186565391462035,-0.0 +890,0.24206278070410606,0.12384985735002395,0.63408736194587,-0.0 +891,0.7061669802754736,0.08078646658336425,0.21304655314116216,-0.0 +892,0.801607156249605,0.056130118835698926,0.14226272491469605,-0.0 +893,0.5404262330396398,0.04999443782936746,0.4095793291309928,-0.0 +894,0.006924681537555273,0.05999332539524096,0.9051023903737102,0.02797960269349356 +895,-0.0,0.06771973851432501,0.9051023903737102,0.027177871111964777 +896,-0.0,0.08010472424932741,0.9051023903737102,0.014792885376962384 +897,-0.0,0.07260555857492229,0.9051023903737102,0.0222920510513675 +898,0.3003204275526689,0.03635959114863088,0.6633199812987003,-0.0 +899,0.8974989418892291,0.05556200022400157,0.04693905788676933,-0.0 +900,1.0,-0.0,-0.0,-0.0 +901,0.6108773408565065,0.05363039694423055,0.335492262199263,-0.0 +902,0.008430047089197723,0.0586298407271673,0.9051023903737102,0.02783772180992477 +903,-0.0,0.06033419656225937,0.9051023903737102,0.03456341306403042 +904,-0.0,0.049085448050651695,0.9051023903737102,0.0458121615756381 +905,-0.0,0.06851510457070133,0.9051023903737102,0.026382505055588465 +906,0.33885778567471564,0.019884151409407518,0.6412580629158768,-0.0 +907,1.0,-0.0,-0.0,-0.0 +908,1.0,-0.0,-0.0,-0.0 +909,0.6319524585795008,0.09703465887790867,0.2710128825425906,-0.0 +910,0.007225754647883762,0.060788691451617256,0.9051023903737102,0.026883163526788774 +911,-0.0,0.1336214974712185,0.8663785025287816,-0.0 +912,-0.0,0.2408822913596796,0.7591177086403205,-0.0 +913,-0.0,0.24156403369371646,0.7584359663062835,-0.0 +914,0.1704073804459254,0.09896626215767969,0.7306263573963949,-0.0 +915,0.5136307262204042,0.11123762417034261,0.37513164960925327,-0.0 +916,0.4833728786323909,0.19827339548237777,0.3183537258852313,-0.0 +917,0.191030888503427,0.17816199662829132,0.6308071148682817,-0.0 +918,0.0016559021068066958,0.15770972660718646,0.8406343712860068,-0.0 +919,-0.0,0.1766848882378782,0.8233151117621218,-0.0 +920,-0.0,0.17793474918361238,0.8220652508163876,-0.0 +921,-0.0,0.13703020914140265,0.8629697908585974,-0.0 +922,0.20924581167830067,0.03465523531353881,0.7560989530081605,-0.0 +923,0.579866810492672,0.10748804133314005,0.312645148174188,-0.0 +924,0.617350412728569,0.10680629899910322,0.27584328827232774,-0.0 +925,0.397416505633607,0.052039664831477955,0.5505438295349151,-0.0 +926,0.004666633210091597,0.022270249578536416,0.9051023903737102,0.06796072683766177 +927,-0.0,0.04090454004220974,0.9051023903737102,0.05399306958408005 +928,-0.0,0.06715161990262766,0.9051023903737102,0.02774598972366213 +929,-0.0,0.0977164012119455,0.9022835987880545,-0.0 +930,0.16649343001165506,0.08419517825354839,0.7493113917347966,-0.0 +931,0.5079103371241628,0.13555310075098953,0.35653656212484763,-0.0 +932,0.5342542342779057,0.18509304369099908,0.28065272203109515,-0.0 +933,0.29715915989421976,0.24417737930752428,0.458663460798256,-0.0 +934,0.003913950434270372,0.19850064292705671,0.797585406638673,-0.0 +935,-0.0,0.18122983713145707,0.8187701628685429,-0.0 +936,-0.0,0.15702798427314962,0.8429720157268503,-0.0 +937,-0.0,0.10294309243956119,0.8970569075604388,-0.0 +938,0.19494483893769737,0.06567451151221453,0.7393806495500881,-0.0 +939,0.5563831078870497,0.10362483477359802,0.33999205733935234,-0.0 +940,0.6119310967426561,0.20031862248448828,0.1877502807728556,-0.0 +941,0.36475007316296576,0.2178166757247669,0.4174332511122673,-0.0 +942,0.00978487608567593,0.1488470762647077,0.8413680476496164,-0.0 +943,-0.0,0.17498053240278613,0.8250194675972139,-0.0 +944,-0.0,0.15748247916250752,0.8425175208374924,-0.0 +945,-0.0,0.1420296529243394,0.8579703470756606,-0.0 +946,0.3984702615197567,0.08033197169400635,0.5211977667862371,-0.0 +947,1.0,-0.0,-0.0,-0.0 +948,1.0,-0.0,-0.0,-0.0 +949,0.5133296531100757,0.10794253622249794,0.37872781066742633,-0.0 +950,0.005870925651405557,0.14748359159663402,0.8466454827519604,-0.0 +951,-0.0,0.24270027091711116,0.7572997290828889,-0.0 +952,-0.0,0.22145263483962996,0.77854736516037,-0.0 +953,-0.0,0.16748136672838101,0.832518633271619,-0.0 +954,0.23378327017007258,0.09601204537685343,0.670204684453074,-0.0 +955,0.6795220100114022,0.18532029113567802,0.13515769885291973,-0.0 +956,0.6209632900525108,0.2208845162279326,0.15815219371955658,-0.0 +957,0.38326606944816793,0.14032529708924732,0.4764086334625847,-0.0 +958,0.006171998761734048,0.06362928451010405,0.9051023903737102,0.025096326354451692 +959,-0.0,0.10669267527676375,0.8933073247232363,-0.0 +960,-0.0,0.1288493011329607,0.8711506988670393,-0.0 +961,-0.0,0.09385319465240348,0.9051023903737102,0.0010444149738863123 +962,0.21827800498815533,0.059879701672901485,0.7218422933389432,-0.0 +963,0.496319022376516,0.0626066710090488,0.4410743066144352,-0.0 +964,0.5670712033037112,0.06749249106964608,0.36543630562664275,-0.0 +965,0.3527071487498262,0.05385764438890949,0.5934352068612643,-0.0 +966,0.01008594919600442,0.05965245422822254,0.9051023903737102,0.025159206202062835 +967,-0.0,0.06976496551643552,0.9051023903737102,0.025132644109854274 +968,-0.0,0.042381648432622876,0.9051023903737102,0.052515961193666916 +969,-0.0,0.027156069639133692,0.9051023903737102,0.0677415399871561 +970,0.31161066918998725,0.01647543973922337,0.6719138910707894,-0.0 +971,0.7553924338141818,0.02488359519234426,0.21972397099347396,-0.0 +972,0.7480161426111338,0.02874680175188629,0.22323705563697993,-0.0 +973,0.43459903475917555,0.017043558350920727,0.5483574068899038,-0.0 +974,0.014903118961260262,0.026247079860417917,0.9051023903737102,0.05374741080461162 +975,-0.0,0.08828563225776936,0.9051023903737102,0.0066119773685204275 +976,-0.0,0.12828118252126333,0.8717188174787367,-0.0 +977,-0.0,0.16134568572204952,0.8386543142779505,-0.0 +978,0.22565429619120336,0.06987858923877498,0.7044671145700216,-0.0 +979,0.6215654362731678,0.2238387330087589,0.15459583071807326,-0.0 +980,0.5182973594304957,0.3036025860910679,0.17810005447843646,-0.0 +981,0.16724611278747628,0.31166987037717037,0.5210840168353534,-0.0 +982,0.002258048327463676,0.3672318706011719,0.6305100810713644,-0.0 +983,-0.0,0.35473326114383,0.6452667388561699,-0.0 +984,-0.0,0.33041778456318316,0.6695822154368168,-0.0 +985,-0.0,0.28383205840399983,0.7161679415960002,-0.0 +986,0.24221331725927028,0.18372955902292545,0.5740571237178043,-0.0 +987,0.7090271748235942,0.289172373353955,0.0018004518224506993,-0.0 +988,0.7871556469538376,0.21284435304616245,-0.0,-0.0 +989,0.4794589281981205,0.0710148264621697,0.44952624533970975,-0.0 +990,0.01339775340961781,0.024542724025325847,0.9051023903737102,0.05695713219134614 +991,-0.0,0.24894957564578207,0.751050424354218,-0.0 +992,-0.0,0.4010917398583344,0.5989082601416655,-0.0 +993,-0.0,0.43086115511127593,0.569138844888724,-0.0 +994,0.1586655291431143,0.3462114819683697,0.495122988888516,-0.0 +995,0.48608253662534734,0.48415068088848806,0.029766782486164545,-0.0 +996,0.5328994052814275,0.46710059471857246,-0.0,-0.0 +997,0.3158256927345861,0.6020921046768596,0.08208220258855431,-0.0 +998,0.014903118961260262,0.510625008193585,0.4744718728451547,-0.0 +999,-0.0,0.3157603243813913,0.6842396756186087,-0.0 +1000,-0.0,0.197364405703662,0.802635594296338,-0.0 +1001,-0.0,0.2065679272131592,0.7934320727868408,-0.0 +1002,0.2699120434094914,0.14521111714984458,0.584876839440664,-0.0 +1003,0.748768825386955,0.251231174613045,-0.0,-0.0 +1004,0.784445988960881,0.21555401103911898,-0.0,-0.0 +1005,0.4755449777638502,0.48676402650229594,0.03769099573385393,-0.0 +1006,0.01956975217135186,0.36200517937355625,0.6184250684550919,-0.0 +1007,-0.0,0.3814348358936059,0.6185651641063941,-0.0 +1008,-0.0,0.42438460293792607,0.5756153970620739,-0.0 +1009,-0.0,0.41279498325930003,0.5872050167406999,-0.0 +1010,0.3337395427991313,0.49380869728734317,0.17245175991352546,-0.0 +1011,0.6573931364022582,0.34260686359774184,-0.0,-0.0 +1012,0.7508763371592544,0.2491236628407456,-0.0,-0.0 +1013,0.519652188426974,0.2559942464308293,0.22435356514219662,-0.0 +1014,0.023483702605622228,0.07090120273983022,0.9051023903737102,0.0005127042808373422 +1015,-0.0,0.01942965652004963,0.9051023903737102,0.07546795310624016 +1016,-0.0,0.024542724025325847,0.9051023903737102,0.07035488560096395 +1017,-0.0,0.04090454004220974,0.9051023903737102,0.05399306958408005 +1018,0.3018257931043114,0.00818090800844195,0.6899932988872466,-0.0 +1019,0.8890688948000314,0.00681742334036829,0.10411368185960035,-0.0 +1020,0.8988537708857073,0.0015907321127526012,0.0995554970015401,-0.0 +1021,0.472985856326058,0.001136237223394715,0.5258779064505472,-0.0 +1022,0.014451509295767525,0.10930602089057159,0.8762424698136608,-0.0 +1023,-0.0,0.179525481296365,0.820474518703635,-0.0 +1024,-0.0,0.15509638099337864,0.8449036190066214,-0.0 +1025,-0.0,0.17089007839856515,0.8291099216014348,-0.0 +1026,0.19931039903746045,0.11964577962346351,0.6810438213390759,-0.0 +1027,0.48532985384952615,0.3072385452059309,0.20743160094454288,-0.0 +1028,0.5855871995889134,0.41441280041108663,-0.0,-0.0 +1029,0.3483415886500631,0.4435870120132968,0.20807139933664015,-0.0 +1030,0.0183654597300379,0.3213278867760254,0.6603066534939367,-0.0 +1031,-0.0,0.36825448410222716,0.6317455158977728,-0.0 +1032,-0.0,0.42302111826985245,0.5769788817301476,-0.0 +1033,-0.0,0.45222241491109666,0.5477775850889033,-0.0 +1034,0.25621321688954507,0.504602950909593,0.23918383220086198,-0.0 +1035,0.7761664784268475,0.2238335215731525,-0.0,-0.0 +1036,0.9917348254220466,0.008265174577953394,-0.0,-0.0 +1037,0.649264162423389,0.17793474918361238,0.17280108839299857,-0.0 +1038,0.03176321313965571,0.054312139278267384,0.9051023903737102,0.008822257208366699 +1039,-0.0,0.08839925598010882,0.9051023903737102,0.006498353646180968 +1040,-0.0,0.1687312276741152,0.8312687723258848,-0.0 +1041,-0.0,0.18168433202081494,0.8183156679791851,-0.0 +1042,0.35029856386719826,0.07840036841423535,0.5713010677185664,-0.0 +1043,0.9136063532918033,0.08639364670819671,-0.0,-0.0 +1044,0.9778854623469359,0.022114537653064148,-0.0,-0.0 +1045,0.5672217398588755,0.1699810886198494,0.26279717152127513,-0.0 +1046,0.02619336059857864,0.23701908480013759,0.7367875546012838,-0.0 +1047,-0.0,0.32212325283240173,0.6778767471675983,-0.0 +1048,-0.0,0.2046363239333882,0.7953636760666118,-0.0 +1049,-0.0,0.18725189441544907,0.8127481055845509,-0.0 +1050,0.28601945481206564,0.10078424171511123,0.6131963034728232,-0.0 +1051,0.7966394499291849,0.17691213568255715,0.026448414388257957,-0.0 +1052,0.8240371029690775,0.1759628970309225,-0.0,-0.0 +1053,0.395610066971636,0.5514159245134552,0.0529740085149088,-0.0 +1054,0.022731019829801007,0.5731180554802943,0.4041509246899047,-0.0 +1055,-0.0,0.31053363315377563,0.6894663668462244,-0.0 +1056,-0.0,0.24179128113839538,0.7582087188616047,-0.0 +1057,-0.0,0.28065059417849464,0.7193494058215053,-0.0 +1058,0.4171367943601231,0.299284884642168,0.28357832099770885,-0.0 +1059,0.9617780509443618,0.03822194905563825,-0.0,-0.0 +1060,0.944165273990145,0.05583472600985495,-0.0,-0.0 +1061,0.5082114102344913,0.24417737930752428,0.2476112104579844,-0.0 +1062,0.025892287488250152,0.16770861417305996,0.80639909833869,-0.0 +1063,-0.0,0.1488470762647077,0.8511529237352923,-0.0 +1064,-0.0,0.1307809044127317,0.8692190955872683,-0.0 +1065,-0.0,0.1260087080744739,0.8739912919255262,-0.0 +1066,0.2571164362205306,0.12487247085107919,0.6180110929283902,-0.0 +1067,0.6552856246299588,0.10021612310341388,0.24449825226662736,-0.0 +1068,0.7410914610735785,0.0602205728399199,0.19868796608650163,-0.0 +1069,0.4284270359974415,0.030678405031657308,0.5408945589709012,-0.0 +1070,0.02258048327463676,0.029769415252941536,0.9051023903737102,0.0425477110987115 +1071,-0.0,0.061015938896296196,0.9051023903737102,0.033881670729993596 +1072,-0.0,0.06806060968134343,0.9051023903737102,0.026836999944946358 +1073,-0.0,0.0646518980111593,0.9051023903737102,0.030245711615130497 +1074,0.24627780424870493,0.018520666741333854,0.7352015290099612,-0.0 +1075,0.6859950818834648,0.03158739481037308,0.2824175233061621,-0.0 +1076,0.9732188291368443,0.02678117086315568,-0.0,-0.0 +1077,0.595221539119425,0.07374179579831701,0.33103666508225804,-0.0 +1078,0.04260184511148135,0.07646876513446432,0.8809293897540543,-0.0 +1079,-0.0,0.07635514141212485,0.9051023903737102,0.018542468214164942 +1080,-0.0,0.05158516994212007,0.9051023903737102,0.04331243968416972 +1081,-0.0,0.07112845018450917,0.9051023903737102,0.023769159441780624 +1082,0.3227503742721414,0.07953660563763007,0.5977130200902285,-0.0 +1083,0.7213711723470624,0.20293196809829614,0.07569685955464145,-0.0 +1084,0.5720389096241313,0.31235161271120715,0.11560947766466156,-0.0 +1085,0.25139604712428926,0.3672318706011719,0.38137208227453884,-0.0 +1086,0.01776331350938092,0.3614370607618589,0.6207996257287602,-0.0 +1087,-0.0,0.32007802583029127,0.6799219741697087,-0.0 +1088,-0.0,0.26053919532440817,0.7394608046755918,-0.0 +1089,-0.0,0.2216798822843089,0.7783201177156911,-0.0 +1090,0.3159762292897504,0.26281166977119763,0.421212100939052,-0.0 +1091,0.7304033656569171,0.2695966343430829,-0.0,-0.0 +1092,0.7016508836205463,0.2983491163794537,-0.0,-0.0 +1093,0.48487824418403336,0.38711602201057943,0.12800573380538727,-0.0 +1094,0.03627930979458306,0.18884262652820163,0.7748780636772153,-0.0 +1095,-0.0,0.1008978654374507,0.8991021345625493,-0.0 +1096,-0.0,0.09737553004492708,0.902624469955073,-0.0 +1097,-0.0,0.08749026620139307,0.9051023903737102,0.007407343424896726 +1098,0.34156744366767205,0.035905096259273,0.622527460073055,-0.0 +1099,0.863026070756617,0.1299855383563554,0.006988390887027629,-0.0 +1100,0.9419072256626815,0.05809277433731852,-0.0,-0.0 +1101,0.5410283792602968,0.1774802542942545,0.2814913664454487,-0.0 +1102,0.02860194548120656,0.23724633224481653,0.734151722273977,-0.0 +1103,-0.0,0.2991712609198285,0.7008287390801715,-0.0 +1104,-0.0,0.21361259799820645,0.7863874020017936,-0.0 +1105,0.00015053655516424508,0.18009359990806234,0.8197558635367733,-0.0 +1106,0.23468648950105808,0.23622371874376127,0.5290897917551807,-0.0 +1107,0.6411351884445198,0.3588648115554802,-0.0,-0.0 +1108,0.7219733185677194,0.2780266814322806,-0.0,-0.0 +1109,0.44077103352090957,0.35689211186828,0.20233685461081047,-0.0 +1110,0.03522555390843335,0.1388481886988342,0.8259262573927324,-0.0 +1111,-0.0,0.12123651173621611,0.8787634882637839,-0.0 +1112,-0.0,0.12066839312451874,0.8793316068754813,-0.0 +1113,0.00030107311032849017,0.09828451982364285,0.9014144070660287,-0.0 +1114,0.21210600622642128,0.05749360350377258,0.7304003902698062,-0.0 +1115,0.4966200954868445,0.06431102684414088,0.4390688776690146,-0.0 +1116,0.5363617460502051,0.0654472640675356,0.3981909898822592,-0.0 +1117,0.3505996369775268,0.07442353813235385,0.5749768248901195,-0.0 +1118,0.03658038290491155,0.07749137863551957,0.8859282384595689,-0.0 +1119,-0.0,0.1431658901477341,0.8568341098522659,-0.0 +1120,-0.0,0.14577923576154195,0.854220764238458,-0.0 +1121,0.00015053655516424508,0.09794364865662444,0.9019058147882113,-0.0 +1122,0.36399739038714457,0.05533475277932263,0.5806678568335328,-0.0 +1123,0.6960810310794692,0.07476440929937225,0.2291545596211586,-0.0 +1124,0.6409846518893555,0.09680741143322973,0.2622079366774148,-0.0 +1125,0.4618461512439039,0.09101260159391669,0.44714124716217946,-0.0 +1126,0.05344047708330699,0.05022168527404641,0.8963378376426465,-0.0 +1127,-0.0,0.04999443782936746,0.9051023903737102,0.04490317179692233 +1128,-0.0,0.04510861776877019,0.9051023903737102,0.0497889918575196 +1129,0.00030107311032849017,0.04306339076665971,0.9051023903737102,0.051533145749301595 +1130,0.4385129851934459,0.015339202515828654,0.5461478122907255,-0.0 +1131,0.9971541414079593,0.002845858592040673,-0.0,-0.0 +1132,0.8859076271415822,0.03931380792945714,0.07477856492896062,-0.0 +1133,0.49737277826266574,0.04806283454959644,0.4545643871877378,-0.0 +1134,0.034924480798104854,0.03215551342207044,0.9051023903737102,0.027817615406114507 +1135,-0.0,0.04635847871450438,0.9051023903737102,0.04853913091178541 +1136,-0.0,0.06522001662285665,0.9051023903737102,0.029677593003433145 +1137,-0.0,0.05965245422822254,0.9051023903737102,0.03524515539806725 +1138,0.18079440275225833,0.03931380792945714,0.7798917893182845,-0.0 +1139,0.6369201648999209,0.057266356059093644,0.30581347904098544,-0.0 +1140,0.5715872999586384,0.08612678153331942,0.34228591850804213,-0.0 +1141,0.33780402978856594,0.11396459350648992,0.5482313767049442,-0.0 +1142,0.03281696902580542,0.12135013545855558,0.845832895515639,-0.0 +1143,-0.0,0.13305337885952115,0.8669466211404788,-0.0 +1144,-0.0,0.0749916567440512,0.9051023903737102,0.019905952882238592 +1145,0.00015053655516424508,0.03306450320078621,0.9051023903737102,0.061682569870339335 +1146,0.2702131165198199,0.004090454004220975,0.7256964294759591,-0.0 +1147,0.763521407793051,0.0021588507244499587,0.234319741482499,-0.0 +1148,0.7739084300993839,0.0036359591148630885,0.22245561078575293,-0.0 +1149,0.4886416580631395,0.006249304728670932,0.5051090372081897,-0.0 +1150,0.048322234207722664,0.008749026620139306,0.9051023903737102,0.03782634879842782 +1151,-0.0,0.021361259799820644,0.9051023903737102,0.07353634982646914 +1152,-0.0,0.054084891833588444,0.9051023903737102,0.04081271779270135 +1153,0.00015053655516424508,0.08328618847483263,0.9051023903737102,0.011460884596292917 +1154,0.2118049331160928,0.02886042547422576,0.7593346414096814,-0.0 +1155,0.5864904189198988,0.03624596742629141,0.3772636136538098,-0.0 +1156,0.4939104374938881,0.06328841334308563,0.44280114916302626,-0.0 +1157,0.32350305704796267,0.08624040525565888,0.5902565376963784,-0.0 +1158,0.04651579554575173,0.10078424171511123,0.8526999627391371,-0.0 +1159,-0.0,0.1360075956403474,0.8639924043596526,-0.0 +1160,-0.0,0.1348713584169527,0.8651286415830473,-0.0 +1161,0.00015053655516424508,0.10680629899910322,0.8930431644457325,-0.0 +1162,0.2706647261853126,0.0789684870259327,0.6503667867887546,-0.0 +1163,0.5727915923999525,0.13373512119355796,0.2934732864064895,-0.0 +1164,0.42752381666645595,0.187479141860128,0.384997041473416,-0.0 +1165,0.31552461962425765,0.2240659804534378,0.4604093999223045,-0.0 +1166,0.04019326022885344,0.18725189441544907,0.7725548453556975,-0.0 +1167,-0.0,0.2038409578770119,0.796159042122988,-0.0 +1168,-0.0,0.19634179220260678,0.8036582077973933,-0.0 +1169,0.00015053655516424508,0.1520285404902129,0.8478209229546229,-0.0 +1170,0.2274607348531743,0.17213993934429933,0.6003993258025264,-0.0 +1171,0.4507064461617497,0.19202409075370686,0.35726946308454344,-0.0 +1172,0.4750933680983574,0.19281945681008314,0.33208717509155944,-0.0 +1173,0.3621909517251736,0.13737108030842105,0.5004379679664053,-0.0 +1174,0.04470935688378078,0.044654122879412304,0.9051023903737102,0.005534129863096704 +1175,-0.0,0.12623595551915284,0.8737640444808472,-0.0 +1176,-0.0,0.21031751005036176,0.7896824899496382,-0.0 +1177,-0.0,0.25338090081702147,0.7466190991829785,-0.0 +1178,0.2846646258155874,0.29496718319326803,0.42036819099114453,-0.0 +1179,0.7809836481921034,0.21901635180789658,-0.0,-0.0 +1180,0.8116931054456095,0.18830689455439054,-0.0,-0.0 +1181,0.42571737800448506,0.33871231629396453,0.23557030570155046,-0.0 +1182,0.03883843123237523,0.1738442951793914,0.7873172735882334,-0.0 +1183,-0.0,0.11657793912029776,0.8834220608797022,-0.0 +1184,-0.0,0.08260444614079579,0.9051023903737102,0.012293163485494002 +1185,0.0007526827758212254,0.09123984903859562,0.9051023903737102,0.002905077811872942 +1186,0.38477143499981037,0.04738109221555962,0.56784747278463,-0.0 +1187,0.7355216085325014,0.10055699427043228,0.16392139719706633,-0.0 +1188,0.6703392801463832,0.1666860006720047,0.1629747191816121,-0.0 +1189,0.39545953041647175,0.2145215877769222,0.39001888180660604,-0.0 +1190,0.035827700129090326,0.2070224221025171,0.7571498777683926,-0.0 +1191,-0.0,0.33053140828552263,0.6694685917144774,-0.0 +1192,-0.0,0.3280316863940543,0.6719683136059458,-0.0 +1193,-0.0,0.28110508906785253,0.7188949109321474,-0.0 +1194,0.27879370016418187,0.3810939647265874,0.3401123351092307,-0.0 +1195,0.6334578241311433,0.36654217586885673,-0.0,-0.0 +1196,0.6846402528869865,0.3153597471130135,-0.0,-0.0 +1197,0.47539444120868596,0.4092726478667764,0.11533291092453762,-0.0 +1198,0.05328994052814275,0.18543391485801752,0.7612761446138397,-0.0 +1199,-0.0,0.1746396612357677,0.8253603387642323,-0.0 +1200,-0.0,0.17850286779530974,0.8214971322046902,-0.0 +1201,0.00045160966549273514,0.17475328495810719,0.8247951053764001,-0.0 +1202,0.35014802731203404,0.12453159968406077,0.5253203730039051,-0.0 +1203,0.8103382764491311,0.18966172355086885,-0.0,-0.0 +1204,0.7499731178282689,0.2500268821717311,-0.0,-0.0 +1205,0.4212012813495577,0.4304066602219181,0.1483920584285242,-0.0 +1206,0.03447287113261212,0.43995105289843367,0.5255760759689543,-0.0 +1207,-0.0,0.4283614332198076,0.5716385667801924,-0.0 +1208,-0.0,0.3700724636596587,0.6299275363403414,-0.0 +1209,-0.0,0.24383650814050586,0.7561634918594942,-0.0 +1210,0.2354391722768793,0.1598685773316364,0.6046922503914843,-0.0 +1211,0.5905549059093333,0.10407932966295591,0.3053657644277108,-0.0 +1212,0.601995684101816,0.06692437245794872,0.3310799434402353,-0.0 +1213,0.402384211954027,0.03601871998161247,0.5615970680643605,-0.0 +1214,0.048021161097394176,0.01886153790835227,0.9051023903737102,0.02801491062054335 +1215,-0.0,0.13453048724993427,0.8654695127500658,-0.0 +1216,-0.0,0.24951769425747944,0.7504823057425205,-0.0 +1217,0.00045160966549273514,0.3275771915046964,0.6719711988298109,-0.0 +1218,0.28601945481206564,0.5381219489997371,0.17585859618819727,-0.0 +1219,0.6847907894421509,0.3152092105578491,-0.0,-0.0 +1220,0.7189625874644345,0.28103741253556547,-0.0,-0.0 +1221,0.49255560849740987,0.5074443915025901,-0.0,-0.0 +1222,0.04154808922533164,0.688218886210179,0.27023302456448933,-0.0 +1223,-0.0,0.6448146242765008,0.3551853757234992,-0.0 +1224,-0.0,0.5806172211546994,0.4193827788453006,-0.0 +1225,0.00030107311032849017,0.4387011919526995,0.560997734936972,-0.0 +1226,0.36354578072165183,0.4549493842472439,0.18150483503110432,-0.0 +1227,0.7338657064256947,0.2661342935743053,-0.0,-0.0 +1228,0.8153059827695512,0.18469401723044876,-0.0,-0.0 +1229,0.5839312974821067,0.4160687025178933,-0.0,-0.0 +1230,0.06593501116193934,0.21270360821949066,0.72136138061857,-0.0 +1231,-0.0,0.13680296169672368,0.8631970383032763,-0.0 +1232,-0.0,0.08010472424932741,0.9051023903737102,0.014792885376962384 +1233,0.0007526827758212254,0.043404261933678114,0.9051023903737102,0.050740664916790454 +1234,0.4799105378636132,0.008521779175460363,0.5115676829609264,-0.0 +1235,1.0,-0.0,-0.0,-0.0 +1236,1.0,-0.0,-0.0,-0.0 +1237,0.7096293210442512,0.004999443782936747,0.28537123517281204,-0.0 +1238,0.07722525279925772,0.06203855239735145,0.8607361948033908,-0.0 +1239,-0.0,0.25576699898615035,0.7442330010138496,-0.0 +1240,-0.0,0.2545171380404162,0.7454828619595838,-0.0 +1241,0.0018064386619709406,0.21747580455774845,0.7807177567802805,-0.0 +1242,0.4808137571945988,0.09748915376726656,0.4216970890381347,-0.0 +1243,1.0,-0.0,-0.0,-0.0 +1244,1.0,-0.0,-0.0,-0.0 +1245,0.6933713730865128,0.04999443782936746,0.25663418908411983,-0.0 +1246,0.07406398514080857,0.16577701089328895,0.7601590039659024,-0.0 +1247,-0.0,0.38098034100424794,0.6190196589957521,-0.0 +1248,-0.0,0.31985077838561227,0.6801492216143877,-0.0 +1249,0.00045160966549273514,0.20599980860146183,0.7935485817330454,-0.0 +1250,0.2903850149118287,0.12021389823516086,0.5894010868530104,-0.0 +1251,0.8449616841369076,0.15503831586309236,-0.0,-0.0 +1252,0.9637350261614969,0.036264973838503134,-0.0,-0.0 +1253,0.5893506134680194,0.22633845490022725,0.18431093163175338,-0.0 +1254,0.06367696283447566,0.16941297000815203,0.7669100671573723,-0.0 +1255,-0.0,0.17486690868044666,0.8251330913195534,-0.0 +1256,-0.0,0.11078312928098473,0.8892168707190152,-0.0 +1257,0.00030107311032849017,0.09703465887790867,0.9026642680117629,-0.0 +1258,0.34502978443644966,0.06408377939946193,0.5908864361640884,-0.0 +1259,0.8810904573763264,0.06806060968134343,0.05084893294233017,-0.0 +1260,0.9953477027459884,0.004652297254011617,-0.0,-0.0 +1261,0.6397803594480416,0.08635402897799835,0.27386561157396005,-0.0 +1262,0.0671393036032533,0.1391890598658526,0.7936716365308941,-0.0 +1263,-0.0,0.217362180835409,0.782637819164591,-0.0 +1264,-0.0,0.14191602920199992,0.8580839707980001,-0.0 +1265,0.0006021462206569803,0.10748804133314005,0.8919098124462029,-0.0 +1266,0.3037827683214466,0.12657682668617126,0.5696404049923821,-0.0 +1267,0.6322535316898292,0.11430546467350833,0.25344100363666244,-0.0 +1268,0.6336083606863074,0.13691658541906318,0.2294750538946294,-0.0 +1269,0.36234148828033785,0.18361593530058595,0.4540425764190762,-0.0 +1270,0.04636525899058748,0.21293085566416964,0.7407038853452429,-0.0 +1271,-0.0,0.21134012355141701,0.788659876448583,-0.0 +1272,-0.0,0.16429990250287582,0.8357000974971242,-0.0 +1273,0.0006021462206569803,0.12339536246066607,0.8760024913186769,-0.0 +1274,0.24733156013485466,0.11680518656497671,0.6358632533001686,-0.0 +1275,0.4940609740490523,0.09158072020561404,0.41435830574533367,-0.0 +1276,0.45853434703029045,0.08385430708652998,0.4576113458831796,-0.0 +1277,0.3632447076113233,0.08124096147272213,0.5555143309159546,-0.0 +1278,0.052687794307485776,0.0965801639885508,0.8507320417039633,-0.0 +1279,-0.0,0.14702909670727612,0.8529709032927238,-0.0 +1280,-0.0,0.11021501066928736,0.8897849893307126,-0.0 +1281,0.0010537558861497154,0.08737664247905359,0.9051023903737102,0.006467211261086484 +1282,0.3320836406923246,0.05647099000271734,0.611445369304958,-0.0 +1283,0.7767686246475045,0.0646518980111593,0.15857947734133615,-0.0 +1284,0.7060164437203094,0.040222797708172914,0.2537607585715177,-0.0 +1285,0.4893943408389607,0.042836143321980755,0.46776951583905857,-0.0 +1286,0.07060164437203094,0.0965801639885508,0.8328181916394182,-0.0 +1287,-0.0,0.16498164483691263,0.8350183551630874,-0.0 +1288,-0.0,0.11703243400965566,0.8829675659903443,-0.0 +1289,0.0012042924413139607,0.07465078557703278,0.9051023903737102,0.01904253160794305 +1290,0.37016938914887865,0.02840593058486788,0.6014246802662535,-0.0 +1291,0.852036902229627,0.022838368190233774,0.1251247295801392,-0.0 +1292,0.87838079938337,0.014884707626470768,0.10673449299015925,-0.0 +1293,0.5782109083858653,0.015111955071149711,0.406677136542985,-0.0 +1294,0.07421452169597281,0.047835587104917504,0.8779498911991097,-0.0 +1295,-0.0,0.13373512119355796,0.866264878806442,-0.0 +1296,-0.0,0.1172596814543346,0.8827403185456654,-0.0 +1297,0.00045160966549273514,0.0921488388173114,0.9051023903737102,0.0022971611434856607 +1298,0.4278248897767845,0.018293419296654914,0.5538816909265605,-0.0 +1299,1.0,-0.0,-0.0,-0.0 +1300,1.0,-0.0,-0.0,-0.0 +1301,0.5932645639022898,0.005908433561652518,0.40082700253605774,-0.0 +1302,0.0660855477171036,0.026587951027436333,0.9051023903737102,0.002224110881749869 +1303,-0.0,0.10237497382786383,0.8976250261721361,-0.0 +1304,-0.0,0.11351009861713204,0.886489901382868,-0.0 +1305,0.00045160966549273514,0.09555755048749554,0.9039908398470118,-0.0 +1306,0.29475057501159185,0.025906208693399504,0.6793432162950086,-0.0 +1307,0.6473071872062538,0.0810137140280432,0.27167909876570306,-0.0 +1308,0.6476082603165824,0.14941519487640503,0.20297654480701255,-0.0 +1309,0.4171367943601231,0.1958872973132489,0.3869759083266281,-0.0 +1310,0.05705335440724889,0.18509304369099908,0.757853601901752,-0.0 +1311,-0.0,0.18793363674948588,0.8120663632505141,-0.0 +1312,-0.0,0.18259332179953072,0.8174066782004693,-0.0 +1313,0.00045160966549273514,0.14975606604342345,0.8497923242910839,-0.0 +1314,0.32530949570993356,0.13737108030842105,0.5373194239816454,-0.0 +1315,0.7596074573587807,0.1024885975502033,0.137903945091016,-0.0 +1316,0.7785750633094755,0.0654472640675356,0.1559776726229889,-0.0 +1317,0.49481365682487355,0.06249304728670933,0.4426932958884171,-0.0 +1318,0.060816768286355,0.12555421318511603,0.813629018528529,-0.0 +1319,-0.0,0.17248081051131775,0.8275191894886822,-0.0 +1320,-0.0,0.149642442321084,0.850357557678916,-0.0 +1321,0.0007526827758212254,0.12225912523727135,0.8769881919869075,-0.0 +1322,0.23498756261138654,0.09567117420983501,0.6693412631787784,-0.0 +1323,0.6543824052989733,0.07510528046639067,0.27051231423463606,-0.0 +1324,0.723027074453869,0.09396681837474294,0.183006107171388,-0.0 +1325,0.4978243879281584,0.13009916207869487,0.3720764499931467,-0.0 +1326,0.06593501116193934,0.13805282264245788,0.7960121661956028,-0.0 +1327,-0.0,0.14691547298493665,0.8530845270150633,-0.0 +1328,-0.0,0.0921488388173114,0.9051023903737102,0.0027487708089783958 +1329,0.00045160966549273514,0.05772085094845152,0.9051023903737102,0.03672514901234553 +1330,0.321245008720499,0.044994994046430724,0.6337599972330703,-0.0 +1331,0.6322535316898292,0.06760611479198554,0.30014035351818524,-0.0 +1332,0.586640955475063,0.10635180410974533,0.3070072404151917,-0.0 +1333,0.4338463519833543,0.14225690036901834,0.4238967476476273,-0.0 +1334,0.06397803594480415,0.10521556688635063,0.8308063971688453,-0.0 +1335,-0.0,0.10782891250015846,0.8921710874998415,-0.0 +1336,-0.0,0.07646876513446432,0.9051023903737102,0.01842884449182547 +1337,0.0009032193309854703,0.052494159720835834,0.9051023903737102,0.041500230574468486 +1338,0.44137317974156653,0.03931380792945714,0.5193130123289763,-0.0 +1339,0.9902294598704041,0.009770540129595884,-0.0,-0.0 +1340,1.0,-0.0,-0.0,-0.0 +1341,0.5332004783917561,0.13703020914140265,0.32976931246684127,-0.0 +1342,0.04651579554575173,0.13816644636479736,0.815317758089451,-0.0 +1343,-0.0,0.1838431827452649,0.8161568172547351,-0.0 +1344,-0.0,0.189410745139899,0.810589254860101,-0.0 +1345,-0.0,0.18009359990806234,0.8199064000919376,-0.0 +1346,0.15354728626752995,0.16134568572204952,0.6851070280104206,-0.0 +1347,0.4160830384739733,0.2528127822053241,0.3311041793207026,-0.0 +1348,0.4412226431864023,0.34473437357795655,0.21404298323564114,-0.0 +1349,0.3102558401935091,0.3716631957724113,0.31808096403407965,-0.0 +1350,0.04169862578049588,0.2540626431510583,0.7042387310684458,-0.0 +1351,-0.0,0.21872566550348266,0.7812743344965174,-0.0 +1352,-0.0,0.22440685162045623,0.7755931483795437,-0.0 +1353,0.00015053655516424508,0.18441130135696226,0.8154381620878735,-0.0 +1354,0.1686009417839545,0.2500858128691768,0.5813132453468688,-0.0 +1355,0.5011361921417717,0.32337311377813593,0.17549069408009232,-0.0 +1356,0.4672654672298167,0.340984790740754,0.19174974202942935,-0.0 +1357,0.31552461962425765,0.30462519959212314,0.37985018078361915,-0.0 +1358,0.04711794176640871,0.15055143209979976,0.8023306261337915,-0.0 +1359,-0.0,0.15759610288484696,0.8424038971151531,-0.0 +1360,-0.0,0.1468018492625972,0.8531981507374028,-0.0 +1361,0.00015053655516424508,0.13657571425204476,0.863273749192791,-0.0 +1362,0.16182679680156345,0.13737108030842105,0.7008021228900155,-0.0 +1363,0.4746417584328647,0.143620385037092,0.3817378565300432,-0.0 +1364,0.5119748241135975,0.16623150578264684,0.3217936701037556,-0.0 +1365,0.38326606944816793,0.14293864270305515,0.47379528784877695,-0.0 +1366,0.06292428005865443,0.08033197169400635,0.8567437482473392,-0.0 +1367,-0.0,0.09578479793217448,0.9042152020678256,-0.0 +1368,-0.0,0.08033197169400635,0.9051023903737102,0.014565637932283437 +1369,-0.0,0.05647099000271734,0.9051023903737102,0.03842661962357245 +1370,0.20322434947173085,0.045563112658128076,0.7512125378701411,-0.0 +1371,0.49375990093872385,0.051357922497441116,0.45488217656383506,-0.0 +1372,0.46229776090939656,0.05681186116973576,0.48089037792086764,-0.0 +1373,0.39681435941295,0.07396904324299595,0.5292165973440541,-0.0 +1374,0.05193511153166455,0.0849905443099247,0.8630743441584108,-0.0 +1375,-0.0,0.15418739121466282,0.8458126087853371,-0.0 +1376,-0.0,0.17134457328792302,0.8286554267120769,-0.0 +1377,0.00015053655516424508,0.13384874491589743,0.8660007185289383,-0.0 +1378,0.19389108305154765,0.09283058115134822,0.7132783357971041,-0.0 +1379,0.4379108389727889,0.1787301152399887,0.3833590457872224,-0.0 +1380,0.3889864585444093,0.23315587824059553,0.37785766321499514,-0.0 +1381,0.19614913137901133,0.1671404955613626,0.6367103730596261,-0.0 +1382,0.033419115246462405,0.10157960777148753,0.8650012769820501,-0.0 +1383,-0.0,0.12157738290323451,0.8784226170967655,-0.0 +1384,-0.0,0.12532696574043709,0.8746730342595629,-0.0 +1385,0.0006021462206569803,0.12828118252126333,0.8711166712580797,-0.0 +1386,0.22595536930153187,0.187479141860128,0.58656548883834,-0.0 +1387,0.509867312341298,0.2569032362095451,0.23322945144915685,-0.0 +1388,0.6102751946358494,0.2512220500925715,0.13850275527157907,-0.0 +1389,0.40449172372632647,0.22508859395449307,0.37041968231918043,-0.0 +1390,0.060515695176026514,0.1730489291230151,0.7664353757009584,-0.0 +1391,-0.0,0.2544035143180767,0.7455964856819233,-0.0 +1392,-0.0,0.26542501538500546,0.7345749846149945,-0.0 +1393,-0.0,0.24679072492133214,0.7532092750786679,-0.0 +1394,0.17326757499404608,0.28826338357523923,0.5384690414307146,-0.0 +1395,0.4171367943601231,0.2701972117232633,0.31266599391661365,-0.0 +1396,0.5896516865783479,0.2377008271341744,0.17264748628747772,-0.0 +1397,0.5097167757861338,0.17577589845916244,0.31450732575470375,-0.0 +1398,0.076171496913108,0.08521779175460363,0.8386107113322884,-0.0 +1399,-0.0,0.14725634415195507,0.8527436558480449,-0.0 +1400,-0.0,0.1703219597868678,0.8296780402131322,-0.0 +1401,0.00015053655516424508,0.12396348107236342,0.8758859823724723,-0.0 +1402,0.3346427621301168,0.034996106480557225,0.630361131389326,-0.0 +1403,0.8506820732331489,0.04647210243684385,0.10284582433000722,-0.0 +1404,0.8570046085500472,0.04329063821133865,0.09970475323861416,-0.0 +1405,0.5390714040431616,0.03692770976032824,0.4240008861965102,-0.0 +1406,0.06533286494128236,0.013293975513718167,0.9051023903737102,0.016270769171289265 +1407,-0.0,0.00908989778715772,0.9051023903737102,0.08580771183913208 +1408,-0.0,0.037950323261383485,0.9051023903737102,0.056947286364906306 +1409,0.00030107311032849017,0.05760722722611206,0.9051023903737102,0.03698930928984925 +1410,0.30453545109726776,0.012044114567983981,0.6834204343347483,-0.0 +1411,0.8208758353106284,0.0351097302028967,0.144014434486475,-0.0 +1412,0.7624676519069012,0.05272140716551478,0.184810940927584,-0.0 +1413,0.4016315291782058,0.03624596742629141,0.5621225033955028,-0.0 +1414,0.04019326022885344,0.013066728069039224,0.9051023903737102,0.041637621328397136 +1415,-0.0,0.03340537436780462,0.9051023903737102,0.06149223525848517 +1416,-0.0,0.115668949341582,0.884331050658418,-0.0 +1417,-0.0,0.14850620509768928,0.8514937949023107,-0.0 +1418,0.1532462131572015,0.08624040525565888,0.7605133815871397,-0.0 +1419,0.43414742509368276,0.16611788206030734,0.39973469284600993,-0.0 +1420,0.5619529604281268,0.2512220500925715,0.1868249894793017,-0.0 +1421,0.39500792075097907,0.2851955430720735,0.3197965361769474,-0.0 +1422,0.06819305948940302,0.22145263483962996,0.710354305670967,-0.0 +1423,-0.0,0.21304447938650908,0.7869555206134909,-0.0 +1424,-0.0,0.17804837290595185,0.8219516270940481,-0.0 +1425,0.00030107311032849017,0.12066839312451874,0.8790305337651528,-0.0 +1426,0.39681435941295,0.06374290823244351,0.5394427323546065,-0.0 +1427,0.9455201029866233,0.042381648432622876,0.012098248580753812,-0.0 +1428,0.9951971661908242,0.004802833809175788,-0.0,-0.0 +1429,0.5333510149469203,0.01738442951793914,0.44926455553514055,-0.0 +1430,0.050128672869693615,0.009317145231836665,0.9051023903737102,0.03545179152475952 +1431,-0.0,0.03340537436780462,0.9051023903737102,0.06149223525848517 +1432,-0.0,0.06283391845372775,0.9051023903737102,0.03206369117256204 +1433,0.0007526827758212254,0.08942186948116408,0.9051023903737102,0.004723057369304485 +1434,0.26870775096817745,0.021361259799820644,0.7099309892320019,-0.0 +1435,0.9408534697765316,0.021474883522160117,0.03767164670130829,-0.0 +1436,0.9525953210793429,0.04740467892065714,-0.0,-0.0 +1437,0.6012430013259947,0.07260555857492229,0.3261514400990829,-0.0 +1438,0.07632203346827225,0.08908099831414566,0.8345969682175821,-0.0 +1439,-0.0,0.09442131326410082,0.9051023903737102,0.0004762963621889743 +1440,-0.0,0.06249304728670933,0.9051023903737102,0.03240456233958046 +1441,0.0015053655516424507,0.04306339076665971,0.9051023903737102,0.050328853307987635 +1442,0.43068508432490515,0.01499833134881024,0.5543165843262846,-0.0 +1443,1.0,-0.0,-0.0,-0.0 +1444,1.0,-0.0,-0.0,-0.0 +1445,0.7142959542543428,0.02386098169128902,0.2618430640543682,-0.0 +1446,0.09664446841544533,0.06851510457070133,0.8348404270138534,-0.0 +1447,-0.0,0.18441130135696226,0.8155886986430377,-0.0 +1448,-0.0,0.16055031966567326,0.8394496803343268,-0.0 +1449,0.0007526827758212254,0.12873567741062122,0.8705116398135576,-0.0 +1450,0.4606418588025899,0.047608339660238565,0.4917498015371715,-0.0 +1451,1.0,-0.0,-0.0,-0.0 +1452,1.0,-0.0,-0.0,-0.0 +1453,0.7139948811440143,0.12225912523727135,0.16374599361871434,-0.0 +1454,0.09408534697765317,0.21202186588545383,0.6938927871368931,-0.0 +1455,-0.0,0.3759808972213113,0.6240191027786888,-0.0 +1456,-0.0,0.3159875718260703,0.6840124281739297,-0.0 +1457,0.0006021462206569803,0.257357731098903,0.74204012268044,-0.0 +1458,0.4473946419481364,0.13248526024782378,0.4201200978040398,-0.0 +1459,1.0,-0.0,-0.0,-0.0 +1460,1.0,-0.0,-0.0,-0.0 +1461,0.7004465911792322,0.16134568572204952,0.1382077230987182,-0.0 +1462,0.08911764065723309,0.24554086397559793,0.665341495367169,-0.0 +1463,-0.0,0.3350763571791015,0.6649236428208984,-0.0 +1464,-0.0,0.2178166757247669,0.7821833242752331,-0.0 +1465,0.00045160966549273514,0.14055254453392627,0.858995845800581,-0.0 +1466,0.37498655891413446,0.026133456138078447,0.598879984947787,-0.0 +1467,0.8750689951697567,0.017838924407297025,0.10709208042294627,-0.0 +1468,1.0,-0.0,-0.0,-0.0 +1469,0.633307287575979,0.009203521509497192,0.3574891909145238,-0.0 +1470,0.08174134945418507,0.07328730090895913,0.8449713496368557,-0.0 +1471,-0.0,0.14430212737112882,0.8556978726288712,-0.0 +1472,-0.0,0.12157738290323451,0.8784226170967655,-0.0 +1473,-0.0,0.11316922745011362,0.8868307725498864,-0.0 +1474,0.40358850439534105,0.022383873300875885,0.574027622303783,-0.0 +1475,0.9833047783328488,0.016695221667151205,-0.0,-0.0 +1476,1.0,-0.0,-0.0,-0.0 +1477,0.6138880719597913,0.05385764438890949,0.33225428365129916,-0.0 +1478,0.05705335440724889,0.13975717847754995,0.8031894671152011,-0.0 +1479,-0.0,0.13248526024782378,0.8675147397521762,-0.0 +1480,-0.0,0.09248970998432982,0.9051023903737102,0.0024078996419599763 +1481,0.0006021462206569803,0.06294754217606721,0.9051023903737102,0.031347921229565597 +1482,0.359330757177053,0.006476552173349876,0.6341926906495972,-0.0 +1483,0.8726604102871286,0.009203521509497192,0.11813606820337419,-0.0 +1484,0.8652841190840806,0.015339202515828654,0.1193766784000907,-0.0 +1485,0.4991792169246366,0.05851621700482783,0.4423045660705356,-0.0 +1486,0.049526526649036624,0.13987080219988943,0.8106026711510739,-0.0 +1487,-0.0,0.2715606963913369,0.7284393036086632,-0.0 +1488,-0.0,0.2741740420051447,0.7258259579948553,-0.0 +1489,0.00045160966549273514,0.3164420667154282,0.6831063236190791,-0.0 +1490,0.32530949570993356,0.2813323365125314,0.393358167777535,-0.0 +1491,0.896896795668572,0.10310320433142794,-0.0,-0.0 +1492,1.0,-0.0,-0.0,-0.0 +1493,0.6774144982391028,0.16180018061140744,0.16078532114948973,-0.0 +1494,0.0885154944365761,0.052039664831477955,0.8594448407319459,-0.0 +1495,-0.0,0.018975161630691743,0.9051023903737102,0.07592244799559805 +1496,-0.0,0.03385986925716251,0.9051023903737102,0.061037740369127284 +1497,0.00015053655516424508,0.08192270380675895,0.9051023903737102,0.012824369264366595 +1498,0.4332442057626973,0.032041889699730965,0.5347139045375717,-0.0 +1499,0.9991111166250944,0.000888883374905558,-0.0,-0.0 +1500,0.9568103446239418,0.043189655376058234,-0.0,-0.0 +1501,0.4850287807391976,0.09953438076937704,0.4154368384914254,-0.0 +1502,0.039290040897867966,0.11055588183630578,0.8501540772658263,-0.0 +1503,-0.0,0.20395458159935134,0.7960454184006487,-0.0 +1504,-0.0,0.25269915848298463,0.7473008415170154,-0.0 +1505,-0.0,0.28110508906785253,0.7188949109321474,-0.0 +1506,0.21948229742946931,0.42381648432622876,0.3567012182443019,-0.0 +1507,0.6066623173119077,0.3933376826880923,-0.0,-0.0 +1508,0.6152429009562697,0.38475709904373034,-0.0,-0.0 +1509,0.4260184511148135,0.5739815488851865,-0.0,-0.0 +1510,0.06096730484151926,0.44210990362288366,0.49692279153559704,-0.0 +1511,-0.0,0.3570057355906195,0.6429942644093805,-0.0 +1512,-0.0,0.34916569874919595,0.650834301250804,-0.0 +1513,-0.0,0.33212214039827526,0.6678778596017247,-0.0 +1514,0.2491379987968256,0.5233508650956058,0.2275111361075686,-0.0 +1515,0.6175009492837332,0.3824990507162668,-0.0,-0.0 +1516,0.6540813321886448,0.34591866781135516,-0.0,-0.0 +1517,0.5119748241135975,0.4880251758864025,-0.0,-0.0 +1518,0.06894574226522424,0.5397126811124896,0.3913415766222862,-0.0 +1519,-0.0,0.3934789504615898,0.6065210495384101,-0.0 +1520,-0.0,0.3553013797555274,0.6446986202444727,-0.0 +1521,-0.0,0.340984790740754,0.6590152092592461,-0.0 +1522,0.3829649963378395,0.4250663452719629,0.19196865839019756,-0.0 +1523,0.8646819728634237,0.13531802713657626,-0.0,-0.0 +1524,0.9875198018774477,0.0124801981225523,-0.0,-0.0 +1525,0.6768123520184458,0.27894623834340254,0.04424140963815171,-0.0 +1526,0.08881656754690459,0.1124874851160768,0.7986959473370187,-0.0 +1527,-0.0,0.06453827428881982,0.9051023903737102,0.03035933533746997 +1528,-0.0,0.07328730090895913,0.9051023903737102,0.021610308717330662 +1529,0.00015053655516424508,0.07601427024510644,0.9051023903737102,0.018732802826019104 +1530,0.3841692887791534,0.01215773829032345,0.6036729729305232,-0.0 +1531,0.8570046085500472,0.02090676491046276,0.12208862653949004,-0.0 +1532,0.9726166829161874,0.027383317083812635,-0.0,-0.0 +1533,0.6248772404867813,0.0398819265411545,0.3352408329720642,-0.0 +1534,0.07677364313376497,0.049653566662349054,0.8735727902038859,-0.0 +1535,-0.0,0.10498831944167167,0.8950116805583284,-0.0 +1536,-0.0,0.08896737459180619,0.9051023903737102,0.0059302350344836025 +1537,-0.0,0.08805838481309042,0.9051023903737102,0.006839224813199374 +1538,0.2962559405632343,0.049312695495330634,0.654431363941435,-0.0 +1539,0.7167045391369709,0.10907877344589265,0.17421668741713647,-0.0 +1540,0.5944688563436037,0.17077645467622568,0.2347546889801706,-0.0 +1541,0.28225604093295953,0.197364405703662,0.5203795533633785,-0.0 +1542,0.025892287488250152,0.10101148915979018,0.8730962233519597,-0.0 +1543,-0.0,0.14259777153603675,0.8574022284639633,-0.0 +1544,-0.0,0.15816422149654433,0.8418357785034557,-0.0 +1545,-0.0,0.1293037960223186,0.8706962039776814,-0.0 +1546,0.12705285255862284,0.11146487161502155,0.7614822758263556,-0.0 +1547,0.41939484268758676,0.17486690868044666,0.4057382486319666,-0.0 +1548,0.3462340768777637,0.2388370643575691,0.4149288587646672,-0.0 +1549,0.20548239779919453,0.1846385488016412,0.6098790533991643,-0.0 +1550,0.03356965180162665,0.0649927691781777,0.9014375790201956,-0.0 +1551,-0.0,0.10044337054809283,0.8995566294519072,-0.0 +1552,-0.0,0.1108967530033242,0.8891032469966758,-0.0 +1553,-0.0,0.10816978366717689,0.8918302163328231,-0.0 +1554,0.1406011425234049,0.1072607938884611,0.7521380635881341,-0.0 +1555,0.4154808922533164,0.19043335864095426,0.39408574910572935,-0.0 +1556,0.525222040968051,0.2536081482617004,0.22116981077024855,-0.0 +1557,0.4032874312850126,0.2308834037938061,0.3658291649211813,-0.0 +1558,0.05328994052814275,0.11339647489479257,0.8333135845770647,-0.0 +1559,-0.0,0.10862427855653477,0.8913757214434652,-0.0 +1560,-0.0,0.11896403728942667,0.8810359627105733,-0.0 +1561,-0.0,0.09953438076937704,0.900465619230623,-0.0 +1562,0.2965570136735628,0.047267468493220145,0.656175517833217,-0.0 +1563,0.7773707708681615,0.06147043378565409,0.16115879534618438,-0.0 +1564,0.845864903467893,0.06567451151221453,0.08846058501989251,-0.0 +1565,0.5395230137086543,0.042608895877301815,0.41786809041404394,-0.0 +1566,0.06111784139668349,0.01363484668073658,0.9051023903737102,0.020144921548869717 +1567,-0.0,0.02886042547422576,0.9051023903737102,0.06603718415206403 +1568,-0.0,0.05567562394634104,0.9051023903737102,0.03922198567994875 +1569,-0.0,0.056925484892075225,0.9051023903737102,0.03797212473421457 +1570,0.29444950190126334,0.014771083904131295,0.6907794141946053,-0.0 +1571,0.8265962244068698,0.01443021273711288,0.15897356285601738,-0.0 +1572,0.9032193309854704,0.020565893743444346,0.07621477527108533,-0.0 +1573,0.6415867981100125,0.0179525481296365,0.34046065376035095,-0.0 +1574,0.07301022925465886,0.03863206559542032,0.8883577051499209,-0.0 +1575,-0.0,0.11680518656497671,0.8831948134350233,-0.0 +1576,-0.0,0.1268040741308502,0.8731959258691497,-0.0 +1577,-0.0,0.10839703111185582,0.8916029688881442,-0.0 +1578,0.29173984390830693,0.07487803302171173,0.6333821230699814,-0.0 +1579,0.7517795564902399,0.18918349769522008,0.05903694581454011,-0.0 +1580,0.9261008873704356,0.07389911262956439,-0.0,-0.0 +1581,0.6047053420947724,0.26383428327225283,0.13146037463297466,-0.0 +1582,0.06894574226522424,0.1566871131061312,0.7743671446286446,-0.0 +1583,-0.0,0.11998665079048192,0.8800133492095181,-0.0 +1584,-0.0,0.11362372233947152,0.8863762776605285,-0.0 +1585,-0.0,0.1008978654374507,0.8991021345625493,-0.0 +1586,0.2827076505984522,0.10476107199699274,0.612531277404555,-0.0 +1587,0.7352205354221729,0.19872789037173566,0.06605157420609142,-0.0 +1588,0.7209195626815696,0.2661067577190423,0.012973679599388,-0.0 +1589,0.5312435031746209,0.21315810310884853,0.2555983937165306,-0.0 +1590,0.0660855477171036,0.10180685521616648,0.8321075970667299,-0.0 +1591,-0.0,0.07680963630148273,0.9051023903737102,0.018087973324807063 +1592,-0.0,0.056698237447396285,0.9051023903737102,0.038199372178893506 +1593,-0.0,0.06646987756859084,0.9051023903737102,0.028427732057698954 +1594,0.16453645479451984,0.06067506772927779,0.7747884774762024,-0.0 +1595,0.4373086927521319,0.20543168998976447,0.3572596172581036,-0.0 +1596,0.4794589281981205,0.3141695922686388,0.20637147953324075,-0.0 +1597,0.3490942714258843,0.35280165786405904,0.2981040707100567,-0.0 +1598,0.040795406449510414,0.18520666741333858,0.773997926137151,-0.0 +1599,-0.0,0.1803208473527413,0.8196791526472587,-0.0 +1600,-0.0,0.22861092934701666,0.7713890706529833,-0.0 +1601,-0.0,0.19088785353031215,0.8091121464696879,-0.0 +1602,0.31161066918998725,0.16520889228159158,0.5231804385284212,-0.0 +1603,0.7386828761909506,0.16975384117517045,0.09156328263387892,-0.0 +1604,0.6998444449585753,0.1399844259222289,0.16017112911919573,-0.0 +1605,0.5094157026758053,0.07430991441001437,0.4162743829141804,-0.0 +1606,0.05494584263494945,0.021588507244499587,0.9051023903737102,0.01836325974684075 +1607,-0.0,0.06510639290051717,0.9051023903737102,0.029791216725772618 +1608,-0.0,0.1838431827452649,0.8161568172547351,-0.0 +1609,-0.0,0.23542835268738496,0.7645716473126151,-0.0 +1610,0.28511623548108017,0.21713493339073006,0.49774883112818974,-0.0 +1611,0.740188241742593,0.259811758257407,-0.0,-0.0 +1612,0.8959935763375866,0.10400642366241342,-0.0,-0.0 +1613,0.6316513854691722,0.30928377220804143,0.059064842322786304,-0.0 +1614,0.07511774102695828,0.09385319465240348,0.8310290643206382,-0.0 +1615,-0.0,0.021361259799820644,0.9051023903737102,0.07353634982646914 +1616,-0.0,0.01647543973922337,0.9051023903737102,0.07842216988706642 +1617,-0.0,0.04692659732620174,0.9051023903737102,0.04797101230008805 +1618,0.3156751561794219,0.02499721891468373,0.6593276249058944,-0.0 +1619,0.8434563185852652,0.0734009246312986,0.0831427567834363,-0.0 +1620,0.8679937770770371,0.12612233179681337,0.00588389112614951,-0.0 +1621,0.4708783445537586,0.13737108030842105,0.39175057513782036,-0.0 +1622,0.0427523816666456,0.11623706795327936,0.841010550380075,-0.0 +1623,-0.0,0.10203410266084542,0.8979658973391546,-0.0 +1624,-0.0,0.08601315781097994,0.9051023903737102,0.008884451815309849 +1625,-0.0,0.09146709648327457,0.9051023903737102,0.003430513143015221 +1626,0.2378477571595072,0.061129562618635676,0.7010226802218571,-0.0 +1627,0.6896079592074067,0.09430768954176136,0.21608435125083192,-0.0 +1628,0.6215654362731678,0.12691769785318968,0.2515168658736425,-0.0 +1629,0.4683192231159664,0.10317033988424014,0.4285104369997934,-0.0 +1630,0.0581071102933986,0.06067506772927779,0.8812178219773237,-0.0 +1631,-0.0,0.05749360350377258,0.9051023903737102,0.03740400612251721 +1632,-0.0,0.04624485499216491,0.9051023903737102,0.048652754634124884 +1633,-0.0,0.045563112658128076,0.9051023903737102,0.049334496968161716 +1634,0.2873742838085438,0.017498053240278613,0.6951276629511776,-0.0 +1635,0.9056279158680983,0.015339202515828654,0.07903288161607303,-0.0 +1636,0.8764238241662349,0.011589619678626095,0.11198655615513908,-0.0 +1637,0.5304908203987996,0.044426875434733365,0.4250823041664671,-0.0 +1638,0.041999698890824375,0.22077089250559315,0.7372294086035825,-0.0 +1639,-0.0,0.31439683971331767,0.6856031602866823,-0.0 +1640,-0.0,0.281218712790192,0.718781287209808,-0.0 +1641,-0.0,0.27701463506363155,0.7229853649363684,-0.0 +1642,0.24010580548697089,0.25963020554569244,0.5002639889673366,-0.0 +1643,0.6515222107508527,0.3484777892491473,-0.0,-0.0 +1644,0.6512211376405241,0.34877886235947586,-0.0,-0.0 +1645,0.38928753165473773,0.6107124683452623,-0.0,-0.0 +1646,0.03898896778753947,0.8013881136602926,0.15962291855216793,-0.0 +1647,-0.0,0.8047968253304768,0.1952031746695232,-0.0 +1648,-0.0,0.7580974754489539,0.2419025245510461,-0.0 +1649,-0.0,0.5931158306120413,0.4068841693879587,-0.0 +1650,0.328621299923547,0.4891501246714248,0.18222857540502818,-0.0 +1651,0.8694991426286796,0.13050085737132044,-0.0,-0.0 +1652,0.899907526771857,0.10009247322814296,-0.0,-0.0 +1653,0.5237166754164085,0.10816978366717689,0.36811354091641457,-0.0 +1654,0.030257847588013257,0.03170101853271255,0.9051023903737102,0.032938743505563986 +1655,-0.0,0.1299855383563554,0.8700144616436446,-0.0 +1656,-0.0,0.18247969807719125,0.8175203019228088,-0.0 +1657,-0.0,0.19952325642811197,0.800476743571888,-0.0 +1658,0.144966702623168,0.24110953880435854,0.6139237585724735,-0.0 +1659,0.45281395793404916,0.3633686640416299,0.18381737802432102,-0.0 +1660,0.5015878018072646,0.44256439851224155,0.055847799680493804,-0.0 +1661,0.38838431232375226,0.47062945793009103,0.1409862297461567,-0.0 +1662,0.043354527887302576,0.3632550403192904,0.593390431793407,-0.0 +1663,-0.0,0.4133631018709974,0.5866368981290027,-0.0 +1664,-0.0,0.40654567853062906,0.5934543214693709,-0.0 +1665,-0.0,0.38268469683934003,0.61731530316066,-0.0 +1666,0.20713829990600122,0.5658461372505681,0.2270155628434307,-0.0 +1667,0.5151360917720467,0.48486390822795333,-0.0,-0.0 +1668,0.5830280781511211,0.4169719218488789,-0.0,-0.0 +1669,0.3530082218601547,0.6469917781398453,-0.0,-0.0 +1670,0.027548189595056848,0.4081364106433817,0.5643153997615614,-0.0 +1671,-0.0,0.32291861888877804,0.677081381111222,-0.0 +1672,-0.0,0.3293951710621279,0.6706048289378721,-0.0 +1673,-0.0,0.3159875718260703,0.6840124281739297,-0.0 +1674,0.21993390709496205,0.42165763360177877,0.35840845930325915,-0.0 +1675,0.5470498414668665,0.4529501585331335,-0.0,-0.0 +1676,0.5207059443131237,0.47929405568687633,-0.0,-0.0 +1677,0.4141260632568382,0.5858739367431618,-0.0,-0.0 +1678,0.045462039659602015,0.5013078629617483,0.45323009737864967,-0.0 +1679,-0.0,0.5472118467868947,0.4527881532131053,-0.0 +1680,-0.0,0.5644826525824945,0.4355173474175055,-0.0 +1681,-0.0,0.49619479545647205,0.503805204543528,-0.0 +1682,0.2693098971888344,0.5638009102484576,0.166889192562708,-0.0 +1683,0.6641672813846492,0.3358327186153508,-0.0,-0.0 +1684,0.6330062144656505,0.36699378553434947,-0.0,-0.0 +1685,0.42797542633194874,0.5184650450350085,0.05355952863304281,-0.0 +1686,0.043203991332138335,0.3422346516864882,0.6145613569813734,-0.0 +1687,-0.0,0.27383317083812636,0.7261668291618737,-0.0 +1688,-0.0,0.24667710119899264,0.7533228988010073,-0.0 +1689,-0.0,0.20225022576425927,0.7977497742357407,-0.0 +1690,0.34518032099161394,0.1739579189017309,0.4808617601066552,-0.0 +1691,0.7856502814021951,0.15384652004764443,0.06050319855016051,-0.0 +1692,0.8229833470829278,0.11987302706814244,0.05714362584892985,-0.0 +1693,0.6016946109914876,0.06624263012391189,0.3320627588846006,-0.0 +1694,0.05690281785208463,0.05851621700482783,0.8845809651430876,-0.0 +1695,-0.0,0.11794142378837143,0.8820585762116285,-0.0 +1696,-0.0,0.07805949724721692,0.9051023903737102,0.016838112379072873 +1697,-0.0,0.06794698595900396,0.9051023903737102,0.02695062366728583 +1698,0.2635895080925931,0.024088229135967962,0.712322262771439,-0.0 +1699,0.8214779815312853,0.025678961248720558,0.15284305721999414,-0.0 +1700,0.9628318068305114,0.008067284286102478,0.029100908883386123,-0.0 +1701,0.4905986332802747,0.0036359591148630885,0.5057654076048621,-0.0 +1702,0.03853735812204674,0.061129562618635676,0.9003330792593176,-0.0 +1703,-0.0,0.10782891250015846,0.8921710874998415,-0.0 +1704,-0.0,0.056698237447396285,0.9051023903737102,0.038199372178893506 +1705,-0.0,0.039995550263493974,0.9051023903737102,0.05490205936279582 +1706,0.3323847138026531,0.010680629899910322,0.6569346562974365,-0.0 +1707,0.9026171847648135,0.005113067505276217,0.09226974772991037,-0.0 +1708,1.0,-0.0,-0.0,-0.0 +1709,0.6187052417250472,0.015566449960507597,0.36572830831444525,-0.0 +1710,0.04139755267016739,0.09714828260024815,0.8614541647295845,-0.0 +1711,-0.0,0.17532140356980452,0.8246785964301955,-0.0 +1712,-0.0,0.14612010692856037,0.8538798930714396,-0.0 +1713,-0.0,0.1415751580349815,0.8584248419650184,-0.0 +1714,0.3782983631277479,0.05181241738679901,0.5698892194854531,-0.0 +1715,0.9953477027459884,0.004652297254011617,-0.0,-0.0 +1716,1.0,-0.0,-0.0,-0.0 +1717,0.6598017212848861,0.0482900819942754,0.29190819672083845,-0.0 +1718,0.046967405211244456,0.1069199227214427,0.8461126720673129,-0.0 +1719,-0.0,0.20236384948659877,0.7976361505134012,-0.0 +1720,-0.0,0.21315810310884853,0.7868418968911515,-0.0 +1721,-0.0,0.2237251092864194,0.7762748907135806,-0.0 +1722,0.2983634523355337,0.05238053599849637,0.64925601166597,-0.0 +1723,0.7761664784268475,0.08646765270033782,0.13736586887281466,-0.0 +1724,0.6641672813846492,0.1307809044127317,0.2050518142026191,-0.0 +1725,0.33118042136133913,0.16827673278475733,0.5005428458539036,-0.0 +1726,0.021225654278158555,0.1860020334697149,0.7927723122521265,-0.0 +1727,-0.0,0.17657126451553873,0.8234287354844613,-0.0 +1728,-0.0,0.13680296169672368,0.8631970383032763,-0.0 +1729,-0.0,0.09737553004492708,0.902624469955073,-0.0 +1730,0.2988150620010265,0.04079091631987027,0.6603940216791032,-0.0 +1731,0.942960981548831,0.04635847871450438,0.010680539736664607,-0.0 +1732,1.0,-0.0,-0.0,-0.0 +1733,0.6558877708506157,0.005567562394634104,0.33854466675475026,-0.0 +1734,0.037031992570404286,0.03488248275821776,0.9051023903737102,0.022983134297667747 +1735,-0.0,0.19520555497921208,0.8047944450207879,-0.0 +1736,-0.0,0.19475106008985416,0.8052489399101459,-0.0 +1737,-0.0,0.1711173258432441,0.8288826741567559,-0.0 +1738,0.34518032099161394,0.02954216780826259,0.6252775112001234,-0.0 +1739,0.9462727857624446,0.03454161159119934,0.019185602646356048,-0.0 +1740,1.0,-0.0,-0.0,-0.0 +1741,0.5949204660090965,0.03215551342207044,0.372924020568833,-0.0 +1742,0.03627930979458306,0.05578924766868051,0.9051023903737102,0.002829052163026223 +1743,-0.0,0.11066950555864526,0.8893304944413547,-0.0 +1744,-0.0,0.08942186948116408,0.9051023903737102,0.00547574014512571 +1745,-0.0,0.0554483765016621,0.9051023903737102,0.03944923312462769 +1746,0.29700862333905553,0.008067284286102478,0.694924092374842,-0.0 +1747,0.8306607113963043,0.012044114567983981,0.1572951740357118,-0.0 +1748,0.9512404920828647,0.008635402897799835,0.0401241050193355,-0.0 +1749,0.5750496407274162,0.011021501066928736,0.41392885820565506,-0.0 +1750,0.03597823668425457,0.10442020082997432,0.8596015624857711,-0.0 +1751,-0.0,0.13543947702865003,0.86456052297135,-0.0 +1752,-0.0,0.10646542783208482,0.8935345721679152,-0.0 +1753,-0.0,0.1108967530033242,0.8891032469966758,-0.0 +1754,0.23935312271114967,0.06476552173349877,0.6958813555553516,-0.0 +1755,0.7441021921768634,0.06408377939946193,0.19181402842367468,-0.0 +1756,0.7975426692601705,0.05533475277932263,0.14712257796050687,-0.0 +1757,0.4755449777638502,0.13180351791378697,0.39265150432236284,-0.0 +1758,0.026494433708907132,0.2365645899107797,0.7369409763803132,-0.0 +1759,-0.0,0.1620274280560864,0.8379725719439136,-0.0 +1760,-0.0,0.09226246253965086,0.9051023903737102,0.0026351470866389365 +1761,-0.0,0.07169656879620653,0.9051023903737102,0.023201040830083258 +1762,0.27879370016418187,0.05272140716551478,0.6684848926703033,-0.0 +1763,0.8484240249056851,0.02931492036358365,0.12226105473073123,-0.0 +1764,0.9851112169948197,0.014888783005180262,-0.0,-0.0 +1765,0.5770066159445513,0.02851955430720735,0.3944738297482414,-0.0 +1766,0.025741750933085908,0.1468018492625972,0.8274563998043168,-0.0 +1767,-0.0,0.16020944849865482,0.8397905515013452,-0.0 +1768,-0.0,0.09521667932047713,0.9047833206795228,-0.0 +1769,-0.0,0.0789684870259327,0.9051023903737102,0.015929122600357087 +1770,0.2348370260562223,0.02886042547422576,0.736302548469552,-0.0 +1771,0.7793277460852968,0.040222797708172914,0.1804494562065303,-0.0 +1772,0.8889183582448672,0.025906208693399504,0.08517543306173325,-0.0 +1773,0.49210399883191713,0.03704133348266771,0.4708546676854152,-0.0 +1774,0.021225654278158555,0.17054920723154676,0.8082251384902946,-0.0 +1775,-0.0,0.1415751580349815,0.8584248419650184,-0.0 +1776,-0.0,0.0953303030428166,0.9046696969571834,-0.0 +1777,-0.0,0.07783224980253799,0.9051023903737102,0.017065359823751805 +1778,0.2425143903695988,0.03011028641995995,0.7273753232104413,-0.0 +1779,0.8177145676521792,0.02397460541362849,0.1583108269341923,-0.0 +1780,0.9217353272706725,0.042040777265604456,0.036223895463723066,-0.0 +1781,0.5539745230044218,0.08010472424932741,0.3659207527462508,-0.0 +1782,0.027397653039892604,0.15146042187851552,0.8211419250815919,-0.0 +1783,-0.0,0.1308945281350712,0.8691054718649288,-0.0 +1784,-0.0,0.08476329686524575,0.9051023903737102,0.01013431276104404 +1785,-0.0,0.0602205728399199,0.9051023903737102,0.034677036786369894 +1786,0.21707371254684138,0.042268024710283396,0.7406582627428753,-0.0 +1787,0.7132421983681931,0.07453716185469332,0.21222063977711358,-0.0 +1788,0.770295552775442,0.05510750533464368,0.17459694188991426,-0.0 +1789,0.45386771382019886,0.06272029473138828,0.48341199144841285,-0.0 +1790,0.016559021068066955,0.13930268358819206,0.844138295343741,-0.0 +1791,-0.0,0.13680296169672368,0.8631970383032763,-0.0 +1792,-0.0,0.12271362012662923,0.8772863798733708,-0.0 +1793,-0.0,0.13259888397016326,0.8674011160298367,-0.0 +1794,0.28346033337427345,0.14168878175732097,0.5748508848684056,-0.0 +1795,0.9425093718833384,0.05749062811666161,-0.0,-0.0 +1796,1.0,-0.0,-0.0,-0.0 +1797,0.6027483668776372,0.1870246469707701,0.21022698615159263,-0.0 +1798,0.023032092940129496,0.3115562466548309,0.6654116604050395,-0.0 +1799,-0.0,0.29439906458157067,0.7056009354184294,-0.0 +1800,-0.0,0.2816732076795499,0.7183267923204502,-0.0 +1801,-0.0,0.27701463506363155,0.7229853649363684,-0.0 +1802,0.2315252218426089,0.2089540253822881,0.5595207527751029,-0.0 +1803,0.7663816023411716,0.19725078198132254,0.036367615677505905,-0.0 +1804,0.8714561178458147,0.12854388215418533,-0.0,-0.0 +1805,0.4123196245948672,0.11669156284263724,0.47098881256249553,-0.0 +1806,0.0106880954166614,0.16191380433374689,0.8273981002495917,-0.0 +1807,-0.0,0.14475662226048672,0.8552433777395133,-0.0 +1808,-0.0,0.10112511288212964,0.8988748871178703,-0.0 +1809,-0.0,0.07590064652276697,0.9051023903737102,0.01899696310352282 +1810,0.19042874228277,0.04601760754748596,0.763553650169744,-0.0 +1811,0.5852861264785848,0.04169990609858605,0.3730139674228291,-0.0 +1812,0.5837807609269423,0.03317812692312568,0.383041112149932,-0.0 +1813,0.3518039294188407,0.02942854408592312,0.6187675264952361,-0.0 +1814,0.01249453407863234,0.04988081410702799,0.9051023903737102,0.03252226144062946 +1815,-0.0,0.05851621700482783,0.9051023903737102,0.036381392621461964 +1816,-0.0,0.05181241738679901,0.9051023903737102,0.04308519223949078 +1817,-0.0,0.053516773221891085,0.9051023903737102,0.041380836404398706 +1818,0.07858008179573593,0.04624485499216491,0.8751750632120991,-0.0 +1819,0.2938473556806064,0.06749249106964608,0.6386601532497476,-0.0 +1820,0.3346427621301168,0.08408155453120891,0.5812756833386743,-0.0 +1821,0.19193410783441248,0.09839814354598232,0.7096677486196052,-0.0 +1822,0.007225754647883762,0.12191825407025293,0.8708559912818633,-0.0 +1823,-0.0,0.13032640952337382,0.8696735904766262,-0.0 +1824,-0.0,0.13737108030842105,0.862628919691579,-0.0 +1825,-0.0,0.12816755879892386,0.8718324412010762,-0.0 +1826,0.06292428005865443,0.10635180410974533,0.8307239158316002,-0.0 +1827,0.22174034575693297,0.16634512950498628,0.6119145247380808,-0.0 +1828,0.2789442367193461,0.2025910969312777,0.5184646663493763,-0.0 +1829,0.13608504586847753,0.15441463865934177,0.7095003154721806,-0.0 +1830,0.004365560099763107,0.15293753026892865,0.8426969096313083,-0.0 +1831,-0.0,0.16725411928370207,0.832745880716298,-0.0 +1832,-0.0,0.1638454076135179,0.8361545923864822,-0.0 +1833,-0.0,0.13555310075098953,0.8644468992490104,-0.0 +1834,0.10522505205980731,0.12203187779259239,0.7727430701476004,-0.0 +1835,0.37107260847986406,0.14009804964456837,0.48882934187556754,-0.0 +1836,0.3853735812204674,0.09203521509497192,0.5225912036845607,-0.0 +1837,0.23303058739425137,0.04135903493156763,0.7256103776741809,-0.0 +1838,0.005419315985912822,0.038177570706062425,0.9051023903737102,0.05130072293431454 +1839,-0.0,0.05476663416762527,0.9051023903737102,0.04013097545866452 +1840,-0.0,0.051357922497441116,0.9051023903737102,0.043539687128848675 +1841,-0.0,0.048517329438954336,0.9051023903737102,0.046380280187335456 +1842,0.18681586495882813,0.01977052768706804,0.7934136073541038,-0.0 +1843,0.568124959189861,0.03317812692312568,0.39869691388701334,-0.0 +1844,0.5241682850819014,0.022156625856196946,0.4536750890619017,-0.0 +1845,0.2890301859153505,0.010794253622249794,0.7001755604623996,-0.0 +1846,0.006774144982391027,0.02329286307959166,0.9051023903737102,0.0648306015643071 +1847,-0.0,0.022838368190233774,0.9051023903737102,0.07205924143605602 +1848,-0.0,0.023065615634912714,0.9051023903737102,0.07183199399137707 +1849,-0.0,0.02556533752638109,0.9051023903737102,0.0693322720999087 +1850,0.17582669643183824,0.00749916567440512,0.8166741378937566,-0.0 +1851,0.648662016202732,0.029087672918904708,0.3222503108783633,-0.0 +1852,0.6632640620536637,0.014884707626470768,0.3218512303198655,-0.0 +1853,0.3963627497474572,0.007840036841423535,0.5957972134111192,-0.0 +1854,0.007526827758212253,0.03158739481037308,0.9051023903737102,0.055783387057704456 +1855,-0.0,0.06147043378565409,0.9051023903737102,0.0334271758406357 +1856,-0.0,0.05647099000271734,0.9051023903737102,0.03842661962357245 +1857,-0.0,0.0382911944284019,0.9051023903737102,0.056606415197887894 +1858,0.2815033581571383,0.010567006177570849,0.7079296356652909,-0.0 +1859,0.9497351265312222,0.03579147253693352,0.014473400931844349,-0.0 +1860,0.9407029332213674,0.01817979557431544,0.041117271204317074,-0.0 +1861,0.5607486679868129,0.012384985735002395,0.42686634627818476,-0.0 +1862,0.00978487608567593,0.0594252067835436,0.9051023903737102,0.025687526757070263 +1863,-0.0,0.11157849533736101,0.888421504662639,-0.0 +1864,-0.0,0.10203410266084542,0.8979658973391546,-0.0 +1865,-0.0,0.07760500235785904,0.9051023903737102,0.01729260726843075 +1866,0.3135676444071225,0.0179525481296365,0.668479807463241,-0.0 +1867,0.9611759047237047,0.03882409527629527,-0.0,-0.0 +1868,0.9973046779631235,0.002695322036876502,-0.0,-0.0 +1869,0.5765550062790586,0.09112622531625615,0.3323187684046852,-0.0 +1870,0.00797843742370499,0.22883817679169563,0.7631833857845994,-0.0 +1871,-0.0,0.27735550623065,0.72264449376935,-0.0 +1872,-0.0,0.23190601729486135,0.7680939827051386,-0.0 +1873,-0.0,0.21918016039284052,0.7808198396071595,-0.0 +1874,0.2949011115667561,0.1148735832852057,0.5902253051480382,-0.0 +1875,0.9522942479690144,0.04770575203098559,-0.0,-0.0 +1876,0.9197783520535373,0.08022164794646269,-0.0,-0.0 +1877,0.4326420595420403,0.36882260271392453,0.19853533774403515,-0.0 +1878,0.004516096654927352,0.398819265411545,0.5966646379335276,-0.0 +1879,-0.0,0.36109618959484047,0.6389038104051595,-0.0 +1880,-0.0,0.2508811789255531,0.7491188210744468,-0.0 +1881,-0.0,0.17191269189962038,0.8280873081003797,-0.0 +1882,0.1592676753637713,0.11612344423093988,0.7246088804052888,-0.0 +1883,0.14481616606800374,0.2165668147790327,0.6386170191529635,-0.0 +1884,0.10883792938374919,0.25974382926803186,0.631418241348219,-0.0 +1885,-0.0,0.209408520271646,0.790591479728354,-0.0 +1886,-0.0,0.3280316863940543,0.6719683136059458,-0.0 +1887,-0.0,0.37086782971603505,0.629132170283965,-0.0 +1888,-0.0,0.4294976704432023,0.5705023295567977,-0.0 +1889,-0.0,0.41893066426563147,0.5810693357343686,-0.0 +1890,0.1326227050996999,0.49028636189481956,0.3770909330054806,-0.0 +1891,0.48246965930140545,0.5175303406985945,-0.0,-0.0 +1892,0.574296957951595,0.42570304204840503,-0.0,-0.0 +1893,0.4151798191429879,0.40609118364127117,0.17872899721574093,-0.0 +1894,0.003161267658449146,0.20929489654930652,0.7875438357922443,-0.0 +1895,-0.0,0.2555397515414714,0.7444602484585285,-0.0 +1896,-0.0,0.3051933182038205,0.6948066817961795,-0.0 +1897,-0.0,0.2938309459698733,0.7061690540301266,-0.0 +1898,0.23935312271114967,0.16259554666778372,0.5980513306210666,-0.0 +1899,0.8965957225582436,0.1034042774417564,-0.0,-0.0 +1900,1.0,-0.0,-0.0,-0.0 +1901,0.5420821351464464,0.21429434033224326,0.2436235245213103,-0.0 +1902,0.0028601945481206564,0.2634934121052344,0.7336463933466449,-0.0 +1903,-0.0,0.4064320548082896,0.5935679451917104,-0.0 +1904,-0.0,0.33882594001630406,0.6611740599836959,-0.0 +1905,-0.0,0.2825821974582656,0.7174178025417344,-0.0 +1906,0.1406011425234049,0.1827069455218702,0.676691911954725,-0.0 +1907,0.5359101363847124,0.22429322789811676,0.2397966357171708,-0.0 +1908,0.5569852541077067,0.23440573918632973,0.2086090067059636,-0.0 +1909,0.2693098971888344,0.1348713584169527,0.5958187443942129,-0.0 +1910,0.00045160966549273514,0.1104422581139663,0.8891061322205409,-0.0 +1911,-0.0,0.11794142378837143,0.8820585762116285,-0.0 +1912,-0.0,0.11510083072988464,0.8848991692701154,-0.0 +1913,-0.0,0.08828563225776936,0.9051023903737102,0.0066119773685204275 +1914,0.12208514623820275,0.06226579984203039,0.8156490539197668,-0.0 +1915,0.4391151314141029,0.06908322318239868,0.4918016454034984,-0.0 +1916,0.5083619467896556,0.0921488388173114,0.399489214393033,-0.0 +1917,0.3540619777463044,0.08703577131203517,0.5589022509416604,-0.0 +1918,0.0012042924413139607,0.05272140716551478,0.9051023903737102,0.04097191001946105 +1919,-0.0,0.054084891833588444,0.9051023903737102,0.04081271779270135 +1920,-0.0,0.05158516994212007,0.9051023903737102,0.04331243968416972 +1921,-0.0,0.038404818150741364,0.9051023903737102,0.05649279147554843 +1922,0.24401975592124125,0.01670268718390231,0.7392775568948564,-0.0 +1923,0.8696496791838437,0.01806617185197597,0.11228414896418026,-0.0 +1924,0.944165273990145,0.013748470403076052,0.04208625560677892,-0.0 +1925,0.5374155019363549,0.003976830281881503,0.45860766778176365,-0.0 +1926,0.001956975217135186,0.019202409075370682,0.9051023903737102,0.07373822533378392 +1927,-0.0,0.09123984903859562,0.9051023903737102,0.003657760587694167 +1928,-0.0,0.08237719869611684,0.9051023903737102,0.012520410930172948 +1929,-0.0,0.06442465056648035,0.9051023903737102,0.030472959059809443 +1930,0.24146063448344907,0.026928822194454746,0.7316105433220963,-0.0 +1931,0.7820374040782531,0.031473771088033606,0.18648882483371332,-0.0 +1932,0.9187245961673876,0.03170101853271255,0.04957438529989988,-0.0 +1933,0.49451258371454504,0.02545171380404162,0.4800357024814133,-0.0 +1934,0.0010537558861497154,0.06999221296111445,0.9051023903737102,0.023851640779025626 +1935,-0.0,0.09044448298221933,0.9051023903737102,0.004453126644070465 +1936,-0.0,0.06840148084836184,0.9051023903737102,0.026496128777927952 +1937,-0.0,0.06522001662285665,0.9051023903737102,0.029677593003433145 +1938,0.2241489306395609,0.038745689317759784,0.7371053800426793,-0.0 +1939,0.8576067547707041,0.0634020370654251,0.07899120816387073,-0.0 +1940,0.9646382454924824,0.03536175450751755,-0.0,-0.0 +1941,0.5271790161851863,0.10737441761080058,0.36544656620401317,-0.0 +1942,0.0012042924413139607,0.13589397191800792,0.8629017356406782,-0.0 +1943,-0.0,0.19088785353031215,0.8091121464696879,-0.0 +1944,-0.0,0.2116809947184354,0.7883190052815646,-0.0 +1945,-0.0,0.2027047206536172,0.7972952793463828,-0.0 +1946,0.20337488602689507,0.11544170189690305,0.6811834120762019,-0.0 +1947,0.581673249154643,0.15805059777420488,0.26027615307115215,-0.0 +1948,0.4743406853225362,0.2445182504745427,0.2811410642029211,-0.0 +1949,0.2099984944541219,0.2701972117232633,0.5198042938226148,-0.0 +1950,-0.0,0.2912176003560655,0.7087823996439345,-0.0 +1951,-0.0,0.28053697045615517,0.7194630295438449,-0.0 +1952,-0.0,0.23440573918632973,0.7655942608136703,-0.0 +1953,-0.0,0.20190935459724088,0.7980906454027591,-0.0 +1954,0.2288155638496525,0.19634179220260678,0.5748426439477408,-0.0 +1955,0.7442527287320276,0.25574727126797236,-0.0,-0.0 +1956,0.6742532305806537,0.22361148556407995,0.10213528385526638,-0.0 +1957,0.3527071487498262,0.1463473543732393,0.5009454968769345,-0.0 +1958,-0.0,0.0829453173078142,0.9051023903737102,0.011952292318475596 +1959,-0.0,0.07567339907808804,0.9051023903737102,0.019224210548201753 +1960,-0.0,0.07487803302171173,0.9051023903737102,0.020019576604578065 +1961,-0.0,0.06385653195478298,0.9051023903737102,0.03104107767150681 +1962,0.15475157870884393,0.03226913714440991,0.8129792841467461,-0.0 +1963,0.547953060797852,0.06010694911758043,0.3919399900845676,-0.0 +1964,0.5475014511323594,0.061356810063314615,0.391141738804326,-0.0 +1965,0.3460835403225994,0.049653566662349054,0.6042628930150515,-0.0 +1966,-0.0,0.047267468493220145,0.9051023903737102,0.047630141133069646 +1967,-0.0,0.05851621700482783,0.9051023903737102,0.036381392621461964 +1968,-0.0,0.09839814354598232,0.9016018564540177,-0.0 +1969,-0.0,0.14123428686796308,0.858765713132037,-0.0 +1970,0.19765449693065376,0.10555643805336903,0.6967890650159773,-0.0 +1971,0.6987906890724256,0.2229297432300431,0.0782795676975313,-0.0 +1972,0.6062107076464149,0.30462519959212314,0.08916409276146187,-0.0 +1973,0.3630941710561591,0.32212325283240173,0.31478257611143917,-0.0 +1974,-0.0,0.27224243872537374,0.7277575612746263,-0.0 +1975,-0.0,0.2995121320868469,0.7004878679131531,-0.0 +1976,-0.0,0.33632621812483565,0.6636737818751643,-0.0 +1977,-0.0,0.35871009142571153,0.6412899085742885,-0.0 +1978,0.1679987955632975,0.37279943299580603,0.4592017714408965,-0.0 +1979,0.510469458561955,0.489530541438045,-0.0,-0.0 +1980,0.5052006791312065,0.4947993208687935,-0.0,-0.0 +1981,0.2741270669540903,0.6741295446400846,0.05174338840582515,-0.0 +1982,-0.0,0.5081252863021166,0.4918747136978834,-0.0 +1983,-0.0,0.5055119406883087,0.49448805931169126,-0.0 +1984,-0.0,0.5532339040708868,0.4467660959291132,-0.0 +1985,-0.0,0.5911842273322703,0.4088157726677297,-0.0 +1986,0.1482785068367814,0.6306116589840669,0.2211098341791517,-0.0 +1987,0.47012566177793735,0.5298743382220626,-0.0,-0.0 +1988,0.4836739517427194,0.5163260482572807,-0.0,-0.0 +1989,0.3350943717956095,0.6525410373955849,0.012364590808805587,-0.0 +1990,-0.0,0.49005911445014066,0.5099408855498593,-0.0 +1991,-0.0,0.3458706108013513,0.6541293891986487,-0.0 +1992,-0.0,0.24338201325114797,0.7566179867488521,-0.0 +1993,-0.0,0.2078177881588934,0.7921822118411066,-0.0 +1994,0.14300972740603282,0.14850620509768928,0.7084840674962779,-0.0 +1995,0.5217597001992734,0.18009359990806234,0.2981466998926642,-0.0 +1996,0.5910065155748261,0.1760031459038414,0.23299033852133255,-0.0 +1997,0.33539544490593803,0.1731625528453546,0.4914420022487073,-0.0 +1998,-0.0,0.15521000471571808,0.8447899952842819,-0.0 +1999,-0.0,0.10510194316401114,0.8948980568359889,-0.0 +2000,-0.0,0.08589953408864047,0.9051023903737102,0.008998075537649322 +2001,-0.0,0.09362594720772453,0.9051023903737102,0.0012716624185652586 +2002,0.18485888974169296,0.050789803885743764,0.7643513063725633,-0.0 +2003,0.6647694276053062,0.07442353813235385,0.26080703426234,-0.0 +2004,0.6281890447003947,0.07465078557703278,0.29716016972257253,-0.0 +2005,0.36264256139066636,0.08237719869611684,0.5549802399132169,-0.0 +2006,-0.0,0.0977164012119455,0.9022835987880545,-0.0 +2007,-0.0,0.08374068336419051,0.9051023903737102,0.011156926262099284 +2008,-0.0,0.06476552173349877,0.9051023903737102,0.030132087892791024 +2009,-0.0,0.0602205728399199,0.9051023903737102,0.034677036786369894 +2010,0.23468648950105808,0.023520110524270603,0.7417933999746713,-0.0 +2011,0.7695428699996207,0.07056033157281182,0.15989679842756743,-0.0 +2012,0.7537365317073751,0.06987858923877498,0.17638487905384992,-0.0 +2013,0.43505064442466823,0.09317145231836664,0.4717779032569651,-0.0 +2014,-0.0,0.20134123598554352,0.7986587640144565,-0.0 +2015,-0.0,0.19225133819838577,0.8077486618016142,-0.0 +2016,-0.0,0.13737108030842105,0.862628919691579,-0.0 +2017,-0.0,0.11680518656497671,0.8831948134350233,-0.0 +2018,0.2258048327463676,0.044086004267714946,0.7301091629859175,-0.0 +2019,0.8311123210617971,0.12919017229997912,0.0396975066382238,-0.0 +2020,0.733414096760202,0.1323716365254843,0.1342142667143137,-0.0 +2021,0.4016315291782058,0.1830478166888886,0.4153206541329056,-0.0 +2022,-0.0,0.24429100302986376,0.7557089969701363,-0.0 +2023,-0.0,0.2245204753427957,0.7754795246572043,-0.0 +2024,-0.0,0.189410745139899,0.810589254860101,-0.0 +2025,-0.0,0.1568007368284707,0.8431992631715293,-0.0 +2026,0.18681586495882813,0.09112622531625615,0.7220579097249158,-0.0 +2027,0.6914143978693775,0.13316700258186062,0.17541859954876182,-0.0 +2028,0.7483172157214623,0.12862205368828175,0.12306073059025602,-0.0 +2029,0.38462089844464614,0.10771528877781898,0.5076638127775348,-0.0 +2030,-0.0,0.1771393831272361,0.8228606168727639,-0.0 +2031,-0.0,0.18906987397288058,0.8109301260271194,-0.0 +2032,-0.0,0.14464299853814722,0.8553570014618528,-0.0 +2033,-0.0,0.10987413950226894,0.890125860497731,-0.0 +2034,0.1309668029928932,0.05056255644106482,0.818470640566042,-0.0 +2035,0.6915649344245418,0.09839814354598232,0.21003692202947588,-0.0 +2036,0.9253482045946144,0.07465179540538558,-0.0,-0.0 +2037,0.3737822664728205,0.08192270380675895,0.5442950297204205,-0.0 +2038,-0.0,0.2946263120262496,0.7053736879737504,-0.0 +2039,-0.0,0.24417737930752428,0.7558226206924757,-0.0 +2040,-0.0,0.15498275727103913,0.8450172427289608,-0.0 +2041,-0.0,0.115668949341582,0.884331050658418,-0.0 +2042,0.13277324165486415,0.05953883050588307,0.8076879278392528,-0.0 +2043,0.5619529604281268,0.06226579984203039,0.3757812397298428,-0.0 +2044,0.5568347175525425,0.07226468740790389,0.3709005950395535,-0.0 +2045,0.2267080520773531,0.10112511288212964,0.6721668350405172,-0.0 +2046,-0.0,0.2537217719840399,0.7462782280159601,-0.0 +2047,-0.0,0.27269693361473163,0.7273030663852684,-0.0 +2048,-0.0,0.2728105573370711,0.7271894426629288,-0.0 +2049,-0.0,0.21531695383329852,0.7846830461667015,-0.0 +2050,0.09333266420183194,0.13021278580103435,0.7764545499971337,-0.0 +2051,0.3463846134329279,0.13987080219988943,0.5137445843671826,-0.0 +2052,0.37799729001741933,0.2106583812173802,0.41134432876520044,-0.0 +2053,0.24733156013485466,0.23190601729486135,0.5207624225702839,-0.0 +2054,-0.0,0.33632621812483565,0.6636737818751643,-0.0 +2055,-0.0,0.38098034100424794,0.6190196589957521,-0.0 +2056,-0.0,0.3434845126322224,0.6565154873677777,-0.0 +2057,-0.0,0.31882816488455706,0.681171835115443,-0.0 +2058,0.14045060596824063,0.23417849174165078,0.6253709022901086,-0.0 +2059,0.49737277826266574,0.5026272217373342,-0.0,-0.0 +2060,0.506706044682849,0.49329395531715103,-0.0,-0.0 +2061,0.2644927274235786,0.5001716257383536,0.2353356468380678,-0.0 +2062,-0.0,0.3850707950084689,0.6149292049915311,-0.0 +2063,-0.0,0.30507969448148103,0.6949203055185189,-0.0 +2064,-0.0,0.21315810310884853,0.7868418968911515,-0.0 +2065,-0.0,0.18054809479742023,0.8194519052025797,-0.0 +2066,0.06352642627931142,0.18486579624632016,0.7516077774743685,-0.0 +2067,0.321245008720499,0.33450823856740414,0.3442467527120969,-0.0 +2068,0.3447287113261212,0.4139312204826947,0.2413400681911841,-0.0 +2069,0.17386972121470307,0.35018831225025115,0.4759419665350458,-0.0 +2070,-0.0,0.4435870120132968,0.5564129879867032,-0.0 +2071,-0.0,0.44847283207389405,0.551527167926106,-0.0 +2072,-0.0,0.47835587104917504,0.5216441289508249,-0.0 +2073,-0.0,0.42847505694214705,0.571524943057853,-0.0 +2074,0.11982709791073907,0.394956058852003,0.4852168432372579,-0.0 +2075,0.4213518179047219,0.578648182095278,-0.0,-0.0 +2076,0.342169589888329,0.4784694947715145,0.17936091534015652,-0.0 +2077,0.1019132478461939,0.4357469751718733,0.46233977698193285,-0.0 +2078,-0.0,0.43052028394425756,0.5694797160557424,-0.0 +2079,-0.0,0.4363150937835706,0.5636849062164294,-0.0 +2080,-0.0,0.36257329798525356,0.6374267020147464,-0.0 +2081,-0.0,0.26497052049564757,0.7350294795043524,-0.0 +2082,0.15159031105039478,0.17168544445494147,0.6767242444946637,-0.0 +2083,0.6545329418541376,0.2217935060066484,0.12367355213921405,-0.0 +2084,0.5992860261088596,0.17338980029003354,0.2273241736011068,-0.0 +2085,0.29113769768764997,0.1188504135670872,0.5900118887452629,-0.0 +2086,-0.0,0.10021612310341388,0.8997838768965861,-0.0 +2087,-0.0,0.1468018492625972,0.8531981507374028,-0.0 +2088,-0.0,0.15407376749232338,0.8459262325076766,-0.0 +2089,-0.0,0.13259888397016326,0.8674011160298367,-0.0 +2090,0.14346133707152553,0.039995550263493974,0.8165431126649805,-0.0 +2091,0.5301897472884711,0.04169990609858605,0.4281103466129428,-0.0 +2092,0.4455882032861654,0.06237942356436986,0.49203237314946474,-0.0 +2093,0.2519981933449462,0.0686287282930408,0.679373078362013,-0.0 +2094,-0.0,0.12373623362768447,0.8762637663723155,-0.0 +2095,-0.0,0.1439612562041104,0.8560387437958896,-0.0 +2096,-0.0,0.15373289632530496,0.846267103674695,-0.0 +2097,-0.0,0.16929934628581256,0.8307006537141874,-0.0 +2098,0.10447236928398608,0.12100926429153716,0.7745183664244768,-0.0 +2099,0.43896459485893863,0.2146352114992617,0.3464001936417997,-0.0 +2100,0.4719321004399083,0.297126033917718,0.23094186564237373,-0.0 +2101,0.26705184886137073,0.22963354284807191,0.5033146082905573,-0.0 +2102,-0.0,0.19850064292705671,0.8014993570729433,-0.0 +2103,-0.0,0.16748136672838101,0.832518633271619,-0.0 +2104,-0.0,0.1304400332457133,0.8695599667542867,-0.0 +2105,-0.0,0.10192047893850595,0.898079521061494,-0.0 +2106,0.14511723917833225,0.06737886734730661,0.7875038934743612,-0.0 +2107,0.6635651351639923,0.0853314154769431,0.2511034493590646,-0.0 +2108,0.5977806605572171,0.0810137140280432,0.32120562541473974,-0.0 +2109,0.29475057501159185,0.03692770976032824,0.6683217152280799,-0.0 +2110,-0.0,0.06522001662285665,0.9051023903737102,0.029677593003433145 +2111,-0.0,0.19145597214200952,0.8085440278579905,-0.0 +2112,-0.0,0.18418405391228332,0.8158159460877167,-0.0 +2113,-0.0,0.19566004986856994,0.8043399501314301,-0.0 +2114,0.08369832467132025,0.17441241379108877,0.741889261537591,-0.0 +2115,0.34758890587424185,0.2149760826662801,0.43743501145947805,-0.0 +2116,0.34427710166062847,0.21588507244499588,0.43983782589437564,-0.0 +2117,0.1326227050996999,0.1169188102873162,0.7504584846129839,-0.0 +2118,-0.0,0.15441463865934177,0.8455853613406582,-0.0 +2119,-0.0,0.12953104346699754,0.8704689565330025,-0.0 +2120,-0.0,0.10669267527676375,0.8933073247232363,-0.0 +2121,-0.0,0.09794364865662444,0.9020563513433756,-0.0 +2122,0.06683823049292481,0.06306116589840668,0.8701006036086685,-0.0 +2123,0.2792453098296746,0.061129562618635676,0.6596251275516898,-0.0 +2124,0.3817607038965255,0.05294865461019373,0.5652906414932808,-0.0 +2125,0.1847083531865287,0.051357922497441116,0.7639337243160302,-0.0 +2126,-0.0,0.0702194604057934,0.9051023903737102,0.024678149220496395 +2127,-0.0,0.131576270469108,0.868423729530892,-0.0 +2128,-0.0,0.15725523171782857,0.8427447682821714,-0.0 +2129,-0.0,0.17020833606452831,0.8297916639354717,-0.0 +2130,0.11937548824524633,0.12271362012662923,0.7579108916281244,-0.0 +2131,0.5378671116018476,0.2037273341546724,0.25840555424348,-0.0 +2132,0.5562325713318855,0.2665612526084002,0.17720617605971423,-0.0 +2133,0.26660023919587805,0.2396324304139454,0.4937673303901765,-0.0 +2134,-0.0,0.21134012355141701,0.788659876448583,-0.0 +2135,-0.0,0.18861537908352272,0.8113846209164772,-0.0 +2136,-0.0,0.1520285404902129,0.8479714595097871,-0.0 +2137,-0.0,0.11532807817456359,0.8846719218254364,-0.0 +2138,0.11124651426637709,0.05851621700482783,0.8302372687287951,-0.0 +2139,0.6004903185501735,0.037609452094365066,0.3619002293554614,-0.0 +2140,0.4907491698354389,0.052039664831477955,0.45721116533308315,-0.0 +2141,0.21857907809848384,0.057266356059093644,0.7241545658424224,-0.0 +2142,-0.0,0.07749137863551957,0.9051023903737102,0.017406230990770225 +2143,-0.0,0.06794698595900396,0.9051023903737102,0.02695062366728583 +2144,-0.0,0.07067395529515128,0.9051023903737102,0.024223654331138517 +2145,-0.0,0.06760611479198554,0.9051023903737102,0.02729149483430425 +2146,0.11305295292834805,0.026474327305096863,0.860472719766555,-0.0 +2147,0.5863398823647346,0.029769415252941536,0.3838907023823239,-0.0 +2148,0.39410470141999354,0.042268024710283396,0.5636272738697231,-0.0 +2149,0.20217059358558112,0.07567339907808804,0.7221560073363309,-0.0 +2150,-0.0,0.1499833134881024,0.8500166865118977,-0.0 +2151,-0.0,0.14464299853814722,0.8553570014618528,-0.0 +2152,-0.0,0.10407932966295591,0.8959206703370441,-0.0 +2153,-0.0,0.0853314154769431,0.9051023903737102,0.009566194149346688 +2154,0.14722475095063167,0.050789803885743764,0.8019854451636246,-0.0 +2155,0.7578010186968096,0.0749916567440512,0.16720732455913923,-0.0 +2156,0.5640604722004262,0.07351454835363806,0.36242497944593577,-0.0 +2157,0.3149224734036007,0.09623929282153237,0.588838233774867,-0.0 +2158,-0.0,0.14612010692856037,0.8538798930714396,-0.0 +2159,-0.0,0.11498720700754517,0.8850127929924548,-0.0 +2160,-0.0,0.07919573447061164,0.9051023903737102,0.015701875155678155 +2161,-0.0,0.058402593282488355,0.9051023903737102,0.03649501634380144 +2162,0.14586992195415346,0.031814642255052025,0.8223154357907945,-0.0 +2163,0.7466613136146555,0.05044893271872535,0.2028897536666192,-0.0 +2164,0.6656726469362917,0.05794809839313047,0.2763792546705779,-0.0 +2165,0.3269653978167403,0.10192047893850595,0.5711141232447537,-0.0 +2166,-0.0,0.2240659804534378,0.7759340195465622,-0.0 +2167,-0.0,0.1706628309538862,0.8293371690461138,-0.0 +2168,-0.0,0.13384874491589743,0.8661512550841026,-0.0 +2169,-0.0,0.11907766101176614,0.8809223389882339,-0.0 +2170,0.15159031105039478,0.06624263012391189,0.7821670588256934,-0.0 +2171,0.8034135949115758,0.16020944849865482,0.03637695658976936,-0.0 +2172,0.622318119048989,0.1730489291230151,0.2046329518279959,-0.0 +2173,0.3072451090902242,0.19804614803769885,0.494708742872077,-0.0 +2174,-0.0,0.30769304009528886,0.6923069599047111,-0.0 +2175,-0.0,0.25428989059573726,0.7457101094042627,-0.0 +2176,-0.0,0.18554753858035697,0.814452461419643,-0.0 +2177,-0.0,0.1312353993020896,0.8687646006979104,-0.0 +2178,0.14737528750579593,0.06249304728670933,0.7901316652074948,-0.0 +2179,0.8348757349409032,0.13691658541906318,0.028207679640033678,-0.0 +2180,0.8398434412613232,0.13180351791378697,0.028353040824889875,-0.0 +2181,0.35421251430146866,0.15225578793489183,0.49353169776363953,-0.0 +2182,-0.0,0.2025910969312777,0.7974089030687224,-0.0 +2183,-0.0,0.15032418465512082,0.8496758153448791,-0.0 +2184,-0.0,0.10510194316401114,0.8948980568359889,-0.0 +2185,-0.0,0.08874012714712726,0.9051023903737102,0.006157482479162535 +2186,0.17462240399052428,0.05976607795056202,0.7656115180589137,-0.0 +2187,0.928810545363392,0.07118945463660797,-0.0,-0.0 +2188,0.9948960930804958,0.005103906919504242,-0.0,-0.0 +2189,0.40825513760543264,0.06658350129093031,0.5251613611036371,-0.0 +2190,-0.0,0.09305782859602717,0.9051023903737102,0.0018397810302626244 +2191,-0.0,0.07760500235785904,0.9051023903737102,0.01729260726843075 +2192,-0.0,0.061356810063314615,0.9051023903737102,0.033540799562975177 +2193,-0.0,0.0634020370654251,0.9051023903737102,0.03149557256086469 +2194,0.15008494549875231,0.0351097302028967,0.8148053242983511,-0.0 +2195,0.8500799270124919,0.02851955430720735,0.12140051868030077,-0.0 +2196,0.9697564883680667,0.030243511631933284,-0.0,-0.0 +2197,0.38522304466530316,0.044994994046430724,0.5697819612882662,-0.0 +2198,-0.0,0.13509860586163164,0.8649013941383683,-0.0 +2199,-0.0,0.14339313759241307,0.8566068624075869,-0.0 +2200,-0.0,0.11816867123305036,0.8818313287669497,-0.0 +2201,-0.0,0.08680852386735623,0.9051023903737102,0.008089085758933565 +2202,0.13412807065134236,0.05499388161230421,0.8108780477363534,-0.0 +2203,0.8014566196944407,0.02999666269762048,0.16854671760793882,-0.0 +2204,0.8643808997530953,0.04010917398583344,0.09550992626107124,-0.0 +2205,0.289331259025679,0.023633734246610073,0.6870350067277109,-0.0 +2206,-0.0,0.037609452094365066,0.9051023903737102,0.057288157531924726 +2207,-0.0,0.05385764438890949,0.9051023903737102,0.0410399652373803 +2208,-0.0,0.07601427024510644,0.9051023903737102,0.018883339381183348 +2209,-0.0,0.11180574278203997,0.88819425721796,-0.0 +2210,0.07150486370301641,0.09419406581942188,0.8343010704775616,-0.0 +2211,0.46500741890235303,0.057834474670791,0.4771581064268559,-0.0 +2212,0.5195016518718097,0.05442576300060685,0.4260725851275834,-0.0 +2213,0.21120278689543584,0.0374958283720256,0.7513013847325386,-0.0 +2214,-0.0,0.04692659732620174,0.9051023903737102,0.04797101230008805 +2215,-0.0,0.04999443782936746,0.9051023903737102,0.04490317179692233 +2216,-0.0,0.04976719038468852,0.9051023903737102,0.04513041924160127 +2217,-0.0,0.05453938672294632,0.9051023903737102,0.04035822290334347 +2218,0.08896710410206883,0.05363039694423055,0.8574024989537006,-0.0 +2219,0.6217159728283321,0.05397126811124897,0.3243127590604189,-0.0 +2220,0.718059368133449,0.07885486330359323,0.2030857685629578,-0.0 +2221,0.2294177100703095,0.08374068336419051,0.6868416065655,-0.0 +2222,-0.0,0.26190267999248185,0.7380973200075182,-0.0 +2223,-0.0,0.263152540938216,0.736847459061784,-0.0 +2224,-0.0,0.2613345613807845,0.7386654386192155,-0.0 +2225,-0.0,0.27110620150197906,0.7288937984980209,-0.0 +2226,0.05027920942485785,0.26212992743716074,0.6875908631379815,-0.0 +2227,0.3239546667134554,0.31985077838561227,0.3561945549009323,-0.0 +2228,0.3090515477521951,0.2825821974582656,0.40836625478953925,-0.0 +2229,0.10507451550464306,0.23247413590655872,0.6624513485887982,-0.0 +2230,-0.0,0.3225777477217596,0.6774222522782404,-0.0 +2231,-0.0,0.2714470726689974,0.7285529273310025,-0.0 +2232,-0.0,0.23338312568527447,0.7666168743147255,-0.0 +2233,-0.0,0.2216798822843089,0.7783201177156911,-0.0 +2234,0.03883843123237523,0.2089540253822881,0.7522075433853367,-0.0 +2235,0.22174034575693297,0.2170213096683906,0.5612383445746765,-0.0 +2236,0.19524591204802585,0.19895513781641463,0.6057989501355595,-0.0 +2237,0.08354778811615601,0.1423705240913578,0.7740816877924862,-0.0 +2238,-0.0,0.18122983713145707,0.8187701628685429,-0.0 +2239,-0.0,0.1571416079954891,0.842858392004511,-0.0 +2240,-0.0,0.11339647489479257,0.8866035251052075,-0.0 +2241,-0.0,0.0829453173078142,0.9051023903737102,0.011952292318475596 +2242,0.0427523816666456,0.06760611479198554,0.8896415035413688,-0.0 +2243,0.23032092940129495,0.042268024710283396,0.7274110458884218,-0.0 +2244,0.1857621090726784,0.02499721891468373,0.7892406720126378,-0.0 +2245,0.09002085998821854,0.01499833134881024,0.8949808086629713,-0.0 +2246,-0.0,0.03170101853271255,0.9051023903737102,0.06319659109357724 +2247,-0.0,0.03317812692312568,0.9051023903737102,0.06171948270316411 +2248,-0.0,0.026815198472115276,0.9051023903737102,0.06808241115417452 +2249,-0.0,0.027951435695509994,0.9051023903737102,0.0669461739307798 +2250,0.0367309194600758,0.02874680175188629,0.9051023903737102,0.029419888414327702 +2251,0.28451408926042315,0.03885931304009926,0.6766265976994776,-0.0 +2252,0.3113095960796588,0.03215551342207044,0.6565348904982707,-0.0 +2253,0.1403000694130764,0.052494159720835834,0.8072057708660878,-0.0 +2254,-0.0,0.13475773469461322,0.8652422653053868,-0.0 +2255,-0.0,0.12100926429153716,0.8789907357084629,-0.0 +2256,-0.0,0.115668949341582,0.884331050658418,-0.0 +2257,-0.0,0.12078201684685823,0.8792179831531418,-0.0 +2258,0.05554798885560643,0.10055699427043228,0.8438950168739613,-0.0 +2259,0.5029426308037428,0.1539601437699839,0.3430972254262733,-0.0 +2260,0.6196084610560327,0.18816088419416482,0.1922306547498024,-0.0 +2261,0.21466512766421347,0.23076978007146665,0.5545650922643199,-0.0 +2262,-0.0,0.34007580096203827,0.6599241990379617,-0.0 +2263,-0.0,0.2796279806774394,0.7203720193225607,-0.0 +2264,-0.0,0.24417737930752428,0.7558226206924757,-0.0 +2265,-0.0,0.22327061439706153,0.7767293856029385,-0.0 +2266,0.08791334821591912,0.18895625025054114,0.7231304015335398,-0.0 +2267,0.7642740905688723,0.2357259094311277,-0.0,-0.0 +2268,0.8457143669127287,0.15428563308727128,-0.0,-0.0 +2269,0.2888796493601863,0.336894336736533,0.3742260139032807,-0.0 +2270,-0.0,0.3591645863150694,0.6408354136849306,-0.0 +2271,-0.0,0.27269693361473163,0.7273030663852684,-0.0 +2272,-0.0,0.2146352114992617,0.7853647885007383,-0.0 +2273,-0.0,0.17850286779530974,0.8214971322046902,-0.0 +2274,0.0931821276466677,0.13259888397016326,0.774218988383169,-0.0 +2275,0.8281015899585121,0.17189841004148787,-0.0,-0.0 +2276,0.8461659765782216,0.15146042187851552,0.0023736015432629376,-0.0 +2277,0.21556834699519892,0.14657460181791826,0.6378570511868829,-0.0 +2278,-0.0,0.253267277094682,0.746732722905318,-0.0 +2279,-0.0,0.17634401707085978,0.8236559829291402,-0.0 +2280,-0.0,0.13214438908080536,0.8678556109191946,-0.0 +2281,-0.0,0.13305337885952115,0.8669466211404788,-0.0 +2282,0.04004272367368918,0.11078312928098473,0.849174147045326,-0.0 +2283,0.2762345787263897,0.12146375918089504,0.6023016620927153,-0.0 +2284,0.1950953754928616,0.07544615163340908,0.7294584728737293,-0.0 +2285,0.06427910905513265,0.08953549320350354,0.8461853977413638,-0.0 +2286,-0.0,0.0901036118152009,0.9051023903737102,0.004793997811088885 +2287,-0.0,0.06351566078776458,0.9051023903737102,0.031381948838525214 +2288,-0.0,0.06817423340368291,0.9051023903737102,0.026723376222606884 +2289,-0.0,0.08374068336419051,0.9051023903737102,0.011156926262099284 +2290,0.02002136183684459,0.09635291654387185,0.8836257216192835,-0.0 +2291,0.13171948576871442,0.15725523171782857,0.711025282513457,-0.0 +2292,0.09845090707741627,0.15361927260296546,0.7479298203196183,-0.0 +2293,0.047268478321572945,0.1048746957193322,0.8478568259590948,-0.0 +2294,-0.0,0.12691769785318968,0.8730823021468104,-0.0 +2295,-0.0,0.09748915376726656,0.9025108462327335,-0.0 +2296,-0.0,0.08794476109075094,0.9051023903737102,0.006952848535538847 +2297,-0.0,0.11419184095116887,0.8858081590488311,-0.0 +2298,0.01098916852698989,0.14725634415195507,0.841754487321055,-0.0 +2299,0.08610690955394817,0.1860020334697149,0.7278910569763369,-0.0 +2300,0.06744037671358179,0.18975161630691745,0.7428080069795007,-0.0 +2301,0.04470935688378078,0.16657237694966526,0.788718266166554,-0.0 +2302,-0.0,0.159527706164618,0.840472293835382,-0.0 +2303,-0.0,0.08885375086946673,0.9051023903737102,0.006043858756823062 +2304,-0.0,0.06646987756859084,0.9051023903737102,0.028427732057698954 +2305,-0.0,0.054312139278267384,0.9051023903737102,0.04058547034802241 +2306,0.010537558861497156,0.042836143321980755,0.9051023903737102,0.04152390744281188 +2307,0.08911764065723309,0.032382760866749384,0.8784995984760176,-0.0 +2308,0.0852036902229627,0.028292306862528406,0.8865040029145089,-0.0 +2309,0.05344047708330699,0.03158739481037308,0.9051023903737102,0.00986973773260972 +2310,-0.0,0.035905096259273,0.9051023903737102,0.05899251336701679 +2311,-0.0,0.02999666269762048,0.9051023903737102,0.06490094692866931 +2312,-0.0,0.022497497023215362,0.9051023903737102,0.07240011260307443 +2313,-0.0,0.0171571820732602,0.9051023903737102,0.0777404275530296 +2314,0.01159131474764687,0.01806617185197597,0.9051023903737102,0.06524012302666696 +2315,0.15249353038138025,0.008862650342478778,0.838643819276141,-0.0 +2316,0.11666583025228992,0.005113067505276217,0.8782211022424339,-0.0 +2317,0.051634038421336056,0.005908433561652518,0.9051023903737102,0.037355137643301214 +2318,-0.0,0.01738442951793914,0.9051023903737102,0.07751318010835065 +2319,-0.0,0.022838368190233774,0.9051023903737102,0.07205924143605602 +2320,-0.0,0.030337533864638895,0.9051023903737102,0.0645600757616509 +2321,-0.0,0.038404818150741364,0.9051023903737102,0.05649279147554843 +2322,0.01159131474764687,0.033291750645465155,0.9051023903737102,0.05001454423317776 +2323,0.183052451079722,0.02090676491046276,0.7960407840098153,-0.0 +2324,0.15460104215367967,0.015339202515828654,0.8300597553304917,-0.0 +2325,0.05103189220067908,0.013521222958397111,0.9051023903737102,0.0303444944672136 +2326,-0.0,0.025906208693399504,0.9051023903737102,0.06899140093289029 +2327,-0.0,0.02988303897528101,0.9051023903737102,0.06501457065100878 +2328,-0.0,0.03681408603798877,0.9051023903737102,0.058083523588301024 +2329,-0.0,0.04692659732620174,0.9051023903737102,0.04797101230008805 +2330,0.015354728626752998,0.04715384477088068,0.9051023903737102,0.03238903622865612 +2331,0.21195546967125706,0.02045227002110487,0.7675922603076382,-0.0 +2332,0.1871169380691566,0.01670268718390231,0.796180374746941,-0.0 +2333,0.07541881413728678,0.013748470403076052,0.9051023903737102,0.0057303250859269544 +2334,-0.0,0.02488359519234426,0.9051023903737102,0.07001401443394553 +2335,-0.0,0.014089341570094466,0.9051023903737102,0.08080826805619533 +2336,-0.0,0.008521779175460363,0.9051023903737102,0.08637583045082943 +2337,-0.0,0.011021501066928736,0.9051023903737102,0.08387610855936106 +2338,0.01129024163731838,0.022838368190233774,0.9051023903737102,0.06076899979873764 +2339,0.19554698515835434,0.015566449960507597,0.7888865648811381,-0.0 +2340,0.1539988959330227,0.03931380792945714,0.8066872961375201,-0.0 +2341,0.05359101363847124,0.03931380792945714,0.9051023903737102,0.0019927880583614083 +2342,-0.0,0.06726524362496714,0.9051023903737102,0.027632366001322656 +2343,-0.0,0.05851621700482783,0.9051023903737102,0.036381392621461964 +2344,-0.0,0.052266912276156895,0.9051023903737102,0.0426306973501329 +2345,-0.0,0.039768302818815035,0.9051023903737102,0.05512930680747476 +2346,0.009333266420183194,0.05294865461019373,0.9051023903737102,0.03261568859591287 +2347,0.22836395418415978,0.06215217611969091,0.7094838696961493,-0.0 +2348,0.27969691949516734,0.10601093294272691,0.6142921475621057,-0.0 +2349,0.08339725156099176,0.11441908839584781,0.8021836600431604,-0.0 +2350,-0.0,0.17520777984746508,0.824792220152535,-0.0 +2351,-0.0,0.22508859395449307,0.7749114060455069,-0.0 +2352,-0.0,0.22327061439706153,0.7767293856029385,-0.0 +2353,-0.0,0.21633956733435378,0.7836604326656462,-0.0 +2354,0.016709557623231203,0.217362180835409,0.7659282615413597,-0.0 +2355,0.23619185505270052,0.2156578250003169,0.5481503199469826,-0.0 +2356,0.19690181415483254,0.2706517066126211,0.5324464792325464,-0.0 +2357,0.04501042999410927,0.3051933182038205,0.6497962518020702,-0.0 +2358,-0.0,0.3475749666364434,0.6524250333635566,-0.0 +2359,-0.0,0.30860202987400465,0.6913979701259954,-0.0 +2360,-0.0,0.2933764510805154,0.7066235489194845,-0.0 +2361,-0.0,0.2787189908987236,0.7212810091012765,-0.0 +2362,0.01399989963027479,0.2645160256062897,0.7214840747634355,-0.0 +2363,0.48487824418403336,0.22724744467894303,0.28787431113702366,-0.0 +2364,0.3408147608918508,0.19009248747393584,0.4690927516342134,-0.0 +2365,0.09002085998821854,0.09078535414923773,0.8191937858625438,-0.0 +2366,-0.0,0.07692326002382222,0.9051023903737102,0.017974349602467576 +2367,-0.0,0.06601538267923295,0.9051023903737102,0.028882226947056847 +2368,-0.0,0.08930824575882461,0.9051023903737102,0.005589363867465183 +2369,-0.0,0.11839591867772932,0.8816040813222706,-0.0 +2370,0.012946143744125076,0.1172596814543346,0.8697941748015403,-0.0 +2371,0.3953089938613075,0.061129562618635676,0.5435614435200569,-0.0 +2372,0.47765248953614964,0.09544392676515608,0.42690358369869424,-0.0 +2373,0.09303159109150345,0.07908211074827216,0.8278862981602243,-0.0 +2374,-0.0,0.15725523171782857,0.8427447682821714,-0.0 +2375,-0.0,0.1639590313358574,0.8360409686641426,-0.0 +2376,-0.0,0.159527706164618,0.840472293835382,-0.0 +2377,-0.0,0.1375983277531,0.8624016722469,-0.0 +2378,0.006322535316898292,0.14225690036901834,0.8514205643140833,-0.0 +2379,0.229116636959981,0.11316922745011362,0.6577141355899054,-0.0 +2380,0.2399552689318066,0.12316811501598711,0.6368766160522062,-0.0 +2381,0.06954788848588121,0.06567451151221453,0.8647776000019043,-0.0 +2382,-0.0,0.04431325171239389,0.9051023903737102,0.0505843579138959 +2383,-0.0,0.0367004623156493,0.9051023903737102,0.05819714731064049 +2384,-0.0,0.048517329438954336,0.9051023903737102,0.046380280187335456 +2385,-0.0,0.06374290823244351,0.9051023903737102,0.031154701393846282 +2386,0.009333266420183194,0.0713556976291881,0.9051023903737102,0.014208645576918498 +2387,0.4616956146887396,0.08010472424932741,0.45819966106193305,-0.0 +2388,0.6318019220243366,0.09828451982364285,0.26991355815202056,-0.0 +2389,0.12328943867951671,0.12328173873832658,0.7534288225821567,-0.0 +2390,-0.0,0.2795143569550999,0.7204856430449,-0.0 +2391,-0.0,0.22270249578536416,0.7772975042146358,-0.0 +2392,-0.0,0.16770861417305996,0.8322913858269401,-0.0 +2393,-0.0,0.12634957924149232,0.8736504207585076,-0.0 +2394,0.00948380297534744,0.1001024993810744,0.8904136976435781,-0.0 +2395,0.4555236159270055,0.07590064652276697,0.4685757375502275,-0.0 +2396,0.5845334437027636,0.09317145231836664,0.32229510397886973,-0.0 +2397,0.10131110162553693,0.11748692889901355,0.7812019694754495,-0.0 +2398,-0.0,0.2731514285040895,0.7268485714959105,-0.0 +2399,-0.0,0.18520666741333858,0.8147933325866614,-0.0 +2400,-0.0,0.12135013545855558,0.8786498645414444,-0.0 +2401,-0.0,0.11930490845644508,0.880695091543555,-0.0 +2402,0.006473071872062538,0.12919017229997912,0.8643367558279583,-0.0 +2403,0.3323847138026531,0.11771417634369248,0.5499011098536544,-0.0 +2404,0.43309366920753306,0.12225912523727135,0.4446472055551956,-0.0 +2405,0.07331130236498735,0.1316898941914475,0.7949988034435651,-0.0 +2406,-0.0,0.1817979557431544,0.8182020442568456,-0.0 +2407,-0.0,0.12305449129364764,0.8769455087063524,-0.0 +2408,-0.0,0.07396904324299595,0.9051023903737102,0.020928566383293837 +2409,-0.0,0.04306339076665971,0.9051023903737102,0.051834218859630084 +2410,0.0033118042136133915,0.02942854408592312,0.9051023903737102,0.06215726132675328 +2411,0.24898746224166132,0.0054539386722946325,0.745558599086044,-0.0 +2412,0.25636375344470935,0.009203521509497192,0.7344327250457934,-0.0 +2413,0.05283833086265002,0.003067840503165731,0.9051023903737102,0.03899143826047404 +2414,-0.0,0.007044670785047233,0.9051023903737102,0.08785293884124255 +2415,-0.0,0.015566449960507597,0.9051023903737102,0.07933115966578219 +2416,-0.0,0.018975161630691743,0.9051023903737102,0.07592244799559805 +2417,-0.0,0.03863206559542032,0.9051023903737102,0.056265544030869474 +2418,0.0009032193309854703,0.04522224149110966,0.9051023903737102,0.04877214880419466 +2419,0.1734181115492103,0.025906208693399504,0.8006756797573902,-0.0 +2420,0.2815033581571383,0.02931492036358365,0.6891817214792781,-0.0 +2421,0.043203991332138335,0.06658350129093031,0.8902125073769314,-0.0 +2422,-0.0,0.15918683499759959,0.8408131650024004,-0.0 +2423,-0.0,0.16418627878053632,0.8358137212194636,-0.0 +2424,-0.0,0.13816644636479736,0.8618335536352026,-0.0 +2425,-0.0,0.10839703111185582,0.8916029688881442,-0.0 +2426,0.004215023544598862,0.10714717016612163,0.8886378062892795,-0.0 +2427,0.4427280087380447,0.14293864270305515,0.4143333485589002,-0.0 +2428,0.6379739207860706,0.13259888397016326,0.2294271952437661,-0.0 +2429,0.08189188600934931,0.1787301152399887,0.739377998750662,-0.0 +2430,-0.0,0.2975805288070759,0.7024194711929241,-0.0 +2431,-0.0,0.2278155632906404,0.7721844367093595,-0.0 +2432,-0.0,0.1711173258432441,0.8288826741567559,-0.0 +2433,-0.0,0.11862316612240827,0.8813768338775917,-0.0 +2434,0.007376291203048008,0.09635291654387185,0.89627079225308,-0.0 +2435,0.6525759666370023,0.027951435695509994,0.3194725976674877,-0.0 +2436,0.7203174164609126,0.02181575468917853,0.25786682884990886,-0.0 +2437,0.10582719828046429,0.021702130966839057,0.8724706707526967,-0.0 +2438,-0.0,0.04272251959964129,0.9051023903737102,0.0521750900266485 +2439,-0.0,0.07805949724721692,0.9051023903737102,0.016838112379072873 +2440,-0.0,0.06976496551643552,0.9051023903737102,0.025132644109854274 +2441,-0.0,0.042381648432622876,0.9051023903737102,0.052515961193666916 +2442,0.007526827758212253,0.03295087947844674,0.9051023903737102,0.05441990238963079 +2443,0.5517164746769582,0.004772196338257803,0.44351132898478407,-0.0 +2444,0.5958236853400819,0.004090454004220975,0.4000858606556972,-0.0 +2445,0.09137568898469675,0.014657460181791825,0.8939668508335115,-0.0 +2446,-0.0,0.10930602089057159,0.8906939791094284,-0.0 +2447,-0.0,0.10816978366717689,0.8918302163328231,-0.0 +2448,-0.0,0.08510416803226416,0.9051023903737102,0.009793441594025634 +2449,-0.0,0.060447820284598844,0.9051023903737102,0.03444978934169095 +2450,0.006473071872062538,0.05056255644106482,0.9051023903737102,0.037861981313162436 +2451,0.5676733495243681,0.010794253622249794,0.42153239685338206,-0.0 +2452,0.6376728476757421,0.0027269693361473162,0.3596001829881106,-0.0 +2453,0.0928810545363392,0.004772196338257803,0.902346749125403,-0.0 +2454,-0.0,0.02954216780826259,0.9051023903737102,0.0653554418180272 +2455,-0.0,0.056357366280377866,0.9051023903737102,0.038540243345911926 +2456,-0.0,0.08464967314290628,0.9051023903737102,0.010247936483383513 +2457,-0.0,0.0953303030428166,0.9046696969571834,-0.0 +2458,0.0037634138791061266,0.09487580815345872,0.9013607779674351,-0.0 +2459,0.48758790217698983,0.035564225092254584,0.47684787273075563,-0.0 +2460,0.550963791901137,0.042608895877301815,0.40642731222156125,-0.0 +2461,0.05991354895536954,0.0602205728399199,0.8798658782047106,-0.0 +2462,-0.0,0.15089230326681816,0.8491076967331819,-0.0 +2463,-0.0,0.13896181242117367,0.8610381875788263,-0.0 +2464,-0.0,0.11600982050860041,0.8839901794913996,-0.0 +2465,-0.0,0.10123873660446911,0.8987612633955309,-0.0 +2466,0.0015053655516424507,0.10646542783208482,0.8920292066162727,-0.0 +2467,0.5058028253518635,0.026587951027436333,0.4676092236207001,-0.0 +2468,0.5542755961147504,0.03738220464968613,0.4083421992355635,-0.0 +2469,0.08339725156099176,0.049312695495330634,0.8672900529436776,-0.0 +2470,-0.0,0.16123206199971007,0.8387679380002899,-0.0 +2471,-0.0,0.20531806626742502,0.794681933732575,-0.0 +2472,-0.0,0.17759387801659396,0.822406121983406,-0.0 +2473,-0.0,0.1870246469707701,0.8129753530292299,-0.0 +2474,0.002258048327463676,0.24201852858307432,0.755723423089462,-0.0 +2475,0.23032092940129495,0.15816422149654433,0.6115148491021607,-0.0 +2476,0.3013741834388186,0.18054809479742023,0.5180777217637611,-0.0 +2477,0.04184916233566013,0.1942965652004963,0.7638542724638435,-0.0 +2478,-0.0,0.28371843468166036,0.7162815653183396,-0.0 +2479,-0.0,0.3191690360515755,0.6808309639484245,-0.0 +2480,-0.0,0.35814197281401416,0.6418580271859858,-0.0 +2481,-0.0,0.3484839564151591,0.6515160435848408,-0.0 +2482,0.00045160966549273514,0.26497052049564757,0.7345778698388596,-0.0 +2483,0.16182679680156345,0.12509971829575814,0.7130734849026784,-0.0 +2484,0.22490161341538215,0.15691436055081015,0.6181840260338077,-0.0 +2485,0.027849262705385337,0.3329175064546515,0.6392332308399632,-0.0 +2486,-0.0,0.5632327916367602,0.4367672083632398,-0.0 +2487,-0.0,0.714011471181239,0.285988528818761,-0.0 +2488,-0.0,0.755938624724504,0.24406137527549598,-0.0 +2489,-0.0,0.5661870084175865,0.4338129915824135,-0.0 +2490,0.002107511772299431,0.37859424283511905,0.6192982453925816,-0.0 +2491,0.5637593990900978,0.18861537908352272,0.24762522182637947,-0.0 +2492,0.6251783135971097,0.093739570930064,0.28108211547282624,-0.0 +2493,0.04365560099763107,0.10294309243956119,0.8534013065628077,-0.0 +2494,-0.0,0.25826672087761876,0.7417332791223812,-0.0 +2495,-0.0,0.37268580927346656,0.6273141907265334,-0.0 +2496,-0.0,0.46006245175252014,0.5399375482474799,-0.0 +2497,-0.0,0.49108172795119587,0.5089182720488041,-0.0 +2498,0.0009032193309854703,0.49551305312243527,0.5035837275465793,-0.0 +2499,0.26163253287545796,0.5151699570871638,0.2231975100373782,-0.0 +2500,0.1630310892428774,0.7062850580621549,0.13068385269496768,-0.0 +2501,0.020623508057501575,0.7530980316660171,0.22627846027648135,-0.0 +2502,-0.0,0.7642331564552853,0.23576684354471467,-0.0 +2503,-0.0,0.7834355655306561,0.21656443446934392,-0.0 +2504,-0.0,0.7959341749879979,0.2040658250120021,-0.0 +2505,-0.0,0.8350207354727761,0.1649792645272239,-0.0 +2506,0.0007526827758212254,0.8307030340238762,0.16854428320030254,-0.0 +2507,0.19885878937196771,0.7637786615659276,0.03736254906210473,-0.0 +2508,0.18591264562784265,0.7218515080226625,0.09223584634949483,-0.0 +2509,0.028300872370878073,0.6105002601299804,0.36119886749914154,-0.0 +2510,-0.0,0.530395535880653,0.469604464119347,-0.0 +2511,-0.0,0.5210783906488163,0.47892160935118366,-0.0 +2512,-0.0,0.4714248239864673,0.5285751760135327,-0.0 +2513,-0.0,0.4172263084305394,0.5827736915694606,-0.0 +2514,0.00045160966549273514,0.39245633696053456,0.6070920533739728,-0.0 +2515,0.21210600622642128,0.3533697764757564,0.4345242172978223,-0.0 +2516,0.31085798641416607,0.36779998921286927,0.32134202437296466,-0.0 +2517,0.039440577453032206,0.32644095428130165,0.6341184682656662,-0.0 +2518,-0.0,0.3481430852481407,0.6518569147518594,-0.0 +2519,-0.0,0.34337088890988293,0.656629111090117,-0.0 +2520,-0.0,0.34734771919176444,0.6526522808082356,-0.0 +2521,-0.0,0.3573466067576379,0.6426533932423621,-0.0 +2522,0.00030107311032849017,0.3673454943235114,0.6323534325661602,-0.0 +2523,0.2542562416724099,0.30451157586978367,0.4412321824578065,-0.0 +2524,0.3545135874117971,0.3234867375004754,0.3219996750877275,-0.0 +2525,0.03402126146711938,0.15907321127526014,0.8069055272576204,-0.0 +2526,-0.0,0.1383936938094763,0.8616063061905237,-0.0 +2527,-0.0,0.20634067976848028,0.7936593202315197,-0.0 +2528,-0.0,0.24349563697348744,0.7565043630265126,-0.0 +2529,-0.0,0.2746285368945026,0.7253714631054974,-0.0 +2530,0.00015053655516424508,0.3921154657935162,0.6077339976513195,-0.0 +2531,0.24296600003509153,0.4020007296370502,0.35503327032785825,-0.0 +2532,0.2655464833097283,0.5055119406883087,0.22894157600196297,-0.0 +2533,0.01339775340961781,0.5353949796635897,0.45120726692679247,-0.0 +2534,-0.0,0.5883436342737836,0.41165636572621644,-0.0 +2535,-0.0,0.7103755120663758,0.28962448793362416,-0.0 +2536,-0.0,0.7303732871981229,0.2696267128018771,-0.0 +2537,-0.0,0.7104891357887153,0.2895108642112847,-0.0 +2538,-0.0,0.7031035938366498,0.2968964061633502,-0.0 +2539,0.327417007482233,0.672582992517767,-0.0,-0.0 +2540,0.2626862887616076,0.7373137112383924,-0.0,-0.0 +2541,0.014150436185439036,0.582207953267452,0.40364161054710895,-0.0 +2542,-0.0,0.46403928203440165,0.5359607179655983,-0.0 +2543,-0.0,0.39984187891260026,0.6001581210873997,-0.0 +2544,-0.0,0.4156355763177868,0.5843644236822132,-0.0 +2545,-0.0,0.49642204290115105,0.503577957098849,-0.0 +2546,0.006473071872062538,0.622203503530946,0.37132342459699147,-0.0 +2547,0.07571988724761526,0.7176474302961021,0.20663268245628263,-0.0 +2548,0.1496333358332596,0.763892285288267,0.08647437887847337,-0.0 +2549,0.015655801737081487,0.723896735024773,0.2604474632381455,-0.0 +2550,-0.0,0.5485753314549685,0.4514246685450315,-0.0 +2551,-0.0,0.41109062742420793,0.5889093725757921,-0.0 +2552,-0.0,0.3551877560331879,0.644812243966812,-0.0 +2553,-0.0,0.3754127786096139,0.624587221390386,-0.0 +2554,0.00015053655516424508,0.47437904076729354,0.5254704226775422,-0.0 +2555,0.24838531602100436,0.48880925350440646,0.26280543047458926,-0.0 +2556,0.2959548674529058,0.4914225991182143,0.21262253342887993,-0.0 +2557,0.018064386619709407,0.44233715106756255,0.539598462312728,-0.0 +2558,-0.0,0.4552902554142623,0.5447097445857376,-0.0 +2559,-0.0,0.3840481815074137,0.6159518184925863,-0.0 +2560,-0.0,0.340984790740754,0.6590152092592461,-0.0 +2561,-0.0,0.3462114819683697,0.6537885180316303,-0.0 +2562,0.00015053655516424508,0.37984410378085326,0.6200053596639824,-0.0 +2563,0.11967656135557483,0.43052028394425756,0.44980315470016763,-0.0 +2564,0.15023548205391657,0.46119868897591487,0.38856582897016856,-0.0 +2565,0.004516096654927352,0.46835698348330157,0.5271269198617711,-0.0 +2566,-0.0,0.616181446246954,0.38381855375304597,-0.0 +2567,-0.0,0.9055810670455879,0.09441893295441206,-0.0 +2568,-0.0,1.0,-0.0,-0.0 +2569,-0.0,1.0,-0.0,-0.0 +2570,-0.0,1.0,-0.0,-0.0 +2571,0.20247166669590963,0.7975283333040903,-0.0,-0.0 +2572,0.30829886497637393,0.6917011350236261,-0.0,-0.0 +2573,0.01716116728872394,0.6661758840763216,0.3166629486349545,-0.0 +2574,-0.0,0.7630969192318906,0.2369030807681094,-0.0 +2575,-0.0,0.9013769893190275,0.09862301068097246,-0.0 +2576,-0.0,0.9069445517136616,0.09305544828633838,-0.0 +2577,-0.0,0.9370548381336216,0.06294516186637844,-0.0 +2578,-0.0,0.9623929282153236,0.03760707178467637,-0.0 +2579,0.22294463819824695,0.777055361801753,-0.0,-0.0 +2580,0.22204141886726148,0.7779585811327385,-0.0,-0.0 +2581,0.01008594919600442,0.7890031279252901,0.20091092287870543,-0.0 +2582,-0.0,0.6080005382385121,0.3919994617614879,-0.0 +2583,-0.0,0.44972269301962825,0.5502773069803717,-0.0 +2584,-0.0,0.41983965404434725,0.5801603459556528,-0.0 +2585,-0.0,0.4043868278061791,0.5956131721938209,-0.0 +2586,-0.0,0.4160900712071447,0.5839099287928553,-0.0 +2587,0.13277324165486415,0.32996328967382527,0.5372634686713106,-0.0 +2588,0.14120328874406188,0.28814975985289976,0.5706469514030384,-0.0 +2589,0.0024085848826279213,0.19134234841967002,0.806249066697702,-0.0 +2590,-0.0,0.1866837758037517,0.8133162241962483,-0.0 +2591,-0.0,0.19600092103558833,0.8039990789644117,-0.0 +2592,-0.0,0.20202297831958035,0.7979770216804196,-0.0 +2593,-0.0,0.19134234841967002,0.80865765158033,-0.0 +2594,-0.0,0.15657348938379176,0.8434265106162082,-0.0 +2595,0.24507351180739098,0.10703354644378217,0.6478929417488268,-0.0 +2596,0.19946093559262473,0.09964800449171651,0.7008910599156588,-0.0 +2597,0.008731120199526213,0.12203187779259239,0.8692370020078813,-0.0 +2598,-0.0,0.16634512950498628,0.8336548704950137,-0.0 +2599,-0.0,0.16691324811668365,0.8330867518833164,-0.0 +2600,-0.0,0.1806617185197597,0.8193382814802403,-0.0 +2601,-0.0,0.21304447938650908,0.7869555206134909,-0.0 +2602,-0.0,0.2348602340756876,0.7651397659243124,-0.0 +2603,0.19328893683089066,0.2851955430720735,0.5215155200970358,-0.0 +2604,0.21436405455388496,0.33678071301419354,0.4488552324319215,-0.0 +2605,0.009935412640840174,0.3382578214046067,0.6518067659545531,-0.0 +2606,-0.0,0.3016709828112969,0.6983290171887031,-0.0 +2607,-0.0,0.24190490486073485,0.7580950951392651,-0.0 +2608,-0.0,0.1703219597868678,0.8296780402131322,-0.0 +2609,-0.0,0.14146153431264202,0.858538465687358,-0.0 +2610,-0.0,0.10589730922038745,0.8941026907796126,-0.0 +2611,0.22264356508791847,0.044654122879412304,0.7327023120326692,-0.0 +2612,0.2875248203637081,0.06306116589840668,0.6494140137378852,-0.0 +2613,0.008430047089197723,0.13691658541906318,0.8546533674917391,-0.0 +2614,-0.0,0.1706628309538862,0.8293371690461138,-0.0 +2615,-0.0,0.19770527687068043,0.8022947231293196,-0.0 +2616,-0.0,0.24610898258729527,0.7538910174127047,-0.0 +2617,-0.0,0.267015747497758,0.732984252502242,-0.0 +2618,-0.0,0.3057614368155178,0.6942385631844822,-0.0 +2619,0.24236385381443457,0.3649593961543825,0.39267675003118296,-0.0 +2620,0.3259116419305906,0.48267357249807497,0.19141478557133446,-0.0 +2621,0.006322535316898292,0.5409625420582239,0.4527149226248778,-0.0 +2622,-0.0,0.49028636189481956,0.5097136381051804,-0.0 +2623,-0.0,0.38427542895209266,0.6157245710479073,-0.0 +2624,-0.0,0.28860425474225765,0.7113957452577424,-0.0 +2625,-0.0,0.21077200493971965,0.7892279950602803,-0.0 +2626,-0.0,0.1842976776346228,0.8157023223653772,-0.0 +2627,0.0852036902229627,0.1323716365254843,0.782424673251553,-0.0 +2628,0.09303159109150345,0.10623818038740586,0.8007302285210907,-0.0 +2629,0.001956975217135186,0.11975940334580297,0.8782836214370618,-0.0 +2630,-0.0,0.12373623362768447,0.8762637663723155,-0.0 +2631,-0.0,0.12544058946277656,0.8745594105372234,-0.0 +2632,-0.0,0.12748581646488702,0.872514183535113,-0.0 +2633,-0.0,0.13373512119355796,0.866264878806442,-0.0 +2634,-0.0,0.1072607938884611,0.8927392061115389,-0.0 +2635,0.18982659606211302,0.048176458271935924,0.761996945665951,-0.0 +2636,0.2535035588965887,0.03954105537413608,0.7069553857292752,-0.0 +2637,0.004064486989434617,0.07146932135152757,0.9051023903737102,0.019363801285327602 +2638,-0.0,0.071810192518546,0.9051023903737102,0.023087417107743785 +2639,-0.0,0.06851510457070133,0.9051023903737102,0.026382505055588465 +2640,-0.0,0.08237719869611684,0.9051023903737102,0.012520410930172948 +2641,-0.0,0.079763853082309,0.9051023903737102,0.01513375654398079 +2642,-0.0,0.1108967530033242,0.8891032469966758,-0.0 +2643,0.23257897772875863,0.15532362843805755,0.6120973938331837,-0.0 +2644,0.3509007100878552,0.17441241379108877,0.47468687612105603,-0.0 +2645,0.0033118042136133915,0.23804169830119282,0.7586464974851937,-0.0 +2646,-0.0,0.33416736740038566,0.6658326325996143,-0.0 +2647,-0.0,0.36916347388094295,0.630836526119057,-0.0 +2648,-0.0,0.3893884964573689,0.6106115035426312,-0.0 +2649,-0.0,0.39506968257434244,0.6049303174256575,-0.0 +2650,-0.0,0.4074546683093448,0.5925453316906553,-0.0 +2651,0.13202055887904293,0.39791027563282927,0.4700691654881278,-0.0 +2652,0.18982659606211302,0.42779331460811026,0.38238008932977663,-0.0 +2653,0.004967706320420087,0.4413145375665074,0.5537177561130725,-0.0 +2654,-0.0,0.4655163904248148,0.5344836095751853,-0.0 +2655,-0.0,0.5713000759228628,0.4286999240771372,-0.0 +2656,-0.0,0.7270781992502783,0.2729218007497217,-0.0 +2657,-0.0,0.8009336187709346,0.1990663812290654,-0.0 +2658,-0.0,0.7859352874221244,0.21406471257787563,-0.0 +2659,0.06171998761734048,0.7524162893319803,0.18586372305067916,-0.0 +2660,0.07602096035794377,0.7224196266343599,0.20155941300769636,-0.0 +2661,0.0009032193309854703,0.8313847763579131,0.16771200431110145,-0.0 +2662,-0.0,0.9877310182970258,0.012268981702974191,-0.0 +2663,-0.0,1.0,-0.0,-0.0 +2664,-0.0,0.9832996931257864,0.016700306874213644,-0.0 +2665,-0.0,0.9177388053359113,0.08226119466408865,-0.0 +2666,-0.0,0.8683124861182413,0.1316875138817587,-0.0 +2667,0.14240758118537583,0.7293506736970676,0.12824174511755654,-0.0 +2668,0.09408534697765317,0.6712889515815976,0.2346257014407492,-0.0 +2669,0.0009032193309854703,0.48608228416825916,0.5130144965007554,-0.0 +2670,-0.0,0.5590287139101998,0.4409712860898002,-0.0 +2671,-0.0,0.6524274136732454,0.3475725863267546,-0.0 +2672,-0.0,0.6843556796506368,0.3156443203493632,-0.0 +2673,-0.0,0.5856166649376361,0.4143833350623639,-0.0 +2674,-0.0,0.4334745007250838,0.5665254992749162,-0.0 +2675,0.19298786372056217,0.34552973963433287,0.46148239664510493,-0.0 +2676,0.19825664315131078,0.33655346556951465,0.46518989127917465,-0.0 +2677,0.0033118042136133915,0.2796279806774394,0.7170602151089472,-0.0 +2678,-0.0,0.23951880669160594,0.760481193308394,-0.0 +2679,-0.0,0.28053697045615517,0.7194630295438449,-0.0 +2680,-0.0,0.4285886806644865,0.5714113193355135,-0.0 +2681,-0.0,0.4669934988152279,0.5330065011847721,-0.0 +2682,-0.0,0.44608673390476516,0.5539132660952348,-0.0 +2683,0.0627737435034902,0.3335992487886884,0.6036270077078214,-0.0 +2684,0.07827900868540744,0.2580394734329398,0.6636815178816526,-0.0 +2685,0.0018064386619709406,0.3654138910437404,0.6327796702942887,-0.0 +2686,-0.0,0.3692770976032824,0.6307229023967176,-0.0 +2687,-0.0,0.36382315893098777,0.6361768410690123,-0.0 +2688,-0.0,0.3362125944024962,0.6637874055975038,-0.0 +2689,-0.0,0.30076199303258105,0.6992380069674189,-0.0 +2690,-0.0,0.2700835880009238,0.7299164119990762,-0.0 +2691,0.25380463200691716,0.1599822010539759,0.586213166939107,-0.0 +2692,0.388835921989245,0.1404389208115868,0.4707251571991682,-0.0 +2693,0.01309668029928932,0.25474438548509515,0.7321589342156155,-0.0 +2694,-0.0,0.2720151912806948,0.7279848087193053,-0.0 +2695,-0.0,0.2861045328507893,0.7138954671492107,-0.0 +2696,-0.0,0.3234867375004754,0.6765132624995246,-0.0 +2697,-0.0,0.47017496304073314,0.5298250369592669,-0.0 +2698,-0.0,0.6551543830093928,0.34484561699060723,-0.0 +2699,0.1600203581395925,0.636179221378701,0.20380042048170652,-0.0 +2700,0.1902782057276058,0.5359630982752871,0.2737586959971071,-0.0 +2701,0.0037634138791061266,0.4927860837862879,0.503450502334606,-0.0 +2702,-0.0,0.47812862360449615,0.5218713763955038,-0.0 +2703,-0.0,0.46881147837265946,0.5311885216273405,-0.0 +2704,-0.0,0.4636984108673833,0.5363015891326167,-0.0 +2705,-0.0,0.4780149998821567,0.5219850001178433,-0.0 +2706,-0.0,0.5394854336678108,0.46051456633218923,-0.0 +2707,0.27849262705385336,0.6141362192448435,0.10737115370130312,-0.0 +2708,0.3012236468836544,0.6987763531163456,-0.0,-0.0 +2709,0.006774144982391027,0.7426446492107858,0.2505812058068232,-0.0 +2710,-0.0,0.771391450962672,0.22860854903732797,-0.0 +2711,-0.0,0.7682099867371669,0.2317900132628331,-0.0 +2712,-0.0,0.7545751400564303,0.24542485994356966,-0.0 +2713,-0.0,0.7492348251064751,0.2507651748935249,-0.0 +2714,-0.0,0.7418492831544096,0.25815071684559043,-0.0 +2715,0.2939978922357706,0.7060021077642293,-0.0,-0.0 +2716,0.3310298848061749,0.6689701151938251,-0.0,-0.0 +2717,0.008128973978869233,0.777868003136022,0.21400302288510875,-0.0 +2718,-0.0,0.6988995161100893,0.30110048388991073,-0.0 +2719,-0.0,0.5433486402273527,0.45665135977264726,-0.0 +2720,-0.0,0.34439350241093813,0.6556064975890619,-0.0 +2721,-0.0,0.19793252431535938,0.8020674756846407,-0.0 +2722,-0.0,0.11873678984474773,0.8812632101552522,-0.0 +2723,0.2679550681923562,0.05453938672294632,0.6775055450846974,-0.0 +2724,0.38868538543408077,0.02090676491046276,0.5904078496554566,-0.0 +2725,0.013247216854453566,0.044086004267714946,0.9051023903737102,0.03756438850412128 +2726,-0.0,0.09430768954176136,0.9051023903737102,0.0005899200845284336 +2727,-0.0,0.12987191463401593,0.8701280853659841,-0.0 +2728,-0.0,0.14100703942328416,0.8589929605767158,-0.0 +2729,-0.0,0.13021278580103435,0.8697872141989657,-0.0 +2730,-0.0,0.131576270469108,0.868423729530892,-0.0 +2731,0.4272227435561275,0.12873567741062122,0.4440415790332513,-0.0 +2732,0.5703830075173246,0.11839591867772932,0.311221073804946,-0.0 +2733,0.010537558861497156,0.2941718171368918,0.695290624001611,-0.0 +2734,-0.0,0.32734994406001744,0.6726500559399826,-0.0 +2735,-0.0,0.25894846321165554,0.7410515367883445,-0.0 +2736,-0.0,0.23520110524270602,0.764798894757294,-0.0 +2737,-0.0,0.21122649982907754,0.7887735001709224,-0.0 +2738,-0.0,0.24406375558518478,0.7559362444148152,-0.0 +2739,0.18591264562784265,0.23895068807990857,0.5751366662922488,-0.0 +2740,0.24236385381443457,0.2508811789255531,0.5067549672600122,-0.0 +2741,0.004967706320420087,0.2673566186647765,0.7276756750148035,-0.0 +2742,-0.0,0.28099146534551306,0.719008534654487,-0.0 +2743,-0.0,0.2345193629086692,0.7654806370913307,-0.0 +2744,-0.0,0.23951880669160594,0.760481193308394,-0.0 +2745,-0.0,0.28996773941033127,0.7100322605896687,-0.0 +2746,-0.0,0.32598645939194376,0.6740135406080563,-0.0 +2747,0.34186851677800056,0.3977966519104898,0.2603348313115096,-0.0 +2748,0.42180342757021466,0.39938738402324236,0.17880918840654297,-0.0 +2749,0.005870925651405557,0.4631302922556859,0.5309987820929085,-0.0 +2750,-0.0,0.4651755192577964,0.5348244807422036,-0.0 +2751,-0.0,0.4513134251323808,0.5486865748676192,-0.0 +2752,-0.0,0.4199532777666867,0.5800467222333133,-0.0 +2753,-0.0,0.40665930225296854,0.5933406977470315,-0.0 +2754,-0.0,0.4375649547293048,0.5624350452706952,-0.0 +2755,0.13849363075110546,0.47835587104917504,0.38315049819971947,-0.0 +2756,0.13171948576871442,0.55255216173685,0.31572835249443565,-0.0 +2757,0.0013548289964782055,0.5799354788206625,0.41870969218285925,-0.0 +2758,-0.0,0.5174424315339533,0.4825575684660467,-0.0 +2759,-0.0,0.5514159245134552,0.4485840754865448,-0.0 +2760,-0.0,0.5452802435071238,0.4547197564928762,-0.0 +2761,-0.0,0.46665262764820953,0.5333473723517905,-0.0 +2762,-0.0,0.424611850382605,0.5753881496173949,-0.0 +2763,0.2274607348531743,0.3359853469578173,0.43655391818900835,-0.0 +2764,0.267804531637192,0.30417070470276525,0.4280247636600427,-0.0 +2765,0.0034623407687776366,0.22827005817999826,0.7682676010512242,-0.0 +2766,-0.0,0.24940407053513997,0.7505959294648601,-0.0 +2767,-0.0,0.42847505694214705,0.571524943057853,-0.0 +2768,-0.0,0.6767428902538923,0.32325710974610766,-0.0 +2769,-0.0,0.8327482610259866,0.16725173897401335,-0.0 +2770,-0.0,0.6586767184019163,0.34132328159808367,-0.0 +2771,0.15595587115015788,0.41620369492948417,0.427840433920358,-0.0 +2772,0.2452240483625552,0.22395235673109834,0.5308235949063465,-0.0 +2773,0.004365560099763107,0.13873456497649472,0.8568998749237422,-0.0 +2774,-0.0,0.07760500235785904,0.9051023903737102,0.01729260726843075 +2775,-0.0,0.0390865604847782,0.9051023903737102,0.05581104914151159 +2776,-0.0,0.02397460541362849,0.9051023903737102,0.0709230042126613 +2777,-0.0,0.02840593058486788,0.9051023903737102,0.06649167904142192 +2778,-0.0,0.058970711894185714,0.9051023903737102,0.03592689773210408 +2779,0.19328893683089066,0.061015938896296196,0.7456951242728131,-0.0 +2780,0.24010580548697089,0.05908433561652518,0.7008098588965039,-0.0 +2781,0.004967706320420087,0.14839258137534977,0.8466397123042302,-0.0 +2782,-0.0,0.177366630571915,0.822633369428085,-0.0 +2783,-0.0,0.14464299853814722,0.8553570014618528,-0.0 +2784,-0.0,0.11794142378837143,0.8820585762116285,-0.0 +2785,-0.0,0.07919573447061164,0.9051023903737102,0.015701875155678155 +2786,-0.0,0.05965245422822254,0.9051023903737102,0.03524515539806725 +2787,0.15610640770532214,0.028065059417849463,0.8158285328768284,-0.0 +2788,0.24010580548697089,0.012612233179681338,0.7472819613333478,-0.0 +2789,0.008279510534033478,0.04124541120922816,0.9051023903737102,0.04537268788302816 +2790,-0.0,0.08090009030570372,0.9051023903737102,0.013997519320586072 +2791,-0.0,0.09396681837474294,0.9051023903737102,0.000930791251546853 +2792,-0.0,0.1124874851160768,0.8875125148839232,-0.0 +2793,-0.0,0.1463473543732393,0.8536526456267607,-0.0 +2794,-0.0,0.19213771447604633,0.8078622855239537,-0.0 +2795,0.10898846593891344,0.1817979557431544,0.7092135783179322,-0.0 +2796,0.18260084141422928,0.1620274280560864,0.6553717305296843,-0.0 +2797,0.0013548289964782055,0.2759920215625763,0.7226531494409455,-0.0 +2798,-0.0,0.29303557991349705,0.706964420086503,-0.0 +2799,-0.0,0.30485244703680203,0.695147552963198,-0.0 +2800,-0.0,0.3136014736569414,0.6863985263430585,-0.0 +2801,-0.0,0.31166987037717037,0.6883301296228297,-0.0 +2802,-0.0,0.30826115870698617,0.6917388412930139,-0.0 +2803,0.09408534697765317,0.2816732076795499,0.624241445342797,-0.0 +2804,0.16543967412550534,0.2780372485646868,0.5565230773098079,-0.0 +2805,0.004064486989434617,0.36291416915227204,0.6330213438582933,-0.0 +2806,-0.0,0.3856389136201663,0.6143610863798337,-0.0 +2807,-0.0,0.37109507716071394,0.628904922839286,-0.0 +2808,-0.0,0.3551877560331879,0.644812243966812,-0.0 +2809,-0.0,0.34314364146520393,0.6568563585347961,-0.0 +2810,-0.0,0.34643872941304865,0.6535612705869513,-0.0 +2811,0.11169812393186984,0.29451268830391014,0.59378918776422,-0.0 +2812,0.14842904339194563,0.26019832415738975,0.5913726324506646,-0.0 +2813,0.004967706320420087,0.2883770072975787,0.7066552863820013,-0.0 +2814,-0.0,0.2941718171368918,0.7058281828631082,-0.0 +2815,-0.0,0.3071249214835915,0.6928750785164085,-0.0 +2816,-0.0,0.2856500379614314,0.7143499620385686,-0.0 +2817,-0.0,0.25428989059573726,0.7457101094042627,-0.0 +2818,-0.0,0.24383650814050586,0.7561634918594942,-0.0 +2819,0.18651479184849964,0.2116809947184354,0.601804213433065,-0.0 +2820,0.36505114627329427,0.20520444254508557,0.4297444111816201,-0.0 +2821,0.006473071872062538,0.3186009174398781,0.6749260106880594,-0.0 +2822,-0.0,0.33064503200786205,0.669354967992138,-0.0 +2823,-0.0,0.29212659013478126,0.7078734098652187,-0.0 +2824,-0.0,0.267015747497758,0.732984252502242,-0.0 +2825,-0.0,0.263379788382895,0.736620211617105,-0.0 +2826,-0.0,0.30314809120171,0.6968519087982901,-0.0 +2827,0.2441702924764055,0.3098518908197388,0.4459778167038557,-0.0 +2828,0.289933405246336,0.2931492036358365,0.4169173911178275,-0.0 +2829,0.005268779430748578,0.34564336335667234,0.6490878572125791,-0.0 +2830,-0.0,0.3533697764757564,0.6466302235242436,-0.0 +2831,-0.0,0.3320085166759358,0.6679914833240642,-0.0 +2832,-0.0,0.3079202875399678,0.6920797124600322,-0.0 +2833,-0.0,0.34427987868859866,0.6557201213114013,-0.0 +2834,-0.0,0.3754127786096139,0.624587221390386,-0.0 +2835,0.144966702623168,0.4131358544263184,0.4418974429505136,-0.0 +2836,0.1847083531865287,0.5961836711152071,0.2191079756982642,-0.0 +2837,0.0030107311032849015,0.7163975693503678,0.2805916995463473,-0.0 +2838,-0.0,0.8357024778068131,0.16429752219318694,-0.0 +2839,-0.0,0.9433041428622926,0.05669585713770742,-0.0 +2840,-0.0,0.8903554882520988,0.10964451174790124,-0.0 +2841,-0.0,0.6632216672954953,0.33677833270450475,-0.0 +2842,-0.0,0.4952858056777563,0.5047141943222437,-0.0 +2843,0.15550426148466515,0.6099321415182831,0.23456359699705173,-0.0 +2844,0.23032092940129495,0.7696790705987051,-0.0,-0.0 +2845,0.00797843742370499,0.8024107271613479,0.18961083541494714,-0.0 +2846,-0.0,0.7674146206807906,0.23258537931920942,-0.0 +2847,-0.0,0.7405994222086754,0.25940057779132464,-0.0 +2848,-0.0,0.7478713404384015,0.25212865956159847,-0.0 +2849,-0.0,0.7694598476829011,0.2305401523170989,-0.0 +2850,-0.0,0.7649148987893223,0.23508510121067772,-0.0 +2851,0.2298693197358022,0.7429855203778043,0.02714515988639349,-0.0 +2852,0.2705141896301484,0.7294858103698516,-0.0,-0.0 +2853,0.0030107311032849015,0.5540292701272631,0.44295999876945197,-0.0 +2854,-0.0,0.38666152712122154,0.6133384728787785,-0.0 +2855,-0.0,0.2365645899107797,0.7634354100892203,-0.0 +2856,-0.0,0.13305337885952115,0.8669466211404788,-0.0 +2857,-0.0,0.14532474087218408,0.854675259127816,-0.0 +2858,-0.0,0.2084995304929302,0.7915004695070698,-0.0 +2859,0.29339574601511365,0.27485578433918156,0.4317484696457048,-0.0 +2860,0.4239109393425141,0.3614370607618589,0.214651999895627,-0.0 +2861,0.010236485751168664,0.5333497526614792,0.45641376158735214,-0.0 +2862,-0.0,0.6201582765288355,0.3798417234711645,-0.0 +2863,-0.0,0.6545862643976953,0.3454137356023047,-0.0 +2864,-0.0,0.5018759815734456,0.4981240184265544,-0.0 +2865,-0.0,0.3859797847871847,0.6140202152128154,-0.0 +2866,-0.0,0.3357580995131383,0.6642419004868617,-0.0 +2867,0.2712668724059696,0.2688337270551896,0.4598994005388408,-0.0 +2868,0.335997591126595,0.27815087228702623,0.3858515365863787,-0.0 +2869,0.008279510534033478,0.30598868426019676,0.6857318052057697,-0.0 +2870,-0.0,0.3509836783066275,0.6490163216933724,-0.0 +2871,-0.0,0.4990353885149589,0.5009646114850411,-0.0 +2872,-0.0,0.7048079496717418,0.29519205032825824,-0.0 +2873,-0.0,0.7865034060338219,0.21349659396617815,-0.0 +2874,-0.0,0.7803677250274903,0.2196322749725097,-0.0 +2875,0.3140192540726152,0.6859807459273848,-0.0,-0.0 +2876,0.46560956512301005,0.5343904348769899,-0.0,-0.0 +2877,0.013548289964782054,0.6875371438761421,0.29891456615907586,-0.0 +2878,-0.0,0.6195901579171381,0.3804098420828619,-0.0 +2879,-0.0,0.5305091596029925,0.4694908403970075,-0.0 +2880,-0.0,0.4435870120132968,0.5564129879867032,-0.0 +2881,-0.0,0.4239301080485682,0.5760698919514318,-0.0 +2882,-0.0,0.4575627298610518,0.5424372701389482,-0.0 +2883,0.28707321069821534,0.47210656632050413,0.24082022298128047,-0.0 +2884,0.3658038290491155,0.47460628821197254,0.1595898827389119,-0.0 +2885,0.01309668029928932,0.48835475861504857,0.49854856108566215,-0.0 +2886,-0.0,0.4215440098794393,0.5784559901205607,-0.0 +2887,-0.0,0.3039434572580863,0.6960565427419136,-0.0 +2888,-0.0,0.17498053240278613,0.8250194675972139,-0.0 +2889,-0.0,0.09680741143322973,0.9031925885667703,-0.0 +2890,-0.0,0.05976607795056202,0.9051023903737102,0.03513153167572777 +2891,0.2998688178871762,0.04181352982092552,0.6583176522918983,-0.0 +2892,0.43821191208311744,0.05681186116973576,0.5049762267471467,-0.0 +2893,0.017462240399052427,0.22429322789811676,0.7582445317028308,-0.0 +2894,-0.0,0.3138287211016203,0.6861712788983797,-0.0 +2895,-0.0,0.31882816488455706,0.681171835115443,-0.0 +2896,-0.0,0.30269359631235215,0.6973064036876478,-0.0 +2897,-0.0,0.3093973959303809,0.6906026040696192,-0.0 +2898,-0.0,0.31837366999519917,0.6816263300048009,-0.0 +2899,0.2504928277933038,0.31530582949203345,0.43420134271466276,-0.0 +2900,0.3731801202521635,0.23474661035334815,0.3920732693944883,-0.0 +2901,0.011440778192482626,0.22315699067472203,0.7654022311327953,-0.0 +2902,-0.0,0.2216798822843089,0.7783201177156911,-0.0 +2903,-0.0,0.1845249250793017,0.8154750749206983,-0.0 +2904,-0.0,0.14146153431264202,0.858538465687358,-0.0 +2905,-0.0,0.1507786795444787,0.8492213204555212,-0.0 +2906,-0.0,0.21804392316944582,0.7819560768305542,-0.0 +2907,0.19855771626163923,0.23554197640972446,0.5659003073286364,-0.0 +2908,0.30077203721816165,0.3075794163729494,0.39164854640888896,-0.0 +2909,0.007677364313376499,0.42938404672086283,0.5629385889657607,-0.0 +2910,-0.0,0.4899454907278012,0.5100545092721989,-0.0 +2911,-0.0,0.47244743748752255,0.5275525625124775,-0.0 +2912,-0.0,0.4709703290971094,0.5290296709028905,-0.0 +2913,-0.0,0.45903983825146494,0.5409601617485351,-0.0 +2914,-0.0,0.44029192406545214,0.5597080759345479,-0.0 +2915,0.1606225043602495,0.3601871998161247,0.4791902958236258,-0.0 +2916,0.22715966174284583,0.2634934121052344,0.5093469261519198,-0.0 +2917,0.009032193309854704,0.231337898683164,0.7596299080069813,-0.0 +2918,-0.0,0.17532140356980452,0.8246785964301955,-0.0 +2919,-0.0,0.12294086757130818,0.8770591324286918,-0.0 diff --git a/PyPSA/source/test/data/benchmark-sp/generators-p.csv b/PyPSA/source/test/data/benchmark-sp/generators-p.csv new file mode 100644 index 0000000000000000000000000000000000000000..47eb658ef9800545eb59815c8299e32843d0560f --- /dev/null +++ b/PyPSA/source/test/data/benchmark-sp/generators-p.csv @@ -0,0 +1,2921 @@ +,solar,wind,gas,lignite +0,-0.0,0.17793474918361238,0.8220652508163876,-0.0 +1,-0.0,0.19827339548237777,0.8017266045176222,-0.0 +2,-0.0,0.22713382095660353,0.7728661790433965,-0.0 +3,0.1901276691724415,0.2786053671763841,0.5312669636511743,-0.0 +4,0.3122128154106443,0.27087895405730006,0.4169082305320557,-0.0 +5,0.0033118042136133915,0.4154083288731078,0.5812798669132787,-0.0 +6,-0.0,0.4972174089575273,0.5027825910424727,-0.0 +7,-0.0,0.628225560814938,0.37177443918506203,-0.0 +8,-0.0,0.7261692094715624,0.2738307905284376,-0.0 +9,-0.0,0.8436561383705761,0.15634386162942393,-0.0 +10,-0.0,0.913307480164672,0.086692519835328,-0.0 +11,0.07933276457155715,0.8883102612499884,0.0323569741784545,-0.0 +12,0.16619235690132655,0.8338076430986734,-0.0,-0.0 +13,0.003913950434270372,0.8326346373036473,0.16345141226208235,-0.0 +14,-0.0,0.8366114675855287,0.16338853241447127,-0.0 +15,-0.0,0.82774881724305,0.17225118275694995,-0.0 +16,-0.0,0.8101371402804318,0.18986285971956818,-0.0 +17,-0.0,0.6534500271743006,0.34654997282569944,-0.0 +18,-0.0,0.5806172211546994,0.4193827788453006,-0.0 +19,0.08294564189549904,0.6051599451800252,0.31189441292447573,-0.0 +20,0.10808524660792797,0.5708455810335048,0.32106917235856725,-0.0 +21,0.0024085848826279213,0.45597199774829916,0.5416194173690728,-0.0 +22,-0.0,0.4273388197187523,0.5726611802812477,-0.0 +23,-0.0,0.46438015320142007,0.53561984679858,-0.0 +24,-0.0,0.5530066566262078,0.44699334337379215,-0.0 +25,-0.0,0.556642615741071,0.443357384258929,-0.0 +26,-0.0,0.5122157403063375,0.48778425969366246,-0.0 +27,0.21903068776397655,0.47858311849385404,0.3023861937421694,-0.0 +28,0.2458261945832122,0.5066481779117035,0.24752562750508433,-0.0 +29,0.005569852541077068,0.48278719622041444,0.5116429512385084,-0.0 +30,-0.0,0.38643427967654265,0.6135657203234574,-0.0 +31,-0.0,0.28814975985289976,0.7118502401471003,-0.0 +32,-0.0,0.2706517066126211,0.7293482933873789,-0.0 +33,-0.0,0.31848729371753864,0.6815127062824613,-0.0 +34,-0.0,0.36643650454479565,0.6335634954552043,-0.0 +35,0.1950953754928616,0.37393567021920077,0.4309689542879377,-0.0 +36,0.27247116484728356,0.2871271463518445,0.4404016888008719,-0.0 +37,0.01821492317487365,0.18009359990806234,0.801691476917064,-0.0 +38,-0.0,0.1085106548341953,0.8914893451658047,-0.0 +39,-0.0,0.0689695994600592,0.9051023903737102,0.025928010166230586 +40,-0.0,0.044994994046430724,0.9051023903737102,0.04990261557985907 +41,-0.0,0.03954105537413608,0.9051023903737102,0.05535655425215371 +42,-0.0,0.06874235201538026,0.9051023903737102,0.026155257610909532 +43,0.1816976220832438,0.0829453173078142,0.735357060608942,-0.0 +44,0.27593350561606117,0.10703354644378217,0.6170329479401566,-0.0 +45,0.009032193309854704,0.24281389463945063,0.7481539120506946,-0.0 +46,-0.0,0.2723560624477132,0.7276439375522867,-0.0 +47,-0.0,0.26360703582757394,0.7363929641724261,-0.0 +48,-0.0,0.24031417274798225,0.7596858272520177,-0.0 +49,-0.0,0.24906319936812155,0.7509368006318784,-0.0 +50,-0.0,0.23701908480013759,0.7629809151998624,-0.0 +51,0.1353323630926563,0.19259220936540422,0.6720754275419395,-0.0 +52,0.2011168376994314,0.1965690396472857,0.6023141226532829,-0.0 +53,0.008731120199526213,0.26587951027436335,0.7253893695261103,-0.0 +54,-0.0,0.3039434572580863,0.6960565427419136,-0.0 +55,-0.0,0.33053140828552263,0.6694685917144774,-0.0 +56,-0.0,0.3956378011860398,0.6043621988139602,-0.0 +57,-0.0,0.46438015320142007,0.53561984679858,-0.0 +58,-0.0,0.49517218195541685,0.5048278180445831,-0.0 +59,0.0578060371830701,0.49210434145225107,0.4500896213646789,-0.0 +60,0.08264456878517054,0.520510272037119,0.39684515917771046,-0.0 +61,0.0024085848826279213,0.4792648608278908,0.5183265542894812,-0.0 +62,-0.0,0.526305081876432,0.473694918123568,-0.0 +63,-0.0,0.6064098061257595,0.39359019387424055,-0.0 +64,-0.0,0.6312934013181037,0.36870659868189626,-0.0 +65,-0.0,0.7278735653066545,0.2721264346933455,-0.0 +66,-0.0,0.9706874599461051,0.029312540053894942,-0.0 +67,0.07120379059268792,0.9287962094073121,-0.0,-0.0 +68,0.07647257002343649,0.9235274299765635,-0.0,-0.0 +69,0.004064486989434617,0.9959355130105654,-0.0,-0.0 +70,-0.0,0.9478490917558714,0.0521509082441286,-0.0 +71,-0.0,0.7083302850642653,0.2916697149357347,-0.0 +72,-0.0,0.6435647633307666,0.3564352366692334,-0.0 +73,-0.0,0.7551432586681276,0.2448567413318724,-0.0 +74,-0.0,0.9465992308101371,0.05340076918986292,-0.0 +75,0.13006358366190773,0.8699364163380923,-0.0,-0.0 +76,0.18380513385554323,0.8161948661444568,-0.0,-0.0 +77,0.013247216854453566,0.9867527831455465,-0.0,-0.0 +78,-0.0,1.0,-0.0,-0.0 +79,-0.0,0.9096715210498089,0.09032847895019114,-0.0 +80,-0.0,0.8430880197588786,0.1569119802411214,-0.0 +81,-0.0,0.7940025717082269,0.20599742829177314,-0.0 +82,-0.0,0.8470648500407602,0.15293514995923985,-0.0 +83,0.15098816482973781,0.8490118351702622,-0.0,-0.0 +84,0.2274607348531743,0.7725392651468257,-0.0,-0.0 +85,0.02408584882627921,0.9759141511737208,-0.0,-0.0 +86,-0.0,0.945122122419724,0.05487787758027596,-0.0 +87,-0.0,0.8692214758969571,0.13077852410304291,-0.0 +88,-0.0,0.7736639254094615,0.2263360745905385,-0.0 +89,-0.0,0.7138978474588995,0.2861021525411005,-0.0 +90,-0.0,0.7763908947456089,0.22360910525439115,-0.0 +91,0.07210700992367339,0.9060355619349458,0.02185742814138078,-0.0 +92,0.08971978687789006,0.9102802131221099,-0.0,-0.0 +93,0.009634339530511685,0.9586433453781211,0.03172231509136719,-0.0 +94,-0.0,0.9217156356177929,0.0782843643822071,-0.0 +95,-0.0,0.8264989562973158,0.17350104370268415,-0.0 +96,-0.0,0.733554751423628,0.266445248576372,-0.0 +97,-0.0,0.6967406653856393,0.3032593346143607,-0.0 +98,-0.0,0.7837764366976745,0.2162235633023255,-0.0 +99,0.3659543656042798,0.6340456343957201,-0.0,-0.0 +100,0.5089640930103125,0.49103590698968747,-0.0,-0.0 +101,0.04531150310443776,0.7121934916238074,0.24249500527175483,-0.0 +102,-0.0,0.5816398346557546,0.41836016534424536,-0.0 +103,-0.0,0.5376674541103792,0.4623325458896208,-0.0 +104,-0.0,0.4890365009490854,0.5109634990509146,-0.0 +105,-0.0,0.44881370324091246,0.5511862967590875,-0.0 +106,-0.0,0.5356222271082687,0.4643777728917313,-0.0 +107,0.2157188835503632,0.7001493770558234,0.08413173939381344,-0.0 +108,0.25922394799283,0.6195901579171381,0.12118589409003189,-0.0 +109,0.018515996285202143,0.44449600179201254,0.5369880019227853,-0.0 +110,-0.0,0.4059775599189317,0.5940224400810683,-0.0 +111,-0.0,0.4174535558752183,0.5825464441247816,-0.0 +112,-0.0,0.3773443818893849,0.6226556181106151,-0.0 +113,-0.0,0.4917634702852327,0.5082365297147673,-0.0 +114,-0.0,0.6725388125273318,0.32746118747266817,-0.0 +115,0.13141841265838594,0.7830946943636377,0.0854868929779764,-0.0 +116,0.1920846443895767,0.8079153556104233,-0.0,-0.0 +117,0.021827800498815535,0.8102507640027713,0.16792143549841318,-0.0 +118,-0.0,0.8007063713262558,0.19929362867374423,-0.0 +119,-0.0,0.7897984939816666,0.21020150601833343,-0.0 +120,-0.0,0.6589039658465953,0.3410960341534047,-0.0 +121,-0.0,0.588002763106765,0.411997236893235,-0.0 +122,-0.0,0.5066481779117035,0.49335182208829653,-0.0 +123,0.23017039284613072,0.31723743277180444,0.4525921743820649,-0.0 +124,0.32290091082730565,0.21474883522160115,0.4623502539510932,-0.0 +125,0.023483702605622228,0.1803208473527413,0.7961954500416364,-0.0 +126,-0.0,0.15441463865934177,0.8455853613406582,-0.0 +127,-0.0,0.1124874851160768,0.8875125148839232,-0.0 +128,-0.0,0.09419406581942188,0.9051023903737102,0.0007035438068679067 +129,-0.0,0.12271362012662923,0.8772863798733708,-0.0 +130,-0.0,0.1487334525423682,0.8512665474576318,-0.0 +131,0.25636375344470935,0.12305449129364764,0.620581755261643,-0.0 +132,0.37995426523455456,0.15384652004764443,0.466199214717801,-0.0 +133,0.041999698890824375,0.1628227941124627,0.795177506996713,-0.0 +134,-0.0,0.20429545276636976,0.7957045472336303,-0.0 +135,-0.0,0.20838590677059077,0.7916140932294092,-0.0 +136,-0.0,0.2065679272131592,0.7934320727868408,-0.0 +137,-0.0,0.18895625025054114,0.8110437497494589,-0.0 +138,-0.0,0.16702687183902312,0.8329731281609769,-0.0 +139,0.2815033581571383,0.13464411097227375,0.583852530870588,-0.0 +140,0.43309366920753306,0.14191602920199992,0.4249903015904671,-0.0 +141,0.05208564808682879,0.14430212737112882,0.8036122245420424,-0.0 +142,-0.0,0.1711173258432441,0.8288826741567559,-0.0 +143,-0.0,0.1491879474317261,0.8508120525682739,-0.0 +144,-0.0,0.10317033988424014,0.8968296601157598,-0.0 +145,-0.0,0.04601760754748596,0.9051023903737102,0.04888000207880383 +146,-0.0,0.03647321487097035,0.9051023903737102,0.05842439475531944 +147,0.19464376582736886,0.02022502257642593,0.7851312115962052,-0.0 +148,0.27773994427803217,0.016134568572204955,0.7061254871497629,-0.0 +149,0.038085748456554,0.05681186116973576,0.9051023903737102,-0.0 +150,-0.0,0.06533364034519612,0.9051023903737102,0.02956396928109367 +151,-0.0,0.044426875434733365,0.9051023903737102,0.05047073419155643 +152,-0.0,0.031132899921015193,0.9051023903737102,0.06376470970527459 +153,-0.0,0.026133456138078447,0.9051023903737102,0.06876415348821134 +154,-0.0,0.023633734246610073,0.9051023903737102,0.07126387537967972 +155,0.1279560718896083,0.011021501066928736,0.8610224270434629,-0.0 +156,0.16393430857386287,0.008749026620139306,0.8273166648059977,-0.0 +157,0.020322434947173083,0.017043558350920727,0.9051023903737102,0.057531616328195985 +158,-0.0,0.02329286307959166,0.9051023903737102,0.07160474654669813 +159,-0.0,0.0367004623156493,0.9051023903737102,0.05819714731064049 +160,-0.0,0.03931380792945714,0.9051023903737102,0.05558380169683265 +161,-0.0,0.03340537436780462,0.9051023903737102,0.06149223525848517 +162,-0.0,0.03215551342207044,0.9051023903737102,0.06274209620421936 +163,0.17989118342127286,0.02726969336147316,0.792839123217254,-0.0 +164,0.24808424291067588,0.03363262181248357,0.7182831352768405,-0.0 +165,0.02995677447768477,0.07283280601960124,0.897210419502714,-0.0 +166,-0.0,0.12907654857763964,0.8709234514223604,-0.0 +167,-0.0,0.10623818038740586,0.8937618196125942,-0.0 +168,-0.0,0.0810137140280432,0.9051023903737102,0.013883895598246598 +169,-0.0,0.08203632752909844,0.9051023903737102,0.012861282097191354 +170,-0.0,0.09317145231836664,0.9051023903737102,0.0017261573079231513 +171,0.11576261092130445,0.10157960777148753,0.782657781307208,-0.0 +172,0.13789148453044847,0.09612566909919289,0.7659828463703586,-0.0 +173,0.024687995046936192,0.11782780006603195,0.8574842048870318,-0.0 +174,-0.0,0.1072607938884611,0.8927392061115389,-0.0 +175,-0.0,0.07601427024510644,0.9051023903737102,0.018883339381183348 +176,-0.0,0.07260555857492229,0.9051023903737102,0.0222920510513675 +177,-0.0,0.06487914545583823,0.9051023903737102,0.030018464170451564 +178,-0.0,0.06056144400693832,0.9051023903737102,0.034336165619351475 +179,0.11546153781097597,0.0454494889357886,0.8390889732532354,-0.0 +180,0.11395617225933352,0.027496940806152104,0.8585468869345144,-0.0 +181,0.015505265181917242,0.030337533864638895,0.9051023903737102,0.04905481057973366 +182,-0.0,0.04919907177299116,0.9051023903737102,0.04569853785329863 +183,-0.0,0.08987636437052197,0.9051023903737102,0.005021245255767817 +184,-0.0,0.1671404955613626,0.8328595044386374,-0.0 +185,-0.0,0.19168321958668844,0.8083167804133116,-0.0 +186,-0.0,0.22031639761623525,0.7796836023837648,-0.0 +187,0.10070895540487995,0.18838813163884377,0.7109029129562763,-0.0 +188,0.12268729245885973,0.1406661682562657,0.7366465392848744,-0.0 +189,0.01896760595069488,0.17452603751342824,0.8065063565358769,-0.0 +190,-0.0,0.1994096327057725,0.8005903672942275,-0.0 +191,-0.0,0.18520666741333858,0.8147933325866614,-0.0 +192,-0.0,0.2006594936515067,0.7993405063484933,-0.0 +193,-0.0,0.20327283926531453,0.7967271607346855,-0.0 +194,-0.0,0.17475328495810719,0.8252467150418928,-0.0 +195,0.1266012428931301,0.12066839312451874,0.7527303639823512,-0.0 +196,0.1539988959330227,0.09930713332469811,0.7466939707422792,-0.0 +197,0.03281696902580542,0.1088515260012137,0.8583315049729808,-0.0 +198,-0.0,0.09328507604070611,0.9051023903737102,0.001612533585583678 +199,-0.0,0.07419629068767489,0.9051023903737102,0.020701318938614904 +200,-0.0,0.0849905443099247,0.9051023903737102,0.009907065316365093 +201,-0.0,0.1191912847341056,0.8808087152658944,-0.0 +202,-0.0,0.1802072236304018,0.8197927763695982,-0.0 +203,0.09619285874995259,0.2133853505535275,0.69042179069652,-0.0 +204,0.11982709791073907,0.3234867375004754,0.5566861645887855,-0.0 +205,0.02815033581571383,0.40211435335938966,0.5697353108248966,-0.0 +206,-0.0,0.4255208401613208,0.5744791598386791,-0.0 +207,-0.0,0.3991601365785634,0.6008398634214366,-0.0 +208,-0.0,0.45483576052490443,0.5451642394750955,-0.0 +209,-0.0,0.42233937593581555,0.5776606240641844,-0.0 +210,-0.0,0.43881481567503894,0.5611851843249611,-0.0 +211,0.18786962084497782,0.3516654206406643,0.46046495851435787,-0.0 +212,0.21827800498815533,0.3476885903587828,0.43403340465306195,-0.0 +213,0.05359101363847124,0.336894336736533,0.6095146496249957,-0.0 +214,-0.0,0.24656347747665316,0.7534365225233468,-0.0 +215,-0.0,0.15055143209979976,0.8494485679002002,-0.0 +216,-0.0,0.15043780837746026,0.8495621916225398,-0.0 +217,-0.0,0.20554531371210397,0.7944546862878961,-0.0 +218,-0.0,0.30837478242932564,0.6916252175706743,-0.0 +219,0.11546153781097597,0.44983631674196767,0.4347021454470564,-0.0 +220,0.15143977449523052,0.7344637412023438,0.11409648430242567,-0.0 +221,0.027397653039892604,0.9096715210498089,0.06293082591029853,-0.0 +222,-0.0,0.9061491856572853,0.0938508143427147,-0.0 +223,-0.0,0.8259308376856184,0.17406916231438163,-0.0 +224,-0.0,0.7278735653066545,0.2721264346933455,-0.0 +225,-0.0,0.6403832991052614,0.35961670089473863,-0.0 +226,-0.0,0.6232261170320013,0.37677388296799874,-0.0 +227,0.2539551685620814,0.6870826489867843,0.058962182451134326,-0.0 +228,0.2298693197358022,0.6857191643187106,0.08441151594548718,-0.0 +229,0.06292428005865443,0.46631175648119105,0.47076396346015453,-0.0 +230,-0.0,0.3524607866970406,0.6475392133029594,-0.0 +231,-0.0,0.287013522629505,0.712986477370495,-0.0 +232,-0.0,0.21247636077481172,0.7875236392251883,-0.0 +233,-0.0,0.1646407736698942,0.8353592263301057,-0.0 +234,-0.0,0.19213771447604633,0.8078622855239537,-0.0 +235,0.15595587115015788,0.1528239065465892,0.691220222303253,-0.0 +236,0.1803427930867656,0.14532474087218408,0.6743324660410503,-0.0 +237,0.055246915745277944,0.10317033988424014,0.8415827443704819,-0.0 +238,-0.0,0.13975717847754995,0.86024282152245,-0.0 +239,-0.0,0.14055254453392627,0.8594474554660737,-0.0 +240,-0.0,0.1568007368284707,0.8431992631715293,-0.0 +241,-0.0,0.17429879006874932,0.8257012099312506,-0.0 +242,-0.0,0.1730489291230151,0.8269510708769849,-0.0 +243,0.19088035194826275,0.13964355475521048,0.6694760932965267,-0.0 +244,0.20563293435435875,0.16770861417305996,0.6266584514725813,-0.0 +245,0.08189188600934931,0.1579369740518654,0.7601711399387853,-0.0 +246,-0.0,0.19088785353031215,0.8091121464696879,-0.0 +247,-0.0,0.17861649151764922,0.8213835084823508,-0.0 +248,-0.0,0.1439612562041104,0.8560387437958896,-0.0 +249,-0.0,0.11464633584052676,0.8853536641594733,-0.0 +250,-0.0,0.11521445445222411,0.8847855455477759,-0.0 +251,0.1947943023825331,0.08362705964185103,0.7215786379756159,-0.0 +252,0.20412756880271632,0.13964355475521048,0.6562288764420732,-0.0 +253,0.04455882032861654,0.17020833606452831,0.7852328436068551,-0.0 +254,-0.0,0.2423593997500927,0.7576406002499073,-0.0 +255,-0.0,0.23940518296926647,0.7605948170307335,-0.0 +256,-0.0,0.2397460541362849,0.7602539458637151,-0.0 +257,-0.0,0.23735995596715598,0.762640044032844,-0.0 +258,-0.0,0.24758609097770842,0.7524139090222915,-0.0 +259,0.19614913137901133,0.2308834037938061,0.5729674648271825,-0.0 +260,0.1959985948238471,0.30008025069854427,0.5039211544776087,-0.0 +261,0.05238672119715728,0.263379788382895,0.6842334904199477,-0.0 +262,-0.0,0.2456544876979374,0.7543455123020626,-0.0 +263,-0.0,0.2270201972342641,0.772979802765736,-0.0 +264,-0.0,0.19759165314834096,0.8024083468516591,-0.0 +265,-0.0,0.17793474918361238,0.8220652508163876,-0.0 +266,-0.0,0.15407376749232338,0.8459262325076766,-0.0 +267,0.18320298763488624,0.13418961608291585,0.6826073962821979,-0.0 +268,0.2574175093308591,0.12100926429153716,0.6215732263776037,-0.0 +269,0.07526827758212254,0.09123984903859562,0.8334918733792819,-0.0 +270,-0.0,0.09703465887790867,0.9029653411220914,-0.0 +271,-0.0,0.0773777549131801,0.9051023903737102,0.017519854713109698 +272,-0.0,0.06771973851432501,0.9051023903737102,0.027177871111964777 +273,-0.0,0.05749360350377258,0.9051023903737102,0.03740400612251721 +274,-0.0,0.052494159720835834,0.9051023903737102,0.04240344990545396 +275,0.20834259234731517,0.0406772925975308,0.750980115055154,-0.0 +276,0.24627780424870493,0.0554483765016621,0.6982738192496329,-0.0 +277,0.06834359604456726,0.06374290823244351,0.8679134957229891,-0.0 +278,-0.0,0.06453827428881982,0.9051023903737102,0.03035933533746997 +279,-0.0,0.03158739481037308,0.9051023903737102,0.06331021481591671 +280,-0.0,0.026701574749775806,0.9051023903737102,0.06819603487651399 +281,-0.0,0.03306450320078621,0.9051023903737102,0.06183310642550358 +282,-0.0,0.04942631921767011,0.9051023903737102,0.045471290408619684 +283,0.20819205579215094,0.07771862608019851,0.7140893181276505,-0.0 +284,0.24462190214189825,0.1431658901477341,0.6122122077103677,-0.0 +285,0.06171998761734048,0.19600092103558833,0.7422790913470713,-0.0 +286,-0.0,0.2819004551242288,0.7180995448757712,-0.0 +287,-0.0,0.23531472896504552,0.7646852710349545,-0.0 +288,-0.0,0.1873655181377885,0.8126344818622115,-0.0 +289,-0.0,0.18543391485801752,0.8145660851419825,-0.0 +290,-0.0,0.19145597214200952,0.8085440278579905,-0.0 +291,0.26103038665480094,0.22940629540339297,0.5095633179418061,-0.0 +292,0.3998250905162349,0.21633956733435378,0.38383534214941134,-0.0 +293,0.11395617225933352,0.1774802542942545,0.708563573446412,-0.0 +294,-0.0,0.15180129304553394,0.8481987069544661,-0.0 +295,-0.0,0.10407932966295591,0.8959206703370441,-0.0 +296,-0.0,0.08874012714712726,0.9051023903737102,0.006157482479162535 +297,-0.0,0.11260110883841626,0.8873988911615838,-0.0 +298,-0.0,0.15521000471571808,0.8447899952842819,-0.0 +299,0.31778266795172133,0.18759276558246746,0.49462456646581127,-0.0 +300,0.3913950434270372,0.3136014736569414,0.29500348291602135,-0.0 +301,0.08941871376756157,0.4676752411492648,0.4429060450831736,-0.0 +302,-0.0,0.6056144400693831,0.39438555993061686,-0.0 +303,-0.0,0.7404857984863358,0.2595142015136642,-0.0 +304,-0.0,0.8235447395164895,0.17645526048351046,-0.0 +305,-0.0,0.7879805144242349,0.2120194855757651,-0.0 +306,-0.0,0.6478824647796666,0.3521175352203334,-0.0 +307,0.3102558401935091,0.5598240799665761,0.12992007983991477,-0.0 +308,0.44498605706550837,0.5550139429344916,-0.0,-0.0 +309,0.16182679680156345,0.4173399321528789,0.4208332710455577,-0.0 +310,-0.0,0.3484839564151591,0.6515160435848408,-0.0 +311,-0.0,0.3760945209436507,0.6239054790563493,-0.0 +312,-0.0,0.4574491061387123,0.5425508938612877,-0.0 +313,-0.0,0.5273276953774872,0.47267230462251275,-0.0 +314,-0.0,0.5370993354986818,0.46290066450131817,-0.0 +315,0.1098916852698989,0.5105113844712454,0.37959693025885566,-0.0 +316,0.1129024163731838,0.49551305312243527,0.391584530504381,-0.0 +317,0.031462140029327214,0.4474502185728388,0.5210876413978339,-0.0 +318,-0.0,0.3875705168999373,0.6124294831000627,-0.0 +319,-0.0,0.32337311377813593,0.6766268862218641,-0.0 +320,-0.0,0.28337756351464194,0.7166224364853581,-0.0 +321,-0.0,0.28553641423909193,0.7144635857609081,-0.0 +322,-0.0,0.24474549791922165,0.7552545020807784,-0.0 +323,0.12885929122059378,0.159527706164618,0.7116130026147882,-0.0 +324,0.136988265199463,0.179525481296365,0.6834862535041719,-0.0 +325,0.040945943004674655,0.18407043018994385,0.7749836268053815,-0.0 +326,-0.0,0.1667996243943442,0.8332003756056559,-0.0 +327,-0.0,0.11078312928098473,0.8892168707190152,-0.0 +328,-0.0,0.07453716185469332,0.9051023903737102,0.02036044777159647 +329,-0.0,0.03715495720500719,0.9051023903737102,0.057742652421282605 +330,-0.0,0.026133456138078447,0.9051023903737102,0.06876415348821134 +331,0.1175690495832754,0.013862094125415525,0.8685688562913091,-0.0 +332,0.1847083531865287,0.009317145231836665,0.8059745015816346,-0.0 +333,0.09995627262905872,0.014657460181791825,0.8853862671891495,-0.0 +334,-0.0,0.06226579984203039,0.9051023903737102,0.032631809784259405 +335,-0.0,0.08839925598010882,0.9051023903737102,0.006498353646180968 +336,-0.0,0.0646518980111593,0.9051023903737102,0.030245711615130497 +337,-0.0,0.04181352982092552,0.9051023903737102,0.053084079805364275 +338,-0.0,0.043404261933678114,0.9051023903737102,0.05149334769261168 +339,0.22204141886726148,0.02090676491046276,0.7570518162222758,-0.0 +340,0.3013741834388186,0.01272585690202081,0.6858999596591606,-0.0 +341,0.12584856011730886,0.016134568572204955,0.8580168713104862,-0.0 +342,-0.0,0.07760500235785904,0.9051023903737102,0.01729260726843075 +343,-0.0,0.0813545851950616,0.9051023903737102,0.013543024431228193 +344,-0.0,0.06476552173349877,0.9051023903737102,0.030132087892791024 +345,-0.0,0.07465078557703278,0.9051023903737102,0.020246824049257012 +346,-0.0,0.09226246253965086,0.9051023903737102,0.0026351470866389365 +347,0.36761026771108646,0.06760611479198554,0.5647836174969281,-0.0 +348,0.5794152008271793,0.07715050746850116,0.3434342917043195,-0.0 +349,0.22685858863251732,0.13714383286374213,0.6359975785037406,-0.0 +350,-0.0,0.2720151912806948,0.7279848087193053,-0.0 +351,-0.0,0.21270360821949066,0.7872963917805094,-0.0 +352,-0.0,0.16134568572204952,0.8386543142779505,-0.0 +353,-0.0,0.13532585330631056,0.8646741466936895,-0.0 +354,-0.0,0.13009916207869487,0.8699008379213051,-0.0 +355,0.49451258371454504,0.052607783443175314,0.45287963284227967,-0.0 +356,0.7008982008447251,0.04272251959964129,0.25637927955563367,-0.0 +357,0.24928853535198983,0.0972619063225876,0.6534495583254226,-0.0 +358,-0.0,0.19213771447604633,0.8078622855239537,-0.0 +359,-0.0,0.14657460181791826,0.8534253981820817,-0.0 +360,-0.0,0.13055365696805277,0.8694463430319472,-0.0 +361,-0.0,0.13828007008713683,0.8617199299128632,-0.0 +362,0.00015053655516424508,0.1650952685592521,0.8347541948855836,-0.0 +363,0.38853484887891654,0.1774802542942545,0.43398489682682895,-0.0 +364,0.588447394137034,0.2045227002110487,0.20702990565191737,-0.0 +365,0.20758990957149395,0.2600847004350503,0.5323253899934558,-0.0 +366,-0.0,0.36711824687883243,0.6328817531211676,-0.0 +367,-0.0,0.25065393148087417,0.7493460685191258,-0.0 +368,-0.0,0.17407154262407035,0.8259284573759297,-0.0 +369,-0.0,0.16305004155714162,0.8369499584428584,-0.0 +370,0.00015053655516424508,0.20361371043233295,0.7962357530125027,-0.0 +371,0.35827700129090323,0.24179128113839538,0.39993171757070145,-0.0 +372,0.5333510149469203,0.2240659804534378,0.24258300459964188,-0.0 +373,0.3099547670831806,0.2105447574950407,0.47950047542177865,-0.0 +374,-0.0,0.26326616466055547,0.7367338353394446,-0.0 +375,-0.0,0.15577812332741545,0.8442218766725845,-0.0 +376,-0.0,0.0865812764226773,0.9051023903737102,0.008316333203612497 +377,-0.0,0.05328952577721213,0.9051023903737102,0.04160808384907766 +378,0.00030107311032849017,0.043404261933678114,0.9051023903737102,0.05119227458228319 +379,0.3522555390843335,0.03579147253693352,0.611952988378733,-0.0 +380,0.3462340768777637,0.038518441873080844,0.6152474812491555,-0.0 +381,0.10928953904924192,0.06090231517395673,0.8298081457768014,-0.0 +382,-0.0,0.1128283562830952,0.8871716437169048,-0.0 +383,-0.0,0.09646654026621132,0.9035334597337887,-0.0 +384,-0.0,0.07215106368556441,0.9051023903737102,0.02274654594072538 +385,-0.0,0.065788135234554,0.9051023903737102,0.029109474391735793 +386,-0.0,0.07442353813235385,0.9051023903737102,0.020474071493935944 +387,0.21767585876749837,0.1085106548341953,0.6738134863983063,-0.0 +388,0.4249646952286638,0.16907209884113358,0.4059632059302026,-0.0 +389,0.1803427930867656,0.17259443423365722,0.6470627726795772,-0.0 +390,-0.0,0.2914448478007444,0.7085551521992556,-0.0 +391,-0.0,0.30905652476336254,0.6909434752366375,-0.0 +392,-0.0,0.28462742446037614,0.7153725755396239,-0.0 +393,-0.0,0.2277019395683009,0.7722980604316991,-0.0 +394,0.0007526827758212254,0.22588396001086936,0.7733633572133095,-0.0 +395,0.41563142880848064,0.30837478242932564,0.27599378876219366,-0.0 +396,0.6015440744363233,0.30826115870698617,0.09019476685669059,-0.0 +397,0.2625357522064434,0.2819004551242288,0.45556379266932784,-0.0 +398,-0.0,0.3655275147660798,0.6344724852339202,-0.0 +399,-0.0,0.3279180626717148,0.6720819373282851,-0.0 +400,-0.0,0.30292084375703104,0.697079156242969,-0.0 +401,-0.0,0.3155330769367124,0.6844669230632876,-0.0 +402,0.0010537558861497154,0.3729130567181455,0.6260331873957047,-0.0 +403,0.4446849839551799,0.5409625420582239,0.01435247398659617,-0.0 +404,0.5761033966135658,0.4238966033864342,-0.0,-0.0 +405,0.21707371254684138,0.3987056416892055,0.3842206457639531,-0.0 +406,-0.0,0.39666041468709506,0.6033395853129049,-0.0 +407,-0.0,0.3810939647265874,0.6189060352734126,-0.0 +408,-0.0,0.41086337997952893,0.5891366200204711,-0.0 +409,-0.0,0.39825114679984763,0.6017488532001524,-0.0 +410,0.002709657992956411,0.3895021201797083,0.6077882218273353,-0.0 +411,0.3027290124352968,0.3239412323898333,0.3733297551748699,-0.0 +412,0.3346427621301168,0.4690387258173384,0.1963185120525448,-0.0 +413,0.1542999690433512,0.2880361361305603,0.5576638948260885,-0.0 +414,-0.0,0.1927058330877437,0.8072941669122563,-0.0 +415,-0.0,0.15895958755292064,0.8410404124470794,-0.0 +416,-0.0,0.10794253622249794,0.8920574637775021,-0.0 +417,-0.0,0.08885375086946673,0.9051023903737102,0.006043858756823062 +418,0.0018064386619709406,0.09351232348538505,0.904681237852644,-0.0 +419,0.40494333339181926,0.09805727237896392,0.49699939422921685,-0.0 +420,0.33313739657847435,0.12430435223938183,0.5425582511821438,-0.0 +421,0.199009325927132,0.10112511288212964,0.6998655611907383,-0.0 +422,-0.0,0.23008803773742983,0.7699119622625702,-0.0 +423,-0.0,0.37552640233195334,0.6244735976680467,-0.0 +424,-0.0,0.47551527799068827,0.5244847220093117,-0.0 +425,-0.0,0.5619829306910261,0.4380170693089739,-0.0 +426,0.0006021462206569803,0.5580061004091446,0.4413917533701985,-0.0 +427,0.15023548205391657,0.49028636189481956,0.3594781560512639,-0.0 +428,0.18229976830390077,0.32905429989510954,0.48864593180098964,-0.0 +429,0.07662310657860075,0.2198619027268774,0.703514990694522,-0.0 +430,-0.0,0.29212659013478126,0.7078734098652187,-0.0 +431,-0.0,0.2883770072975787,0.7116229927024214,-0.0 +432,-0.0,0.29803502369643375,0.7019649763035662,-0.0 +433,-0.0,0.32428210355685166,0.6757178964431483,-0.0 +434,0.0030107311032849015,0.36234605054057467,0.6346432183561405,-0.0 +435,0.41472820947749517,0.48562778927890127,0.09964400124360356,-0.0 +436,0.37001885259371436,0.47278830865454097,0.1571928387517446,-0.0 +437,0.15746123670180034,0.3179191751058413,0.5246195881923584,-0.0 +438,-0.0,0.33814419768226717,0.6618558023177328,-0.0 +439,-0.0,0.25974382926803186,0.7402561707319681,-0.0 +440,-0.0,0.15975495360929695,0.8402450463907031,-0.0 +441,-0.0,0.12339536246066607,0.8766046375393339,-0.0 +442,0.0024085848826279213,0.1085106548341953,0.8890807602831767,-0.0 +443,0.5217597001992734,0.10760166505547952,0.37063863474524705,-0.0 +444,0.49963082659012936,0.11362372233947152,0.38674545107039915,-0.0 +445,0.2798474560503316,0.052039664831477955,0.6681128791181905,-0.0 +446,-0.0,0.058970711894185714,0.9051023903737102,0.03592689773210408 +447,-0.0,0.07362817207597754,0.9051023903737102,0.021269437550312256 +448,-0.0,0.06953771807175656,0.9051023903737102,0.025359891554533234 +449,-0.0,0.08783113736841147,0.9051023903737102,0.00706647225787832 +450,0.008128973978869233,0.10351121105125854,0.8883598149698722,-0.0 +451,0.6721457188083543,0.13555310075098953,0.19230118044065614,-0.0 +452,0.7892631587261368,0.21073684127386316,-0.0,-0.0 +453,0.3605350496183669,0.24554086397559793,0.39392408640603516,-0.0 +454,-0.0,0.4113178748688869,0.5886821251311132,-0.0 +455,-0.0,0.36666375198947454,0.6333362480105255,-0.0 +456,-0.0,0.2563351175978477,0.7436648824021523,-0.0 +457,-0.0,0.18975161630691745,0.8102483836930825,-0.0 +458,0.00797843742370499,0.13543947702865003,0.856582085547645,-0.0 +459,0.4338463519833543,0.10044337054809283,0.46571027746855287,-0.0 +460,0.4990286803694724,0.13668933797438423,0.3642819816561434,-0.0 +461,0.20096630114426717,0.12703132157552915,0.6720023772802036,-0.0 +462,-0.0,0.15566449960507597,0.844335500394924,-0.0 +463,-0.0,0.12691769785318968,0.8730823021468104,-0.0 +464,-0.0,0.09907988588001916,0.9009201141199809,-0.0 +465,-0.0,0.08749026620139307,0.9051023903737102,0.007407343424896726 +466,0.002709657992956411,0.08669490014501677,0.9051023903737102,0.005493051488316613 +467,0.5896516865783479,0.13134902302442905,0.27899929039722304,-0.0 +468,0.5795657373823435,0.18213882691017283,0.23829543570748368,-0.0 +469,0.38326606944816793,0.18077534224209915,0.43595858830973294,-0.0 +470,-0.0,0.35223353925236167,0.6477664607476383,-0.0 +471,-0.0,0.4657436378694937,0.5342563621305063,-0.0 +472,-0.0,0.47483353565665143,0.5251664643433486,-0.0 +473,-0.0,0.5439167588390501,0.4560832411609499,-0.0 +474,0.0007526827758212254,0.6145907141342014,0.38465660308997734,-0.0 +475,0.1457193853989892,0.6703799618028818,0.18390065279812895,-0.0 +476,0.24100902481795633,0.6656077654646241,0.09338320971741959,-0.0 +477,0.14150436185439036,0.49596754801179316,0.36252809013381654,-0.0 +478,-0.0,0.4357469751718733,0.5642530248281268,-0.0 +479,-0.0,0.378821490279798,0.621178509720202,-0.0 +480,-0.0,0.39950100774558184,0.6004989922544182,-0.0 +481,-0.0,0.5041484560202351,0.49585154397976494,-0.0 +482,0.005118242875584332,0.6317478962074616,0.36313386091695404,-0.0 +483,0.3573737819599178,0.6426262180400821,-0.0,-0.0 +484,0.43505064442466823,0.5649493555753318,-0.0,-0.0 +485,0.2312241487322804,0.6365200925457194,0.1322557587220002,-0.0 +486,-0.0,0.5731180554802943,0.42688194451970574,-0.0 +487,-0.0,0.594479315280115,0.405520684719885,-0.0 +488,-0.0,0.6411786651616378,0.3588213348383622,-0.0 +489,-0.0,0.5911842273322703,0.4088157726677297,-0.0 +490,0.01399989963027479,0.48449155205550654,0.5015085483142186,-0.0 +491,0.5789635911616865,0.4210364088383135,-0.0,-0.0 +492,0.5499100360149872,0.4500899639850128,-0.0,-0.0 +493,0.21165439656092858,0.32689544917065955,0.46145015426841196,-0.0 +494,-0.0,0.36223242681823514,0.6377675731817649,-0.0 +495,-0.0,0.3379169502375883,0.6620830497624117,-0.0 +496,-0.0,0.3902974862360847,0.6097025137639154,-0.0 +497,-0.0,0.35836922025869317,0.6416307797413068,-0.0 +498,0.01159131474764687,0.33837144512694617,0.650037240125407,-0.0 +499,0.4094594300467466,0.5576652292421262,0.03287534071112713,-0.0 +500,0.2846646258155874,0.6035692130672726,0.11176616111714,-0.0 +501,0.1833535241900505,0.43006578905489967,0.38658068675504986,-0.0 +502,-0.0,0.44029192406545214,0.5597080759345479,-0.0 +503,-0.0,0.4001827500796187,0.5998172499203813,-0.0 +504,-0.0,0.3097382670973993,0.6902617329026006,-0.0 +505,-0.0,0.24463187419688215,0.7553681258031179,-0.0 +506,0.007827900868540743,0.16770861417305996,0.8244634849583993,-0.0 +507,0.32786861714772575,0.17009471234218884,0.5020366705100854,-0.0 +508,0.37859943623807635,0.2238387330087589,0.39756183075316476,-0.0 +509,0.2151167373297062,0.1320307653584659,0.6528524973118279,-0.0 +510,-0.0,0.1468018492625972,0.8531981507374028,-0.0 +511,-0.0,0.145551988316863,0.854448011683137,-0.0 +512,-0.0,0.1339623686382369,0.866037631361763,-0.0 +513,-0.0,0.133507873748879,0.8664921262511209,-0.0 +514,0.011440778192482626,0.13134902302442905,0.8572101987830882,-0.0 +515,0.32515895915476933,0.1758895221815019,0.4989515186637288,-0.0 +516,0.30287954899046104,0.20929489654930652,0.4878255544602324,-0.0 +517,0.1660418203461623,0.10396570594061644,0.7299924737132212,-0.0 +518,-0.0,0.16929934628581256,0.8307006537141874,-0.0 +519,-0.0,0.21009026260568284,0.7899097373943171,-0.0 +520,-0.0,0.22588396001086936,0.7741160399891307,-0.0 +521,-0.0,0.2325877596288982,0.7674122403711018,-0.0 +522,0.022429946719472516,0.24179128113839538,0.7357787721421322,-0.0 +523,0.5347058439433985,0.30905652476336254,0.15623763129323898,-0.0 +524,0.7751127225406979,0.22488727745930215,-0.0,-0.0 +525,0.31416979062777944,0.3098518908197388,0.3759783185524818,-0.0 +526,-0.0,0.4268843248293944,0.5731156751706056,-0.0 +527,-0.0,0.4014326110253528,0.5985673889746472,-0.0 +528,-0.0,0.36836810782456664,0.6316318921754334,-0.0 +529,-0.0,0.31882816488455706,0.681171835115443,-0.0 +530,0.03522555390843335,0.2626980460488581,0.7020764000427084,-0.0 +531,0.767134285116993,0.18407043018994385,0.04879528469306316,-0.0 +532,0.91526225539861,0.08473774460139005,-0.0,-0.0 +533,0.3590296840667245,0.18520666741333858,0.45576364851993695,-0.0 +534,-0.0,0.28883150218693654,0.7111684978130635,-0.0 +535,-0.0,0.25622149387550824,0.7437785061244917,-0.0 +536,-0.0,0.19634179220260678,0.8036582077973933,-0.0 +537,-0.0,0.1252133420180976,0.8747866579819024,-0.0 +538,0.0320642862499842,0.042608895877301815,0.9051023903737102,0.02022442749900378 +539,0.5905549059093333,0.01363484668073658,0.3958102474099301,-0.0 +540,0.7781234536439828,0.005113067505276217,0.216763478850741,-0.0 +541,0.4088572838260896,0.010567006177570849,0.5805757099963396,-0.0 +542,-0.0,0.07226468740790389,0.9051023903737102,0.022632922218385906 +543,-0.0,0.22202075345132732,0.7779792465486727,-0.0 +544,-0.0,0.2197482790045379,0.7802517209954621,-0.0 +545,-0.0,0.12657682668617126,0.8734231733138287,-0.0 +546,0.034773944242940606,0.09476218443111924,0.8704638713259402,-0.0 +547,0.5170930669891819,0.08044559541634583,0.4024613375944722,-0.0 +548,0.51844789598566,0.10930602089057159,0.3722460831237684,-0.0 +549,0.2491379987968256,0.14646097809557876,0.6044010231075956,-0.0 +550,-0.0,0.23951880669160594,0.760481193308394,-0.0 +551,-0.0,0.22270249578536416,0.7772975042146358,-0.0 +552,-0.0,0.14566561203920247,0.8543343879607975,-0.0 +553,-0.0,0.13248526024782378,0.8675147397521762,-0.0 +554,0.026494433708907132,0.12657682668617126,0.8469287396049215,-0.0 +555,0.5002329728107864,0.16634512950498628,0.33342189768422736,-0.0 +556,0.7273926345536322,0.15021056093278135,0.12239680451358648,-0.0 +557,0.3813090942310328,0.10703354644378217,0.511657359325185,-0.0 +558,-0.0,0.1332806263042001,0.8667193736957999,-0.0 +559,-0.0,0.09987525193639546,0.9001247480636045,-0.0 +560,-0.0,0.05215328855381743,0.9051023903737102,0.04274432107247236 +561,-0.0,0.01886153790835227,0.9051023903737102,0.07603607171793753 +562,0.03778467534622551,0.011248748511607681,0.9051023903737102,0.045864185768456606 +563,0.5453939393600599,0.026133456138078447,0.42847260450186164,-0.0 +564,0.5777592987203726,0.0678333622366645,0.35440733904296295,-0.0 +565,0.32922344614420396,0.10998776322460842,0.5607887906311876,-0.0 +566,-0.0,0.21213548960779333,0.7878645103922066,-0.0 +567,-0.0,0.18827450791650427,0.8117254920834958,-0.0 +568,-0.0,0.12782668763190547,0.8721733123680946,-0.0 +569,-0.0,0.1072607938884611,0.8927392061115389,-0.0 +570,0.04169862578049588,0.10669267527676375,0.8516086989427404,-0.0 +571,0.5017383383624288,0.12430435223938183,0.3739573093981894,-0.0 +572,0.4499537633859285,0.12794031135424494,0.4221059252598265,-0.0 +573,0.23107361217711617,0.1650952685592521,0.6038311192636316,-0.0 +574,-0.0,0.18725189441544907,0.8127481055845509,-0.0 +575,-0.0,0.15645986566145226,0.8435401343385478,-0.0 +576,-0.0,0.1423705240913578,0.8576294759086422,-0.0 +577,-0.0,0.11112400044800314,0.8888759995519968,-0.0 +578,0.018666532840366387,0.09896626215767969,0.882367205001954,-0.0 +579,0.23920258615598544,0.16657237694966526,0.5942250368943494,-0.0 +580,0.22550375963603908,0.1739579189017309,0.6005383214622301,-0.0 +581,0.12690231600345858,0.1746396612357677,0.6984580227607737,-0.0 +582,-0.0,0.21361259799820645,0.7863874020017936,-0.0 +583,-0.0,0.17225356306663883,0.8277464369333611,-0.0 +584,-0.0,0.1427113952583762,0.8572886047416238,-0.0 +585,-0.0,0.12714494529786863,0.8728550547021314,-0.0 +586,0.02529014126759317,0.13975717847754995,0.8349526802548568,-0.0 +587,0.26539594675456407,0.2901949868550103,0.4444090663904257,-0.0 +588,0.3012236468836544,0.2950808069156075,0.40369554620073816,-0.0 +589,0.19855771626163923,0.2826958211806051,0.5187464625577557,-0.0 +590,-0.0,0.42620258249535764,0.5737974175046423,-0.0 +591,-0.0,0.3948424351296635,0.6051575648703365,-0.0 +592,-0.0,0.3378033265152488,0.6621966734847512,-0.0 +593,-0.0,0.2799688518444578,0.7200311481555421,-0.0 +594,0.06337588972414718,0.29258108502413915,0.6440430252517136,-0.0 +595,0.6718446456980257,0.3281553543019743,-0.0,-0.0 +596,0.8059727163493681,0.19402728365063193,-0.0,-0.0 +597,0.36625543871460825,0.42404373177090765,0.20970082951448415,-0.0 +598,-0.0,0.4419962799005442,0.5580037200994559,-0.0 +599,-0.0,0.35871009142571153,0.6412899085742885,-0.0 +600,-0.0,0.27440128944982367,0.7255987105501763,-0.0 +601,-0.0,0.18282056924420964,0.8171794307557904,-0.0 +602,0.07903169146122865,0.1264632029638318,0.7945051055749396,-0.0 +603,0.7505752640489259,0.187479141860128,0.06194559409094602,-0.0 +604,0.9534985404103282,0.04650145958967178,-0.0,-0.0 +605,0.42887864566293415,0.17145819701026252,0.3996631573268033,-0.0 +606,-0.0,0.3298496659514858,0.6701503340485142,-0.0 +607,-0.0,0.263152540938216,0.736847459061784,-0.0 +608,-0.0,0.19554642614623047,0.8044535738537695,-0.0 +609,-0.0,0.14032529708924732,0.8596747029107527,-0.0 +610,0.10913900249407767,0.07851399213657481,0.8123470053693475,-0.0 +611,0.8619723148704672,0.09635291654387185,0.04167476858566088,-0.0 +612,1.0,-0.0,-0.0,-0.0 +613,0.49932975347980085,0.08862650342478778,0.4120437430954114,-0.0 +614,-0.0,0.24042779647032173,0.7595722035296782,-0.0 +615,-0.0,0.17532140356980452,0.8246785964301955,-0.0 +616,-0.0,0.09442131326410082,0.9051023903737102,0.0004762963621889743 +617,-0.0,0.06487914545583823,0.9051023903737102,0.030018464170451564 +618,0.10522505205980731,0.03738220464968613,0.8573927432905066,-0.0 +619,0.7371775106393081,0.04158628237624657,0.22123620698444524,-0.0 +620,0.9915842888668822,0.008415711133117787,-0.0,-0.0 +621,0.5012867286969361,0.014089341570094466,0.4846239297329694,-0.0 +622,-0.0,0.0897627406481825,0.9051023903737102,0.00513486897810729 +623,-0.0,0.05238053599849637,0.9051023903737102,0.042517073627793424 +624,-0.0,0.018975161630691743,0.9051023903737102,0.07592244799559805 +625,-0.0,0.028178683140188933,0.9051023903737102,0.06671892648610087 +626,0.12118192690721728,0.0203386462987654,0.8584794267940173,-0.0 +627,0.9265524970359285,0.011135124789268208,0.062312378174803285,-0.0 +628,0.9522942479690144,0.019884151409407518,0.027821600621578124,-0.0 +629,0.46741600378498094,0.03772307581670454,0.49486092039831453,-0.0 +630,-0.0,0.05294865461019373,0.9051023903737102,0.041948955016096065 +631,-0.0,0.07590064652276697,0.9051023903737102,0.01899696310352282 +632,-0.0,0.10555643805336903,0.894443561946631,-0.0 +633,-0.0,0.08635402897799835,0.9051023903737102,0.008543580648291443 +634,0.08309617845066328,0.06953771807175656,0.8473661034775801,-0.0 +635,0.43505064442466823,0.07874123958125376,0.486208115994078,-0.0 +636,0.3721263643660138,0.10976051577992949,0.5181131198540567,-0.0 +637,0.16107411402574223,0.18850175536118322,0.6504241306130746,-0.0 +638,-0.0,0.32519109333556745,0.6748089066644325,-0.0 +639,-0.0,0.4050685701402159,0.5949314298597841,-0.0 +640,-0.0,0.3535970239204353,0.6464029760795647,-0.0 +641,-0.0,0.27031083544560275,0.7296891645543973,-0.0 +642,0.0775263259095862,0.1906606060856332,0.7318130680047806,-0.0 +643,0.4564268352579911,0.1830478166888886,0.3605253480531203,-0.0 +644,0.6287911909210516,0.06294754217606721,0.30826126690288114,-0.0 +645,0.4037390409505053,0.03658683859330983,0.5596741204561849,-0.0 +646,-0.0,0.07260555857492229,0.9051023903737102,0.0222920510513675 +647,-0.0,0.10544281433102956,0.8945571856689705,-0.0 +648,-0.0,0.11305560372777416,0.8869443962722259,-0.0 +649,-0.0,0.09430768954176136,0.9051023903737102,0.0005899200845284336 +650,0.11049383149055589,0.04363150937835706,0.8458746591310871,-0.0 +651,0.6674790855982626,0.05056255644106482,0.2819583579606726,-0.0 +652,0.879585091824684,0.07635514141212485,0.04405976676319112,-0.0 +653,0.4260184511148135,0.06385653195478298,0.5101250169304035,-0.0 +654,-0.0,0.05419851555592791,0.9051023903737102,0.04069909407036188 +655,-0.0,0.056698237447396285,0.9051023903737102,0.038199372178893506 +656,-0.0,0.0398819265411545,0.9051023903737102,0.05501568308513529 +657,-0.0,0.028292306862528406,0.9051023903737102,0.06660530276376139 +658,0.1038702230633291,0.01363484668073658,0.8824949302559344,-0.0 +659,0.656640453626437,0.007726413119084063,0.33563313325447897,-0.0 +660,0.7129411252578647,0.008408155453120892,0.2786507192890144,-0.0 +661,0.3161267658449146,0.005340314949955161,0.6785329192051303,-0.0 +662,-0.0,0.0179525481296365,0.9051023903737102,0.07694506149665328 +663,-0.0,0.06919684690473815,0.9051023903737102,0.02570076272155164 +664,-0.0,0.09192159137263245,0.9051023903737102,0.002976018253657342 +665,-0.0,0.1032839636065796,0.8967160363934203,-0.0 +666,0.13051519332740047,0.0813545851950616,0.788130221477538,-0.0 +667,0.7585537014726309,0.1488470762647077,0.09259922226266137,-0.0 +668,0.8809399208211622,0.11906007917883776,-0.0,-0.0 +669,0.3162773024000789,0.1388481886988342,0.5448745089010869,-0.0 +670,-0.0,0.27224243872537374,0.7277575612746263,-0.0 +671,-0.0,0.19975050387279092,0.8002494961272091,-0.0 +672,-0.0,0.16145930944438902,0.838540690555611,-0.0 +673,-0.0,0.12612233179681337,0.8738776682031866,-0.0 +674,0.04892438042837965,0.0805592191386853,0.8705164004329351,-0.0 +675,0.20864366545764365,0.08362705964185103,0.7077292749005053,-0.0 +676,0.2756324325057327,0.10317033988424014,0.621197227610027,-0.0 +677,0.14150436185439036,0.0869221475896957,0.771573490555914,-0.0 +678,-0.0,0.07851399213657481,0.9051023903737102,0.01638361748971498 +679,-0.0,0.1466882255402577,0.8533117744597423,-0.0 +680,-0.0,0.17861649151764922,0.8213835084823508,-0.0 +681,-0.0,0.1902061111962753,0.8097938888037247,-0.0 +682,0.07707471624409348,0.16043669594333376,0.7624885878125728,-0.0 +683,0.3605350496183669,0.2958761729719838,0.3435887774096492,-0.0 +684,0.33765349323340166,0.36405040637566677,0.2982961003909316,-0.0 +685,0.18847176706563484,0.3654138910437404,0.4461143418906248,-0.0 +686,-0.0,0.2647432730509686,0.7352567269490313,-0.0 +687,-0.0,0.22997441401509033,0.7700255859849097,-0.0 +688,-0.0,0.16032307222099432,0.8396769277790057,-0.0 +689,-0.0,0.1001024993810744,0.8998975006189256,-0.0 +690,0.11500992814548322,0.043177014488999174,0.8418130573655176,-0.0 +691,0.6399308960032057,0.08237719869611684,0.27769190530067744,-0.0 +692,0.7391344858564433,0.18838813163884377,0.07247738250471292,-0.0 +693,0.3123633519658085,0.23645096618844022,0.4511856818457513,-0.0 +694,-0.0,0.3834800628957164,0.6165199371042835,-0.0 +695,-0.0,0.48449155205550654,0.5155084479444935,-0.0 +696,-0.0,0.5447121248954264,0.4552878751045736,-0.0 +697,-0.0,0.6045918265683279,0.3954081734316721,-0.0 +698,0.039591114008196454,0.566300632139926,0.39410825385187753,-0.0 +699,0.2214392726466045,0.6878780150431606,0.09068271231023489,-0.0 +700,0.25260033956560324,0.6159541988022751,0.13144546163212167,-0.0 +701,0.11741851302811115,0.5334633763838187,0.3491181105880702,-0.0 +702,-0.0,0.7513936758309251,0.2486063241690749,-0.0 +703,-0.0,0.8458149890950258,0.15418501090497416,-0.0 +704,-0.0,0.8800157295192068,0.11998427048079319,-0.0 +705,-0.0,0.8327482610259866,0.16725173897401335,-0.0 +706,0.1008594919600442,0.873311929901178,0.025828578138777786,-0.0 +707,0.4016315291782058,0.5983684708217942,-0.0,-0.0 +708,0.5849850533682563,0.41501494663174365,-0.0,-0.0 +709,0.3715242181453568,0.6284757818546431,-0.0,-0.0 +710,-0.0,0.6811742154251318,0.3188257845748682,-0.0 +711,-0.0,0.4098407664784738,0.5901592335215262,-0.0 +712,-0.0,0.4342698667814601,0.5657301332185398,-0.0 +713,-0.0,0.6631080435731558,0.3368919564268442,-0.0 +714,0.05434369641429247,0.9027404739871011,0.04291582959860642,-0.0 +715,0.3063418897592387,0.6936581102407613,-0.0,-0.0 +716,0.4172873309152873,0.5827126690847126,-0.0,-0.0 +717,0.25621321688954507,0.743786783110455,-0.0,-0.0 +718,-0.0,1.0,-0.0,-0.0 +719,-0.0,0.9230791202858666,0.07692087971413342,-0.0 +720,-0.0,0.8411564164791075,0.15884358352089245,-0.0 +721,-0.0,0.7833219418083166,0.2166780581916834,-0.0 +722,0.10070895540487995,0.7917300972614375,0.10756094733368254,-0.0 +723,0.4234593296770214,0.5765406703229786,-0.0,-0.0 +724,0.5765550062790586,0.42344499372094135,-0.0,-0.0 +725,0.29911613511135493,0.7008838648886451,-0.0,-0.0 +726,-0.0,0.5859575361046546,0.4140424638953454,-0.0 +727,-0.0,0.4476774660175178,0.5523225339824822,-0.0 +728,-0.0,0.4509725539653624,0.5490274460346376,-0.0 +729,-0.0,0.39597867235305817,0.6040213276469418,-0.0 +730,0.05238672119715728,0.3401894246843777,0.607423854118465,-0.0 +731,0.3468362230984206,0.5584605952985024,0.09470318160307695,-0.0 +732,0.5527702305631079,0.4472297694368921,-0.0,-0.0 +733,0.35662109918409657,0.6433789008159034,-0.0,-0.0 +734,-0.0,0.48278719622041444,0.5172128037795856,-0.0 +735,-0.0,0.3895021201797083,0.6104978798202917,-0.0 +736,-0.0,0.30030749814322316,0.6996925018567768,-0.0 +737,-0.0,0.23633734246610072,0.7636626575338993,-0.0 +738,0.1496333358332596,0.18838813163884377,0.6619785325278966,-0.0 +739,0.6086192925290428,0.21861204178114318,0.17276866568981397,-0.0 +740,0.7356721450876657,0.19043335864095426,0.07389449627138012,-0.0 +741,0.4302334746594124,0.13418961608291585,0.4355769092576717,-0.0 +742,-0.0,0.11839591867772932,0.8816040813222706,-0.0 +743,-0.0,0.11794142378837143,0.8820585762116285,-0.0 +744,-0.0,0.08567228664396151,0.9051023903737102,0.009225322982328282 +745,-0.0,0.04613123126982543,0.9051023903737102,0.048766378356464364 +746,0.11124651426637709,0.026133456138078447,0.8626200295955444,-0.0 +747,0.5642110087555905,0.038518441873080844,0.39727054937132866,-0.0 +748,0.5425337448119392,0.07669601257914327,0.38077024260891756,-0.0 +749,0.3358470545714307,0.1316898941914475,0.5324630512371218,-0.0 +750,0.00015053655516424508,0.17179906817728094,0.8280503952675548,-0.0 +751,-0.0,0.1488470762647077,0.8511529237352923,-0.0 +752,-0.0,0.08919462203648514,0.9051023903737102,0.005702987589804656 +753,-0.0,0.0710148264621697,0.9051023903737102,0.023882783164120097 +754,0.24763263324518314,0.016248192294544425,0.7361191744602724,-0.0 +755,0.8967462591134079,0.04772196338257804,0.05553177750401406,-0.0 +756,0.7173066853576278,0.0749916567440512,0.20770165789832107,-0.0 +757,0.37859943623807635,0.11510083072988464,0.506299733032039,-0.0 +758,0.00015053655516424508,0.1379391989201184,0.8619102645247173,-0.0 +759,-0.0,0.12169100662557399,0.878308993374426,-0.0 +760,-0.0,0.09794364865662444,0.9020563513433756,-0.0 +761,-0.0,0.08510416803226416,0.9051023903737102,0.009793441594025634 +762,0.15143977449523052,0.04590398382514649,0.802656241679623,-0.0 +763,0.5536734498940934,0.11635069167561883,0.32997585843028776,-0.0 +764,0.6215654362731678,0.13191714163612642,0.2465174220907057,-0.0 +765,0.44859893438945025,0.12373623362768447,0.42766483198286526,-0.0 +766,0.00030107311032849017,0.11271473256075573,0.8869841943289158,-0.0 +767,-0.0,0.054653010445285796,0.9051023903737102,0.040244599181003995 +768,-0.0,0.022383873300875885,0.9051023903737102,0.07251373632541391 +769,-0.0,0.0694240943494171,0.9051023903737102,0.025473515276872694 +770,0.220536053315619,0.06840148084836184,0.7110624658360192,-0.0 +771,0.7639730174585436,0.1887290028058622,0.04729797973559424,-0.0 +772,0.7611128229104231,0.23888717708957685,-0.0,-0.0 +773,0.40554547961247617,0.2856500379614314,0.30880448242609243,-0.0 +774,0.00030107311032849017,0.2338376205746324,0.7658613063150391,-0.0 +775,-0.0,0.20247747320893822,0.7975225267910617,-0.0 +776,-0.0,0.16452714994755477,0.8354728500524452,-0.0 +777,-0.0,0.1487334525423682,0.8512665474576318,-0.0 +778,0.11937548824524633,0.14532474087218408,0.7352997708825696,-0.0 +779,0.49556633960069474,0.12987191463401593,0.37456174576528933,-0.0 +780,0.6846402528869865,0.08374068336419051,0.23161906374882302,-0.0 +781,0.4479967881687933,0.04045004515285186,0.5115531666783548,-0.0 +782,0.00045160966549273514,0.03545060136991511,0.9051023903737102,0.058995398590881945 +783,-0.0,0.034087116701841454,0.9051023903737102,0.06081049292444834 +784,-0.0,0.03635959114863088,0.9051023903737102,0.05853801847765891 +785,-0.0,0.047040221048541206,0.9051023903737102,0.047857388577748586 +786,0.21406298144355648,0.027383317083812635,0.7585537014726309,-0.0 +787,0.8881656754690459,0.025678961248720558,0.0861553632822335,-0.0 +788,1.0,-0.0,-0.0,-0.0 +789,0.573694811730938,0.0019316032797710157,0.424373584989291,-0.0 +790,0.0007526827758212254,0.00977164012119455,0.9051023903737102,0.08437328672927401 +791,-0.0,0.030451157586978365,0.9051023903737102,0.06444645203931143 +792,-0.0,0.03249638458908885,0.9051023903737102,0.06240122503720094 +793,-0.0,0.02726969336147316,0.9051023903737102,0.06762791626481662 +794,0.2644927274235786,0.011021501066928736,0.7244857715094926,-0.0 +795,0.9840574611086699,0.004204077726560446,0.011738461164769731,-0.0 +796,1.0,-0.0,-0.0,-0.0 +797,0.5426842813671034,0.007840036841423535,0.449475681791473,-0.0 +798,0.0007526827758212254,0.06681074873560924,0.9051023903737102,0.027334178114859324 +799,-0.0,0.20520444254508557,0.7947955574549144,-0.0 +800,-0.0,0.26417515443927125,0.7358248455607288,-0.0 +801,-0.0,0.2786053671763841,0.7213946328236158,-0.0 +802,0.19133196161375546,0.14441575109346827,0.6642522872927763,-0.0 +803,0.7352205354221729,0.2647794645778271,-0.0,-0.0 +804,0.6080171463083858,0.39198285369161423,-0.0,-0.0 +805,0.23333166050457985,0.5633464153590998,0.20332192413632036,-0.0 +806,0.00030107311032849017,0.512556611473356,0.48714231541631553,-0.0 +807,-0.0,0.43199739233467066,0.5680026076653293,-0.0 +808,-0.0,0.3729130567181455,0.6270869432818544,-0.0 +809,-0.0,0.22747469212362195,0.7725253078763781,-0.0 +810,0.1789879640902874,0.14816533393067083,0.6728467019790418,-0.0 +811,0.8484240249056851,0.15157597509431486,-0.0,-0.0 +812,0.8850044078105967,0.11499559218940325,-0.0,-0.0 +813,0.5187489690959886,0.28496829562739456,0.19628273527661688,-0.0 +814,0.0013548289964782055,0.4040459566391607,0.594599214364361,-0.0 +815,-0.0,0.590161613831215,0.409838386168785,-0.0 +816,-0.0,0.7207152707992678,0.2792847292007322,-0.0 +817,-0.0,0.6368609637127378,0.3631390362872622,-0.0 +818,0.21135332345060007,0.4908544805065169,0.297792196042883,-0.0 +819,0.7167045391369709,0.28329546086302915,-0.0,-0.0 +820,0.7356721450876657,0.26432785491233435,-0.0,-0.0 +821,0.43173884021105485,0.42040777265604456,0.14785338713290058,-0.0 +822,0.002107511772299431,0.25179016870426885,0.7461023195234318,-0.0 +823,-0.0,0.14180240547966044,0.8581975945203395,-0.0 +824,-0.0,0.09476218443111924,0.9051023903737102,0.00013542519517055485 +825,-0.0,0.12964466718933698,0.870355332810663,-0.0 +826,0.2595250211031585,0.11078312928098473,0.6296918496158568,-0.0 +827,0.8822947498176402,0.11770525018235978,-0.0,-0.0 +828,0.8320155403927825,0.16798445960721753,-0.0,-0.0 +829,0.39350255519933663,0.4227938708251735,0.18370357397548986,-0.0 +830,0.0013548289964782055,0.39268358440521356,0.6059615865983082,-0.0 +831,-0.0,0.2926947087464786,0.7073052912535214,-0.0 +832,-0.0,0.22247524834068522,0.7775247516593148,-0.0 +833,-0.0,0.23929155924692702,0.760708440753073,-0.0 +834,0.2929441363496209,0.13850731753181578,0.5685485461185633,-0.0 +835,1.0,-0.0,-0.0,-0.0 +836,1.0,-0.0,-0.0,-0.0 +837,0.6047053420947724,0.39529465790522755,-0.0,-0.0 +838,0.0018064386619709406,0.42097589126774193,0.5772176700702871,-0.0 +839,-0.0,0.3232594900557964,0.6767405099442036,-0.0 +840,-0.0,0.21997552644921683,0.7800244735507832,-0.0 +841,-0.0,0.14566561203920247,0.8543343879607975,-0.0 +842,0.25500892444823114,0.09794364865662444,0.6470474268951445,-0.0 +843,0.8232844201932563,0.13305337885952115,0.04366220094722251,-0.0 +844,0.8928323086791374,0.10716769132086257,-0.0,-0.0 +845,0.4546203965960201,0.21474883522160115,0.33063076818237874,-0.0 +846,0.0018064386619709406,0.18497941996865963,0.8132141413693694,-0.0 +847,-0.0,0.1560053707720944,0.8439946292279056,-0.0 +848,-0.0,0.12464522340640026,0.8753547765935997,-0.0 +849,-0.0,0.10271584499488223,0.8972841550051178,-0.0 +850,0.19675127759966832,0.09283058115134822,0.7104181412489834,-0.0 +851,0.5870925651405557,0.1252133420180976,0.2876940928413466,-0.0 +852,0.592812954236797,0.17361704773471245,0.23356999802849054,-0.0 +853,0.33118042136133913,0.26769748983179487,0.401122088806866,-0.0 +854,0.002559121437792166,0.2337239968522929,0.763716881709915,-0.0 +855,-0.0,0.2206572687832537,0.7793427312167462,-0.0 +856,-0.0,0.16702687183902312,0.8329731281609769,-0.0 +857,-0.0,0.0897627406481825,0.9051023903737102,0.00513486897810729 +858,0.3349438352404453,0.04033642143051238,0.6247197433290423,-0.0 +859,0.8195210063141501,0.09817089610130339,0.08230809758454649,-0.0 +860,0.9006602095476783,0.0901036118152009,0.009236178637120784,-0.0 +861,0.5867914920302273,0.10748804133314005,0.30572046663663266,-0.0 +862,0.004516096654927352,0.09601204537685343,0.8994718579682193,-0.0 +863,-0.0,0.08158183263974055,0.9051023903737102,0.013315776986549246 +864,-0.0,0.06760611479198554,0.9051023903737102,0.02729149483430425 +865,-0.0,0.051471546219780596,0.9051023903737102,0.043426063406509195 +866,0.3296750558096967,0.0060220572839919904,0.6643028869063113,-0.0 +867,1.0,-0.0,-0.0,-0.0 +868,1.0,-0.0,-0.0,-0.0 +869,0.6345115800172929,0.03647321487097035,0.32901520511173676,-0.0 +870,0.005268779430748578,0.09953438076937704,0.8951968397998744,-0.0 +871,-0.0,0.1256678369074555,0.8743321630925445,-0.0 +872,-0.0,0.09976162821405599,0.900238371785944,-0.0 +873,-0.0,0.1240771047947029,0.8759228952052971,-0.0 +874,0.3688145601524004,0.056698237447396285,0.5744872024002032,-0.0 +875,1.0,-0.0,-0.0,-0.0 +876,1.0,-0.0,-0.0,-0.0 +877,0.6277374350349019,0.1965690396472857,0.17569352531781246,-0.0 +878,0.005419315985912822,0.21020388632802228,0.7843767976860649,-0.0 +879,-0.0,0.13418961608291585,0.8658103839170841,-0.0 +880,-0.0,0.08158183263974055,0.9051023903737102,0.013315776986549246 +881,-0.0,0.17645764079319926,0.8235423592068007,-0.0 +882,0.3841692887791534,0.1128283562830952,0.5030023549377514,-0.0 +883,1.0,-0.0,-0.0,-0.0 +884,1.0,-0.0,-0.0,-0.0 +885,0.6393287497825488,0.36067125021745117,-0.0,-0.0 +886,0.005870925651405557,0.34837033269281964,0.6457587416557748,-0.0 +887,-0.0,0.28553641423909193,0.7144635857609081,-0.0 +888,-0.0,0.22599758373320883,0.7740024162667911,-0.0 +889,-0.0,0.18134346085379652,0.8186565391462035,-0.0 +890,0.24206278070410606,0.12384985735002395,0.63408736194587,-0.0 +891,0.7061669802754736,0.08078646658336425,0.21304655314116216,-0.0 +892,0.801607156249605,0.056130118835698926,0.14226272491469605,-0.0 +893,0.5404262330396398,0.04999443782936746,0.4095793291309928,-0.0 +894,0.006924681537555273,0.05999332539524096,0.9051023903737102,0.02797960269349356 +895,-0.0,0.06771973851432501,0.9051023903737102,0.027177871111964777 +896,-0.0,0.08010472424932741,0.9051023903737102,0.014792885376962384 +897,-0.0,0.07260555857492229,0.9051023903737102,0.0222920510513675 +898,0.3003204275526689,0.03635959114863088,0.6633199812987003,-0.0 +899,0.8974989418892291,0.05556200022400157,0.04693905788676933,-0.0 +900,1.0,-0.0,-0.0,-0.0 +901,0.6108773408565065,0.05363039694423055,0.335492262199263,-0.0 +902,0.008430047089197723,0.0586298407271673,0.9051023903737102,0.02783772180992477 +903,-0.0,0.06033419656225937,0.9051023903737102,0.03456341306403042 +904,-0.0,0.049085448050651695,0.9051023903737102,0.0458121615756381 +905,-0.0,0.06851510457070133,0.9051023903737102,0.026382505055588465 +906,0.33885778567471564,0.019884151409407518,0.6412580629158768,-0.0 +907,1.0,-0.0,-0.0,-0.0 +908,1.0,-0.0,-0.0,-0.0 +909,0.6319524585795008,0.09703465887790867,0.2710128825425906,-0.0 +910,0.007225754647883762,0.060788691451617256,0.9051023903737102,0.026883163526788774 +911,-0.0,0.1336214974712185,0.8663785025287816,-0.0 +912,-0.0,0.2408822913596796,0.7591177086403205,-0.0 +913,-0.0,0.24156403369371646,0.7584359663062835,-0.0 +914,0.1704073804459254,0.09896626215767969,0.7306263573963949,-0.0 +915,0.5136307262204042,0.11123762417034261,0.37513164960925327,-0.0 +916,0.4833728786323909,0.19827339548237777,0.3183537258852313,-0.0 +917,0.191030888503427,0.17816199662829132,0.6308071148682817,-0.0 +918,0.0016559021068066958,0.15770972660718646,0.8406343712860068,-0.0 +919,-0.0,0.1766848882378782,0.8233151117621218,-0.0 +920,-0.0,0.17793474918361238,0.8220652508163876,-0.0 +921,-0.0,0.13703020914140265,0.8629697908585974,-0.0 +922,0.20924581167830067,0.03465523531353881,0.7560989530081605,-0.0 +923,0.579866810492672,0.10748804133314005,0.312645148174188,-0.0 +924,0.617350412728569,0.10680629899910322,0.27584328827232774,-0.0 +925,0.397416505633607,0.052039664831477955,0.5505438295349151,-0.0 +926,0.004666633210091597,0.022270249578536416,0.9051023903737102,0.06796072683766177 +927,-0.0,0.04090454004220974,0.9051023903737102,0.05399306958408005 +928,-0.0,0.06715161990262766,0.9051023903737102,0.02774598972366213 +929,-0.0,0.0977164012119455,0.9022835987880545,-0.0 +930,0.16649343001165506,0.08419517825354839,0.7493113917347966,-0.0 +931,0.5079103371241628,0.13555310075098953,0.35653656212484763,-0.0 +932,0.5342542342779057,0.18509304369099908,0.28065272203109515,-0.0 +933,0.29715915989421976,0.24417737930752428,0.458663460798256,-0.0 +934,0.003913950434270372,0.19850064292705671,0.797585406638673,-0.0 +935,-0.0,0.18122983713145707,0.8187701628685429,-0.0 +936,-0.0,0.15702798427314962,0.8429720157268503,-0.0 +937,-0.0,0.10294309243956119,0.8970569075604388,-0.0 +938,0.19494483893769737,0.06567451151221453,0.7393806495500881,-0.0 +939,0.5563831078870497,0.10362483477359802,0.33999205733935234,-0.0 +940,0.6119310967426561,0.20031862248448828,0.1877502807728556,-0.0 +941,0.36475007316296576,0.2178166757247669,0.4174332511122673,-0.0 +942,0.00978487608567593,0.1488470762647077,0.8413680476496164,-0.0 +943,-0.0,0.17498053240278613,0.8250194675972139,-0.0 +944,-0.0,0.15748247916250752,0.8425175208374924,-0.0 +945,-0.0,0.1420296529243394,0.8579703470756606,-0.0 +946,0.3984702615197567,0.08033197169400635,0.5211977667862371,-0.0 +947,1.0,-0.0,-0.0,-0.0 +948,1.0,-0.0,-0.0,-0.0 +949,0.5133296531100757,0.10794253622249794,0.37872781066742633,-0.0 +950,0.005870925651405557,0.14748359159663402,0.8466454827519604,-0.0 +951,-0.0,0.24270027091711116,0.7572997290828889,-0.0 +952,-0.0,0.22145263483962996,0.77854736516037,-0.0 +953,-0.0,0.16748136672838101,0.832518633271619,-0.0 +954,0.23378327017007258,0.09601204537685343,0.670204684453074,-0.0 +955,0.6795220100114022,0.18532029113567802,0.13515769885291973,-0.0 +956,0.6209632900525108,0.2208845162279326,0.15815219371955658,-0.0 +957,0.38326606944816793,0.14032529708924732,0.4764086334625847,-0.0 +958,0.006171998761734048,0.06362928451010405,0.9051023903737102,0.025096326354451692 +959,-0.0,0.10669267527676375,0.8933073247232363,-0.0 +960,-0.0,0.1288493011329607,0.8711506988670393,-0.0 +961,-0.0,0.09385319465240348,0.9051023903737102,0.0010444149738863123 +962,0.21827800498815533,0.059879701672901485,0.7218422933389432,-0.0 +963,0.496319022376516,0.0626066710090488,0.4410743066144352,-0.0 +964,0.5670712033037112,0.06749249106964608,0.36543630562664275,-0.0 +965,0.3527071487498262,0.05385764438890949,0.5934352068612643,-0.0 +966,0.01008594919600442,0.05965245422822254,0.9051023903737102,0.025159206202062835 +967,-0.0,0.06976496551643552,0.9051023903737102,0.025132644109854274 +968,-0.0,0.042381648432622876,0.9051023903737102,0.052515961193666916 +969,-0.0,0.027156069639133692,0.9051023903737102,0.0677415399871561 +970,0.31161066918998725,0.01647543973922337,0.6719138910707894,-0.0 +971,0.7553924338141818,0.02488359519234426,0.21972397099347396,-0.0 +972,0.7480161426111338,0.02874680175188629,0.22323705563697993,-0.0 +973,0.43459903475917555,0.017043558350920727,0.5483574068899038,-0.0 +974,0.014903118961260262,0.026247079860417917,0.9051023903737102,0.05374741080461162 +975,-0.0,0.08828563225776936,0.9051023903737102,0.0066119773685204275 +976,-0.0,0.12828118252126333,0.8717188174787367,-0.0 +977,-0.0,0.16134568572204952,0.8386543142779505,-0.0 +978,0.22565429619120336,0.06987858923877498,0.7044671145700216,-0.0 +979,0.6215654362731678,0.2238387330087589,0.15459583071807326,-0.0 +980,0.5182973594304957,0.3036025860910679,0.17810005447843646,-0.0 +981,0.16724611278747628,0.31166987037717037,0.5210840168353534,-0.0 +982,0.002258048327463676,0.3672318706011719,0.6305100810713644,-0.0 +983,-0.0,0.35473326114383,0.6452667388561699,-0.0 +984,-0.0,0.33041778456318316,0.6695822154368168,-0.0 +985,-0.0,0.28383205840399983,0.7161679415960002,-0.0 +986,0.24221331725927028,0.18372955902292545,0.5740571237178043,-0.0 +987,0.7090271748235942,0.289172373353955,0.0018004518224506993,-0.0 +988,0.7871556469538376,0.21284435304616245,-0.0,-0.0 +989,0.4794589281981205,0.0710148264621697,0.44952624533970975,-0.0 +990,0.01339775340961781,0.024542724025325847,0.9051023903737102,0.05695713219134614 +991,-0.0,0.24894957564578207,0.751050424354218,-0.0 +992,-0.0,0.4010917398583344,0.5989082601416655,-0.0 +993,-0.0,0.43086115511127593,0.569138844888724,-0.0 +994,0.1586655291431143,0.3462114819683697,0.495122988888516,-0.0 +995,0.48608253662534734,0.48415068088848806,0.029766782486164545,-0.0 +996,0.5328994052814275,0.46710059471857246,-0.0,-0.0 +997,0.3158256927345861,0.6020921046768596,0.08208220258855431,-0.0 +998,0.014903118961260262,0.510625008193585,0.4744718728451547,-0.0 +999,-0.0,0.3157603243813913,0.6842396756186087,-0.0 +1000,-0.0,0.197364405703662,0.802635594296338,-0.0 +1001,-0.0,0.2065679272131592,0.7934320727868408,-0.0 +1002,0.2699120434094914,0.14521111714984458,0.584876839440664,-0.0 +1003,0.748768825386955,0.251231174613045,-0.0,-0.0 +1004,0.784445988960881,0.21555401103911898,-0.0,-0.0 +1005,0.4755449777638502,0.48676402650229594,0.03769099573385393,-0.0 +1006,0.01956975217135186,0.36200517937355625,0.6184250684550919,-0.0 +1007,-0.0,0.3814348358936059,0.6185651641063941,-0.0 +1008,-0.0,0.42438460293792607,0.5756153970620739,-0.0 +1009,-0.0,0.41279498325930003,0.5872050167406999,-0.0 +1010,0.3337395427991313,0.49380869728734317,0.17245175991352546,-0.0 +1011,0.6573931364022582,0.34260686359774184,-0.0,-0.0 +1012,0.7508763371592544,0.2491236628407456,-0.0,-0.0 +1013,0.519652188426974,0.2559942464308293,0.22435356514219662,-0.0 +1014,0.023483702605622228,0.07090120273983022,0.9051023903737102,0.0005127042808373422 +1015,-0.0,0.01942965652004963,0.9051023903737102,0.07546795310624016 +1016,-0.0,0.024542724025325847,0.9051023903737102,0.07035488560096395 +1017,-0.0,0.04090454004220974,0.9051023903737102,0.05399306958408005 +1018,0.3018257931043114,0.00818090800844195,0.6899932988872466,-0.0 +1019,0.8890688948000314,0.00681742334036829,0.10411368185960035,-0.0 +1020,0.8988537708857073,0.0015907321127526012,0.0995554970015401,-0.0 +1021,0.472985856326058,0.001136237223394715,0.5258779064505472,-0.0 +1022,0.014451509295767525,0.10930602089057159,0.8762424698136608,-0.0 +1023,-0.0,0.179525481296365,0.820474518703635,-0.0 +1024,-0.0,0.15509638099337864,0.8449036190066214,-0.0 +1025,-0.0,0.17089007839856515,0.8291099216014348,-0.0 +1026,0.19931039903746045,0.11964577962346351,0.6810438213390759,-0.0 +1027,0.48532985384952615,0.3072385452059309,0.20743160094454288,-0.0 +1028,0.5855871995889134,0.41441280041108663,-0.0,-0.0 +1029,0.3483415886500631,0.4435870120132968,0.20807139933664015,-0.0 +1030,0.0183654597300379,0.3213278867760254,0.6603066534939367,-0.0 +1031,-0.0,0.36825448410222716,0.6317455158977728,-0.0 +1032,-0.0,0.42302111826985245,0.5769788817301476,-0.0 +1033,-0.0,0.45222241491109666,0.5477775850889033,-0.0 +1034,0.25621321688954507,0.504602950909593,0.23918383220086198,-0.0 +1035,0.7761664784268475,0.2238335215731525,-0.0,-0.0 +1036,0.9917348254220466,0.008265174577953394,-0.0,-0.0 +1037,0.649264162423389,0.17793474918361238,0.17280108839299857,-0.0 +1038,0.03176321313965571,0.054312139278267384,0.9051023903737102,0.008822257208366699 +1039,-0.0,0.08839925598010882,0.9051023903737102,0.006498353646180968 +1040,-0.0,0.1687312276741152,0.8312687723258848,-0.0 +1041,-0.0,0.18168433202081494,0.8183156679791851,-0.0 +1042,0.35029856386719826,0.07840036841423535,0.5713010677185664,-0.0 +1043,0.9136063532918033,0.08639364670819671,-0.0,-0.0 +1044,0.9778854623469359,0.022114537653064148,-0.0,-0.0 +1045,0.5672217398588755,0.1699810886198494,0.26279717152127513,-0.0 +1046,0.02619336059857864,0.23701908480013759,0.7367875546012838,-0.0 +1047,-0.0,0.32212325283240173,0.6778767471675983,-0.0 +1048,-0.0,0.2046363239333882,0.7953636760666118,-0.0 +1049,-0.0,0.18725189441544907,0.8127481055845509,-0.0 +1050,0.28601945481206564,0.10078424171511123,0.6131963034728232,-0.0 +1051,0.7966394499291849,0.17691213568255715,0.026448414388257957,-0.0 +1052,0.8240371029690775,0.1759628970309225,-0.0,-0.0 +1053,0.395610066971636,0.5514159245134552,0.0529740085149088,-0.0 +1054,0.022731019829801007,0.5731180554802943,0.4041509246899047,-0.0 +1055,-0.0,0.31053363315377563,0.6894663668462244,-0.0 +1056,-0.0,0.24179128113839538,0.7582087188616047,-0.0 +1057,-0.0,0.28065059417849464,0.7193494058215053,-0.0 +1058,0.4171367943601231,0.299284884642168,0.28357832099770885,-0.0 +1059,0.9617780509443618,0.03822194905563825,-0.0,-0.0 +1060,0.944165273990145,0.05583472600985495,-0.0,-0.0 +1061,0.5082114102344913,0.24417737930752428,0.2476112104579844,-0.0 +1062,0.025892287488250152,0.16770861417305996,0.80639909833869,-0.0 +1063,-0.0,0.1488470762647077,0.8511529237352923,-0.0 +1064,-0.0,0.1307809044127317,0.8692190955872683,-0.0 +1065,-0.0,0.1260087080744739,0.8739912919255262,-0.0 +1066,0.2571164362205306,0.12487247085107919,0.6180110929283902,-0.0 +1067,0.6552856246299588,0.10021612310341388,0.24449825226662736,-0.0 +1068,0.7410914610735785,0.0602205728399199,0.19868796608650163,-0.0 +1069,0.4284270359974415,0.030678405031657308,0.5408945589709012,-0.0 +1070,0.02258048327463676,0.029769415252941536,0.9051023903737102,0.0425477110987115 +1071,-0.0,0.061015938896296196,0.9051023903737102,0.033881670729993596 +1072,-0.0,0.06806060968134343,0.9051023903737102,0.026836999944946358 +1073,-0.0,0.0646518980111593,0.9051023903737102,0.030245711615130497 +1074,0.24627780424870493,0.018520666741333854,0.7352015290099612,-0.0 +1075,0.6859950818834648,0.03158739481037308,0.2824175233061621,-0.0 +1076,0.9732188291368443,0.02678117086315568,-0.0,-0.0 +1077,0.595221539119425,0.07374179579831701,0.33103666508225804,-0.0 +1078,0.04260184511148135,0.07646876513446432,0.8809293897540543,-0.0 +1079,-0.0,0.07635514141212485,0.9051023903737102,0.018542468214164942 +1080,-0.0,0.05158516994212007,0.9051023903737102,0.04331243968416972 +1081,-0.0,0.07112845018450917,0.9051023903737102,0.023769159441780624 +1082,0.3227503742721414,0.07953660563763007,0.5977130200902285,-0.0 +1083,0.7213711723470624,0.20293196809829614,0.07569685955464145,-0.0 +1084,0.5720389096241313,0.31235161271120715,0.11560947766466156,-0.0 +1085,0.25139604712428926,0.3672318706011719,0.38137208227453884,-0.0 +1086,0.01776331350938092,0.3614370607618589,0.6207996257287602,-0.0 +1087,-0.0,0.32007802583029127,0.6799219741697087,-0.0 +1088,-0.0,0.26053919532440817,0.7394608046755918,-0.0 +1089,-0.0,0.2216798822843089,0.7783201177156911,-0.0 +1090,0.3159762292897504,0.26281166977119763,0.421212100939052,-0.0 +1091,0.7304033656569171,0.2695966343430829,-0.0,-0.0 +1092,0.7016508836205463,0.2983491163794537,-0.0,-0.0 +1093,0.48487824418403336,0.38711602201057943,0.12800573380538727,-0.0 +1094,0.03627930979458306,0.18884262652820163,0.7748780636772153,-0.0 +1095,-0.0,0.1008978654374507,0.8991021345625493,-0.0 +1096,-0.0,0.09737553004492708,0.902624469955073,-0.0 +1097,-0.0,0.08749026620139307,0.9051023903737102,0.007407343424896726 +1098,0.34156744366767205,0.035905096259273,0.622527460073055,-0.0 +1099,0.863026070756617,0.1299855383563554,0.006988390887027629,-0.0 +1100,0.9419072256626815,0.05809277433731852,-0.0,-0.0 +1101,0.5410283792602968,0.1774802542942545,0.2814913664454487,-0.0 +1102,0.02860194548120656,0.23724633224481653,0.734151722273977,-0.0 +1103,-0.0,0.2991712609198285,0.7008287390801715,-0.0 +1104,-0.0,0.21361259799820645,0.7863874020017936,-0.0 +1105,0.00015053655516424508,0.18009359990806234,0.8197558635367733,-0.0 +1106,0.23468648950105808,0.23622371874376127,0.5290897917551807,-0.0 +1107,0.6411351884445198,0.3588648115554802,-0.0,-0.0 +1108,0.7219733185677194,0.2780266814322806,-0.0,-0.0 +1109,0.44077103352090957,0.35689211186828,0.20233685461081047,-0.0 +1110,0.03522555390843335,0.1388481886988342,0.8259262573927324,-0.0 +1111,-0.0,0.12123651173621611,0.8787634882637839,-0.0 +1112,-0.0,0.12066839312451874,0.8793316068754813,-0.0 +1113,0.00030107311032849017,0.09828451982364285,0.9014144070660287,-0.0 +1114,0.21210600622642128,0.05749360350377258,0.7304003902698062,-0.0 +1115,0.4966200954868445,0.06431102684414088,0.4390688776690146,-0.0 +1116,0.5363617460502051,0.0654472640675356,0.3981909898822592,-0.0 +1117,0.3505996369775268,0.07442353813235385,0.5749768248901195,-0.0 +1118,0.03658038290491155,0.07749137863551957,0.8859282384595689,-0.0 +1119,-0.0,0.1431658901477341,0.8568341098522659,-0.0 +1120,-0.0,0.14577923576154195,0.854220764238458,-0.0 +1121,0.00015053655516424508,0.09794364865662444,0.9019058147882113,-0.0 +1122,0.36399739038714457,0.05533475277932263,0.5806678568335328,-0.0 +1123,0.6960810310794692,0.07476440929937225,0.2291545596211586,-0.0 +1124,0.6409846518893555,0.09680741143322973,0.2622079366774148,-0.0 +1125,0.4618461512439039,0.09101260159391669,0.44714124716217946,-0.0 +1126,0.05344047708330699,0.05022168527404641,0.8963378376426465,-0.0 +1127,-0.0,0.04999443782936746,0.9051023903737102,0.04490317179692233 +1128,-0.0,0.04510861776877019,0.9051023903737102,0.0497889918575196 +1129,0.00030107311032849017,0.04306339076665971,0.9051023903737102,0.051533145749301595 +1130,0.4385129851934459,0.015339202515828654,0.5461478122907255,-0.0 +1131,0.9971541414079593,0.002845858592040673,-0.0,-0.0 +1132,0.8859076271415822,0.03931380792945714,0.07477856492896062,-0.0 +1133,0.49737277826266574,0.04806283454959644,0.4545643871877378,-0.0 +1134,0.034924480798104854,0.03215551342207044,0.9051023903737102,0.027817615406114507 +1135,-0.0,0.04635847871450438,0.9051023903737102,0.04853913091178541 +1136,-0.0,0.06522001662285665,0.9051023903737102,0.029677593003433145 +1137,-0.0,0.05965245422822254,0.9051023903737102,0.03524515539806725 +1138,0.18079440275225833,0.03931380792945714,0.7798917893182845,-0.0 +1139,0.6369201648999209,0.057266356059093644,0.30581347904098544,-0.0 +1140,0.5715872999586384,0.08612678153331942,0.34228591850804213,-0.0 +1141,0.33780402978856594,0.11396459350648992,0.5482313767049442,-0.0 +1142,0.03281696902580542,0.12135013545855558,0.845832895515639,-0.0 +1143,-0.0,0.13305337885952115,0.8669466211404788,-0.0 +1144,-0.0,0.0749916567440512,0.9051023903737102,0.019905952882238592 +1145,0.00015053655516424508,0.03306450320078621,0.9051023903737102,0.061682569870339335 +1146,0.2702131165198199,0.004090454004220975,0.7256964294759591,-0.0 +1147,0.763521407793051,0.0021588507244499587,0.234319741482499,-0.0 +1148,0.7739084300993839,0.0036359591148630885,0.22245561078575293,-0.0 +1149,0.4886416580631395,0.006249304728670932,0.5051090372081897,-0.0 +1150,0.048322234207722664,0.008749026620139306,0.9051023903737102,0.03782634879842782 +1151,-0.0,0.021361259799820644,0.9051023903737102,0.07353634982646914 +1152,-0.0,0.054084891833588444,0.9051023903737102,0.04081271779270135 +1153,0.00015053655516424508,0.08328618847483263,0.9051023903737102,0.011460884596292917 +1154,0.2118049331160928,0.02886042547422576,0.7593346414096814,-0.0 +1155,0.5864904189198988,0.03624596742629141,0.3772636136538098,-0.0 +1156,0.4939104374938881,0.06328841334308563,0.44280114916302626,-0.0 +1157,0.32350305704796267,0.08624040525565888,0.5902565376963784,-0.0 +1158,0.04651579554575173,0.10078424171511123,0.8526999627391371,-0.0 +1159,-0.0,0.1360075956403474,0.8639924043596526,-0.0 +1160,-0.0,0.1348713584169527,0.8651286415830473,-0.0 +1161,0.00015053655516424508,0.10680629899910322,0.8930431644457325,-0.0 +1162,0.2706647261853126,0.0789684870259327,0.6503667867887546,-0.0 +1163,0.5727915923999525,0.13373512119355796,0.2934732864064895,-0.0 +1164,0.42752381666645595,0.187479141860128,0.384997041473416,-0.0 +1165,0.31552461962425765,0.2240659804534378,0.4604093999223045,-0.0 +1166,0.04019326022885344,0.18725189441544907,0.7725548453556975,-0.0 +1167,-0.0,0.2038409578770119,0.796159042122988,-0.0 +1168,-0.0,0.19634179220260678,0.8036582077973933,-0.0 +1169,0.00015053655516424508,0.1520285404902129,0.8478209229546229,-0.0 +1170,0.2274607348531743,0.17213993934429933,0.6003993258025264,-0.0 +1171,0.4507064461617497,0.19202409075370686,0.35726946308454344,-0.0 +1172,0.4750933680983574,0.19281945681008314,0.33208717509155944,-0.0 +1173,0.3621909517251736,0.13737108030842105,0.5004379679664053,-0.0 +1174,0.04470935688378078,0.044654122879412304,0.9051023903737102,0.005534129863096704 +1175,-0.0,0.12623595551915284,0.8737640444808472,-0.0 +1176,-0.0,0.21031751005036176,0.7896824899496382,-0.0 +1177,-0.0,0.25338090081702147,0.7466190991829785,-0.0 +1178,0.2846646258155874,0.29496718319326803,0.42036819099114453,-0.0 +1179,0.7809836481921034,0.21901635180789658,-0.0,-0.0 +1180,0.8116931054456095,0.18830689455439054,-0.0,-0.0 +1181,0.42571737800448506,0.33871231629396453,0.23557030570155046,-0.0 +1182,0.03883843123237523,0.1738442951793914,0.7873172735882334,-0.0 +1183,-0.0,0.11657793912029776,0.8834220608797022,-0.0 +1184,-0.0,0.08260444614079579,0.9051023903737102,0.012293163485494002 +1185,0.0007526827758212254,0.09123984903859562,0.9051023903737102,0.002905077811872942 +1186,0.38477143499981037,0.04738109221555962,0.56784747278463,-0.0 +1187,0.7355216085325014,0.10055699427043228,0.16392139719706633,-0.0 +1188,0.6703392801463832,0.1666860006720047,0.1629747191816121,-0.0 +1189,0.39545953041647175,0.2145215877769222,0.39001888180660604,-0.0 +1190,0.035827700129090326,0.2070224221025171,0.7571498777683926,-0.0 +1191,-0.0,0.33053140828552263,0.6694685917144774,-0.0 +1192,-0.0,0.3280316863940543,0.6719683136059458,-0.0 +1193,-0.0,0.28110508906785253,0.7188949109321474,-0.0 +1194,0.27879370016418187,0.3810939647265874,0.3401123351092307,-0.0 +1195,0.6334578241311433,0.36654217586885673,-0.0,-0.0 +1196,0.6846402528869865,0.3153597471130135,-0.0,-0.0 +1197,0.47539444120868596,0.4092726478667764,0.11533291092453762,-0.0 +1198,0.05328994052814275,0.18543391485801752,0.7612761446138397,-0.0 +1199,-0.0,0.1746396612357677,0.8253603387642323,-0.0 +1200,-0.0,0.17850286779530974,0.8214971322046902,-0.0 +1201,0.00045160966549273514,0.17475328495810719,0.8247951053764001,-0.0 +1202,0.35014802731203404,0.12453159968406077,0.5253203730039051,-0.0 +1203,0.8103382764491311,0.18966172355086885,-0.0,-0.0 +1204,0.7499731178282689,0.2500268821717311,-0.0,-0.0 +1205,0.4212012813495577,0.4304066602219181,0.1483920584285242,-0.0 +1206,0.03447287113261212,0.43995105289843367,0.5255760759689543,-0.0 +1207,-0.0,0.4283614332198076,0.5716385667801924,-0.0 +1208,-0.0,0.3700724636596587,0.6299275363403414,-0.0 +1209,-0.0,0.24383650814050586,0.7561634918594942,-0.0 +1210,0.2354391722768793,0.1598685773316364,0.6046922503914843,-0.0 +1211,0.5905549059093333,0.10407932966295591,0.3053657644277108,-0.0 +1212,0.601995684101816,0.06692437245794872,0.3310799434402353,-0.0 +1213,0.402384211954027,0.03601871998161247,0.5615970680643605,-0.0 +1214,0.048021161097394176,0.01886153790835227,0.9051023903737102,0.02801491062054335 +1215,-0.0,0.13453048724993427,0.8654695127500658,-0.0 +1216,-0.0,0.24951769425747944,0.7504823057425205,-0.0 +1217,0.00045160966549273514,0.3275771915046964,0.6719711988298109,-0.0 +1218,0.28601945481206564,0.5381219489997371,0.17585859618819727,-0.0 +1219,0.6847907894421509,0.3152092105578491,-0.0,-0.0 +1220,0.7189625874644345,0.28103741253556547,-0.0,-0.0 +1221,0.49255560849740987,0.5074443915025901,-0.0,-0.0 +1222,0.04154808922533164,0.688218886210179,0.27023302456448933,-0.0 +1223,-0.0,0.6448146242765008,0.3551853757234992,-0.0 +1224,-0.0,0.5806172211546994,0.4193827788453006,-0.0 +1225,0.00030107311032849017,0.4387011919526995,0.560997734936972,-0.0 +1226,0.36354578072165183,0.4549493842472439,0.18150483503110432,-0.0 +1227,0.7338657064256947,0.2661342935743053,-0.0,-0.0 +1228,0.8153059827695512,0.18469401723044876,-0.0,-0.0 +1229,0.5839312974821067,0.4160687025178933,-0.0,-0.0 +1230,0.06593501116193934,0.21270360821949066,0.72136138061857,-0.0 +1231,-0.0,0.13680296169672368,0.8631970383032763,-0.0 +1232,-0.0,0.08010472424932741,0.9051023903737102,0.014792885376962384 +1233,0.0007526827758212254,0.043404261933678114,0.9051023903737102,0.050740664916790454 +1234,0.4799105378636132,0.008521779175460363,0.5115676829609264,-0.0 +1235,1.0,-0.0,-0.0,-0.0 +1236,1.0,-0.0,-0.0,-0.0 +1237,0.7096293210442512,0.004999443782936747,0.28537123517281204,-0.0 +1238,0.07722525279925772,0.06203855239735145,0.8607361948033908,-0.0 +1239,-0.0,0.25576699898615035,0.7442330010138496,-0.0 +1240,-0.0,0.2545171380404162,0.7454828619595838,-0.0 +1241,0.0018064386619709406,0.21747580455774845,0.7807177567802805,-0.0 +1242,0.4808137571945988,0.09748915376726656,0.4216970890381347,-0.0 +1243,1.0,-0.0,-0.0,-0.0 +1244,1.0,-0.0,-0.0,-0.0 +1245,0.6933713730865128,0.04999443782936746,0.25663418908411983,-0.0 +1246,0.07406398514080857,0.16577701089328895,0.7601590039659024,-0.0 +1247,-0.0,0.38098034100424794,0.6190196589957521,-0.0 +1248,-0.0,0.31985077838561227,0.6801492216143877,-0.0 +1249,0.00045160966549273514,0.20599980860146183,0.7935485817330454,-0.0 +1250,0.2903850149118287,0.12021389823516086,0.5894010868530104,-0.0 +1251,0.8449616841369076,0.15503831586309236,-0.0,-0.0 +1252,0.9637350261614969,0.036264973838503134,-0.0,-0.0 +1253,0.5893506134680194,0.22633845490022725,0.18431093163175338,-0.0 +1254,0.06367696283447566,0.16941297000815203,0.7669100671573723,-0.0 +1255,-0.0,0.17486690868044666,0.8251330913195534,-0.0 +1256,-0.0,0.11078312928098473,0.8892168707190152,-0.0 +1257,0.00030107311032849017,0.09703465887790867,0.9026642680117629,-0.0 +1258,0.34502978443644966,0.06408377939946193,0.5908864361640884,-0.0 +1259,0.8810904573763264,0.06806060968134343,0.05084893294233017,-0.0 +1260,0.9953477027459884,0.004652297254011617,-0.0,-0.0 +1261,0.6397803594480416,0.08635402897799835,0.27386561157396005,-0.0 +1262,0.0671393036032533,0.1391890598658526,0.7936716365308941,-0.0 +1263,-0.0,0.217362180835409,0.782637819164591,-0.0 +1264,-0.0,0.14191602920199992,0.8580839707980001,-0.0 +1265,0.0006021462206569803,0.10748804133314005,0.8919098124462029,-0.0 +1266,0.3037827683214466,0.12657682668617126,0.5696404049923821,-0.0 +1267,0.6322535316898292,0.11430546467350833,0.25344100363666244,-0.0 +1268,0.6336083606863074,0.13691658541906318,0.2294750538946294,-0.0 +1269,0.36234148828033785,0.18361593530058595,0.4540425764190762,-0.0 +1270,0.04636525899058748,0.21293085566416964,0.7407038853452429,-0.0 +1271,-0.0,0.21134012355141701,0.788659876448583,-0.0 +1272,-0.0,0.16429990250287582,0.8357000974971242,-0.0 +1273,0.0006021462206569803,0.12339536246066607,0.8760024913186769,-0.0 +1274,0.24733156013485466,0.11680518656497671,0.6358632533001686,-0.0 +1275,0.4940609740490523,0.09158072020561404,0.41435830574533367,-0.0 +1276,0.45853434703029045,0.08385430708652998,0.4576113458831796,-0.0 +1277,0.3632447076113233,0.08124096147272213,0.5555143309159546,-0.0 +1278,0.052687794307485776,0.0965801639885508,0.8507320417039633,-0.0 +1279,-0.0,0.14702909670727612,0.8529709032927238,-0.0 +1280,-0.0,0.11021501066928736,0.8897849893307126,-0.0 +1281,0.0010537558861497154,0.08737664247905359,0.9051023903737102,0.006467211261086484 +1282,0.3320836406923246,0.05647099000271734,0.611445369304958,-0.0 +1283,0.7767686246475045,0.0646518980111593,0.15857947734133615,-0.0 +1284,0.7060164437203094,0.040222797708172914,0.2537607585715177,-0.0 +1285,0.4893943408389607,0.042836143321980755,0.46776951583905857,-0.0 +1286,0.07060164437203094,0.0965801639885508,0.8328181916394182,-0.0 +1287,-0.0,0.16498164483691263,0.8350183551630874,-0.0 +1288,-0.0,0.11703243400965566,0.8829675659903443,-0.0 +1289,0.0012042924413139607,0.07465078557703278,0.9051023903737102,0.01904253160794305 +1290,0.37016938914887865,0.02840593058486788,0.6014246802662535,-0.0 +1291,0.852036902229627,0.022838368190233774,0.1251247295801392,-0.0 +1292,0.87838079938337,0.014884707626470768,0.10673449299015925,-0.0 +1293,0.5782109083858653,0.015111955071149711,0.406677136542985,-0.0 +1294,0.07421452169597281,0.047835587104917504,0.8779498911991097,-0.0 +1295,-0.0,0.13373512119355796,0.866264878806442,-0.0 +1296,-0.0,0.1172596814543346,0.8827403185456654,-0.0 +1297,0.00045160966549273514,0.0921488388173114,0.9051023903737102,0.0022971611434856607 +1298,0.4278248897767845,0.018293419296654914,0.5538816909265605,-0.0 +1299,1.0,-0.0,-0.0,-0.0 +1300,1.0,-0.0,-0.0,-0.0 +1301,0.5932645639022898,0.005908433561652518,0.40082700253605774,-0.0 +1302,0.0660855477171036,0.026587951027436333,0.9051023903737102,0.002224110881749869 +1303,-0.0,0.10237497382786383,0.8976250261721361,-0.0 +1304,-0.0,0.11351009861713204,0.886489901382868,-0.0 +1305,0.00045160966549273514,0.09555755048749554,0.9039908398470118,-0.0 +1306,0.29475057501159185,0.025906208693399504,0.6793432162950086,-0.0 +1307,0.6473071872062538,0.0810137140280432,0.27167909876570306,-0.0 +1308,0.6476082603165824,0.14941519487640503,0.20297654480701255,-0.0 +1309,0.4171367943601231,0.1958872973132489,0.3869759083266281,-0.0 +1310,0.05705335440724889,0.18509304369099908,0.757853601901752,-0.0 +1311,-0.0,0.18793363674948588,0.8120663632505141,-0.0 +1312,-0.0,0.18259332179953072,0.8174066782004693,-0.0 +1313,0.00045160966549273514,0.14975606604342345,0.8497923242910839,-0.0 +1314,0.32530949570993356,0.13737108030842105,0.5373194239816454,-0.0 +1315,0.7596074573587807,0.1024885975502033,0.137903945091016,-0.0 +1316,0.7785750633094755,0.0654472640675356,0.1559776726229889,-0.0 +1317,0.49481365682487355,0.06249304728670933,0.4426932958884171,-0.0 +1318,0.060816768286355,0.12555421318511603,0.813629018528529,-0.0 +1319,-0.0,0.17248081051131775,0.8275191894886822,-0.0 +1320,-0.0,0.149642442321084,0.850357557678916,-0.0 +1321,0.0007526827758212254,0.12225912523727135,0.8769881919869075,-0.0 +1322,0.23498756261138654,0.09567117420983501,0.6693412631787784,-0.0 +1323,0.6543824052989733,0.07510528046639067,0.27051231423463606,-0.0 +1324,0.723027074453869,0.09396681837474294,0.183006107171388,-0.0 +1325,0.4978243879281584,0.13009916207869487,0.3720764499931467,-0.0 +1326,0.06593501116193934,0.13805282264245788,0.7960121661956028,-0.0 +1327,-0.0,0.14691547298493665,0.8530845270150633,-0.0 +1328,-0.0,0.0921488388173114,0.9051023903737102,0.0027487708089783958 +1329,0.00045160966549273514,0.05772085094845152,0.9051023903737102,0.03672514901234553 +1330,0.321245008720499,0.044994994046430724,0.6337599972330703,-0.0 +1331,0.6322535316898292,0.06760611479198554,0.30014035351818524,-0.0 +1332,0.586640955475063,0.10635180410974533,0.3070072404151917,-0.0 +1333,0.4338463519833543,0.14225690036901834,0.4238967476476273,-0.0 +1334,0.06397803594480415,0.10521556688635063,0.8308063971688453,-0.0 +1335,-0.0,0.10782891250015846,0.8921710874998415,-0.0 +1336,-0.0,0.07646876513446432,0.9051023903737102,0.01842884449182547 +1337,0.0009032193309854703,0.052494159720835834,0.9051023903737102,0.041500230574468486 +1338,0.44137317974156653,0.03931380792945714,0.5193130123289763,-0.0 +1339,0.9902294598704041,0.009770540129595884,-0.0,-0.0 +1340,1.0,-0.0,-0.0,-0.0 +1341,0.5332004783917561,0.13703020914140265,0.32976931246684127,-0.0 +1342,0.04651579554575173,0.13816644636479736,0.815317758089451,-0.0 +1343,-0.0,0.1838431827452649,0.8161568172547351,-0.0 +1344,-0.0,0.189410745139899,0.810589254860101,-0.0 +1345,-0.0,0.18009359990806234,0.8199064000919376,-0.0 +1346,0.15354728626752995,0.16134568572204952,0.6851070280104206,-0.0 +1347,0.4160830384739733,0.2528127822053241,0.3311041793207026,-0.0 +1348,0.4412226431864023,0.34473437357795655,0.21404298323564114,-0.0 +1349,0.3102558401935091,0.3716631957724113,0.31808096403407965,-0.0 +1350,0.04169862578049588,0.2540626431510583,0.7042387310684458,-0.0 +1351,-0.0,0.21872566550348266,0.7812743344965174,-0.0 +1352,-0.0,0.22440685162045623,0.7755931483795437,-0.0 +1353,0.00015053655516424508,0.18441130135696226,0.8154381620878735,-0.0 +1354,0.1686009417839545,0.2500858128691768,0.5813132453468688,-0.0 +1355,0.5011361921417717,0.32337311377813593,0.17549069408009232,-0.0 +1356,0.4672654672298167,0.340984790740754,0.19174974202942935,-0.0 +1357,0.31552461962425765,0.30462519959212314,0.37985018078361915,-0.0 +1358,0.04711794176640871,0.15055143209979976,0.8023306261337915,-0.0 +1359,-0.0,0.15759610288484696,0.8424038971151531,-0.0 +1360,-0.0,0.1468018492625972,0.8531981507374028,-0.0 +1361,0.00015053655516424508,0.13657571425204476,0.863273749192791,-0.0 +1362,0.16182679680156345,0.13737108030842105,0.7008021228900155,-0.0 +1363,0.4746417584328647,0.143620385037092,0.3817378565300432,-0.0 +1364,0.5119748241135975,0.16623150578264684,0.3217936701037556,-0.0 +1365,0.38326606944816793,0.14293864270305515,0.47379528784877695,-0.0 +1366,0.06292428005865443,0.08033197169400635,0.8567437482473392,-0.0 +1367,-0.0,0.09578479793217448,0.9042152020678256,-0.0 +1368,-0.0,0.08033197169400635,0.9051023903737102,0.014565637932283437 +1369,-0.0,0.05647099000271734,0.9051023903737102,0.03842661962357245 +1370,0.20322434947173085,0.045563112658128076,0.7512125378701411,-0.0 +1371,0.49375990093872385,0.051357922497441116,0.45488217656383506,-0.0 +1372,0.46229776090939656,0.05681186116973576,0.48089037792086764,-0.0 +1373,0.39681435941295,0.07396904324299595,0.5292165973440541,-0.0 +1374,0.05193511153166455,0.0849905443099247,0.8630743441584108,-0.0 +1375,-0.0,0.15418739121466282,0.8458126087853371,-0.0 +1376,-0.0,0.17134457328792302,0.8286554267120769,-0.0 +1377,0.00015053655516424508,0.13384874491589743,0.8660007185289383,-0.0 +1378,0.19389108305154765,0.09283058115134822,0.7132783357971041,-0.0 +1379,0.4379108389727889,0.1787301152399887,0.3833590457872224,-0.0 +1380,0.3889864585444093,0.23315587824059553,0.37785766321499514,-0.0 +1381,0.19614913137901133,0.1671404955613626,0.6367103730596261,-0.0 +1382,0.033419115246462405,0.10157960777148753,0.8650012769820501,-0.0 +1383,-0.0,0.12157738290323451,0.8784226170967655,-0.0 +1384,-0.0,0.12532696574043709,0.8746730342595629,-0.0 +1385,0.0006021462206569803,0.12828118252126333,0.8711166712580797,-0.0 +1386,0.22595536930153187,0.187479141860128,0.58656548883834,-0.0 +1387,0.509867312341298,0.2569032362095451,0.23322945144915685,-0.0 +1388,0.6102751946358494,0.2512220500925715,0.13850275527157907,-0.0 +1389,0.40449172372632647,0.22508859395449307,0.37041968231918043,-0.0 +1390,0.060515695176026514,0.1730489291230151,0.7664353757009584,-0.0 +1391,-0.0,0.2544035143180767,0.7455964856819233,-0.0 +1392,-0.0,0.26542501538500546,0.7345749846149945,-0.0 +1393,-0.0,0.24679072492133214,0.7532092750786679,-0.0 +1394,0.17326757499404608,0.28826338357523923,0.5384690414307146,-0.0 +1395,0.4171367943601231,0.2701972117232633,0.31266599391661365,-0.0 +1396,0.5896516865783479,0.2377008271341744,0.17264748628747772,-0.0 +1397,0.5097167757861338,0.17577589845916244,0.31450732575470375,-0.0 +1398,0.076171496913108,0.08521779175460363,0.8386107113322884,-0.0 +1399,-0.0,0.14725634415195507,0.8527436558480449,-0.0 +1400,-0.0,0.1703219597868678,0.8296780402131322,-0.0 +1401,0.00015053655516424508,0.12396348107236342,0.8758859823724723,-0.0 +1402,0.3346427621301168,0.034996106480557225,0.630361131389326,-0.0 +1403,0.8506820732331489,0.04647210243684385,0.10284582433000722,-0.0 +1404,0.8570046085500472,0.04329063821133865,0.09970475323861416,-0.0 +1405,0.5390714040431616,0.03692770976032824,0.4240008861965102,-0.0 +1406,0.06533286494128236,0.013293975513718167,0.9051023903737102,0.016270769171289265 +1407,-0.0,0.00908989778715772,0.9051023903737102,0.08580771183913208 +1408,-0.0,0.037950323261383485,0.9051023903737102,0.056947286364906306 +1409,0.00030107311032849017,0.05760722722611206,0.9051023903737102,0.03698930928984925 +1410,0.30453545109726776,0.012044114567983981,0.6834204343347483,-0.0 +1411,0.8208758353106284,0.0351097302028967,0.144014434486475,-0.0 +1412,0.7624676519069012,0.05272140716551478,0.184810940927584,-0.0 +1413,0.4016315291782058,0.03624596742629141,0.5621225033955028,-0.0 +1414,0.04019326022885344,0.013066728069039224,0.9051023903737102,0.041637621328397136 +1415,-0.0,0.03340537436780462,0.9051023903737102,0.06149223525848517 +1416,-0.0,0.115668949341582,0.884331050658418,-0.0 +1417,-0.0,0.14850620509768928,0.8514937949023107,-0.0 +1418,0.1532462131572015,0.08624040525565888,0.7605133815871397,-0.0 +1419,0.43414742509368276,0.16611788206030734,0.39973469284600993,-0.0 +1420,0.5619529604281268,0.2512220500925715,0.1868249894793017,-0.0 +1421,0.39500792075097907,0.2851955430720735,0.3197965361769474,-0.0 +1422,0.06819305948940302,0.22145263483962996,0.710354305670967,-0.0 +1423,-0.0,0.21304447938650908,0.7869555206134909,-0.0 +1424,-0.0,0.17804837290595185,0.8219516270940481,-0.0 +1425,0.00030107311032849017,0.12066839312451874,0.8790305337651528,-0.0 +1426,0.39681435941295,0.06374290823244351,0.5394427323546065,-0.0 +1427,0.9455201029866233,0.042381648432622876,0.012098248580753812,-0.0 +1428,0.9951971661908242,0.004802833809175788,-0.0,-0.0 +1429,0.5333510149469203,0.01738442951793914,0.44926455553514055,-0.0 +1430,0.050128672869693615,0.009317145231836665,0.9051023903737102,0.03545179152475952 +1431,-0.0,0.03340537436780462,0.9051023903737102,0.06149223525848517 +1432,-0.0,0.06283391845372775,0.9051023903737102,0.03206369117256204 +1433,0.0007526827758212254,0.08942186948116408,0.9051023903737102,0.004723057369304485 +1434,0.26870775096817745,0.021361259799820644,0.7099309892320019,-0.0 +1435,0.9408534697765316,0.021474883522160117,0.03767164670130829,-0.0 +1436,0.9525953210793429,0.04740467892065714,-0.0,-0.0 +1437,0.6012430013259947,0.07260555857492229,0.3261514400990829,-0.0 +1438,0.07632203346827225,0.08908099831414566,0.8345969682175821,-0.0 +1439,-0.0,0.09442131326410082,0.9051023903737102,0.0004762963621889743 +1440,-0.0,0.06249304728670933,0.9051023903737102,0.03240456233958046 +1441,0.0015053655516424507,0.04306339076665971,0.9051023903737102,0.050328853307987635 +1442,0.43068508432490515,0.01499833134881024,0.5543165843262846,-0.0 +1443,1.0,-0.0,-0.0,-0.0 +1444,1.0,-0.0,-0.0,-0.0 +1445,0.7142959542543428,0.02386098169128902,0.2618430640543682,-0.0 +1446,0.09664446841544533,0.06851510457070133,0.8348404270138534,-0.0 +1447,-0.0,0.18441130135696226,0.8155886986430377,-0.0 +1448,-0.0,0.16055031966567326,0.8394496803343268,-0.0 +1449,0.0007526827758212254,0.12873567741062122,0.8705116398135576,-0.0 +1450,0.4606418588025899,0.047608339660238565,0.4917498015371715,-0.0 +1451,1.0,-0.0,-0.0,-0.0 +1452,1.0,-0.0,-0.0,-0.0 +1453,0.7139948811440143,0.12225912523727135,0.16374599361871434,-0.0 +1454,0.09408534697765317,0.21202186588545383,0.6938927871368931,-0.0 +1455,-0.0,0.3759808972213113,0.6240191027786888,-0.0 +1456,-0.0,0.3159875718260703,0.6840124281739297,-0.0 +1457,0.0006021462206569803,0.257357731098903,0.74204012268044,-0.0 +1458,0.4473946419481364,0.13248526024782378,0.4201200978040398,-0.0 +1459,1.0,-0.0,-0.0,-0.0 +1460,1.0,-0.0,-0.0,-0.0 +1461,0.7004465911792322,0.16134568572204952,0.1382077230987182,-0.0 +1462,0.08911764065723309,0.24554086397559793,0.665341495367169,-0.0 +1463,-0.0,0.3350763571791015,0.6649236428208984,-0.0 +1464,-0.0,0.2178166757247669,0.7821833242752331,-0.0 +1465,0.00045160966549273514,0.14055254453392627,0.858995845800581,-0.0 +1466,0.37498655891413446,0.026133456138078447,0.598879984947787,-0.0 +1467,0.8750689951697567,0.017838924407297025,0.10709208042294627,-0.0 +1468,1.0,-0.0,-0.0,-0.0 +1469,0.633307287575979,0.009203521509497192,0.3574891909145238,-0.0 +1470,0.08174134945418507,0.07328730090895913,0.8449713496368557,-0.0 +1471,-0.0,0.14430212737112882,0.8556978726288712,-0.0 +1472,-0.0,0.12157738290323451,0.8784226170967655,-0.0 +1473,-0.0,0.11316922745011362,0.8868307725498864,-0.0 +1474,0.40358850439534105,0.022383873300875885,0.574027622303783,-0.0 +1475,0.9833047783328488,0.016695221667151205,-0.0,-0.0 +1476,1.0,-0.0,-0.0,-0.0 +1477,0.6138880719597913,0.05385764438890949,0.33225428365129916,-0.0 +1478,0.05705335440724889,0.13975717847754995,0.8031894671152011,-0.0 +1479,-0.0,0.13248526024782378,0.8675147397521762,-0.0 +1480,-0.0,0.09248970998432982,0.9051023903737102,0.0024078996419599763 +1481,0.0006021462206569803,0.06294754217606721,0.9051023903737102,0.031347921229565597 +1482,0.359330757177053,0.006476552173349876,0.6341926906495972,-0.0 +1483,0.8726604102871286,0.009203521509497192,0.11813606820337419,-0.0 +1484,0.8652841190840806,0.015339202515828654,0.1193766784000907,-0.0 +1485,0.4991792169246366,0.05851621700482783,0.4423045660705356,-0.0 +1486,0.049526526649036624,0.13987080219988943,0.8106026711510739,-0.0 +1487,-0.0,0.2715606963913369,0.7284393036086632,-0.0 +1488,-0.0,0.2741740420051447,0.7258259579948553,-0.0 +1489,0.00045160966549273514,0.3164420667154282,0.6831063236190791,-0.0 +1490,0.32530949570993356,0.2813323365125314,0.393358167777535,-0.0 +1491,0.896896795668572,0.10310320433142794,-0.0,-0.0 +1492,1.0,-0.0,-0.0,-0.0 +1493,0.6774144982391028,0.16180018061140744,0.16078532114948973,-0.0 +1494,0.0885154944365761,0.052039664831477955,0.8594448407319459,-0.0 +1495,-0.0,0.018975161630691743,0.9051023903737102,0.07592244799559805 +1496,-0.0,0.03385986925716251,0.9051023903737102,0.061037740369127284 +1497,0.00015053655516424508,0.08192270380675895,0.9051023903737102,0.012824369264366595 +1498,0.4332442057626973,0.032041889699730965,0.5347139045375717,-0.0 +1499,0.9991111166250944,0.000888883374905558,-0.0,-0.0 +1500,0.9568103446239418,0.043189655376058234,-0.0,-0.0 +1501,0.4850287807391976,0.09953438076937704,0.4154368384914254,-0.0 +1502,0.039290040897867966,0.11055588183630578,0.8501540772658263,-0.0 +1503,-0.0,0.20395458159935134,0.7960454184006487,-0.0 +1504,-0.0,0.25269915848298463,0.7473008415170154,-0.0 +1505,-0.0,0.28110508906785253,0.7188949109321474,-0.0 +1506,0.21948229742946931,0.42381648432622876,0.3567012182443019,-0.0 +1507,0.6066623173119077,0.3933376826880923,-0.0,-0.0 +1508,0.6152429009562697,0.38475709904373034,-0.0,-0.0 +1509,0.4260184511148135,0.5739815488851865,-0.0,-0.0 +1510,0.06096730484151926,0.44210990362288366,0.49692279153559704,-0.0 +1511,-0.0,0.3570057355906195,0.6429942644093805,-0.0 +1512,-0.0,0.34916569874919595,0.650834301250804,-0.0 +1513,-0.0,0.33212214039827526,0.6678778596017247,-0.0 +1514,0.2491379987968256,0.5233508650956058,0.2275111361075686,-0.0 +1515,0.6175009492837332,0.3824990507162668,-0.0,-0.0 +1516,0.6540813321886448,0.34591866781135516,-0.0,-0.0 +1517,0.5119748241135975,0.4880251758864025,-0.0,-0.0 +1518,0.06894574226522424,0.5397126811124896,0.3913415766222862,-0.0 +1519,-0.0,0.3934789504615898,0.6065210495384101,-0.0 +1520,-0.0,0.3553013797555274,0.6446986202444727,-0.0 +1521,-0.0,0.340984790740754,0.6590152092592461,-0.0 +1522,0.3829649963378395,0.4250663452719629,0.19196865839019756,-0.0 +1523,0.8646819728634237,0.13531802713657626,-0.0,-0.0 +1524,0.9875198018774477,0.0124801981225523,-0.0,-0.0 +1525,0.6768123520184458,0.27894623834340254,0.04424140963815171,-0.0 +1526,0.08881656754690459,0.1124874851160768,0.7986959473370187,-0.0 +1527,-0.0,0.06453827428881982,0.9051023903737102,0.03035933533746997 +1528,-0.0,0.07328730090895913,0.9051023903737102,0.021610308717330662 +1529,0.00015053655516424508,0.07601427024510644,0.9051023903737102,0.018732802826019104 +1530,0.3841692887791534,0.01215773829032345,0.6036729729305232,-0.0 +1531,0.8570046085500472,0.02090676491046276,0.12208862653949004,-0.0 +1532,0.9726166829161874,0.027383317083812635,-0.0,-0.0 +1533,0.6248772404867813,0.0398819265411545,0.3352408329720642,-0.0 +1534,0.07677364313376497,0.049653566662349054,0.8735727902038859,-0.0 +1535,-0.0,0.10498831944167167,0.8950116805583284,-0.0 +1536,-0.0,0.08896737459180619,0.9051023903737102,0.0059302350344836025 +1537,-0.0,0.08805838481309042,0.9051023903737102,0.006839224813199374 +1538,0.2962559405632343,0.049312695495330634,0.654431363941435,-0.0 +1539,0.7167045391369709,0.10907877344589265,0.17421668741713647,-0.0 +1540,0.5944688563436037,0.17077645467622568,0.2347546889801706,-0.0 +1541,0.28225604093295953,0.197364405703662,0.5203795533633785,-0.0 +1542,0.025892287488250152,0.10101148915979018,0.8730962233519597,-0.0 +1543,-0.0,0.14259777153603675,0.8574022284639633,-0.0 +1544,-0.0,0.15816422149654433,0.8418357785034557,-0.0 +1545,-0.0,0.1293037960223186,0.8706962039776814,-0.0 +1546,0.12705285255862284,0.11146487161502155,0.7614822758263556,-0.0 +1547,0.41939484268758676,0.17486690868044666,0.4057382486319666,-0.0 +1548,0.3462340768777637,0.2388370643575691,0.4149288587646672,-0.0 +1549,0.20548239779919453,0.1846385488016412,0.6098790533991643,-0.0 +1550,0.03356965180162665,0.0649927691781777,0.9014375790201956,-0.0 +1551,-0.0,0.10044337054809283,0.8995566294519072,-0.0 +1552,-0.0,0.1108967530033242,0.8891032469966758,-0.0 +1553,-0.0,0.10816978366717689,0.8918302163328231,-0.0 +1554,0.1406011425234049,0.1072607938884611,0.7521380635881341,-0.0 +1555,0.4154808922533164,0.19043335864095426,0.39408574910572935,-0.0 +1556,0.525222040968051,0.2536081482617004,0.22116981077024855,-0.0 +1557,0.4032874312850126,0.2308834037938061,0.3658291649211813,-0.0 +1558,0.05328994052814275,0.11339647489479257,0.8333135845770647,-0.0 +1559,-0.0,0.10862427855653477,0.8913757214434652,-0.0 +1560,-0.0,0.11896403728942667,0.8810359627105733,-0.0 +1561,-0.0,0.09953438076937704,0.900465619230623,-0.0 +1562,0.2965570136735628,0.047267468493220145,0.656175517833217,-0.0 +1563,0.7773707708681615,0.06147043378565409,0.16115879534618438,-0.0 +1564,0.845864903467893,0.06567451151221453,0.08846058501989251,-0.0 +1565,0.5395230137086543,0.042608895877301815,0.41786809041404394,-0.0 +1566,0.06111784139668349,0.01363484668073658,0.9051023903737102,0.020144921548869717 +1567,-0.0,0.02886042547422576,0.9051023903737102,0.06603718415206403 +1568,-0.0,0.05567562394634104,0.9051023903737102,0.03922198567994875 +1569,-0.0,0.056925484892075225,0.9051023903737102,0.03797212473421457 +1570,0.29444950190126334,0.014771083904131295,0.6907794141946053,-0.0 +1571,0.8265962244068698,0.01443021273711288,0.15897356285601738,-0.0 +1572,0.9032193309854704,0.020565893743444346,0.07621477527108533,-0.0 +1573,0.6415867981100125,0.0179525481296365,0.34046065376035095,-0.0 +1574,0.07301022925465886,0.03863206559542032,0.8883577051499209,-0.0 +1575,-0.0,0.11680518656497671,0.8831948134350233,-0.0 +1576,-0.0,0.1268040741308502,0.8731959258691497,-0.0 +1577,-0.0,0.10839703111185582,0.8916029688881442,-0.0 +1578,0.29173984390830693,0.07487803302171173,0.6333821230699814,-0.0 +1579,0.7517795564902399,0.18918349769522008,0.05903694581454011,-0.0 +1580,0.9261008873704356,0.07389911262956439,-0.0,-0.0 +1581,0.6047053420947724,0.26383428327225283,0.13146037463297466,-0.0 +1582,0.06894574226522424,0.1566871131061312,0.7743671446286446,-0.0 +1583,-0.0,0.11998665079048192,0.8800133492095181,-0.0 +1584,-0.0,0.11362372233947152,0.8863762776605285,-0.0 +1585,-0.0,0.1008978654374507,0.8991021345625493,-0.0 +1586,0.2827076505984522,0.10476107199699274,0.612531277404555,-0.0 +1587,0.7352205354221729,0.19872789037173566,0.06605157420609142,-0.0 +1588,0.7209195626815696,0.2661067577190423,0.012973679599388,-0.0 +1589,0.5312435031746209,0.21315810310884853,0.2555983937165306,-0.0 +1590,0.0660855477171036,0.10180685521616648,0.8321075970667299,-0.0 +1591,-0.0,0.07680963630148273,0.9051023903737102,0.018087973324807063 +1592,-0.0,0.056698237447396285,0.9051023903737102,0.038199372178893506 +1593,-0.0,0.06646987756859084,0.9051023903737102,0.028427732057698954 +1594,0.16453645479451984,0.06067506772927779,0.7747884774762024,-0.0 +1595,0.4373086927521319,0.20543168998976447,0.3572596172581036,-0.0 +1596,0.4794589281981205,0.3141695922686388,0.20637147953324075,-0.0 +1597,0.3490942714258843,0.35280165786405904,0.2981040707100567,-0.0 +1598,0.040795406449510414,0.18520666741333858,0.773997926137151,-0.0 +1599,-0.0,0.1803208473527413,0.8196791526472587,-0.0 +1600,-0.0,0.22861092934701666,0.7713890706529833,-0.0 +1601,-0.0,0.19088785353031215,0.8091121464696879,-0.0 +1602,0.31161066918998725,0.16520889228159158,0.5231804385284212,-0.0 +1603,0.7386828761909506,0.16975384117517045,0.09156328263387892,-0.0 +1604,0.6998444449585753,0.1399844259222289,0.16017112911919573,-0.0 +1605,0.5094157026758053,0.07430991441001437,0.4162743829141804,-0.0 +1606,0.05494584263494945,0.021588507244499587,0.9051023903737102,0.01836325974684075 +1607,-0.0,0.06510639290051717,0.9051023903737102,0.029791216725772618 +1608,-0.0,0.1838431827452649,0.8161568172547351,-0.0 +1609,-0.0,0.23542835268738496,0.7645716473126151,-0.0 +1610,0.28511623548108017,0.21713493339073006,0.49774883112818974,-0.0 +1611,0.740188241742593,0.259811758257407,-0.0,-0.0 +1612,0.8959935763375866,0.10400642366241342,-0.0,-0.0 +1613,0.6316513854691722,0.30928377220804143,0.059064842322786304,-0.0 +1614,0.07511774102695828,0.09385319465240348,0.8310290643206382,-0.0 +1615,-0.0,0.021361259799820644,0.9051023903737102,0.07353634982646914 +1616,-0.0,0.01647543973922337,0.9051023903737102,0.07842216988706642 +1617,-0.0,0.04692659732620174,0.9051023903737102,0.04797101230008805 +1618,0.3156751561794219,0.02499721891468373,0.6593276249058944,-0.0 +1619,0.8434563185852652,0.0734009246312986,0.0831427567834363,-0.0 +1620,0.8679937770770371,0.12612233179681337,0.00588389112614951,-0.0 +1621,0.4708783445537586,0.13737108030842105,0.39175057513782036,-0.0 +1622,0.0427523816666456,0.11623706795327936,0.841010550380075,-0.0 +1623,-0.0,0.10203410266084542,0.8979658973391546,-0.0 +1624,-0.0,0.08601315781097994,0.9051023903737102,0.008884451815309849 +1625,-0.0,0.09146709648327457,0.9051023903737102,0.003430513143015221 +1626,0.2378477571595072,0.061129562618635676,0.7010226802218571,-0.0 +1627,0.6896079592074067,0.09430768954176136,0.21608435125083192,-0.0 +1628,0.6215654362731678,0.12691769785318968,0.2515168658736425,-0.0 +1629,0.4683192231159664,0.10317033988424014,0.4285104369997934,-0.0 +1630,0.0581071102933986,0.06067506772927779,0.8812178219773237,-0.0 +1631,-0.0,0.05749360350377258,0.9051023903737102,0.03740400612251721 +1632,-0.0,0.04624485499216491,0.9051023903737102,0.048652754634124884 +1633,-0.0,0.045563112658128076,0.9051023903737102,0.049334496968161716 +1634,0.2873742838085438,0.017498053240278613,0.6951276629511776,-0.0 +1635,0.9056279158680983,0.015339202515828654,0.07903288161607303,-0.0 +1636,0.8764238241662349,0.011589619678626095,0.11198655615513908,-0.0 +1637,0.5304908203987996,0.044426875434733365,0.4250823041664671,-0.0 +1638,0.041999698890824375,0.22077089250559315,0.7372294086035825,-0.0 +1639,-0.0,0.31439683971331767,0.6856031602866823,-0.0 +1640,-0.0,0.281218712790192,0.718781287209808,-0.0 +1641,-0.0,0.27701463506363155,0.7229853649363684,-0.0 +1642,0.24010580548697089,0.25963020554569244,0.5002639889673366,-0.0 +1643,0.6515222107508527,0.3484777892491473,-0.0,-0.0 +1644,0.6512211376405241,0.34877886235947586,-0.0,-0.0 +1645,0.38928753165473773,0.6107124683452623,-0.0,-0.0 +1646,0.03898896778753947,0.8013881136602926,0.15962291855216793,-0.0 +1647,-0.0,0.8047968253304768,0.1952031746695232,-0.0 +1648,-0.0,0.7580974754489539,0.2419025245510461,-0.0 +1649,-0.0,0.5931158306120413,0.4068841693879587,-0.0 +1650,0.328621299923547,0.4891501246714248,0.18222857540502818,-0.0 +1651,0.8694991426286796,0.13050085737132044,-0.0,-0.0 +1652,0.899907526771857,0.10009247322814296,-0.0,-0.0 +1653,0.5237166754164085,0.10816978366717689,0.36811354091641457,-0.0 +1654,0.030257847588013257,0.03170101853271255,0.9051023903737102,0.032938743505563986 +1655,-0.0,0.1299855383563554,0.8700144616436446,-0.0 +1656,-0.0,0.18247969807719125,0.8175203019228088,-0.0 +1657,-0.0,0.19952325642811197,0.800476743571888,-0.0 +1658,0.144966702623168,0.24110953880435854,0.6139237585724735,-0.0 +1659,0.45281395793404916,0.3633686640416299,0.18381737802432102,-0.0 +1660,0.5015878018072646,0.44256439851224155,0.055847799680493804,-0.0 +1661,0.38838431232375226,0.47062945793009103,0.1409862297461567,-0.0 +1662,0.043354527887302576,0.3632550403192904,0.593390431793407,-0.0 +1663,-0.0,0.4133631018709974,0.5866368981290027,-0.0 +1664,-0.0,0.40654567853062906,0.5934543214693709,-0.0 +1665,-0.0,0.38268469683934003,0.61731530316066,-0.0 +1666,0.20713829990600122,0.5658461372505681,0.2270155628434307,-0.0 +1667,0.5151360917720467,0.48486390822795333,-0.0,-0.0 +1668,0.5830280781511211,0.4169719218488789,-0.0,-0.0 +1669,0.3530082218601547,0.6469917781398453,-0.0,-0.0 +1670,0.027548189595056848,0.4081364106433817,0.5643153997615614,-0.0 +1671,-0.0,0.32291861888877804,0.677081381111222,-0.0 +1672,-0.0,0.3293951710621279,0.6706048289378721,-0.0 +1673,-0.0,0.3159875718260703,0.6840124281739297,-0.0 +1674,0.21993390709496205,0.42165763360177877,0.35840845930325915,-0.0 +1675,0.5470498414668665,0.4529501585331335,-0.0,-0.0 +1676,0.5207059443131237,0.47929405568687633,-0.0,-0.0 +1677,0.4141260632568382,0.5858739367431618,-0.0,-0.0 +1678,0.045462039659602015,0.5013078629617483,0.45323009737864967,-0.0 +1679,-0.0,0.5472118467868947,0.4527881532131053,-0.0 +1680,-0.0,0.5644826525824945,0.4355173474175055,-0.0 +1681,-0.0,0.49619479545647205,0.503805204543528,-0.0 +1682,0.2693098971888344,0.5638009102484576,0.166889192562708,-0.0 +1683,0.6641672813846492,0.3358327186153508,-0.0,-0.0 +1684,0.6330062144656505,0.36699378553434947,-0.0,-0.0 +1685,0.42797542633194874,0.5184650450350085,0.05355952863304281,-0.0 +1686,0.043203991332138335,0.3422346516864882,0.6145613569813734,-0.0 +1687,-0.0,0.27383317083812636,0.7261668291618737,-0.0 +1688,-0.0,0.24667710119899264,0.7533228988010073,-0.0 +1689,-0.0,0.20225022576425927,0.7977497742357407,-0.0 +1690,0.34518032099161394,0.1739579189017309,0.4808617601066552,-0.0 +1691,0.7856502814021951,0.15384652004764443,0.06050319855016051,-0.0 +1692,0.8229833470829278,0.11987302706814244,0.05714362584892985,-0.0 +1693,0.6016946109914876,0.06624263012391189,0.3320627588846006,-0.0 +1694,0.05690281785208463,0.05851621700482783,0.8845809651430876,-0.0 +1695,-0.0,0.11794142378837143,0.8820585762116285,-0.0 +1696,-0.0,0.07805949724721692,0.9051023903737102,0.016838112379072873 +1697,-0.0,0.06794698595900396,0.9051023903737102,0.02695062366728583 +1698,0.2635895080925931,0.024088229135967962,0.712322262771439,-0.0 +1699,0.8214779815312853,0.025678961248720558,0.15284305721999414,-0.0 +1700,0.9628318068305114,0.008067284286102478,0.029100908883386123,-0.0 +1701,0.4905986332802747,0.0036359591148630885,0.5057654076048621,-0.0 +1702,0.03853735812204674,0.061129562618635676,0.9003330792593176,-0.0 +1703,-0.0,0.10782891250015846,0.8921710874998415,-0.0 +1704,-0.0,0.056698237447396285,0.9051023903737102,0.038199372178893506 +1705,-0.0,0.039995550263493974,0.9051023903737102,0.05490205936279582 +1706,0.3323847138026531,0.010680629899910322,0.6569346562974365,-0.0 +1707,0.9026171847648135,0.005113067505276217,0.09226974772991037,-0.0 +1708,1.0,-0.0,-0.0,-0.0 +1709,0.6187052417250472,0.015566449960507597,0.36572830831444525,-0.0 +1710,0.04139755267016739,0.09714828260024815,0.8614541647295845,-0.0 +1711,-0.0,0.17532140356980452,0.8246785964301955,-0.0 +1712,-0.0,0.14612010692856037,0.8538798930714396,-0.0 +1713,-0.0,0.1415751580349815,0.8584248419650184,-0.0 +1714,0.3782983631277479,0.05181241738679901,0.5698892194854531,-0.0 +1715,0.9953477027459884,0.004652297254011617,-0.0,-0.0 +1716,1.0,-0.0,-0.0,-0.0 +1717,0.6598017212848861,0.0482900819942754,0.29190819672083845,-0.0 +1718,0.046967405211244456,0.1069199227214427,0.8461126720673129,-0.0 +1719,-0.0,0.20236384948659877,0.7976361505134012,-0.0 +1720,-0.0,0.21315810310884853,0.7868418968911515,-0.0 +1721,-0.0,0.2237251092864194,0.7762748907135806,-0.0 +1722,0.2983634523355337,0.05238053599849637,0.64925601166597,-0.0 +1723,0.7761664784268475,0.08646765270033782,0.13736586887281466,-0.0 +1724,0.6641672813846492,0.1307809044127317,0.2050518142026191,-0.0 +1725,0.33118042136133913,0.16827673278475733,0.5005428458539036,-0.0 +1726,0.021225654278158555,0.1860020334697149,0.7927723122521265,-0.0 +1727,-0.0,0.17657126451553873,0.8234287354844613,-0.0 +1728,-0.0,0.13680296169672368,0.8631970383032763,-0.0 +1729,-0.0,0.09737553004492708,0.902624469955073,-0.0 +1730,0.2988150620010265,0.04079091631987027,0.6603940216791032,-0.0 +1731,0.942960981548831,0.04635847871450438,0.010680539736664607,-0.0 +1732,1.0,-0.0,-0.0,-0.0 +1733,0.6558877708506157,0.005567562394634104,0.33854466675475026,-0.0 +1734,0.037031992570404286,0.03488248275821776,0.9051023903737102,0.022983134297667747 +1735,-0.0,0.19520555497921208,0.8047944450207879,-0.0 +1736,-0.0,0.19475106008985416,0.8052489399101459,-0.0 +1737,-0.0,0.1711173258432441,0.8288826741567559,-0.0 +1738,0.34518032099161394,0.02954216780826259,0.6252775112001234,-0.0 +1739,0.9462727857624446,0.03454161159119934,0.019185602646356048,-0.0 +1740,1.0,-0.0,-0.0,-0.0 +1741,0.5949204660090965,0.03215551342207044,0.372924020568833,-0.0 +1742,0.03627930979458306,0.05578924766868051,0.9051023903737102,0.002829052163026223 +1743,-0.0,0.11066950555864526,0.8893304944413547,-0.0 +1744,-0.0,0.08942186948116408,0.9051023903737102,0.00547574014512571 +1745,-0.0,0.0554483765016621,0.9051023903737102,0.03944923312462769 +1746,0.29700862333905553,0.008067284286102478,0.694924092374842,-0.0 +1747,0.8306607113963043,0.012044114567983981,0.1572951740357118,-0.0 +1748,0.9512404920828647,0.008635402897799835,0.0401241050193355,-0.0 +1749,0.5750496407274162,0.011021501066928736,0.41392885820565506,-0.0 +1750,0.03597823668425457,0.10442020082997432,0.8596015624857711,-0.0 +1751,-0.0,0.13543947702865003,0.86456052297135,-0.0 +1752,-0.0,0.10646542783208482,0.8935345721679152,-0.0 +1753,-0.0,0.1108967530033242,0.8891032469966758,-0.0 +1754,0.23935312271114967,0.06476552173349877,0.6958813555553516,-0.0 +1755,0.7441021921768634,0.06408377939946193,0.19181402842367468,-0.0 +1756,0.7975426692601705,0.05533475277932263,0.14712257796050687,-0.0 +1757,0.4755449777638502,0.13180351791378697,0.39265150432236284,-0.0 +1758,0.026494433708907132,0.2365645899107797,0.7369409763803132,-0.0 +1759,-0.0,0.1620274280560864,0.8379725719439136,-0.0 +1760,-0.0,0.09226246253965086,0.9051023903737102,0.0026351470866389365 +1761,-0.0,0.07169656879620653,0.9051023903737102,0.023201040830083258 +1762,0.27879370016418187,0.05272140716551478,0.6684848926703033,-0.0 +1763,0.8484240249056851,0.02931492036358365,0.12226105473073123,-0.0 +1764,0.9851112169948197,0.014888783005180262,-0.0,-0.0 +1765,0.5770066159445513,0.02851955430720735,0.3944738297482414,-0.0 +1766,0.025741750933085908,0.1468018492625972,0.8274563998043168,-0.0 +1767,-0.0,0.16020944849865482,0.8397905515013452,-0.0 +1768,-0.0,0.09521667932047713,0.9047833206795228,-0.0 +1769,-0.0,0.0789684870259327,0.9051023903737102,0.015929122600357087 +1770,0.2348370260562223,0.02886042547422576,0.736302548469552,-0.0 +1771,0.7793277460852968,0.040222797708172914,0.1804494562065303,-0.0 +1772,0.8889183582448672,0.025906208693399504,0.08517543306173325,-0.0 +1773,0.49210399883191713,0.03704133348266771,0.4708546676854152,-0.0 +1774,0.021225654278158555,0.17054920723154676,0.8082251384902946,-0.0 +1775,-0.0,0.1415751580349815,0.8584248419650184,-0.0 +1776,-0.0,0.0953303030428166,0.9046696969571834,-0.0 +1777,-0.0,0.07783224980253799,0.9051023903737102,0.017065359823751805 +1778,0.2425143903695988,0.03011028641995995,0.7273753232104413,-0.0 +1779,0.8177145676521792,0.02397460541362849,0.1583108269341923,-0.0 +1780,0.9217353272706725,0.042040777265604456,0.036223895463723066,-0.0 +1781,0.5539745230044218,0.08010472424932741,0.3659207527462508,-0.0 +1782,0.027397653039892604,0.15146042187851552,0.8211419250815919,-0.0 +1783,-0.0,0.1308945281350712,0.8691054718649288,-0.0 +1784,-0.0,0.08476329686524575,0.9051023903737102,0.01013431276104404 +1785,-0.0,0.0602205728399199,0.9051023903737102,0.034677036786369894 +1786,0.21707371254684138,0.042268024710283396,0.7406582627428753,-0.0 +1787,0.7132421983681931,0.07453716185469332,0.21222063977711358,-0.0 +1788,0.770295552775442,0.05510750533464368,0.17459694188991426,-0.0 +1789,0.45386771382019886,0.06272029473138828,0.48341199144841285,-0.0 +1790,0.016559021068066955,0.13930268358819206,0.844138295343741,-0.0 +1791,-0.0,0.13680296169672368,0.8631970383032763,-0.0 +1792,-0.0,0.12271362012662923,0.8772863798733708,-0.0 +1793,-0.0,0.13259888397016326,0.8674011160298367,-0.0 +1794,0.28346033337427345,0.14168878175732097,0.5748508848684056,-0.0 +1795,0.9425093718833384,0.05749062811666161,-0.0,-0.0 +1796,1.0,-0.0,-0.0,-0.0 +1797,0.6027483668776372,0.1870246469707701,0.21022698615159263,-0.0 +1798,0.023032092940129496,0.3115562466548309,0.6654116604050395,-0.0 +1799,-0.0,0.29439906458157067,0.7056009354184294,-0.0 +1800,-0.0,0.2816732076795499,0.7183267923204502,-0.0 +1801,-0.0,0.27701463506363155,0.7229853649363684,-0.0 +1802,0.2315252218426089,0.2089540253822881,0.5595207527751029,-0.0 +1803,0.7663816023411716,0.19725078198132254,0.036367615677505905,-0.0 +1804,0.8714561178458147,0.12854388215418533,-0.0,-0.0 +1805,0.4123196245948672,0.11669156284263724,0.47098881256249553,-0.0 +1806,0.0106880954166614,0.16191380433374689,0.8273981002495917,-0.0 +1807,-0.0,0.14475662226048672,0.8552433777395133,-0.0 +1808,-0.0,0.10112511288212964,0.8988748871178703,-0.0 +1809,-0.0,0.07590064652276697,0.9051023903737102,0.01899696310352282 +1810,0.19042874228277,0.04601760754748596,0.763553650169744,-0.0 +1811,0.5852861264785848,0.04169990609858605,0.3730139674228291,-0.0 +1812,0.5837807609269423,0.03317812692312568,0.383041112149932,-0.0 +1813,0.3518039294188407,0.02942854408592312,0.6187675264952361,-0.0 +1814,0.01249453407863234,0.04988081410702799,0.9051023903737102,0.03252226144062946 +1815,-0.0,0.05851621700482783,0.9051023903737102,0.036381392621461964 +1816,-0.0,0.05181241738679901,0.9051023903737102,0.04308519223949078 +1817,-0.0,0.053516773221891085,0.9051023903737102,0.041380836404398706 +1818,0.07858008179573593,0.04624485499216491,0.8751750632120991,-0.0 +1819,0.2938473556806064,0.06749249106964608,0.6386601532497476,-0.0 +1820,0.3346427621301168,0.08408155453120891,0.5812756833386743,-0.0 +1821,0.19193410783441248,0.09839814354598232,0.7096677486196052,-0.0 +1822,0.007225754647883762,0.12191825407025293,0.8708559912818633,-0.0 +1823,-0.0,0.13032640952337382,0.8696735904766262,-0.0 +1824,-0.0,0.13737108030842105,0.862628919691579,-0.0 +1825,-0.0,0.12816755879892386,0.8718324412010762,-0.0 +1826,0.06292428005865443,0.10635180410974533,0.8307239158316002,-0.0 +1827,0.22174034575693297,0.16634512950498628,0.6119145247380808,-0.0 +1828,0.2789442367193461,0.2025910969312777,0.5184646663493763,-0.0 +1829,0.13608504586847753,0.15441463865934177,0.7095003154721806,-0.0 +1830,0.004365560099763107,0.15293753026892865,0.8426969096313083,-0.0 +1831,-0.0,0.16725411928370207,0.832745880716298,-0.0 +1832,-0.0,0.1638454076135179,0.8361545923864822,-0.0 +1833,-0.0,0.13555310075098953,0.8644468992490104,-0.0 +1834,0.10522505205980731,0.12203187779259239,0.7727430701476004,-0.0 +1835,0.37107260847986406,0.14009804964456837,0.48882934187556754,-0.0 +1836,0.3853735812204674,0.09203521509497192,0.5225912036845607,-0.0 +1837,0.23303058739425137,0.04135903493156763,0.7256103776741809,-0.0 +1838,0.005419315985912822,0.038177570706062425,0.9051023903737102,0.05130072293431454 +1839,-0.0,0.05476663416762527,0.9051023903737102,0.04013097545866452 +1840,-0.0,0.051357922497441116,0.9051023903737102,0.043539687128848675 +1841,-0.0,0.048517329438954336,0.9051023903737102,0.046380280187335456 +1842,0.18681586495882813,0.01977052768706804,0.7934136073541038,-0.0 +1843,0.568124959189861,0.03317812692312568,0.39869691388701334,-0.0 +1844,0.5241682850819014,0.022156625856196946,0.4536750890619017,-0.0 +1845,0.2890301859153505,0.010794253622249794,0.7001755604623996,-0.0 +1846,0.006774144982391027,0.02329286307959166,0.9051023903737102,0.0648306015643071 +1847,-0.0,0.022838368190233774,0.9051023903737102,0.07205924143605602 +1848,-0.0,0.023065615634912714,0.9051023903737102,0.07183199399137707 +1849,-0.0,0.02556533752638109,0.9051023903737102,0.0693322720999087 +1850,0.17582669643183824,0.00749916567440512,0.8166741378937566,-0.0 +1851,0.648662016202732,0.029087672918904708,0.3222503108783633,-0.0 +1852,0.6632640620536637,0.014884707626470768,0.3218512303198655,-0.0 +1853,0.3963627497474572,0.007840036841423535,0.5957972134111192,-0.0 +1854,0.007526827758212253,0.03158739481037308,0.9051023903737102,0.055783387057704456 +1855,-0.0,0.06147043378565409,0.9051023903737102,0.0334271758406357 +1856,-0.0,0.05647099000271734,0.9051023903737102,0.03842661962357245 +1857,-0.0,0.0382911944284019,0.9051023903737102,0.056606415197887894 +1858,0.2815033581571383,0.010567006177570849,0.7079296356652909,-0.0 +1859,0.9497351265312222,0.03579147253693352,0.014473400931844349,-0.0 +1860,0.9407029332213674,0.01817979557431544,0.041117271204317074,-0.0 +1861,0.5607486679868129,0.012384985735002395,0.42686634627818476,-0.0 +1862,0.00978487608567593,0.0594252067835436,0.9051023903737102,0.025687526757070263 +1863,-0.0,0.11157849533736101,0.888421504662639,-0.0 +1864,-0.0,0.10203410266084542,0.8979658973391546,-0.0 +1865,-0.0,0.07760500235785904,0.9051023903737102,0.01729260726843075 +1866,0.3135676444071225,0.0179525481296365,0.668479807463241,-0.0 +1867,0.9611759047237047,0.03882409527629527,-0.0,-0.0 +1868,0.9973046779631235,0.002695322036876502,-0.0,-0.0 +1869,0.5765550062790586,0.09112622531625615,0.3323187684046852,-0.0 +1870,0.00797843742370499,0.22883817679169563,0.7631833857845994,-0.0 +1871,-0.0,0.27735550623065,0.72264449376935,-0.0 +1872,-0.0,0.23190601729486135,0.7680939827051386,-0.0 +1873,-0.0,0.21918016039284052,0.7808198396071595,-0.0 +1874,0.2949011115667561,0.1148735832852057,0.5902253051480382,-0.0 +1875,0.9522942479690144,0.04770575203098559,-0.0,-0.0 +1876,0.9197783520535373,0.08022164794646269,-0.0,-0.0 +1877,0.4326420595420403,0.36882260271392453,0.19853533774403515,-0.0 +1878,0.004516096654927352,0.398819265411545,0.5966646379335276,-0.0 +1879,-0.0,0.36109618959484047,0.6389038104051595,-0.0 +1880,-0.0,0.2508811789255531,0.7491188210744468,-0.0 +1881,-0.0,0.17191269189962038,0.8280873081003797,-0.0 +1882,0.1592676753637713,0.11612344423093988,0.7246088804052888,-0.0 +1883,0.14481616606800374,0.2165668147790327,0.6386170191529635,-0.0 +1884,0.10883792938374919,0.25974382926803186,0.631418241348219,-0.0 +1885,-0.0,0.209408520271646,0.790591479728354,-0.0 +1886,-0.0,0.3280316863940543,0.6719683136059458,-0.0 +1887,-0.0,0.37086782971603505,0.629132170283965,-0.0 +1888,-0.0,0.4294976704432023,0.5705023295567977,-0.0 +1889,-0.0,0.41893066426563147,0.5810693357343686,-0.0 +1890,0.1326227050996999,0.49028636189481956,0.3770909330054806,-0.0 +1891,0.48246965930140545,0.5175303406985945,-0.0,-0.0 +1892,0.574296957951595,0.42570304204840503,-0.0,-0.0 +1893,0.4151798191429879,0.40609118364127117,0.17872899721574093,-0.0 +1894,0.003161267658449146,0.20929489654930652,0.7875438357922443,-0.0 +1895,-0.0,0.2555397515414714,0.7444602484585285,-0.0 +1896,-0.0,0.3051933182038205,0.6948066817961795,-0.0 +1897,-0.0,0.2938309459698733,0.7061690540301266,-0.0 +1898,0.23935312271114967,0.16259554666778372,0.5980513306210666,-0.0 +1899,0.8965957225582436,0.1034042774417564,-0.0,-0.0 +1900,1.0,-0.0,-0.0,-0.0 +1901,0.5420821351464464,0.21429434033224326,0.2436235245213103,-0.0 +1902,0.0028601945481206564,0.2634934121052344,0.7336463933466449,-0.0 +1903,-0.0,0.4064320548082896,0.5935679451917104,-0.0 +1904,-0.0,0.33882594001630406,0.6611740599836959,-0.0 +1905,-0.0,0.2825821974582656,0.7174178025417344,-0.0 +1906,0.1406011425234049,0.1827069455218702,0.676691911954725,-0.0 +1907,0.5359101363847124,0.22429322789811676,0.2397966357171708,-0.0 +1908,0.5569852541077067,0.23440573918632973,0.2086090067059636,-0.0 +1909,0.2693098971888344,0.1348713584169527,0.5958187443942129,-0.0 +1910,0.00045160966549273514,0.1104422581139663,0.8891061322205409,-0.0 +1911,-0.0,0.11794142378837143,0.8820585762116285,-0.0 +1912,-0.0,0.11510083072988464,0.8848991692701154,-0.0 +1913,-0.0,0.08828563225776936,0.9051023903737102,0.0066119773685204275 +1914,0.12208514623820275,0.06226579984203039,0.8156490539197668,-0.0 +1915,0.4391151314141029,0.06908322318239868,0.4918016454034984,-0.0 +1916,0.5083619467896556,0.0921488388173114,0.399489214393033,-0.0 +1917,0.3540619777463044,0.08703577131203517,0.5589022509416604,-0.0 +1918,0.0012042924413139607,0.05272140716551478,0.9051023903737102,0.04097191001946105 +1919,-0.0,0.054084891833588444,0.9051023903737102,0.04081271779270135 +1920,-0.0,0.05158516994212007,0.9051023903737102,0.04331243968416972 +1921,-0.0,0.038404818150741364,0.9051023903737102,0.05649279147554843 +1922,0.24401975592124125,0.01670268718390231,0.7392775568948564,-0.0 +1923,0.8696496791838437,0.01806617185197597,0.11228414896418026,-0.0 +1924,0.944165273990145,0.013748470403076052,0.04208625560677892,-0.0 +1925,0.5374155019363549,0.003976830281881503,0.45860766778176365,-0.0 +1926,0.001956975217135186,0.019202409075370682,0.9051023903737102,0.07373822533378392 +1927,-0.0,0.09123984903859562,0.9051023903737102,0.003657760587694167 +1928,-0.0,0.08237719869611684,0.9051023903737102,0.012520410930172948 +1929,-0.0,0.06442465056648035,0.9051023903737102,0.030472959059809443 +1930,0.24146063448344907,0.026928822194454746,0.7316105433220963,-0.0 +1931,0.7820374040782531,0.031473771088033606,0.18648882483371332,-0.0 +1932,0.9187245961673876,0.03170101853271255,0.04957438529989988,-0.0 +1933,0.49451258371454504,0.02545171380404162,0.4800357024814133,-0.0 +1934,0.0010537558861497154,0.06999221296111445,0.9051023903737102,0.023851640779025626 +1935,-0.0,0.09044448298221933,0.9051023903737102,0.004453126644070465 +1936,-0.0,0.06840148084836184,0.9051023903737102,0.026496128777927952 +1937,-0.0,0.06522001662285665,0.9051023903737102,0.029677593003433145 +1938,0.2241489306395609,0.038745689317759784,0.7371053800426793,-0.0 +1939,0.8576067547707041,0.0634020370654251,0.07899120816387073,-0.0 +1940,0.9646382454924824,0.03536175450751755,-0.0,-0.0 +1941,0.5271790161851863,0.10737441761080058,0.36544656620401317,-0.0 +1942,0.0012042924413139607,0.13589397191800792,0.8629017356406782,-0.0 +1943,-0.0,0.19088785353031215,0.8091121464696879,-0.0 +1944,-0.0,0.2116809947184354,0.7883190052815646,-0.0 +1945,-0.0,0.2027047206536172,0.7972952793463828,-0.0 +1946,0.20337488602689507,0.11544170189690305,0.6811834120762019,-0.0 +1947,0.581673249154643,0.15805059777420488,0.26027615307115215,-0.0 +1948,0.4743406853225362,0.2445182504745427,0.2811410642029211,-0.0 +1949,0.2099984944541219,0.2701972117232633,0.5198042938226148,-0.0 +1950,-0.0,0.2912176003560655,0.7087823996439345,-0.0 +1951,-0.0,0.28053697045615517,0.7194630295438449,-0.0 +1952,-0.0,0.23440573918632973,0.7655942608136703,-0.0 +1953,-0.0,0.20190935459724088,0.7980906454027591,-0.0 +1954,0.2288155638496525,0.19634179220260678,0.5748426439477408,-0.0 +1955,0.7442527287320276,0.25574727126797236,-0.0,-0.0 +1956,0.6742532305806537,0.22361148556407995,0.10213528385526638,-0.0 +1957,0.3527071487498262,0.1463473543732393,0.5009454968769345,-0.0 +1958,-0.0,0.0829453173078142,0.9051023903737102,0.011952292318475596 +1959,-0.0,0.07567339907808804,0.9051023903737102,0.019224210548201753 +1960,-0.0,0.07487803302171173,0.9051023903737102,0.020019576604578065 +1961,-0.0,0.06385653195478298,0.9051023903737102,0.03104107767150681 +1962,0.15475157870884393,0.03226913714440991,0.8129792841467461,-0.0 +1963,0.547953060797852,0.06010694911758043,0.3919399900845676,-0.0 +1964,0.5475014511323594,0.061356810063314615,0.391141738804326,-0.0 +1965,0.3460835403225994,0.049653566662349054,0.6042628930150515,-0.0 +1966,-0.0,0.047267468493220145,0.9051023903737102,0.047630141133069646 +1967,-0.0,0.05851621700482783,0.9051023903737102,0.036381392621461964 +1968,-0.0,0.09839814354598232,0.9016018564540177,-0.0 +1969,-0.0,0.14123428686796308,0.858765713132037,-0.0 +1970,0.19765449693065376,0.10555643805336903,0.6967890650159773,-0.0 +1971,0.6987906890724256,0.2229297432300431,0.0782795676975313,-0.0 +1972,0.6062107076464149,0.30462519959212314,0.08916409276146187,-0.0 +1973,0.3630941710561591,0.32212325283240173,0.31478257611143917,-0.0 +1974,-0.0,0.27224243872537374,0.7277575612746263,-0.0 +1975,-0.0,0.2995121320868469,0.7004878679131531,-0.0 +1976,-0.0,0.33632621812483565,0.6636737818751643,-0.0 +1977,-0.0,0.35871009142571153,0.6412899085742885,-0.0 +1978,0.1679987955632975,0.37279943299580603,0.4592017714408965,-0.0 +1979,0.510469458561955,0.489530541438045,-0.0,-0.0 +1980,0.5052006791312065,0.4947993208687935,-0.0,-0.0 +1981,0.2741270669540903,0.6741295446400846,0.05174338840582515,-0.0 +1982,-0.0,0.5081252863021166,0.4918747136978834,-0.0 +1983,-0.0,0.5055119406883087,0.49448805931169126,-0.0 +1984,-0.0,0.5532339040708868,0.4467660959291132,-0.0 +1985,-0.0,0.5911842273322703,0.4088157726677297,-0.0 +1986,0.1482785068367814,0.6306116589840669,0.2211098341791517,-0.0 +1987,0.47012566177793735,0.5298743382220626,-0.0,-0.0 +1988,0.4836739517427194,0.5163260482572807,-0.0,-0.0 +1989,0.3350943717956095,0.6525410373955849,0.012364590808805587,-0.0 +1990,-0.0,0.49005911445014066,0.5099408855498593,-0.0 +1991,-0.0,0.3458706108013513,0.6541293891986487,-0.0 +1992,-0.0,0.24338201325114797,0.7566179867488521,-0.0 +1993,-0.0,0.2078177881588934,0.7921822118411066,-0.0 +1994,0.14300972740603282,0.14850620509768928,0.7084840674962779,-0.0 +1995,0.5217597001992734,0.18009359990806234,0.2981466998926642,-0.0 +1996,0.5910065155748261,0.1760031459038414,0.23299033852133255,-0.0 +1997,0.33539544490593803,0.1731625528453546,0.4914420022487073,-0.0 +1998,-0.0,0.15521000471571808,0.8447899952842819,-0.0 +1999,-0.0,0.10510194316401114,0.8948980568359889,-0.0 +2000,-0.0,0.08589953408864047,0.9051023903737102,0.008998075537649322 +2001,-0.0,0.09362594720772453,0.9051023903737102,0.0012716624185652586 +2002,0.18485888974169296,0.050789803885743764,0.7643513063725633,-0.0 +2003,0.6647694276053062,0.07442353813235385,0.26080703426234,-0.0 +2004,0.6281890447003947,0.07465078557703278,0.29716016972257253,-0.0 +2005,0.36264256139066636,0.08237719869611684,0.5549802399132169,-0.0 +2006,-0.0,0.0977164012119455,0.9022835987880545,-0.0 +2007,-0.0,0.08374068336419051,0.9051023903737102,0.011156926262099284 +2008,-0.0,0.06476552173349877,0.9051023903737102,0.030132087892791024 +2009,-0.0,0.0602205728399199,0.9051023903737102,0.034677036786369894 +2010,0.23468648950105808,0.023520110524270603,0.7417933999746713,-0.0 +2011,0.7695428699996207,0.07056033157281182,0.15989679842756743,-0.0 +2012,0.7537365317073751,0.06987858923877498,0.17638487905384992,-0.0 +2013,0.43505064442466823,0.09317145231836664,0.4717779032569651,-0.0 +2014,-0.0,0.20134123598554352,0.7986587640144565,-0.0 +2015,-0.0,0.19225133819838577,0.8077486618016142,-0.0 +2016,-0.0,0.13737108030842105,0.862628919691579,-0.0 +2017,-0.0,0.11680518656497671,0.8831948134350233,-0.0 +2018,0.2258048327463676,0.044086004267714946,0.7301091629859175,-0.0 +2019,0.8311123210617971,0.12919017229997912,0.0396975066382238,-0.0 +2020,0.733414096760202,0.1323716365254843,0.1342142667143137,-0.0 +2021,0.4016315291782058,0.1830478166888886,0.4153206541329056,-0.0 +2022,-0.0,0.24429100302986376,0.7557089969701363,-0.0 +2023,-0.0,0.2245204753427957,0.7754795246572043,-0.0 +2024,-0.0,0.189410745139899,0.810589254860101,-0.0 +2025,-0.0,0.1568007368284707,0.8431992631715293,-0.0 +2026,0.18681586495882813,0.09112622531625615,0.7220579097249158,-0.0 +2027,0.6914143978693775,0.13316700258186062,0.17541859954876182,-0.0 +2028,0.7483172157214623,0.12862205368828175,0.12306073059025602,-0.0 +2029,0.38462089844464614,0.10771528877781898,0.5076638127775348,-0.0 +2030,-0.0,0.1771393831272361,0.8228606168727639,-0.0 +2031,-0.0,0.18906987397288058,0.8109301260271194,-0.0 +2032,-0.0,0.14464299853814722,0.8553570014618528,-0.0 +2033,-0.0,0.10987413950226894,0.890125860497731,-0.0 +2034,0.1309668029928932,0.05056255644106482,0.818470640566042,-0.0 +2035,0.6915649344245418,0.09839814354598232,0.21003692202947588,-0.0 +2036,0.9253482045946144,0.07465179540538558,-0.0,-0.0 +2037,0.3737822664728205,0.08192270380675895,0.5442950297204205,-0.0 +2038,-0.0,0.2946263120262496,0.7053736879737504,-0.0 +2039,-0.0,0.24417737930752428,0.7558226206924757,-0.0 +2040,-0.0,0.15498275727103913,0.8450172427289608,-0.0 +2041,-0.0,0.115668949341582,0.884331050658418,-0.0 +2042,0.13277324165486415,0.05953883050588307,0.8076879278392528,-0.0 +2043,0.5619529604281268,0.06226579984203039,0.3757812397298428,-0.0 +2044,0.5568347175525425,0.07226468740790389,0.3709005950395535,-0.0 +2045,0.2267080520773531,0.10112511288212964,0.6721668350405172,-0.0 +2046,-0.0,0.2537217719840399,0.7462782280159601,-0.0 +2047,-0.0,0.27269693361473163,0.7273030663852684,-0.0 +2048,-0.0,0.2728105573370711,0.7271894426629288,-0.0 +2049,-0.0,0.21531695383329852,0.7846830461667015,-0.0 +2050,0.09333266420183194,0.13021278580103435,0.7764545499971337,-0.0 +2051,0.3463846134329279,0.13987080219988943,0.5137445843671826,-0.0 +2052,0.37799729001741933,0.2106583812173802,0.41134432876520044,-0.0 +2053,0.24733156013485466,0.23190601729486135,0.5207624225702839,-0.0 +2054,-0.0,0.33632621812483565,0.6636737818751643,-0.0 +2055,-0.0,0.38098034100424794,0.6190196589957521,-0.0 +2056,-0.0,0.3434845126322224,0.6565154873677777,-0.0 +2057,-0.0,0.31882816488455706,0.681171835115443,-0.0 +2058,0.14045060596824063,0.23417849174165078,0.6253709022901086,-0.0 +2059,0.49737277826266574,0.5026272217373342,-0.0,-0.0 +2060,0.506706044682849,0.49329395531715103,-0.0,-0.0 +2061,0.2644927274235786,0.5001716257383536,0.2353356468380678,-0.0 +2062,-0.0,0.3850707950084689,0.6149292049915311,-0.0 +2063,-0.0,0.30507969448148103,0.6949203055185189,-0.0 +2064,-0.0,0.21315810310884853,0.7868418968911515,-0.0 +2065,-0.0,0.18054809479742023,0.8194519052025797,-0.0 +2066,0.06352642627931142,0.18486579624632016,0.7516077774743685,-0.0 +2067,0.321245008720499,0.33450823856740414,0.3442467527120969,-0.0 +2068,0.3447287113261212,0.4139312204826947,0.2413400681911841,-0.0 +2069,0.17386972121470307,0.35018831225025115,0.4759419665350458,-0.0 +2070,-0.0,0.4435870120132968,0.5564129879867032,-0.0 +2071,-0.0,0.44847283207389405,0.551527167926106,-0.0 +2072,-0.0,0.47835587104917504,0.5216441289508249,-0.0 +2073,-0.0,0.42847505694214705,0.571524943057853,-0.0 +2074,0.11982709791073907,0.394956058852003,0.4852168432372579,-0.0 +2075,0.4213518179047219,0.578648182095278,-0.0,-0.0 +2076,0.342169589888329,0.4784694947715145,0.17936091534015652,-0.0 +2077,0.1019132478461939,0.4357469751718733,0.46233977698193285,-0.0 +2078,-0.0,0.43052028394425756,0.5694797160557424,-0.0 +2079,-0.0,0.4363150937835706,0.5636849062164294,-0.0 +2080,-0.0,0.36257329798525356,0.6374267020147464,-0.0 +2081,-0.0,0.26497052049564757,0.7350294795043524,-0.0 +2082,0.15159031105039478,0.17168544445494147,0.6767242444946637,-0.0 +2083,0.6545329418541376,0.2217935060066484,0.12367355213921405,-0.0 +2084,0.5992860261088596,0.17338980029003354,0.2273241736011068,-0.0 +2085,0.29113769768764997,0.1188504135670872,0.5900118887452629,-0.0 +2086,-0.0,0.10021612310341388,0.8997838768965861,-0.0 +2087,-0.0,0.1468018492625972,0.8531981507374028,-0.0 +2088,-0.0,0.15407376749232338,0.8459262325076766,-0.0 +2089,-0.0,0.13259888397016326,0.8674011160298367,-0.0 +2090,0.14346133707152553,0.039995550263493974,0.8165431126649805,-0.0 +2091,0.5301897472884711,0.04169990609858605,0.4281103466129428,-0.0 +2092,0.4455882032861654,0.06237942356436986,0.49203237314946474,-0.0 +2093,0.2519981933449462,0.0686287282930408,0.679373078362013,-0.0 +2094,-0.0,0.12373623362768447,0.8762637663723155,-0.0 +2095,-0.0,0.1439612562041104,0.8560387437958896,-0.0 +2096,-0.0,0.15373289632530496,0.846267103674695,-0.0 +2097,-0.0,0.16929934628581256,0.8307006537141874,-0.0 +2098,0.10447236928398608,0.12100926429153716,0.7745183664244768,-0.0 +2099,0.43896459485893863,0.2146352114992617,0.3464001936417997,-0.0 +2100,0.4719321004399083,0.297126033917718,0.23094186564237373,-0.0 +2101,0.26705184886137073,0.22963354284807191,0.5033146082905573,-0.0 +2102,-0.0,0.19850064292705671,0.8014993570729433,-0.0 +2103,-0.0,0.16748136672838101,0.832518633271619,-0.0 +2104,-0.0,0.1304400332457133,0.8695599667542867,-0.0 +2105,-0.0,0.10192047893850595,0.898079521061494,-0.0 +2106,0.14511723917833225,0.06737886734730661,0.7875038934743612,-0.0 +2107,0.6635651351639923,0.0853314154769431,0.2511034493590646,-0.0 +2108,0.5977806605572171,0.0810137140280432,0.32120562541473974,-0.0 +2109,0.29475057501159185,0.03692770976032824,0.6683217152280799,-0.0 +2110,-0.0,0.06522001662285665,0.9051023903737102,0.029677593003433145 +2111,-0.0,0.19145597214200952,0.8085440278579905,-0.0 +2112,-0.0,0.18418405391228332,0.8158159460877167,-0.0 +2113,-0.0,0.19566004986856994,0.8043399501314301,-0.0 +2114,0.08369832467132025,0.17441241379108877,0.741889261537591,-0.0 +2115,0.34758890587424185,0.2149760826662801,0.43743501145947805,-0.0 +2116,0.34427710166062847,0.21588507244499588,0.43983782589437564,-0.0 +2117,0.1326227050996999,0.1169188102873162,0.7504584846129839,-0.0 +2118,-0.0,0.15441463865934177,0.8455853613406582,-0.0 +2119,-0.0,0.12953104346699754,0.8704689565330025,-0.0 +2120,-0.0,0.10669267527676375,0.8933073247232363,-0.0 +2121,-0.0,0.09794364865662444,0.9020563513433756,-0.0 +2122,0.06683823049292481,0.06306116589840668,0.8701006036086685,-0.0 +2123,0.2792453098296746,0.061129562618635676,0.6596251275516898,-0.0 +2124,0.3817607038965255,0.05294865461019373,0.5652906414932808,-0.0 +2125,0.1847083531865287,0.051357922497441116,0.7639337243160302,-0.0 +2126,-0.0,0.0702194604057934,0.9051023903737102,0.024678149220496395 +2127,-0.0,0.131576270469108,0.868423729530892,-0.0 +2128,-0.0,0.15725523171782857,0.8427447682821714,-0.0 +2129,-0.0,0.17020833606452831,0.8297916639354717,-0.0 +2130,0.11937548824524633,0.12271362012662923,0.7579108916281244,-0.0 +2131,0.5378671116018476,0.2037273341546724,0.25840555424348,-0.0 +2132,0.5562325713318855,0.2665612526084002,0.17720617605971423,-0.0 +2133,0.26660023919587805,0.2396324304139454,0.4937673303901765,-0.0 +2134,-0.0,0.21134012355141701,0.788659876448583,-0.0 +2135,-0.0,0.18861537908352272,0.8113846209164772,-0.0 +2136,-0.0,0.1520285404902129,0.8479714595097871,-0.0 +2137,-0.0,0.11532807817456359,0.8846719218254364,-0.0 +2138,0.11124651426637709,0.05851621700482783,0.8302372687287951,-0.0 +2139,0.6004903185501735,0.037609452094365066,0.3619002293554614,-0.0 +2140,0.4907491698354389,0.052039664831477955,0.45721116533308315,-0.0 +2141,0.21857907809848384,0.057266356059093644,0.7241545658424224,-0.0 +2142,-0.0,0.07749137863551957,0.9051023903737102,0.017406230990770225 +2143,-0.0,0.06794698595900396,0.9051023903737102,0.02695062366728583 +2144,-0.0,0.07067395529515128,0.9051023903737102,0.024223654331138517 +2145,-0.0,0.06760611479198554,0.9051023903737102,0.02729149483430425 +2146,0.11305295292834805,0.026474327305096863,0.860472719766555,-0.0 +2147,0.5863398823647346,0.029769415252941536,0.3838907023823239,-0.0 +2148,0.39410470141999354,0.042268024710283396,0.5636272738697231,-0.0 +2149,0.20217059358558112,0.07567339907808804,0.7221560073363309,-0.0 +2150,-0.0,0.1499833134881024,0.8500166865118977,-0.0 +2151,-0.0,0.14464299853814722,0.8553570014618528,-0.0 +2152,-0.0,0.10407932966295591,0.8959206703370441,-0.0 +2153,-0.0,0.0853314154769431,0.9051023903737102,0.009566194149346688 +2154,0.14722475095063167,0.050789803885743764,0.8019854451636246,-0.0 +2155,0.7578010186968096,0.0749916567440512,0.16720732455913923,-0.0 +2156,0.5640604722004262,0.07351454835363806,0.36242497944593577,-0.0 +2157,0.3149224734036007,0.09623929282153237,0.588838233774867,-0.0 +2158,-0.0,0.14612010692856037,0.8538798930714396,-0.0 +2159,-0.0,0.11498720700754517,0.8850127929924548,-0.0 +2160,-0.0,0.07919573447061164,0.9051023903737102,0.015701875155678155 +2161,-0.0,0.058402593282488355,0.9051023903737102,0.03649501634380144 +2162,0.14586992195415346,0.031814642255052025,0.8223154357907945,-0.0 +2163,0.7466613136146555,0.05044893271872535,0.2028897536666192,-0.0 +2164,0.6656726469362917,0.05794809839313047,0.2763792546705779,-0.0 +2165,0.3269653978167403,0.10192047893850595,0.5711141232447537,-0.0 +2166,-0.0,0.2240659804534378,0.7759340195465622,-0.0 +2167,-0.0,0.1706628309538862,0.8293371690461138,-0.0 +2168,-0.0,0.13384874491589743,0.8661512550841026,-0.0 +2169,-0.0,0.11907766101176614,0.8809223389882339,-0.0 +2170,0.15159031105039478,0.06624263012391189,0.7821670588256934,-0.0 +2171,0.8034135949115758,0.16020944849865482,0.03637695658976936,-0.0 +2172,0.622318119048989,0.1730489291230151,0.2046329518279959,-0.0 +2173,0.3072451090902242,0.19804614803769885,0.494708742872077,-0.0 +2174,-0.0,0.30769304009528886,0.6923069599047111,-0.0 +2175,-0.0,0.25428989059573726,0.7457101094042627,-0.0 +2176,-0.0,0.18554753858035697,0.814452461419643,-0.0 +2177,-0.0,0.1312353993020896,0.8687646006979104,-0.0 +2178,0.14737528750579593,0.06249304728670933,0.7901316652074948,-0.0 +2179,0.8348757349409032,0.13691658541906318,0.028207679640033678,-0.0 +2180,0.8398434412613232,0.13180351791378697,0.028353040824889875,-0.0 +2181,0.35421251430146866,0.15225578793489183,0.49353169776363953,-0.0 +2182,-0.0,0.2025910969312777,0.7974089030687224,-0.0 +2183,-0.0,0.15032418465512082,0.8496758153448791,-0.0 +2184,-0.0,0.10510194316401114,0.8948980568359889,-0.0 +2185,-0.0,0.08874012714712726,0.9051023903737102,0.006157482479162535 +2186,0.17462240399052428,0.05976607795056202,0.7656115180589137,-0.0 +2187,0.928810545363392,0.07118945463660797,-0.0,-0.0 +2188,0.9948960930804958,0.005103906919504242,-0.0,-0.0 +2189,0.40825513760543264,0.06658350129093031,0.5251613611036371,-0.0 +2190,-0.0,0.09305782859602717,0.9051023903737102,0.0018397810302626244 +2191,-0.0,0.07760500235785904,0.9051023903737102,0.01729260726843075 +2192,-0.0,0.061356810063314615,0.9051023903737102,0.033540799562975177 +2193,-0.0,0.0634020370654251,0.9051023903737102,0.03149557256086469 +2194,0.15008494549875231,0.0351097302028967,0.8148053242983511,-0.0 +2195,0.8500799270124919,0.02851955430720735,0.12140051868030077,-0.0 +2196,0.9697564883680667,0.030243511631933284,-0.0,-0.0 +2197,0.38522304466530316,0.044994994046430724,0.5697819612882662,-0.0 +2198,-0.0,0.13509860586163164,0.8649013941383683,-0.0 +2199,-0.0,0.14339313759241307,0.8566068624075869,-0.0 +2200,-0.0,0.11816867123305036,0.8818313287669497,-0.0 +2201,-0.0,0.08680852386735623,0.9051023903737102,0.008089085758933565 +2202,0.13412807065134236,0.05499388161230421,0.8108780477363534,-0.0 +2203,0.8014566196944407,0.02999666269762048,0.16854671760793882,-0.0 +2204,0.8643808997530953,0.04010917398583344,0.09550992626107124,-0.0 +2205,0.289331259025679,0.023633734246610073,0.6870350067277109,-0.0 +2206,-0.0,0.037609452094365066,0.9051023903737102,0.057288157531924726 +2207,-0.0,0.05385764438890949,0.9051023903737102,0.0410399652373803 +2208,-0.0,0.07601427024510644,0.9051023903737102,0.018883339381183348 +2209,-0.0,0.11180574278203997,0.88819425721796,-0.0 +2210,0.07150486370301641,0.09419406581942188,0.8343010704775616,-0.0 +2211,0.46500741890235303,0.057834474670791,0.4771581064268559,-0.0 +2212,0.5195016518718097,0.05442576300060685,0.4260725851275834,-0.0 +2213,0.21120278689543584,0.0374958283720256,0.7513013847325386,-0.0 +2214,-0.0,0.04692659732620174,0.9051023903737102,0.04797101230008805 +2215,-0.0,0.04999443782936746,0.9051023903737102,0.04490317179692233 +2216,-0.0,0.04976719038468852,0.9051023903737102,0.04513041924160127 +2217,-0.0,0.05453938672294632,0.9051023903737102,0.04035822290334347 +2218,0.08896710410206883,0.05363039694423055,0.8574024989537006,-0.0 +2219,0.6217159728283321,0.05397126811124897,0.3243127590604189,-0.0 +2220,0.718059368133449,0.07885486330359323,0.2030857685629578,-0.0 +2221,0.2294177100703095,0.08374068336419051,0.6868416065655,-0.0 +2222,-0.0,0.26190267999248185,0.7380973200075182,-0.0 +2223,-0.0,0.263152540938216,0.736847459061784,-0.0 +2224,-0.0,0.2613345613807845,0.7386654386192155,-0.0 +2225,-0.0,0.27110620150197906,0.7288937984980209,-0.0 +2226,0.05027920942485785,0.26212992743716074,0.6875908631379815,-0.0 +2227,0.3239546667134554,0.31985077838561227,0.3561945549009323,-0.0 +2228,0.3090515477521951,0.2825821974582656,0.40836625478953925,-0.0 +2229,0.10507451550464306,0.23247413590655872,0.6624513485887982,-0.0 +2230,-0.0,0.3225777477217596,0.6774222522782404,-0.0 +2231,-0.0,0.2714470726689974,0.7285529273310025,-0.0 +2232,-0.0,0.23338312568527447,0.7666168743147255,-0.0 +2233,-0.0,0.2216798822843089,0.7783201177156911,-0.0 +2234,0.03883843123237523,0.2089540253822881,0.7522075433853367,-0.0 +2235,0.22174034575693297,0.2170213096683906,0.5612383445746765,-0.0 +2236,0.19524591204802585,0.19895513781641463,0.6057989501355595,-0.0 +2237,0.08354778811615601,0.1423705240913578,0.7740816877924862,-0.0 +2238,-0.0,0.18122983713145707,0.8187701628685429,-0.0 +2239,-0.0,0.1571416079954891,0.842858392004511,-0.0 +2240,-0.0,0.11339647489479257,0.8866035251052075,-0.0 +2241,-0.0,0.0829453173078142,0.9051023903737102,0.011952292318475596 +2242,0.0427523816666456,0.06760611479198554,0.8896415035413688,-0.0 +2243,0.23032092940129495,0.042268024710283396,0.7274110458884218,-0.0 +2244,0.1857621090726784,0.02499721891468373,0.7892406720126378,-0.0 +2245,0.09002085998821854,0.01499833134881024,0.8949808086629713,-0.0 +2246,-0.0,0.03170101853271255,0.9051023903737102,0.06319659109357724 +2247,-0.0,0.03317812692312568,0.9051023903737102,0.06171948270316411 +2248,-0.0,0.026815198472115276,0.9051023903737102,0.06808241115417452 +2249,-0.0,0.027951435695509994,0.9051023903737102,0.0669461739307798 +2250,0.0367309194600758,0.02874680175188629,0.9051023903737102,0.029419888414327702 +2251,0.28451408926042315,0.03885931304009926,0.6766265976994776,-0.0 +2252,0.3113095960796588,0.03215551342207044,0.6565348904982707,-0.0 +2253,0.1403000694130764,0.052494159720835834,0.8072057708660878,-0.0 +2254,-0.0,0.13475773469461322,0.8652422653053868,-0.0 +2255,-0.0,0.12100926429153716,0.8789907357084629,-0.0 +2256,-0.0,0.115668949341582,0.884331050658418,-0.0 +2257,-0.0,0.12078201684685823,0.8792179831531418,-0.0 +2258,0.05554798885560643,0.10055699427043228,0.8438950168739613,-0.0 +2259,0.5029426308037428,0.1539601437699839,0.3430972254262733,-0.0 +2260,0.6196084610560327,0.18816088419416482,0.1922306547498024,-0.0 +2261,0.21466512766421347,0.23076978007146665,0.5545650922643199,-0.0 +2262,-0.0,0.34007580096203827,0.6599241990379617,-0.0 +2263,-0.0,0.2796279806774394,0.7203720193225607,-0.0 +2264,-0.0,0.24417737930752428,0.7558226206924757,-0.0 +2265,-0.0,0.22327061439706153,0.7767293856029385,-0.0 +2266,0.08791334821591912,0.18895625025054114,0.7231304015335398,-0.0 +2267,0.7642740905688723,0.2357259094311277,-0.0,-0.0 +2268,0.8457143669127287,0.15428563308727128,-0.0,-0.0 +2269,0.2888796493601863,0.336894336736533,0.3742260139032807,-0.0 +2270,-0.0,0.3591645863150694,0.6408354136849306,-0.0 +2271,-0.0,0.27269693361473163,0.7273030663852684,-0.0 +2272,-0.0,0.2146352114992617,0.7853647885007383,-0.0 +2273,-0.0,0.17850286779530974,0.8214971322046902,-0.0 +2274,0.0931821276466677,0.13259888397016326,0.774218988383169,-0.0 +2275,0.8281015899585121,0.17189841004148787,-0.0,-0.0 +2276,0.8461659765782216,0.15146042187851552,0.0023736015432629376,-0.0 +2277,0.21556834699519892,0.14657460181791826,0.6378570511868829,-0.0 +2278,-0.0,0.253267277094682,0.746732722905318,-0.0 +2279,-0.0,0.17634401707085978,0.8236559829291402,-0.0 +2280,-0.0,0.13214438908080536,0.8678556109191946,-0.0 +2281,-0.0,0.13305337885952115,0.8669466211404788,-0.0 +2282,0.04004272367368918,0.11078312928098473,0.849174147045326,-0.0 +2283,0.2762345787263897,0.12146375918089504,0.6023016620927153,-0.0 +2284,0.1950953754928616,0.07544615163340908,0.7294584728737293,-0.0 +2285,0.06427910905513265,0.08953549320350354,0.8461853977413638,-0.0 +2286,-0.0,0.0901036118152009,0.9051023903737102,0.004793997811088885 +2287,-0.0,0.06351566078776458,0.9051023903737102,0.031381948838525214 +2288,-0.0,0.06817423340368291,0.9051023903737102,0.026723376222606884 +2289,-0.0,0.08374068336419051,0.9051023903737102,0.011156926262099284 +2290,0.02002136183684459,0.09635291654387185,0.8836257216192835,-0.0 +2291,0.13171948576871442,0.15725523171782857,0.711025282513457,-0.0 +2292,0.09845090707741627,0.15361927260296546,0.7479298203196183,-0.0 +2293,0.047268478321572945,0.1048746957193322,0.8478568259590948,-0.0 +2294,-0.0,0.12691769785318968,0.8730823021468104,-0.0 +2295,-0.0,0.09748915376726656,0.9025108462327335,-0.0 +2296,-0.0,0.08794476109075094,0.9051023903737102,0.006952848535538847 +2297,-0.0,0.11419184095116887,0.8858081590488311,-0.0 +2298,0.01098916852698989,0.14725634415195507,0.841754487321055,-0.0 +2299,0.08610690955394817,0.1860020334697149,0.7278910569763369,-0.0 +2300,0.06744037671358179,0.18975161630691745,0.7428080069795007,-0.0 +2301,0.04470935688378078,0.16657237694966526,0.788718266166554,-0.0 +2302,-0.0,0.159527706164618,0.840472293835382,-0.0 +2303,-0.0,0.08885375086946673,0.9051023903737102,0.006043858756823062 +2304,-0.0,0.06646987756859084,0.9051023903737102,0.028427732057698954 +2305,-0.0,0.054312139278267384,0.9051023903737102,0.04058547034802241 +2306,0.010537558861497156,0.042836143321980755,0.9051023903737102,0.04152390744281188 +2307,0.08911764065723309,0.032382760866749384,0.8784995984760176,-0.0 +2308,0.0852036902229627,0.028292306862528406,0.8865040029145089,-0.0 +2309,0.05344047708330699,0.03158739481037308,0.9051023903737102,0.00986973773260972 +2310,-0.0,0.035905096259273,0.9051023903737102,0.05899251336701679 +2311,-0.0,0.02999666269762048,0.9051023903737102,0.06490094692866931 +2312,-0.0,0.022497497023215362,0.9051023903737102,0.07240011260307443 +2313,-0.0,0.0171571820732602,0.9051023903737102,0.0777404275530296 +2314,0.01159131474764687,0.01806617185197597,0.9051023903737102,0.06524012302666696 +2315,0.15249353038138025,0.008862650342478778,0.838643819276141,-0.0 +2316,0.11666583025228992,0.005113067505276217,0.8782211022424339,-0.0 +2317,0.051634038421336056,0.005908433561652518,0.9051023903737102,0.037355137643301214 +2318,-0.0,0.01738442951793914,0.9051023903737102,0.07751318010835065 +2319,-0.0,0.022838368190233774,0.9051023903737102,0.07205924143605602 +2320,-0.0,0.030337533864638895,0.9051023903737102,0.0645600757616509 +2321,-0.0,0.038404818150741364,0.9051023903737102,0.05649279147554843 +2322,0.01159131474764687,0.033291750645465155,0.9051023903737102,0.05001454423317776 +2323,0.183052451079722,0.02090676491046276,0.7960407840098153,-0.0 +2324,0.15460104215367967,0.015339202515828654,0.8300597553304917,-0.0 +2325,0.05103189220067908,0.013521222958397111,0.9051023903737102,0.0303444944672136 +2326,-0.0,0.025906208693399504,0.9051023903737102,0.06899140093289029 +2327,-0.0,0.02988303897528101,0.9051023903737102,0.06501457065100878 +2328,-0.0,0.03681408603798877,0.9051023903737102,0.058083523588301024 +2329,-0.0,0.04692659732620174,0.9051023903737102,0.04797101230008805 +2330,0.015354728626752998,0.04715384477088068,0.9051023903737102,0.03238903622865612 +2331,0.21195546967125706,0.02045227002110487,0.7675922603076382,-0.0 +2332,0.1871169380691566,0.01670268718390231,0.796180374746941,-0.0 +2333,0.07541881413728678,0.013748470403076052,0.9051023903737102,0.0057303250859269544 +2334,-0.0,0.02488359519234426,0.9051023903737102,0.07001401443394553 +2335,-0.0,0.014089341570094466,0.9051023903737102,0.08080826805619533 +2336,-0.0,0.008521779175460363,0.9051023903737102,0.08637583045082943 +2337,-0.0,0.011021501066928736,0.9051023903737102,0.08387610855936106 +2338,0.01129024163731838,0.022838368190233774,0.9051023903737102,0.06076899979873764 +2339,0.19554698515835434,0.015566449960507597,0.7888865648811381,-0.0 +2340,0.1539988959330227,0.03931380792945714,0.8066872961375201,-0.0 +2341,0.05359101363847124,0.03931380792945714,0.9051023903737102,0.0019927880583614083 +2342,-0.0,0.06726524362496714,0.9051023903737102,0.027632366001322656 +2343,-0.0,0.05851621700482783,0.9051023903737102,0.036381392621461964 +2344,-0.0,0.052266912276156895,0.9051023903737102,0.0426306973501329 +2345,-0.0,0.039768302818815035,0.9051023903737102,0.05512930680747476 +2346,0.009333266420183194,0.05294865461019373,0.9051023903737102,0.03261568859591287 +2347,0.22836395418415978,0.06215217611969091,0.7094838696961493,-0.0 +2348,0.27969691949516734,0.10601093294272691,0.6142921475621057,-0.0 +2349,0.08339725156099176,0.11441908839584781,0.8021836600431604,-0.0 +2350,-0.0,0.17520777984746508,0.824792220152535,-0.0 +2351,-0.0,0.22508859395449307,0.7749114060455069,-0.0 +2352,-0.0,0.22327061439706153,0.7767293856029385,-0.0 +2353,-0.0,0.21633956733435378,0.7836604326656462,-0.0 +2354,0.016709557623231203,0.217362180835409,0.7659282615413597,-0.0 +2355,0.23619185505270052,0.2156578250003169,0.5481503199469826,-0.0 +2356,0.19690181415483254,0.2706517066126211,0.5324464792325464,-0.0 +2357,0.04501042999410927,0.3051933182038205,0.6497962518020702,-0.0 +2358,-0.0,0.3475749666364434,0.6524250333635566,-0.0 +2359,-0.0,0.30860202987400465,0.6913979701259954,-0.0 +2360,-0.0,0.2933764510805154,0.7066235489194845,-0.0 +2361,-0.0,0.2787189908987236,0.7212810091012765,-0.0 +2362,0.01399989963027479,0.2645160256062897,0.7214840747634355,-0.0 +2363,0.48487824418403336,0.22724744467894303,0.28787431113702366,-0.0 +2364,0.3408147608918508,0.19009248747393584,0.4690927516342134,-0.0 +2365,0.09002085998821854,0.09078535414923773,0.8191937858625438,-0.0 +2366,-0.0,0.07692326002382222,0.9051023903737102,0.017974349602467576 +2367,-0.0,0.06601538267923295,0.9051023903737102,0.028882226947056847 +2368,-0.0,0.08930824575882461,0.9051023903737102,0.005589363867465183 +2369,-0.0,0.11839591867772932,0.8816040813222706,-0.0 +2370,0.012946143744125076,0.1172596814543346,0.8697941748015403,-0.0 +2371,0.3953089938613075,0.061129562618635676,0.5435614435200569,-0.0 +2372,0.47765248953614964,0.09544392676515608,0.42690358369869424,-0.0 +2373,0.09303159109150345,0.07908211074827216,0.8278862981602243,-0.0 +2374,-0.0,0.15725523171782857,0.8427447682821714,-0.0 +2375,-0.0,0.1639590313358574,0.8360409686641426,-0.0 +2376,-0.0,0.159527706164618,0.840472293835382,-0.0 +2377,-0.0,0.1375983277531,0.8624016722469,-0.0 +2378,0.006322535316898292,0.14225690036901834,0.8514205643140833,-0.0 +2379,0.229116636959981,0.11316922745011362,0.6577141355899054,-0.0 +2380,0.2399552689318066,0.12316811501598711,0.6368766160522062,-0.0 +2381,0.06954788848588121,0.06567451151221453,0.8647776000019043,-0.0 +2382,-0.0,0.04431325171239389,0.9051023903737102,0.0505843579138959 +2383,-0.0,0.0367004623156493,0.9051023903737102,0.05819714731064049 +2384,-0.0,0.048517329438954336,0.9051023903737102,0.046380280187335456 +2385,-0.0,0.06374290823244351,0.9051023903737102,0.031154701393846282 +2386,0.009333266420183194,0.0713556976291881,0.9051023903737102,0.014208645576918498 +2387,0.4616956146887396,0.08010472424932741,0.45819966106193305,-0.0 +2388,0.6318019220243366,0.09828451982364285,0.26991355815202056,-0.0 +2389,0.12328943867951671,0.12328173873832658,0.7534288225821567,-0.0 +2390,-0.0,0.2795143569550999,0.7204856430449,-0.0 +2391,-0.0,0.22270249578536416,0.7772975042146358,-0.0 +2392,-0.0,0.16770861417305996,0.8322913858269401,-0.0 +2393,-0.0,0.12634957924149232,0.8736504207585076,-0.0 +2394,0.00948380297534744,0.1001024993810744,0.8904136976435781,-0.0 +2395,0.4555236159270055,0.07590064652276697,0.4685757375502275,-0.0 +2396,0.5845334437027636,0.09317145231836664,0.32229510397886973,-0.0 +2397,0.10131110162553693,0.11748692889901355,0.7812019694754495,-0.0 +2398,-0.0,0.2731514285040895,0.7268485714959105,-0.0 +2399,-0.0,0.18520666741333858,0.8147933325866614,-0.0 +2400,-0.0,0.12135013545855558,0.8786498645414444,-0.0 +2401,-0.0,0.11930490845644508,0.880695091543555,-0.0 +2402,0.006473071872062538,0.12919017229997912,0.8643367558279583,-0.0 +2403,0.3323847138026531,0.11771417634369248,0.5499011098536544,-0.0 +2404,0.43309366920753306,0.12225912523727135,0.4446472055551956,-0.0 +2405,0.07331130236498735,0.1316898941914475,0.7949988034435651,-0.0 +2406,-0.0,0.1817979557431544,0.8182020442568456,-0.0 +2407,-0.0,0.12305449129364764,0.8769455087063524,-0.0 +2408,-0.0,0.07396904324299595,0.9051023903737102,0.020928566383293837 +2409,-0.0,0.04306339076665971,0.9051023903737102,0.051834218859630084 +2410,0.0033118042136133915,0.02942854408592312,0.9051023903737102,0.06215726132675328 +2411,0.24898746224166132,0.0054539386722946325,0.745558599086044,-0.0 +2412,0.25636375344470935,0.009203521509497192,0.7344327250457934,-0.0 +2413,0.05283833086265002,0.003067840503165731,0.9051023903737102,0.03899143826047404 +2414,-0.0,0.007044670785047233,0.9051023903737102,0.08785293884124255 +2415,-0.0,0.015566449960507597,0.9051023903737102,0.07933115966578219 +2416,-0.0,0.018975161630691743,0.9051023903737102,0.07592244799559805 +2417,-0.0,0.03863206559542032,0.9051023903737102,0.056265544030869474 +2418,0.0009032193309854703,0.04522224149110966,0.9051023903737102,0.04877214880419466 +2419,0.1734181115492103,0.025906208693399504,0.8006756797573902,-0.0 +2420,0.2815033581571383,0.02931492036358365,0.6891817214792781,-0.0 +2421,0.043203991332138335,0.06658350129093031,0.8902125073769314,-0.0 +2422,-0.0,0.15918683499759959,0.8408131650024004,-0.0 +2423,-0.0,0.16418627878053632,0.8358137212194636,-0.0 +2424,-0.0,0.13816644636479736,0.8618335536352026,-0.0 +2425,-0.0,0.10839703111185582,0.8916029688881442,-0.0 +2426,0.004215023544598862,0.10714717016612163,0.8886378062892795,-0.0 +2427,0.4427280087380447,0.14293864270305515,0.4143333485589002,-0.0 +2428,0.6379739207860706,0.13259888397016326,0.2294271952437661,-0.0 +2429,0.08189188600934931,0.1787301152399887,0.739377998750662,-0.0 +2430,-0.0,0.2975805288070759,0.7024194711929241,-0.0 +2431,-0.0,0.2278155632906404,0.7721844367093595,-0.0 +2432,-0.0,0.1711173258432441,0.8288826741567559,-0.0 +2433,-0.0,0.11862316612240827,0.8813768338775917,-0.0 +2434,0.007376291203048008,0.09635291654387185,0.89627079225308,-0.0 +2435,0.6525759666370023,0.027951435695509994,0.3194725976674877,-0.0 +2436,0.7203174164609126,0.02181575468917853,0.25786682884990886,-0.0 +2437,0.10582719828046429,0.021702130966839057,0.8724706707526967,-0.0 +2438,-0.0,0.04272251959964129,0.9051023903737102,0.0521750900266485 +2439,-0.0,0.07805949724721692,0.9051023903737102,0.016838112379072873 +2440,-0.0,0.06976496551643552,0.9051023903737102,0.025132644109854274 +2441,-0.0,0.042381648432622876,0.9051023903737102,0.052515961193666916 +2442,0.007526827758212253,0.03295087947844674,0.9051023903737102,0.05441990238963079 +2443,0.5517164746769582,0.004772196338257803,0.44351132898478407,-0.0 +2444,0.5958236853400819,0.004090454004220975,0.4000858606556972,-0.0 +2445,0.09137568898469675,0.014657460181791825,0.8939668508335115,-0.0 +2446,-0.0,0.10930602089057159,0.8906939791094284,-0.0 +2447,-0.0,0.10816978366717689,0.8918302163328231,-0.0 +2448,-0.0,0.08510416803226416,0.9051023903737102,0.009793441594025634 +2449,-0.0,0.060447820284598844,0.9051023903737102,0.03444978934169095 +2450,0.006473071872062538,0.05056255644106482,0.9051023903737102,0.037861981313162436 +2451,0.5676733495243681,0.010794253622249794,0.42153239685338206,-0.0 +2452,0.6376728476757421,0.0027269693361473162,0.3596001829881106,-0.0 +2453,0.0928810545363392,0.004772196338257803,0.902346749125403,-0.0 +2454,-0.0,0.02954216780826259,0.9051023903737102,0.0653554418180272 +2455,-0.0,0.056357366280377866,0.9051023903737102,0.038540243345911926 +2456,-0.0,0.08464967314290628,0.9051023903737102,0.010247936483383513 +2457,-0.0,0.0953303030428166,0.9046696969571834,-0.0 +2458,0.0037634138791061266,0.09487580815345872,0.9013607779674351,-0.0 +2459,0.48758790217698983,0.035564225092254584,0.47684787273075563,-0.0 +2460,0.550963791901137,0.042608895877301815,0.40642731222156125,-0.0 +2461,0.05991354895536954,0.0602205728399199,0.8798658782047106,-0.0 +2462,-0.0,0.15089230326681816,0.8491076967331819,-0.0 +2463,-0.0,0.13896181242117367,0.8610381875788263,-0.0 +2464,-0.0,0.11600982050860041,0.8839901794913996,-0.0 +2465,-0.0,0.10123873660446911,0.8987612633955309,-0.0 +2466,0.0015053655516424507,0.10646542783208482,0.8920292066162727,-0.0 +2467,0.5058028253518635,0.026587951027436333,0.4676092236207001,-0.0 +2468,0.5542755961147504,0.03738220464968613,0.4083421992355635,-0.0 +2469,0.08339725156099176,0.049312695495330634,0.8672900529436776,-0.0 +2470,-0.0,0.16123206199971007,0.8387679380002899,-0.0 +2471,-0.0,0.20531806626742502,0.794681933732575,-0.0 +2472,-0.0,0.17759387801659396,0.822406121983406,-0.0 +2473,-0.0,0.1870246469707701,0.8129753530292299,-0.0 +2474,0.002258048327463676,0.24201852858307432,0.755723423089462,-0.0 +2475,0.23032092940129495,0.15816422149654433,0.6115148491021607,-0.0 +2476,0.3013741834388186,0.18054809479742023,0.5180777217637611,-0.0 +2477,0.04184916233566013,0.1942965652004963,0.7638542724638435,-0.0 +2478,-0.0,0.28371843468166036,0.7162815653183396,-0.0 +2479,-0.0,0.3191690360515755,0.6808309639484245,-0.0 +2480,-0.0,0.35814197281401416,0.6418580271859858,-0.0 +2481,-0.0,0.3484839564151591,0.6515160435848408,-0.0 +2482,0.00045160966549273514,0.26497052049564757,0.7345778698388596,-0.0 +2483,0.16182679680156345,0.12509971829575814,0.7130734849026784,-0.0 +2484,0.22490161341538215,0.15691436055081015,0.6181840260338077,-0.0 +2485,0.027849262705385337,0.3329175064546515,0.6392332308399632,-0.0 +2486,-0.0,0.5632327916367602,0.4367672083632398,-0.0 +2487,-0.0,0.714011471181239,0.285988528818761,-0.0 +2488,-0.0,0.755938624724504,0.24406137527549598,-0.0 +2489,-0.0,0.5661870084175865,0.4338129915824135,-0.0 +2490,0.002107511772299431,0.37859424283511905,0.6192982453925816,-0.0 +2491,0.5637593990900978,0.18861537908352272,0.24762522182637947,-0.0 +2492,0.6251783135971097,0.093739570930064,0.28108211547282624,-0.0 +2493,0.04365560099763107,0.10294309243956119,0.8534013065628077,-0.0 +2494,-0.0,0.25826672087761876,0.7417332791223812,-0.0 +2495,-0.0,0.37268580927346656,0.6273141907265334,-0.0 +2496,-0.0,0.46006245175252014,0.5399375482474799,-0.0 +2497,-0.0,0.49108172795119587,0.5089182720488041,-0.0 +2498,0.0009032193309854703,0.49551305312243527,0.5035837275465793,-0.0 +2499,0.26163253287545796,0.5151699570871638,0.2231975100373782,-0.0 +2500,0.1630310892428774,0.7062850580621549,0.13068385269496768,-0.0 +2501,0.020623508057501575,0.7530980316660171,0.22627846027648135,-0.0 +2502,-0.0,0.7642331564552853,0.23576684354471467,-0.0 +2503,-0.0,0.7834355655306561,0.21656443446934392,-0.0 +2504,-0.0,0.7959341749879979,0.2040658250120021,-0.0 +2505,-0.0,0.8350207354727761,0.1649792645272239,-0.0 +2506,0.0007526827758212254,0.8307030340238762,0.16854428320030254,-0.0 +2507,0.19885878937196771,0.7637786615659276,0.03736254906210473,-0.0 +2508,0.18591264562784265,0.7218515080226625,0.09223584634949483,-0.0 +2509,0.028300872370878073,0.6105002601299804,0.36119886749914154,-0.0 +2510,-0.0,0.530395535880653,0.469604464119347,-0.0 +2511,-0.0,0.5210783906488163,0.47892160935118366,-0.0 +2512,-0.0,0.4714248239864673,0.5285751760135327,-0.0 +2513,-0.0,0.4172263084305394,0.5827736915694606,-0.0 +2514,0.00045160966549273514,0.39245633696053456,0.6070920533739728,-0.0 +2515,0.21210600622642128,0.3533697764757564,0.4345242172978223,-0.0 +2516,0.31085798641416607,0.36779998921286927,0.32134202437296466,-0.0 +2517,0.039440577453032206,0.32644095428130165,0.6341184682656662,-0.0 +2518,-0.0,0.3481430852481407,0.6518569147518594,-0.0 +2519,-0.0,0.34337088890988293,0.656629111090117,-0.0 +2520,-0.0,0.34734771919176444,0.6526522808082356,-0.0 +2521,-0.0,0.3573466067576379,0.6426533932423621,-0.0 +2522,0.00030107311032849017,0.3673454943235114,0.6323534325661602,-0.0 +2523,0.2542562416724099,0.30451157586978367,0.4412321824578065,-0.0 +2524,0.3545135874117971,0.3234867375004754,0.3219996750877275,-0.0 +2525,0.03402126146711938,0.15907321127526014,0.8069055272576204,-0.0 +2526,-0.0,0.1383936938094763,0.8616063061905237,-0.0 +2527,-0.0,0.20634067976848028,0.7936593202315197,-0.0 +2528,-0.0,0.24349563697348744,0.7565043630265126,-0.0 +2529,-0.0,0.2746285368945026,0.7253714631054974,-0.0 +2530,0.00015053655516424508,0.3921154657935162,0.6077339976513195,-0.0 +2531,0.24296600003509153,0.4020007296370502,0.35503327032785825,-0.0 +2532,0.2655464833097283,0.5055119406883087,0.22894157600196297,-0.0 +2533,0.01339775340961781,0.5353949796635897,0.45120726692679247,-0.0 +2534,-0.0,0.5883436342737836,0.41165636572621644,-0.0 +2535,-0.0,0.7103755120663758,0.28962448793362416,-0.0 +2536,-0.0,0.7303732871981229,0.2696267128018771,-0.0 +2537,-0.0,0.7104891357887153,0.2895108642112847,-0.0 +2538,-0.0,0.7031035938366498,0.2968964061633502,-0.0 +2539,0.327417007482233,0.672582992517767,-0.0,-0.0 +2540,0.2626862887616076,0.7373137112383924,-0.0,-0.0 +2541,0.014150436185439036,0.582207953267452,0.40364161054710895,-0.0 +2542,-0.0,0.46403928203440165,0.5359607179655983,-0.0 +2543,-0.0,0.39984187891260026,0.6001581210873997,-0.0 +2544,-0.0,0.4156355763177868,0.5843644236822132,-0.0 +2545,-0.0,0.49642204290115105,0.503577957098849,-0.0 +2546,0.006473071872062538,0.622203503530946,0.37132342459699147,-0.0 +2547,0.07571988724761526,0.7176474302961021,0.20663268245628263,-0.0 +2548,0.1496333358332596,0.763892285288267,0.08647437887847337,-0.0 +2549,0.015655801737081487,0.723896735024773,0.2604474632381455,-0.0 +2550,-0.0,0.5485753314549685,0.4514246685450315,-0.0 +2551,-0.0,0.41109062742420793,0.5889093725757921,-0.0 +2552,-0.0,0.3551877560331879,0.644812243966812,-0.0 +2553,-0.0,0.3754127786096139,0.624587221390386,-0.0 +2554,0.00015053655516424508,0.47437904076729354,0.5254704226775422,-0.0 +2555,0.24838531602100436,0.48880925350440646,0.26280543047458926,-0.0 +2556,0.2959548674529058,0.4914225991182143,0.21262253342887993,-0.0 +2557,0.018064386619709407,0.44233715106756255,0.539598462312728,-0.0 +2558,-0.0,0.4552902554142623,0.5447097445857376,-0.0 +2559,-0.0,0.3840481815074137,0.6159518184925863,-0.0 +2560,-0.0,0.340984790740754,0.6590152092592461,-0.0 +2561,-0.0,0.3462114819683697,0.6537885180316303,-0.0 +2562,0.00015053655516424508,0.37984410378085326,0.6200053596639824,-0.0 +2563,0.11967656135557483,0.43052028394425756,0.44980315470016763,-0.0 +2564,0.15023548205391657,0.46119868897591487,0.38856582897016856,-0.0 +2565,0.004516096654927352,0.46835698348330157,0.5271269198617711,-0.0 +2566,-0.0,0.616181446246954,0.38381855375304597,-0.0 +2567,-0.0,0.9055810670455879,0.09441893295441206,-0.0 +2568,-0.0,1.0,-0.0,-0.0 +2569,-0.0,1.0,-0.0,-0.0 +2570,-0.0,1.0,-0.0,-0.0 +2571,0.20247166669590963,0.7975283333040903,-0.0,-0.0 +2572,0.30829886497637393,0.6917011350236261,-0.0,-0.0 +2573,0.01716116728872394,0.6661758840763216,0.3166629486349545,-0.0 +2574,-0.0,0.7630969192318906,0.2369030807681094,-0.0 +2575,-0.0,0.9013769893190275,0.09862301068097246,-0.0 +2576,-0.0,0.9069445517136616,0.09305544828633838,-0.0 +2577,-0.0,0.9370548381336216,0.06294516186637844,-0.0 +2578,-0.0,0.9623929282153236,0.03760707178467637,-0.0 +2579,0.22294463819824695,0.777055361801753,-0.0,-0.0 +2580,0.22204141886726148,0.7779585811327385,-0.0,-0.0 +2581,0.01008594919600442,0.7890031279252901,0.20091092287870543,-0.0 +2582,-0.0,0.6080005382385121,0.3919994617614879,-0.0 +2583,-0.0,0.44972269301962825,0.5502773069803717,-0.0 +2584,-0.0,0.41983965404434725,0.5801603459556528,-0.0 +2585,-0.0,0.4043868278061791,0.5956131721938209,-0.0 +2586,-0.0,0.4160900712071447,0.5839099287928553,-0.0 +2587,0.13277324165486415,0.32996328967382527,0.5372634686713106,-0.0 +2588,0.14120328874406188,0.28814975985289976,0.5706469514030384,-0.0 +2589,0.0024085848826279213,0.19134234841967002,0.806249066697702,-0.0 +2590,-0.0,0.1866837758037517,0.8133162241962483,-0.0 +2591,-0.0,0.19600092103558833,0.8039990789644117,-0.0 +2592,-0.0,0.20202297831958035,0.7979770216804196,-0.0 +2593,-0.0,0.19134234841967002,0.80865765158033,-0.0 +2594,-0.0,0.15657348938379176,0.8434265106162082,-0.0 +2595,0.24507351180739098,0.10703354644378217,0.6478929417488268,-0.0 +2596,0.19946093559262473,0.09964800449171651,0.7008910599156588,-0.0 +2597,0.008731120199526213,0.12203187779259239,0.8692370020078813,-0.0 +2598,-0.0,0.16634512950498628,0.8336548704950137,-0.0 +2599,-0.0,0.16691324811668365,0.8330867518833164,-0.0 +2600,-0.0,0.1806617185197597,0.8193382814802403,-0.0 +2601,-0.0,0.21304447938650908,0.7869555206134909,-0.0 +2602,-0.0,0.2348602340756876,0.7651397659243124,-0.0 +2603,0.19328893683089066,0.2851955430720735,0.5215155200970358,-0.0 +2604,0.21436405455388496,0.33678071301419354,0.4488552324319215,-0.0 +2605,0.009935412640840174,0.3382578214046067,0.6518067659545531,-0.0 +2606,-0.0,0.3016709828112969,0.6983290171887031,-0.0 +2607,-0.0,0.24190490486073485,0.7580950951392651,-0.0 +2608,-0.0,0.1703219597868678,0.8296780402131322,-0.0 +2609,-0.0,0.14146153431264202,0.858538465687358,-0.0 +2610,-0.0,0.10589730922038745,0.8941026907796126,-0.0 +2611,0.22264356508791847,0.044654122879412304,0.7327023120326692,-0.0 +2612,0.2875248203637081,0.06306116589840668,0.6494140137378852,-0.0 +2613,0.008430047089197723,0.13691658541906318,0.8546533674917391,-0.0 +2614,-0.0,0.1706628309538862,0.8293371690461138,-0.0 +2615,-0.0,0.19770527687068043,0.8022947231293196,-0.0 +2616,-0.0,0.24610898258729527,0.7538910174127047,-0.0 +2617,-0.0,0.267015747497758,0.732984252502242,-0.0 +2618,-0.0,0.3057614368155178,0.6942385631844822,-0.0 +2619,0.24236385381443457,0.3649593961543825,0.39267675003118296,-0.0 +2620,0.3259116419305906,0.48267357249807497,0.19141478557133446,-0.0 +2621,0.006322535316898292,0.5409625420582239,0.4527149226248778,-0.0 +2622,-0.0,0.49028636189481956,0.5097136381051804,-0.0 +2623,-0.0,0.38427542895209266,0.6157245710479073,-0.0 +2624,-0.0,0.28860425474225765,0.7113957452577424,-0.0 +2625,-0.0,0.21077200493971965,0.7892279950602803,-0.0 +2626,-0.0,0.1842976776346228,0.8157023223653772,-0.0 +2627,0.0852036902229627,0.1323716365254843,0.782424673251553,-0.0 +2628,0.09303159109150345,0.10623818038740586,0.8007302285210907,-0.0 +2629,0.001956975217135186,0.11975940334580297,0.8782836214370618,-0.0 +2630,-0.0,0.12373623362768447,0.8762637663723155,-0.0 +2631,-0.0,0.12544058946277656,0.8745594105372234,-0.0 +2632,-0.0,0.12748581646488702,0.872514183535113,-0.0 +2633,-0.0,0.13373512119355796,0.866264878806442,-0.0 +2634,-0.0,0.1072607938884611,0.8927392061115389,-0.0 +2635,0.18982659606211302,0.048176458271935924,0.761996945665951,-0.0 +2636,0.2535035588965887,0.03954105537413608,0.7069553857292752,-0.0 +2637,0.004064486989434617,0.07146932135152757,0.9051023903737102,0.019363801285327602 +2638,-0.0,0.071810192518546,0.9051023903737102,0.023087417107743785 +2639,-0.0,0.06851510457070133,0.9051023903737102,0.026382505055588465 +2640,-0.0,0.08237719869611684,0.9051023903737102,0.012520410930172948 +2641,-0.0,0.079763853082309,0.9051023903737102,0.01513375654398079 +2642,-0.0,0.1108967530033242,0.8891032469966758,-0.0 +2643,0.23257897772875863,0.15532362843805755,0.6120973938331837,-0.0 +2644,0.3509007100878552,0.17441241379108877,0.47468687612105603,-0.0 +2645,0.0033118042136133915,0.23804169830119282,0.7586464974851937,-0.0 +2646,-0.0,0.33416736740038566,0.6658326325996143,-0.0 +2647,-0.0,0.36916347388094295,0.630836526119057,-0.0 +2648,-0.0,0.3893884964573689,0.6106115035426312,-0.0 +2649,-0.0,0.39506968257434244,0.6049303174256575,-0.0 +2650,-0.0,0.4074546683093448,0.5925453316906553,-0.0 +2651,0.13202055887904293,0.39791027563282927,0.4700691654881278,-0.0 +2652,0.18982659606211302,0.42779331460811026,0.38238008932977663,-0.0 +2653,0.004967706320420087,0.4413145375665074,0.5537177561130725,-0.0 +2654,-0.0,0.4655163904248148,0.5344836095751853,-0.0 +2655,-0.0,0.5713000759228628,0.4286999240771372,-0.0 +2656,-0.0,0.7270781992502783,0.2729218007497217,-0.0 +2657,-0.0,0.8009336187709346,0.1990663812290654,-0.0 +2658,-0.0,0.7859352874221244,0.21406471257787563,-0.0 +2659,0.06171998761734048,0.7524162893319803,0.18586372305067916,-0.0 +2660,0.07602096035794377,0.7224196266343599,0.20155941300769636,-0.0 +2661,0.0009032193309854703,0.8313847763579131,0.16771200431110145,-0.0 +2662,-0.0,0.9877310182970258,0.012268981702974191,-0.0 +2663,-0.0,1.0,-0.0,-0.0 +2664,-0.0,0.9832996931257864,0.016700306874213644,-0.0 +2665,-0.0,0.9177388053359113,0.08226119466408865,-0.0 +2666,-0.0,0.8683124861182413,0.1316875138817587,-0.0 +2667,0.14240758118537583,0.7293506736970676,0.12824174511755654,-0.0 +2668,0.09408534697765317,0.6712889515815976,0.2346257014407492,-0.0 +2669,0.0009032193309854703,0.48608228416825916,0.5130144965007554,-0.0 +2670,-0.0,0.5590287139101998,0.4409712860898002,-0.0 +2671,-0.0,0.6524274136732454,0.3475725863267546,-0.0 +2672,-0.0,0.6843556796506368,0.3156443203493632,-0.0 +2673,-0.0,0.5856166649376361,0.4143833350623639,-0.0 +2674,-0.0,0.4334745007250838,0.5665254992749162,-0.0 +2675,0.19298786372056217,0.34552973963433287,0.46148239664510493,-0.0 +2676,0.19825664315131078,0.33655346556951465,0.46518989127917465,-0.0 +2677,0.0033118042136133915,0.2796279806774394,0.7170602151089472,-0.0 +2678,-0.0,0.23951880669160594,0.760481193308394,-0.0 +2679,-0.0,0.28053697045615517,0.7194630295438449,-0.0 +2680,-0.0,0.4285886806644865,0.5714113193355135,-0.0 +2681,-0.0,0.4669934988152279,0.5330065011847721,-0.0 +2682,-0.0,0.44608673390476516,0.5539132660952348,-0.0 +2683,0.0627737435034902,0.3335992487886884,0.6036270077078214,-0.0 +2684,0.07827900868540744,0.2580394734329398,0.6636815178816526,-0.0 +2685,0.0018064386619709406,0.3654138910437404,0.6327796702942887,-0.0 +2686,-0.0,0.3692770976032824,0.6307229023967176,-0.0 +2687,-0.0,0.36382315893098777,0.6361768410690123,-0.0 +2688,-0.0,0.3362125944024962,0.6637874055975038,-0.0 +2689,-0.0,0.30076199303258105,0.6992380069674189,-0.0 +2690,-0.0,0.2700835880009238,0.7299164119990762,-0.0 +2691,0.25380463200691716,0.1599822010539759,0.586213166939107,-0.0 +2692,0.388835921989245,0.1404389208115868,0.4707251571991682,-0.0 +2693,0.01309668029928932,0.25474438548509515,0.7321589342156155,-0.0 +2694,-0.0,0.2720151912806948,0.7279848087193053,-0.0 +2695,-0.0,0.2861045328507893,0.7138954671492107,-0.0 +2696,-0.0,0.3234867375004754,0.6765132624995246,-0.0 +2697,-0.0,0.47017496304073314,0.5298250369592669,-0.0 +2698,-0.0,0.6551543830093928,0.34484561699060723,-0.0 +2699,0.1600203581395925,0.636179221378701,0.20380042048170652,-0.0 +2700,0.1902782057276058,0.5359630982752871,0.2737586959971071,-0.0 +2701,0.0037634138791061266,0.4927860837862879,0.503450502334606,-0.0 +2702,-0.0,0.47812862360449615,0.5218713763955038,-0.0 +2703,-0.0,0.46881147837265946,0.5311885216273405,-0.0 +2704,-0.0,0.4636984108673833,0.5363015891326167,-0.0 +2705,-0.0,0.4780149998821567,0.5219850001178433,-0.0 +2706,-0.0,0.5394854336678108,0.46051456633218923,-0.0 +2707,0.27849262705385336,0.6141362192448435,0.10737115370130312,-0.0 +2708,0.3012236468836544,0.6987763531163456,-0.0,-0.0 +2709,0.006774144982391027,0.7426446492107858,0.2505812058068232,-0.0 +2710,-0.0,0.771391450962672,0.22860854903732797,-0.0 +2711,-0.0,0.7682099867371669,0.2317900132628331,-0.0 +2712,-0.0,0.7545751400564303,0.24542485994356966,-0.0 +2713,-0.0,0.7492348251064751,0.2507651748935249,-0.0 +2714,-0.0,0.7418492831544096,0.25815071684559043,-0.0 +2715,0.2939978922357706,0.7060021077642293,-0.0,-0.0 +2716,0.3310298848061749,0.6689701151938251,-0.0,-0.0 +2717,0.008128973978869233,0.777868003136022,0.21400302288510875,-0.0 +2718,-0.0,0.6988995161100893,0.30110048388991073,-0.0 +2719,-0.0,0.5433486402273527,0.45665135977264726,-0.0 +2720,-0.0,0.34439350241093813,0.6556064975890619,-0.0 +2721,-0.0,0.19793252431535938,0.8020674756846407,-0.0 +2722,-0.0,0.11873678984474773,0.8812632101552522,-0.0 +2723,0.2679550681923562,0.05453938672294632,0.6775055450846974,-0.0 +2724,0.38868538543408077,0.02090676491046276,0.5904078496554566,-0.0 +2725,0.013247216854453566,0.044086004267714946,0.9051023903737102,0.03756438850412128 +2726,-0.0,0.09430768954176136,0.9051023903737102,0.0005899200845284336 +2727,-0.0,0.12987191463401593,0.8701280853659841,-0.0 +2728,-0.0,0.14100703942328416,0.8589929605767158,-0.0 +2729,-0.0,0.13021278580103435,0.8697872141989657,-0.0 +2730,-0.0,0.131576270469108,0.868423729530892,-0.0 +2731,0.4272227435561275,0.12873567741062122,0.4440415790332513,-0.0 +2732,0.5703830075173246,0.11839591867772932,0.311221073804946,-0.0 +2733,0.010537558861497156,0.2941718171368918,0.695290624001611,-0.0 +2734,-0.0,0.32734994406001744,0.6726500559399826,-0.0 +2735,-0.0,0.25894846321165554,0.7410515367883445,-0.0 +2736,-0.0,0.23520110524270602,0.764798894757294,-0.0 +2737,-0.0,0.21122649982907754,0.7887735001709224,-0.0 +2738,-0.0,0.24406375558518478,0.7559362444148152,-0.0 +2739,0.18591264562784265,0.23895068807990857,0.5751366662922488,-0.0 +2740,0.24236385381443457,0.2508811789255531,0.5067549672600122,-0.0 +2741,0.004967706320420087,0.2673566186647765,0.7276756750148035,-0.0 +2742,-0.0,0.28099146534551306,0.719008534654487,-0.0 +2743,-0.0,0.2345193629086692,0.7654806370913307,-0.0 +2744,-0.0,0.23951880669160594,0.760481193308394,-0.0 +2745,-0.0,0.28996773941033127,0.7100322605896687,-0.0 +2746,-0.0,0.32598645939194376,0.6740135406080563,-0.0 +2747,0.34186851677800056,0.3977966519104898,0.2603348313115096,-0.0 +2748,0.42180342757021466,0.39938738402324236,0.17880918840654297,-0.0 +2749,0.005870925651405557,0.4631302922556859,0.5309987820929085,-0.0 +2750,-0.0,0.4651755192577964,0.5348244807422036,-0.0 +2751,-0.0,0.4513134251323808,0.5486865748676192,-0.0 +2752,-0.0,0.4199532777666867,0.5800467222333133,-0.0 +2753,-0.0,0.40665930225296854,0.5933406977470315,-0.0 +2754,-0.0,0.4375649547293048,0.5624350452706952,-0.0 +2755,0.13849363075110546,0.47835587104917504,0.38315049819971947,-0.0 +2756,0.13171948576871442,0.55255216173685,0.31572835249443565,-0.0 +2757,0.0013548289964782055,0.5799354788206625,0.41870969218285925,-0.0 +2758,-0.0,0.5174424315339533,0.4825575684660467,-0.0 +2759,-0.0,0.5514159245134552,0.4485840754865448,-0.0 +2760,-0.0,0.5452802435071238,0.4547197564928762,-0.0 +2761,-0.0,0.46665262764820953,0.5333473723517905,-0.0 +2762,-0.0,0.424611850382605,0.5753881496173949,-0.0 +2763,0.2274607348531743,0.3359853469578173,0.43655391818900835,-0.0 +2764,0.267804531637192,0.30417070470276525,0.4280247636600427,-0.0 +2765,0.0034623407687776366,0.22827005817999826,0.7682676010512242,-0.0 +2766,-0.0,0.24940407053513997,0.7505959294648601,-0.0 +2767,-0.0,0.42847505694214705,0.571524943057853,-0.0 +2768,-0.0,0.6767428902538923,0.32325710974610766,-0.0 +2769,-0.0,0.8327482610259866,0.16725173897401335,-0.0 +2770,-0.0,0.6586767184019163,0.34132328159808367,-0.0 +2771,0.15595587115015788,0.41620369492948417,0.427840433920358,-0.0 +2772,0.2452240483625552,0.22395235673109834,0.5308235949063465,-0.0 +2773,0.004365560099763107,0.13873456497649472,0.8568998749237422,-0.0 +2774,-0.0,0.07760500235785904,0.9051023903737102,0.01729260726843075 +2775,-0.0,0.0390865604847782,0.9051023903737102,0.05581104914151159 +2776,-0.0,0.02397460541362849,0.9051023903737102,0.0709230042126613 +2777,-0.0,0.02840593058486788,0.9051023903737102,0.06649167904142192 +2778,-0.0,0.058970711894185714,0.9051023903737102,0.03592689773210408 +2779,0.19328893683089066,0.061015938896296196,0.7456951242728131,-0.0 +2780,0.24010580548697089,0.05908433561652518,0.7008098588965039,-0.0 +2781,0.004967706320420087,0.14839258137534977,0.8466397123042302,-0.0 +2782,-0.0,0.177366630571915,0.822633369428085,-0.0 +2783,-0.0,0.14464299853814722,0.8553570014618528,-0.0 +2784,-0.0,0.11794142378837143,0.8820585762116285,-0.0 +2785,-0.0,0.07919573447061164,0.9051023903737102,0.015701875155678155 +2786,-0.0,0.05965245422822254,0.9051023903737102,0.03524515539806725 +2787,0.15610640770532214,0.028065059417849463,0.8158285328768284,-0.0 +2788,0.24010580548697089,0.012612233179681338,0.7472819613333478,-0.0 +2789,0.008279510534033478,0.04124541120922816,0.9051023903737102,0.04537268788302816 +2790,-0.0,0.08090009030570372,0.9051023903737102,0.013997519320586072 +2791,-0.0,0.09396681837474294,0.9051023903737102,0.000930791251546853 +2792,-0.0,0.1124874851160768,0.8875125148839232,-0.0 +2793,-0.0,0.1463473543732393,0.8536526456267607,-0.0 +2794,-0.0,0.19213771447604633,0.8078622855239537,-0.0 +2795,0.10898846593891344,0.1817979557431544,0.7092135783179322,-0.0 +2796,0.18260084141422928,0.1620274280560864,0.6553717305296843,-0.0 +2797,0.0013548289964782055,0.2759920215625763,0.7226531494409455,-0.0 +2798,-0.0,0.29303557991349705,0.706964420086503,-0.0 +2799,-0.0,0.30485244703680203,0.695147552963198,-0.0 +2800,-0.0,0.3136014736569414,0.6863985263430585,-0.0 +2801,-0.0,0.31166987037717037,0.6883301296228297,-0.0 +2802,-0.0,0.30826115870698617,0.6917388412930139,-0.0 +2803,0.09408534697765317,0.2816732076795499,0.624241445342797,-0.0 +2804,0.16543967412550534,0.2780372485646868,0.5565230773098079,-0.0 +2805,0.004064486989434617,0.36291416915227204,0.6330213438582933,-0.0 +2806,-0.0,0.3856389136201663,0.6143610863798337,-0.0 +2807,-0.0,0.37109507716071394,0.628904922839286,-0.0 +2808,-0.0,0.3551877560331879,0.644812243966812,-0.0 +2809,-0.0,0.34314364146520393,0.6568563585347961,-0.0 +2810,-0.0,0.34643872941304865,0.6535612705869513,-0.0 +2811,0.11169812393186984,0.29451268830391014,0.59378918776422,-0.0 +2812,0.14842904339194563,0.26019832415738975,0.5913726324506646,-0.0 +2813,0.004967706320420087,0.2883770072975787,0.7066552863820013,-0.0 +2814,-0.0,0.2941718171368918,0.7058281828631082,-0.0 +2815,-0.0,0.3071249214835915,0.6928750785164085,-0.0 +2816,-0.0,0.2856500379614314,0.7143499620385686,-0.0 +2817,-0.0,0.25428989059573726,0.7457101094042627,-0.0 +2818,-0.0,0.24383650814050586,0.7561634918594942,-0.0 +2819,0.18651479184849964,0.2116809947184354,0.601804213433065,-0.0 +2820,0.36505114627329427,0.20520444254508557,0.4297444111816201,-0.0 +2821,0.006473071872062538,0.3186009174398781,0.6749260106880594,-0.0 +2822,-0.0,0.33064503200786205,0.669354967992138,-0.0 +2823,-0.0,0.29212659013478126,0.7078734098652187,-0.0 +2824,-0.0,0.267015747497758,0.732984252502242,-0.0 +2825,-0.0,0.263379788382895,0.736620211617105,-0.0 +2826,-0.0,0.30314809120171,0.6968519087982901,-0.0 +2827,0.2441702924764055,0.3098518908197388,0.4459778167038557,-0.0 +2828,0.289933405246336,0.2931492036358365,0.4169173911178275,-0.0 +2829,0.005268779430748578,0.34564336335667234,0.6490878572125791,-0.0 +2830,-0.0,0.3533697764757564,0.6466302235242436,-0.0 +2831,-0.0,0.3320085166759358,0.6679914833240642,-0.0 +2832,-0.0,0.3079202875399678,0.6920797124600322,-0.0 +2833,-0.0,0.34427987868859866,0.6557201213114013,-0.0 +2834,-0.0,0.3754127786096139,0.624587221390386,-0.0 +2835,0.144966702623168,0.4131358544263184,0.4418974429505136,-0.0 +2836,0.1847083531865287,0.5961836711152071,0.2191079756982642,-0.0 +2837,0.0030107311032849015,0.7163975693503678,0.2805916995463473,-0.0 +2838,-0.0,0.8357024778068131,0.16429752219318694,-0.0 +2839,-0.0,0.9433041428622926,0.05669585713770742,-0.0 +2840,-0.0,0.8903554882520988,0.10964451174790124,-0.0 +2841,-0.0,0.6632216672954953,0.33677833270450475,-0.0 +2842,-0.0,0.4952858056777563,0.5047141943222437,-0.0 +2843,0.15550426148466515,0.6099321415182831,0.23456359699705173,-0.0 +2844,0.23032092940129495,0.7696790705987051,-0.0,-0.0 +2845,0.00797843742370499,0.8024107271613479,0.18961083541494714,-0.0 +2846,-0.0,0.7674146206807906,0.23258537931920942,-0.0 +2847,-0.0,0.7405994222086754,0.25940057779132464,-0.0 +2848,-0.0,0.7478713404384015,0.25212865956159847,-0.0 +2849,-0.0,0.7694598476829011,0.2305401523170989,-0.0 +2850,-0.0,0.7649148987893223,0.23508510121067772,-0.0 +2851,0.2298693197358022,0.7429855203778043,0.02714515988639349,-0.0 +2852,0.2705141896301484,0.7294858103698516,-0.0,-0.0 +2853,0.0030107311032849015,0.5540292701272631,0.44295999876945197,-0.0 +2854,-0.0,0.38666152712122154,0.6133384728787785,-0.0 +2855,-0.0,0.2365645899107797,0.7634354100892203,-0.0 +2856,-0.0,0.13305337885952115,0.8669466211404788,-0.0 +2857,-0.0,0.14532474087218408,0.854675259127816,-0.0 +2858,-0.0,0.2084995304929302,0.7915004695070698,-0.0 +2859,0.29339574601511365,0.27485578433918156,0.4317484696457048,-0.0 +2860,0.4239109393425141,0.3614370607618589,0.214651999895627,-0.0 +2861,0.010236485751168664,0.5333497526614792,0.45641376158735214,-0.0 +2862,-0.0,0.6201582765288355,0.3798417234711645,-0.0 +2863,-0.0,0.6545862643976953,0.3454137356023047,-0.0 +2864,-0.0,0.5018759815734456,0.4981240184265544,-0.0 +2865,-0.0,0.3859797847871847,0.6140202152128154,-0.0 +2866,-0.0,0.3357580995131383,0.6642419004868617,-0.0 +2867,0.2712668724059696,0.2688337270551896,0.4598994005388408,-0.0 +2868,0.335997591126595,0.27815087228702623,0.3858515365863787,-0.0 +2869,0.008279510534033478,0.30598868426019676,0.6857318052057697,-0.0 +2870,-0.0,0.3509836783066275,0.6490163216933724,-0.0 +2871,-0.0,0.4990353885149589,0.5009646114850411,-0.0 +2872,-0.0,0.7048079496717418,0.29519205032825824,-0.0 +2873,-0.0,0.7865034060338219,0.21349659396617815,-0.0 +2874,-0.0,0.7803677250274903,0.2196322749725097,-0.0 +2875,0.3140192540726152,0.6859807459273848,-0.0,-0.0 +2876,0.46560956512301005,0.5343904348769899,-0.0,-0.0 +2877,0.013548289964782054,0.6875371438761421,0.29891456615907586,-0.0 +2878,-0.0,0.6195901579171381,0.3804098420828619,-0.0 +2879,-0.0,0.5305091596029925,0.4694908403970075,-0.0 +2880,-0.0,0.4435870120132968,0.5564129879867032,-0.0 +2881,-0.0,0.4239301080485682,0.5760698919514318,-0.0 +2882,-0.0,0.4575627298610518,0.5424372701389482,-0.0 +2883,0.28707321069821534,0.47210656632050413,0.24082022298128047,-0.0 +2884,0.3658038290491155,0.47460628821197254,0.1595898827389119,-0.0 +2885,0.01309668029928932,0.48835475861504857,0.49854856108566215,-0.0 +2886,-0.0,0.4215440098794393,0.5784559901205607,-0.0 +2887,-0.0,0.3039434572580863,0.6960565427419136,-0.0 +2888,-0.0,0.17498053240278613,0.8250194675972139,-0.0 +2889,-0.0,0.09680741143322973,0.9031925885667703,-0.0 +2890,-0.0,0.05976607795056202,0.9051023903737102,0.03513153167572777 +2891,0.2998688178871762,0.04181352982092552,0.6583176522918983,-0.0 +2892,0.43821191208311744,0.05681186116973576,0.5049762267471467,-0.0 +2893,0.017462240399052427,0.22429322789811676,0.7582445317028308,-0.0 +2894,-0.0,0.3138287211016203,0.6861712788983797,-0.0 +2895,-0.0,0.31882816488455706,0.681171835115443,-0.0 +2896,-0.0,0.30269359631235215,0.6973064036876478,-0.0 +2897,-0.0,0.3093973959303809,0.6906026040696192,-0.0 +2898,-0.0,0.31837366999519917,0.6816263300048009,-0.0 +2899,0.2504928277933038,0.31530582949203345,0.43420134271466276,-0.0 +2900,0.3731801202521635,0.23474661035334815,0.3920732693944883,-0.0 +2901,0.011440778192482626,0.22315699067472203,0.7654022311327953,-0.0 +2902,-0.0,0.2216798822843089,0.7783201177156911,-0.0 +2903,-0.0,0.1845249250793017,0.8154750749206983,-0.0 +2904,-0.0,0.14146153431264202,0.858538465687358,-0.0 +2905,-0.0,0.1507786795444787,0.8492213204555212,-0.0 +2906,-0.0,0.21804392316944582,0.7819560768305542,-0.0 +2907,0.19855771626163923,0.23554197640972446,0.5659003073286364,-0.0 +2908,0.30077203721816165,0.3075794163729494,0.39164854640888896,-0.0 +2909,0.007677364313376499,0.42938404672086283,0.5629385889657607,-0.0 +2910,-0.0,0.4899454907278012,0.5100545092721989,-0.0 +2911,-0.0,0.47244743748752255,0.5275525625124775,-0.0 +2912,-0.0,0.4709703290971094,0.5290296709028905,-0.0 +2913,-0.0,0.45903983825146494,0.5409601617485351,-0.0 +2914,-0.0,0.44029192406545214,0.5597080759345479,-0.0 +2915,0.1606225043602495,0.3601871998161247,0.4791902958236258,-0.0 +2916,0.22715966174284583,0.2634934121052344,0.5093469261519198,-0.0 +2917,0.009032193309854704,0.231337898683164,0.7596299080069813,-0.0 +2918,-0.0,0.17532140356980452,0.8246785964301955,-0.0 +2919,-0.0,0.12294086757130818,0.8770591324286918,-0.0 diff --git a/PyPSA/source/test/data/benchmark-sp/generators-p_max_pu.csv b/PyPSA/source/test/data/benchmark-sp/generators-p_max_pu.csv new file mode 100644 index 0000000000000000000000000000000000000000..2f9ac3bdbfe0562769c0ec33de43bfa2510c6fe8 --- /dev/null +++ b/PyPSA/source/test/data/benchmark-sp/generators-p_max_pu.csv @@ -0,0 +1,2921 @@ +,solar,wind +0,0.0,0.1566 +1,0.0,0.1745 +2,0.0,0.1999 +3,0.1263,0.2452 +4,0.2074,0.2384 +5,0.0022,0.3656 +6,0.0,0.4376 +7,0.0,0.5529 +8,0.0,0.6391 +9,0.0,0.7425 +10,0.0,0.8038 +11,0.0527,0.7818 +12,0.1104,0.7435 +13,0.0026,0.7328 +14,0.0,0.7363 +15,0.0,0.7285 +16,0.0,0.713 +17,0.0,0.5751 +18,0.0,0.511 +19,0.0551,0.5326 +20,0.0718,0.5024 +21,0.0016,0.4013 +22,0.0,0.3761 +23,0.0,0.4087 +24,0.0,0.4867 +25,0.0,0.4899 +26,0.0,0.4508 +27,0.1455,0.4212 +28,0.1633,0.4459 +29,0.0037,0.4249 +30,0.0,0.3401 +31,0.0,0.2536 +32,0.0,0.2382 +33,0.0,0.2803 +34,0.0,0.3225 +35,0.1296,0.3291 +36,0.181,0.2527 +37,0.0121,0.1585 +38,0.0,0.0955 +39,0.0,0.0607 +40,0.0,0.0396 +41,0.0,0.0348 +42,0.0,0.0605 +43,0.1207,0.073 +44,0.1833,0.0942 +45,0.006,0.2137 +46,0.0,0.2397 +47,0.0,0.232 +48,0.0,0.2115 +49,0.0,0.2192 +50,0.0,0.2086 +51,0.0899,0.1695 +52,0.1336,0.173 +53,0.0058,0.234 +54,0.0,0.2675 +55,0.0,0.2909 +56,0.0,0.3482 +57,0.0,0.4087 +58,0.0,0.4358 +59,0.0384,0.4331 +60,0.0549,0.4581 +61,0.0016,0.4218 +62,0.0,0.4632 +63,0.0,0.5337 +64,0.0,0.5556 +65,0.0,0.6406 +66,0.0,0.8543 +67,0.0473,0.9471 +68,0.0508,0.9395 +69,0.0027,0.914 +70,0.0,0.8342 +71,0.0,0.6234 +72,0.0,0.5664 +73,0.0,0.6646 +74,0.0,0.8331 +75,0.0864,0.9326 +76,0.1221,0.9643 +77,0.0088,0.957 +78,0.0,0.885 +79,0.0,0.8006 +80,0.0,0.742 +81,0.0,0.6988 +82,0.0,0.7455 +83,0.1003,0.8247 +84,0.1511,0.8733 +85,0.016,0.8589 +86,0.0,0.8318 +87,0.0,0.765 +88,0.0,0.6809 +89,0.0,0.6283 +90,0.0,0.6833 +91,0.0479,0.7974 +92,0.0596,0.8476 +93,0.0064,0.8437 +94,0.0,0.8112 +95,0.0,0.7274 +96,0.0,0.6456 +97,0.0,0.6132 +98,0.0,0.6898 +99,0.2431,0.7214 +100,0.3381,0.7177 +101,0.0301,0.6268 +102,0.0,0.5119 +103,0.0,0.4732 +104,0.0,0.4304 +105,0.0,0.395 +106,0.0,0.4714 +107,0.1433,0.6162 +108,0.1722,0.5453 +109,0.0123,0.3912 +110,0.0,0.3573 +111,0.0,0.3674 +112,0.0,0.3321 +113,0.0,0.4328 +114,0.0,0.5919 +115,0.0873,0.6892 +116,0.1276,0.7257 +117,0.0145,0.7131 +118,0.0,0.7047 +119,0.0,0.6951 +120,0.0,0.5799 +121,0.0,0.5175 +122,0.0,0.4459 +123,0.1529,0.2792 +124,0.2145,0.189 +125,0.0156,0.1587 +126,0.0,0.1359 +127,0.0,0.099 +128,0.0,0.0829 +129,0.0,0.108 +130,0.0,0.1309 +131,0.1703,0.1083 +132,0.2524,0.1354 +133,0.0279,0.1433 +134,0.0,0.1798 +135,0.0,0.1834 +136,0.0,0.1818 +137,0.0,0.1663 +138,0.0,0.147 +139,0.187,0.1185 +140,0.2877,0.1249 +141,0.0346,0.127 +142,0.0,0.1506 +143,0.0,0.1313 +144,0.0,0.0908 +145,0.0,0.0405 +146,0.0,0.0321 +147,0.1293,0.0178 +148,0.1845,0.0142 +149,0.0253,0.05 +150,0.0,0.0575 +151,0.0,0.0391 +152,0.0,0.0274 +153,0.0,0.023 +154,0.0,0.0208 +155,0.085,0.0097 +156,0.1089,0.0077 +157,0.0135,0.015 +158,0.0,0.0205 +159,0.0,0.0323 +160,0.0,0.0346 +161,0.0,0.0294 +162,0.0,0.0283 +163,0.1195,0.024 +164,0.1648,0.0296 +165,0.0199,0.0641 +166,0.0,0.1136 +167,0.0,0.0935 +168,0.0,0.0713 +169,0.0,0.0722 +170,0.0,0.082 +171,0.0769,0.0894 +172,0.0916,0.0846 +173,0.0164,0.1037 +174,0.0,0.0944 +175,0.0,0.0669 +176,0.0,0.0639 +177,0.0,0.0571 +178,0.0,0.0533 +179,0.0767,0.04 +180,0.0757,0.0242 +181,0.0103,0.0267 +182,0.0,0.0433 +183,0.0,0.0791 +184,0.0,0.1471 +185,0.0,0.1687 +186,0.0,0.1939 +187,0.0669,0.1658 +188,0.0815,0.1238 +189,0.0126,0.1536 +190,0.0,0.1755 +191,0.0,0.163 +192,0.0,0.1766 +193,0.0,0.1789 +194,0.0,0.1538 +195,0.0841,0.1062 +196,0.1023,0.0874 +197,0.0218,0.0958 +198,0.0,0.0821 +199,0.0,0.0653 +200,0.0,0.0748 +201,0.0,0.1049 +202,0.0,0.1586 +203,0.0639,0.1878 +204,0.0796,0.2847 +205,0.0187,0.3539 +206,0.0,0.3745 +207,0.0,0.3513 +208,0.0,0.4003 +209,0.0,0.3717 +210,0.0,0.3862 +211,0.1248,0.3095 +212,0.145,0.306 +213,0.0356,0.2965 +214,0.0,0.217 +215,0.0,0.1325 +216,0.0,0.1324 +217,0.0,0.1809 +218,0.0,0.2714 +219,0.0767,0.3959 +220,0.1006,0.6464 +221,0.0182,0.8006 +222,0.0,0.7975 +223,0.0,0.7269 +224,0.0,0.6406 +225,0.0,0.5636 +226,0.0,0.5485 +227,0.1687,0.6047 +228,0.1527,0.6035 +229,0.0418,0.4104 +230,0.0,0.3102 +231,0.0,0.2526 +232,0.0,0.187 +233,0.0,0.1449 +234,0.0,0.1691 +235,0.1036,0.1345 +236,0.1198,0.1279 +237,0.0367,0.0908 +238,0.0,0.123 +239,0.0,0.1237 +240,0.0,0.138 +241,0.0,0.1534 +242,0.0,0.1523 +243,0.1268,0.1229 +244,0.1366,0.1476 +245,0.0544,0.139 +246,0.0,0.168 +247,0.0,0.1572 +248,0.0,0.1267 +249,0.0,0.1009 +250,0.0,0.1014 +251,0.1294,0.0736 +252,0.1356,0.1229 +253,0.0296,0.1498 +254,0.0,0.2133 +255,0.0,0.2107 +256,0.0,0.211 +257,0.0,0.2089 +258,0.0,0.2179 +259,0.1303,0.2032 +260,0.1302,0.2641 +261,0.0348,0.2318 +262,0.0,0.2162 +263,0.0,0.1998 +264,0.0,0.1739 +265,0.0,0.1566 +266,0.0,0.1356 +267,0.1217,0.1181 +268,0.171,0.1065 +269,0.05,0.0803 +270,0.0,0.0854 +271,0.0,0.0681 +272,0.0,0.0596 +273,0.0,0.0506 +274,0.0,0.0462 +275,0.1384,0.0358 +276,0.1636,0.0488 +277,0.0454,0.0561 +278,0.0,0.0568 +279,0.0,0.0278 +280,0.0,0.0235 +281,0.0,0.0291 +282,0.0,0.0435 +283,0.1383,0.0684 +284,0.1625,0.126 +285,0.041,0.1725 +286,0.0,0.2481 +287,0.0,0.2071 +288,0.0,0.1649 +289,0.0,0.1632 +290,0.0,0.1685 +291,0.1734,0.2019 +292,0.2656,0.1904 +293,0.0757,0.1562 +294,0.0,0.1336 +295,0.0,0.0916 +296,0.0,0.0781 +297,0.0,0.0991 +298,0.0,0.1366 +299,0.2111,0.1651 +300,0.26,0.276 +301,0.0594,0.4116 +302,0.0,0.533 +303,0.0,0.6517 +304,0.0,0.7248 +305,0.0,0.6935 +306,0.0,0.5702 +307,0.2061,0.4927 +308,0.2956,0.4944 +309,0.1075,0.3673 +310,0.0,0.3067 +311,0.0,0.331 +312,0.0,0.4026 +313,0.0,0.4641 +314,0.0,0.4727 +315,0.073,0.4493 +316,0.075,0.4361 +317,0.0209,0.3938 +318,0.0,0.3411 +319,0.0,0.2846 +320,0.0,0.2494 +321,0.0,0.2513 +322,0.0,0.2154 +323,0.0856,0.1404 +324,0.091,0.158 +325,0.0272,0.162 +326,0.0,0.1468 +327,0.0,0.0975 +328,0.0,0.0656 +329,0.0,0.0327 +330,0.0,0.023 +331,0.0781,0.0122 +332,0.1227,0.0082 +333,0.0664,0.0129 +334,0.0,0.0548 +335,0.0,0.0778 +336,0.0,0.0569 +337,0.0,0.0368 +338,0.0,0.0382 +339,0.1475,0.0184 +340,0.2002,0.0112 +341,0.0836,0.0142 +342,0.0,0.0683 +343,0.0,0.0716 +344,0.0,0.057 +345,0.0,0.0657 +346,0.0,0.0812 +347,0.2442,0.0595 +348,0.3849,0.0679 +349,0.1507,0.1207 +350,0.0,0.2394 +351,0.0,0.1872 +352,0.0,0.142 +353,0.0,0.1191 +354,0.0,0.1145 +355,0.3285,0.0463 +356,0.4656,0.0376 +357,0.1656,0.0856 +358,0.0,0.1691 +359,0.0,0.129 +360,0.0,0.1149 +361,0.0,0.1217 +362,0.0001,0.1453 +363,0.2581,0.1562 +364,0.3909,0.18 +365,0.1379,0.2289 +366,0.0,0.3231 +367,0.0,0.2206 +368,0.0,0.1532 +369,0.0,0.1435 +370,0.0001,0.1792 +371,0.238,0.2128 +372,0.3543,0.1972 +373,0.2059,0.1853 +374,0.0,0.2317 +375,0.0,0.1371 +376,0.0,0.0762 +377,0.0,0.0469 +378,0.0002,0.0382 +379,0.234,0.0315 +380,0.23,0.0339 +381,0.0726,0.0536 +382,0.0,0.0993 +383,0.0,0.0849 +384,0.0,0.0635 +385,0.0,0.0579 +386,0.0,0.0655 +387,0.1446,0.0955 +388,0.2823,0.1488 +389,0.1198,0.1519 +390,0.0,0.2565 +391,0.0,0.272 +392,0.0,0.2505 +393,0.0,0.2004 +394,0.0005,0.1988 +395,0.2761,0.2714 +396,0.3996,0.2713 +397,0.1744,0.2481 +398,0.0,0.3217 +399,0.0,0.2886 +400,0.0,0.2666 +401,0.0,0.2777 +402,0.0007,0.3282 +403,0.2954,0.4761 +404,0.3827,0.4777 +405,0.1442,0.3509 +406,0.0,0.3491 +407,0.0,0.3354 +408,0.0,0.3616 +409,0.0,0.3505 +410,0.0018,0.3428 +411,0.2011,0.2851 +412,0.2223,0.4128 +413,0.1025,0.2535 +414,0.0,0.1696 +415,0.0,0.1399 +416,0.0,0.095 +417,0.0,0.0782 +418,0.0012,0.0823 +419,0.269,0.0863 +420,0.2213,0.1094 +421,0.1322,0.089 +422,0.0,0.2025 +423,0.0,0.3305 +424,0.0,0.4185 +425,0.0,0.4946 +426,0.0004,0.4911 +427,0.0998,0.4315 +428,0.1211,0.2896 +429,0.0509,0.1935 +430,0.0,0.2571 +431,0.0,0.2538 +432,0.0,0.2623 +433,0.0,0.2854 +434,0.002,0.3189 +435,0.2755,0.4274 +436,0.2458,0.4161 +437,0.1046,0.2798 +438,0.0,0.2976 +439,0.0,0.2286 +440,0.0,0.1406 +441,0.0,0.1086 +442,0.0016,0.0955 +443,0.3466,0.0947 +444,0.3319,0.1 +445,0.1859,0.0458 +446,0.0,0.0519 +447,0.0,0.0648 +448,0.0,0.0612 +449,0.0,0.0773 +450,0.0054,0.0911 +451,0.4465,0.1193 +452,0.5243,0.2003 +453,0.2395,0.2161 +454,0.0,0.362 +455,0.0,0.3227 +456,0.0,0.2256 +457,0.0,0.167 +458,0.0053,0.1192 +459,0.2882,0.0884 +460,0.3315,0.1203 +461,0.1335,0.1118 +462,0.0,0.137 +463,0.0,0.1117 +464,0.0,0.0872 +465,0.0,0.077 +466,0.0018,0.0763 +467,0.3917,0.1156 +468,0.385,0.1603 +469,0.2546,0.1591 +470,0.0,0.31 +471,0.0,0.4099 +472,0.0,0.4179 +473,0.0,0.4787 +474,0.0005,0.5409 +475,0.0968,0.59 +476,0.1601,0.5858 +477,0.094,0.4365 +478,0.0,0.3835 +479,0.0,0.3334 +480,0.0,0.3516 +481,0.0,0.4437 +482,0.0034,0.556 +483,0.2374,0.7406 +484,0.289,0.7293 +485,0.1536,0.5602 +486,0.0,0.5044 +487,0.0,0.5232 +488,0.0,0.5643 +489,0.0,0.5203 +490,0.0093,0.4264 +491,0.3846,0.5553 +492,0.3653,0.5084 +493,0.1406,0.2877 +494,0.0,0.3188 +495,0.0,0.2974 +496,0.0,0.3435 +497,0.0,0.3154 +498,0.0077,0.2978 +499,0.272,0.4908 +500,0.1891,0.5312 +501,0.1218,0.3785 +502,0.0,0.3875 +503,0.0,0.3522 +504,0.0,0.2726 +505,0.0,0.2153 +506,0.0052,0.1476 +507,0.2178,0.1497 +508,0.2515,0.197 +509,0.1429,0.1162 +510,0.0,0.1292 +511,0.0,0.1281 +512,0.0,0.1179 +513,0.0,0.1175 +514,0.0076,0.1156 +515,0.216,0.1548 +516,0.2012,0.1842 +517,0.1103,0.0915 +518,0.0,0.149 +519,0.0,0.1849 +520,0.0,0.1988 +521,0.0,0.2047 +522,0.0149,0.2128 +523,0.3552,0.272 +524,0.5149,0.31 +525,0.2087,0.2727 +526,0.0,0.3757 +527,0.0,0.3533 +528,0.0,0.3242 +529,0.0,0.2806 +530,0.0234,0.2312 +531,0.5096,0.162 +532,0.608,0.1836 +533,0.2385,0.163 +534,0.0,0.2542 +535,0.0,0.2255 +536,0.0,0.1728 +537,0.0,0.1102 +538,0.0213,0.0375 +539,0.3923,0.012 +540,0.5169,0.0045 +541,0.2716,0.0093 +542,0.0,0.0636 +543,0.0,0.1954 +544,0.0,0.1934 +545,0.0,0.1114 +546,0.0231,0.0834 +547,0.3435,0.0708 +548,0.3444,0.0962 +549,0.1655,0.1289 +550,0.0,0.2108 +551,0.0,0.196 +552,0.0,0.1282 +553,0.0,0.1166 +554,0.0176,0.1114 +555,0.3323,0.1464 +556,0.4832,0.1322 +557,0.2533,0.0942 +558,0.0,0.1173 +559,0.0,0.0879 +560,0.0,0.0459 +561,0.0,0.0166 +562,0.0251,0.0099 +563,0.3623,0.023 +564,0.3838,0.0597 +565,0.2187,0.0968 +566,0.0,0.1867 +567,0.0,0.1657 +568,0.0,0.1125 +569,0.0,0.0944 +570,0.0277,0.0939 +571,0.3333,0.1094 +572,0.2989,0.1126 +573,0.1535,0.1453 +574,0.0,0.1648 +575,0.0,0.1377 +576,0.0,0.1253 +577,0.0,0.0978 +578,0.0124,0.0871 +579,0.1589,0.1466 +580,0.1498,0.1531 +581,0.0843,0.1537 +582,0.0,0.188 +583,0.0,0.1516 +584,0.0,0.1256 +585,0.0,0.1119 +586,0.0168,0.123 +587,0.1763,0.2554 +588,0.2001,0.2597 +589,0.1319,0.2488 +590,0.0,0.3751 +591,0.0,0.3475 +592,0.0,0.2973 +593,0.0,0.2464 +594,0.0421,0.2575 +595,0.4463,0.3937 +596,0.5354,0.4427 +597,0.2433,0.3732 +598,0.0,0.389 +599,0.0,0.3157 +600,0.0,0.2415 +601,0.0,0.1609 +602,0.0525,0.1113 +603,0.4986,0.165 +604,0.6334,0.1744 +605,0.2849,0.1509 +606,0.0,0.2903 +607,0.0,0.2316 +608,0.0,0.1721 +609,0.0,0.1235 +610,0.0725,0.0691 +611,0.5726,0.0848 +612,0.7091,0.0861 +613,0.3317,0.078 +614,0.0,0.2116 +615,0.0,0.1543 +616,0.0,0.0831 +617,0.0,0.0571 +618,0.0699,0.0329 +619,0.4897,0.0366 +620,0.6587,0.0078 +621,0.333,0.0124 +622,0.0,0.079 +623,0.0,0.0461 +624,0.0,0.0167 +625,0.0,0.0248 +626,0.0805,0.0179 +627,0.6155,0.0098 +628,0.6326,0.0175 +629,0.3105,0.0332 +630,0.0,0.0466 +631,0.0,0.0668 +632,0.0,0.0929 +633,0.0,0.076 +634,0.0552,0.0612 +635,0.289,0.0693 +636,0.2472,0.0966 +637,0.107,0.1659 +638,0.0,0.2862 +639,0.0,0.3565 +640,0.0,0.3112 +641,0.0,0.2379 +642,0.0515,0.1678 +643,0.3032,0.1611 +644,0.4177,0.0554 +645,0.2682,0.0322 +646,0.0,0.0639 +647,0.0,0.0928 +648,0.0,0.0995 +649,0.0,0.083 +650,0.0734,0.0384 +651,0.4434,0.0445 +652,0.5843,0.0672 +653,0.283,0.0562 +654,0.0,0.0477 +655,0.0,0.0499 +656,0.0,0.0351 +657,0.0,0.0249 +658,0.069,0.012 +659,0.4362,0.0068 +660,0.4736,0.0074 +661,0.21,0.0047 +662,0.0,0.0158 +663,0.0,0.0609 +664,0.0,0.0809 +665,0.0,0.0909 +666,0.0867,0.0716 +667,0.5039,0.131 +668,0.5852,0.1315 +669,0.2101,0.1222 +670,0.0,0.2396 +671,0.0,0.1758 +672,0.0,0.1421 +673,0.0,0.111 +674,0.0325,0.0709 +675,0.1386,0.0736 +676,0.1831,0.0908 +677,0.094,0.0765 +678,0.0,0.0691 +679,0.0,0.1291 +680,0.0,0.1572 +681,0.0,0.1674 +682,0.0512,0.1412 +683,0.2395,0.2604 +684,0.2243,0.3204 +685,0.1252,0.3216 +686,0.0,0.233 +687,0.0,0.2024 +688,0.0,0.1411 +689,0.0,0.0881 +690,0.0764,0.038 +691,0.4251,0.0725 +692,0.491,0.1658 +693,0.2075,0.2081 +694,0.0,0.3375 +695,0.0,0.4264 +696,0.0,0.4794 +697,0.0,0.5321 +698,0.0263,0.4984 +699,0.1471,0.6054 +700,0.1678,0.5421 +701,0.078,0.4695 +702,0.0,0.6613 +703,0.0,0.7444 +704,0.0,0.7745 +705,0.0,0.7329 +706,0.067,0.7686 +707,0.2668,0.8851 +708,0.3886,0.8898 +709,0.2468,0.845 +710,0.0,0.5995 +711,0.0,0.3607 +712,0.0,0.3822 +713,0.0,0.5836 +714,0.0361,0.7945 +715,0.2035,0.8909 +716,0.2772,0.944 +717,0.1702,0.9469 +718,0.0,0.891 +719,0.0,0.8124 +720,0.0,0.7403 +721,0.0,0.6894 +722,0.0669,0.6968 +723,0.2813,0.8036 +724,0.383,0.8269 +725,0.1987,0.7121 +726,0.0,0.5157 +727,0.0,0.394 +728,0.0,0.3969 +729,0.0,0.3485 +730,0.0348,0.2994 +731,0.2304,0.4915 +732,0.3672,0.6157 +733,0.2369,0.5773 +734,0.0,0.4249 +735,0.0,0.3428 +736,0.0,0.2643 +737,0.0,0.208 +738,0.0994,0.1658 +739,0.4043,0.1924 +740,0.4887,0.1676 +741,0.2858,0.1181 +742,0.0,0.1042 +743,0.0,0.1038 +744,0.0,0.0754 +745,0.0,0.0406 +746,0.0739,0.023 +747,0.3748,0.0339 +748,0.3604,0.0675 +749,0.2231,0.1159 +750,0.0001,0.1512 +751,0.0,0.131 +752,0.0,0.0785 +753,0.0,0.0625 +754,0.1645,0.0143 +755,0.5957,0.042 +756,0.4765,0.066 +757,0.2515,0.1013 +758,0.0001,0.1214 +759,0.0,0.1071 +760,0.0,0.0862 +761,0.0,0.0749 +762,0.1006,0.0404 +763,0.3678,0.1024 +764,0.4129,0.1161 +765,0.298,0.1089 +766,0.0002,0.0992 +767,0.0,0.0481 +768,0.0,0.0197 +769,0.0,0.0611 +770,0.1465,0.0602 +771,0.5075,0.1661 +772,0.5056,0.2319 +773,0.2694,0.2514 +774,0.0002,0.2058 +775,0.0,0.1782 +776,0.0,0.1448 +777,0.0,0.1309 +778,0.0793,0.1279 +779,0.3292,0.1143 +780,0.4548,0.0737 +781,0.2976,0.0356 +782,0.0003,0.0312 +783,0.0,0.03 +784,0.0,0.032 +785,0.0,0.0414 +786,0.1422,0.0241 +787,0.59,0.0226 +788,0.699,0.0047 +789,0.3811,0.0017 +790,0.0005,0.0086 +791,0.0,0.0268 +792,0.0,0.0286 +793,0.0,0.024 +794,0.1757,0.0097 +795,0.6537,0.0037 +796,0.7083,0.0044 +797,0.3605,0.0069 +798,0.0005,0.0588 +799,0.0,0.1806 +800,0.0,0.2325 +801,0.0,0.2452 +802,0.1271,0.1271 +803,0.4884,0.3068 +804,0.4039,0.4798 +805,0.155,0.4958 +806,0.0002,0.4511 +807,0.0,0.3802 +808,0.0,0.3282 +809,0.0,0.2002 +810,0.1189,0.1304 +811,0.5636,0.2423 +812,0.5879,0.2504 +813,0.3446,0.2508 +814,0.0009,0.3556 +815,0.0,0.5194 +816,0.0,0.6343 +817,0.0,0.5605 +818,0.1404,0.432 +819,0.4761,0.4108 +820,0.4887,0.4325 +821,0.2868,0.37 +822,0.0014,0.2216 +823,0.0,0.1248 +824,0.0,0.0834 +825,0.0,0.1141 +826,0.1724,0.0975 +827,0.5861,0.2035 +828,0.5527,0.3597 +829,0.2614,0.3721 +830,0.0009,0.3456 +831,0.0,0.2576 +832,0.0,0.1958 +833,0.0,0.2106 +834,0.1946,0.1219 +835,0.6833,0.3224 +836,0.7555,0.5158 +837,0.4017,0.4977 +838,0.0012,0.3705 +839,0.0,0.2845 +840,0.0,0.1936 +841,0.0,0.1282 +842,0.1694,0.0862 +843,0.5469,0.1171 +844,0.5931,0.1725 +845,0.302,0.189 +846,0.0012,0.1628 +847,0.0,0.1373 +848,0.0,0.1097 +849,0.0,0.0904 +850,0.1307,0.0817 +851,0.39,0.1102 +852,0.3938,0.1528 +853,0.22,0.2356 +854,0.0017,0.2057 +855,0.0,0.1942 +856,0.0,0.147 +857,0.0,0.079 +858,0.2225,0.0355 +859,0.5444,0.0864 +860,0.5983,0.0793 +861,0.3898,0.0946 +862,0.003,0.0845 +863,0.0,0.0718 +864,0.0,0.0595 +865,0.0,0.0453 +866,0.219,0.0053 +867,0.6872,0.0162 +868,0.7471,0.0171 +869,0.4215,0.0321 +870,0.0035,0.0876 +871,0.0,0.1106 +872,0.0,0.0878 +873,0.0,0.1092 +874,0.245,0.0499 +875,0.7202,0.1065 +876,0.7838,0.1574 +877,0.417,0.173 +878,0.0036,0.185 +879,0.0,0.1181 +880,0.0,0.0718 +881,0.0,0.1553 +882,0.2552,0.0993 +883,0.7153,0.2924 +884,0.781,0.3812 +885,0.4247,0.3597 +886,0.0039,0.3066 +887,0.0,0.2513 +888,0.0,0.1989 +889,0.0,0.1596 +890,0.1608,0.109 +891,0.4691,0.0711 +892,0.5325,0.0494 +893,0.359,0.044 +894,0.0046,0.0528 +895,0.0,0.0596 +896,0.0,0.0705 +897,0.0,0.0639 +898,0.1995,0.032 +899,0.5962,0.0489 +900,0.7117,0.0478 +901,0.4058,0.0472 +902,0.0056,0.0516 +903,0.0,0.0531 +904,0.0,0.0432 +905,0.0,0.0603 +906,0.2251,0.0175 +907,0.6928,0.049 +908,0.7618,0.0988 +909,0.4198,0.0854 +910,0.0048,0.0535 +911,0.0,0.1176 +912,0.0,0.212 +913,0.0,0.2126 +914,0.1132,0.0871 +915,0.3412,0.0979 +916,0.3211,0.1745 +917,0.1269,0.1568 +918,0.0011,0.1388 +919,0.0,0.1555 +920,0.0,0.1566 +921,0.0,0.1206 +922,0.139,0.0305 +923,0.3852,0.0946 +924,0.4101,0.094 +925,0.264,0.0458 +926,0.0031,0.0196 +927,0.0,0.036 +928,0.0,0.0591 +929,0.0,0.086 +930,0.1106,0.0741 +931,0.3374,0.1193 +932,0.3549,0.1629 +933,0.1974,0.2149 +934,0.0026,0.1747 +935,0.0,0.1595 +936,0.0,0.1382 +937,0.0,0.0906 +938,0.1295,0.0578 +939,0.3696,0.0912 +940,0.4065,0.1763 +941,0.2423,0.1917 +942,0.0065,0.131 +943,0.0,0.154 +944,0.0,0.1386 +945,0.0,0.125 +946,0.2647,0.0707 +947,0.6867,0.1182 +948,0.6986,0.1132 +949,0.341,0.095 +950,0.0039,0.1298 +951,0.0,0.2136 +952,0.0,0.1949 +953,0.0,0.1474 +954,0.1553,0.0845 +955,0.4514,0.1631 +956,0.4125,0.1944 +957,0.2546,0.1235 +958,0.0041,0.056 +959,0.0,0.0939 +960,0.0,0.1134 +961,0.0,0.0826 +962,0.145,0.0527 +963,0.3297,0.0551 +964,0.3767,0.0594 +965,0.2343,0.0474 +966,0.0067,0.0525 +967,0.0,0.0614 +968,0.0,0.0373 +969,0.0,0.0239 +970,0.207,0.0145 +971,0.5018,0.0219 +972,0.4969,0.0253 +973,0.2887,0.015 +974,0.0099,0.0231 +975,0.0,0.0777 +976,0.0,0.1129 +977,0.0,0.142 +978,0.1499,0.0615 +979,0.4129,0.197 +980,0.3443,0.2672 +981,0.1111,0.2743 +982,0.0015,0.3232 +983,0.0,0.3122 +984,0.0,0.2908 +985,0.0,0.2498 +986,0.1609,0.1617 +987,0.471,0.2545 +988,0.5229,0.1944 +989,0.3185,0.0625 +990,0.0089,0.0216 +991,0.0,0.2191 +992,0.0,0.353 +993,0.0,0.3792 +994,0.1054,0.3047 +995,0.3229,0.4261 +996,0.354,0.5058 +997,0.2098,0.5299 +998,0.0099,0.4494 +999,0.0,0.2779 +1000,0.0,0.1737 +1001,0.0,0.1818 +1002,0.1793,0.1278 +1003,0.4974,0.3083 +1004,0.5211,0.4204 +1005,0.3159,0.4284 +1006,0.013,0.3186 +1007,0.0,0.3357 +1008,0.0,0.3735 +1009,0.0,0.3633 +1010,0.2217,0.4346 +1011,0.4367,0.4299 +1012,0.4988,0.3395 +1013,0.3452,0.2253 +1014,0.0156,0.0624 +1015,0.0,0.0171 +1016,0.0,0.0216 +1017,0.0,0.036 +1018,0.2005,0.0072 +1019,0.5906,0.006 +1020,0.5971,0.0014 +1021,0.3142,0.001 +1022,0.0096,0.0962 +1023,0.0,0.158 +1024,0.0,0.1365 +1025,0.0,0.1504 +1026,0.1324,0.1053 +1027,0.3224,0.2704 +1028,0.389,0.3903 +1029,0.2314,0.3904 +1030,0.0122,0.2828 +1031,0.0,0.3241 +1032,0.0,0.3723 +1033,0.0,0.398 +1034,0.1702,0.4441 +1035,0.5156,0.3959 +1036,0.6588,0.2832 +1037,0.4313,0.1566 +1038,0.0211,0.0478 +1039,0.0,0.0778 +1040,0.0,0.1485 +1041,0.0,0.1599 +1042,0.2327,0.069 +1043,0.6069,0.1333 +1044,0.6496,0.1604 +1045,0.3768,0.1496 +1046,0.0174,0.2086 +1047,0.0,0.2835 +1048,0.0,0.1801 +1049,0.0,0.1648 +1050,0.19,0.0887 +1051,0.5292,0.1557 +1052,0.5474,0.3755 +1053,0.2628,0.4853 +1054,0.0151,0.5044 +1055,0.0,0.2733 +1056,0.0,0.2128 +1057,0.0,0.247 +1058,0.2771,0.2634 +1059,0.6389,0.3456 +1060,0.6272,0.3031 +1061,0.3376,0.2149 +1062,0.0172,0.1476 +1063,0.0,0.131 +1064,0.0,0.1151 +1065,0.0,0.1109 +1066,0.1708,0.1099 +1067,0.4353,0.0882 +1068,0.4923,0.053 +1069,0.2846,0.027 +1070,0.015,0.0262 +1071,0.0,0.0537 +1072,0.0,0.0599 +1073,0.0,0.0569 +1074,0.1636,0.0163 +1075,0.4557,0.0278 +1076,0.6465,0.0465 +1077,0.3954,0.0649 +1078,0.0283,0.0673 +1079,0.0,0.0672 +1080,0.0,0.0454 +1081,0.0,0.0626 +1082,0.2144,0.07 +1083,0.4792,0.1786 +1084,0.38,0.2749 +1085,0.167,0.3232 +1086,0.0118,0.3181 +1087,0.0,0.2817 +1088,0.0,0.2293 +1089,0.0,0.1951 +1090,0.2099,0.2313 +1091,0.4852,0.3254 +1092,0.4661,0.3688 +1093,0.3221,0.3407 +1094,0.0241,0.1662 +1095,0.0,0.0888 +1096,0.0,0.0857 +1097,0.0,0.077 +1098,0.2269,0.0316 +1099,0.5733,0.1144 +1100,0.6257,0.1816 +1101,0.3594,0.1562 +1102,0.019,0.2088 +1103,0.0,0.2633 +1104,0.0,0.188 +1105,0.0001,0.1585 +1106,0.1559,0.2079 +1107,0.4259,0.3546 +1108,0.4796,0.3952 +1109,0.2928,0.3141 +1110,0.0234,0.1222 +1111,0.0,0.1067 +1112,0.0,0.1062 +1113,0.0002,0.0865 +1114,0.1409,0.0506 +1115,0.3299,0.0566 +1116,0.3563,0.0576 +1117,0.2329,0.0655 +1118,0.0243,0.0682 +1119,0.0,0.126 +1120,0.0,0.1283 +1121,0.0001,0.0862 +1122,0.2418,0.0487 +1123,0.4624,0.0658 +1124,0.4258,0.0852 +1125,0.3068,0.0801 +1126,0.0355,0.0442 +1127,0.0,0.044 +1128,0.0,0.0397 +1129,0.0002,0.0379 +1130,0.2913,0.0135 +1131,0.6624,0.0246 +1132,0.5885,0.0346 +1133,0.3304,0.0423 +1134,0.0232,0.0283 +1135,0.0,0.0408 +1136,0.0,0.0574 +1137,0.0,0.0525 +1138,0.1201,0.0346 +1139,0.4231,0.0504 +1140,0.3797,0.0758 +1141,0.2244,0.1003 +1142,0.0218,0.1068 +1143,0.0,0.1171 +1144,0.0,0.066 +1145,0.0001,0.0291 +1146,0.1795,0.0036 +1147,0.5072,0.0019 +1148,0.5141,0.0032 +1149,0.3246,0.0055 +1150,0.0321,0.0077 +1151,0.0,0.0188 +1152,0.0,0.0476 +1153,0.0001,0.0733 +1154,0.1407,0.0254 +1155,0.3896,0.0319 +1156,0.3281,0.0557 +1157,0.2149,0.0759 +1158,0.0309,0.0887 +1159,0.0,0.1197 +1160,0.0,0.1187 +1161,0.0001,0.094 +1162,0.1798,0.0695 +1163,0.3805,0.1177 +1164,0.284,0.165 +1165,0.2096,0.1972 +1166,0.0267,0.1648 +1167,0.0,0.1794 +1168,0.0,0.1728 +1169,0.0001,0.1338 +1170,0.1511,0.1515 +1171,0.2994,0.169 +1172,0.3156,0.1697 +1173,0.2406,0.1209 +1174,0.0297,0.0393 +1175,0.0,0.1111 +1176,0.0,0.1851 +1177,0.0,0.223 +1178,0.1891,0.2596 +1179,0.5188,0.4119 +1180,0.5392,0.4194 +1181,0.2828,0.2981 +1182,0.0258,0.153 +1183,0.0,0.1026 +1184,0.0,0.0727 +1185,0.0005,0.0803 +1186,0.2556,0.0417 +1187,0.4886,0.0885 +1188,0.4453,0.1467 +1189,0.2627,0.1888 +1190,0.0238,0.1822 +1191,0.0,0.2909 +1192,0.0,0.2887 +1193,0.0,0.2474 +1194,0.1852,0.3354 +1195,0.4208,0.4133 +1196,0.4548,0.4318 +1197,0.3158,0.3602 +1198,0.0354,0.1632 +1199,0.0,0.1537 +1200,0.0,0.1571 +1201,0.0003,0.1538 +1202,0.2326,0.1096 +1203,0.5383,0.2477 +1204,0.4982,0.332 +1205,0.2798,0.3788 +1206,0.0229,0.3872 +1207,0.0,0.377 +1208,0.0,0.3257 +1209,0.0,0.2146 +1210,0.1564,0.1407 +1211,0.3923,0.0916 +1212,0.3999,0.0589 +1213,0.2673,0.0317 +1214,0.0319,0.0166 +1215,0.0,0.1184 +1216,0.0,0.2196 +1217,0.0003,0.2883 +1218,0.19,0.4736 +1219,0.4549,0.6678 +1220,0.4776,0.7288 +1221,0.3272,0.7127 +1222,0.0276,0.6057 +1223,0.0,0.5675 +1224,0.0,0.511 +1225,0.0002,0.3861 +1226,0.2415,0.4004 +1227,0.4875,0.5205 +1228,0.5416,0.5434 +1229,0.3879,0.4048 +1230,0.0438,0.1872 +1231,0.0,0.1204 +1232,0.0,0.0705 +1233,0.0005,0.0382 +1234,0.3188,0.0075 +1235,0.7046,0.0028 +1236,0.7581,0.0017 +1237,0.4714,0.0044 +1238,0.0513,0.0546 +1239,0.0,0.2251 +1240,0.0,0.224 +1241,0.0012,0.1914 +1242,0.3194,0.0858 +1243,0.7128,0.099 +1244,0.7623,0.0631 +1245,0.4606,0.044 +1246,0.0492,0.1459 +1247,0.0,0.3353 +1248,0.0,0.2815 +1249,0.0003,0.1813 +1250,0.1929,0.1058 +1251,0.5613,0.1732 +1252,0.6402,0.211 +1253,0.3915,0.1992 +1254,0.0423,0.1491 +1255,0.0,0.1539 +1256,0.0,0.0975 +1257,0.0002,0.0854 +1258,0.2292,0.0564 +1259,0.5853,0.0599 +1260,0.6612,0.0666 +1261,0.425,0.076 +1262,0.0446,0.1225 +1263,0.0,0.1913 +1264,0.0,0.1249 +1265,0.0004,0.0946 +1266,0.2018,0.1114 +1267,0.42,0.1006 +1268,0.4209,0.1205 +1269,0.2407,0.1616 +1270,0.0308,0.1874 +1271,0.0,0.186 +1272,0.0,0.1446 +1273,0.0004,0.1086 +1274,0.1643,0.1028 +1275,0.3282,0.0806 +1276,0.3046,0.0738 +1277,0.2413,0.0715 +1278,0.035,0.085 +1279,0.0,0.1294 +1280,0.0,0.097 +1281,0.0007,0.0769 +1282,0.2206,0.0497 +1283,0.516,0.0569 +1284,0.469,0.0354 +1285,0.3251,0.0377 +1286,0.0469,0.085 +1287,0.0,0.1452 +1288,0.0,0.103 +1289,0.0008,0.0657 +1290,0.2459,0.025 +1291,0.566,0.0201 +1292,0.5835,0.0131 +1293,0.3841,0.0133 +1294,0.0493,0.0421 +1295,0.0,0.1177 +1296,0.0,0.1032 +1297,0.0003,0.0811 +1298,0.2842,0.0161 +1299,0.6837,0.0107 +1300,0.7122,0.0052 +1301,0.3941,0.0052 +1302,0.0439,0.0234 +1303,0.0,0.0901 +1304,0.0,0.0999 +1305,0.0003,0.0841 +1306,0.1958,0.0228 +1307,0.43,0.0713 +1308,0.4302,0.1315 +1309,0.2771,0.1724 +1310,0.0379,0.1629 +1311,0.0,0.1654 +1312,0.0,0.1607 +1313,0.0003,0.1318 +1314,0.2161,0.1209 +1315,0.5046,0.0902 +1316,0.5172,0.0576 +1317,0.3287,0.055 +1318,0.0404,0.1105 +1319,0.0,0.1518 +1320,0.0,0.1317 +1321,0.0005,0.1076 +1322,0.1561,0.0842 +1323,0.4347,0.0661 +1324,0.4803,0.0827 +1325,0.3307,0.1145 +1326,0.0438,0.1215 +1327,0.0,0.1293 +1328,0.0,0.0811 +1329,0.0003,0.0508 +1330,0.2134,0.0396 +1331,0.42,0.0595 +1332,0.3897,0.0936 +1333,0.2882,0.1252 +1334,0.0425,0.0926 +1335,0.0,0.0949 +1336,0.0,0.0673 +1337,0.0006,0.0462 +1338,0.2932,0.0346 +1339,0.6578,0.0593 +1340,0.6859,0.0901 +1341,0.3542,0.1206 +1342,0.0309,0.1216 +1343,0.0,0.1618 +1344,0.0,0.1667 +1345,0.0,0.1585 +1346,0.102,0.142 +1347,0.2764,0.2225 +1348,0.2931,0.3034 +1349,0.2061,0.3271 +1350,0.0277,0.2236 +1351,0.0,0.1925 +1352,0.0,0.1975 +1353,0.0001,0.1623 +1354,0.112,0.2201 +1355,0.3329,0.2846 +1356,0.3104,0.3001 +1357,0.2096,0.2681 +1358,0.0313,0.1325 +1359,0.0,0.1387 +1360,0.0,0.1292 +1361,0.0001,0.1202 +1362,0.1075,0.1209 +1363,0.3153,0.1264 +1364,0.3401,0.1463 +1365,0.2546,0.1258 +1366,0.0418,0.0707 +1367,0.0,0.0843 +1368,0.0,0.0707 +1369,0.0,0.0497 +1370,0.135,0.0401 +1371,0.328,0.0452 +1372,0.3071,0.05 +1373,0.2636,0.0651 +1374,0.0345,0.0748 +1375,0.0,0.1357 +1376,0.0,0.1508 +1377,0.0001,0.1178 +1378,0.1288,0.0817 +1379,0.2909,0.1573 +1380,0.2584,0.2052 +1381,0.1303,0.1471 +1382,0.0222,0.0894 +1383,0.0,0.107 +1384,0.0,0.1103 +1385,0.0004,0.1129 +1386,0.1501,0.165 +1387,0.3387,0.2261 +1388,0.4054,0.2211 +1389,0.2687,0.1981 +1390,0.0402,0.1523 +1391,0.0,0.2239 +1392,0.0,0.2336 +1393,0.0,0.2172 +1394,0.1151,0.2537 +1395,0.2771,0.2378 +1396,0.3917,0.2092 +1397,0.3386,0.1547 +1398,0.0506,0.075 +1399,0.0,0.1296 +1400,0.0,0.1499 +1401,0.0001,0.1091 +1402,0.2223,0.0308 +1403,0.5651,0.0409 +1404,0.5693,0.0381 +1405,0.3581,0.0325 +1406,0.0434,0.0117 +1407,0.0,0.008 +1408,0.0,0.0334 +1409,0.0002,0.0507 +1410,0.2023,0.0106 +1411,0.5453,0.0309 +1412,0.5065,0.0464 +1413,0.2668,0.0319 +1414,0.0267,0.0115 +1415,0.0,0.0294 +1416,0.0,0.1018 +1417,0.0,0.1307 +1418,0.1018,0.0759 +1419,0.2884,0.1462 +1420,0.3733,0.2211 +1421,0.2624,0.251 +1422,0.0453,0.1949 +1423,0.0,0.1875 +1424,0.0,0.1567 +1425,0.0002,0.1062 +1426,0.2636,0.0561 +1427,0.6281,0.0373 +1428,0.6611,0.0245 +1429,0.3543,0.0153 +1430,0.0333,0.0082 +1431,0.0,0.0294 +1432,0.0,0.0553 +1433,0.0005,0.0787 +1434,0.1785,0.0188 +1435,0.625,0.0189 +1436,0.6328,0.0529 +1437,0.3994,0.0639 +1438,0.0507,0.0784 +1439,0.0,0.0831 +1440,0.0,0.055 +1441,0.001,0.0379 +1442,0.2861,0.0132 +1443,0.6872,0.0101 +1444,0.7366,0.0195 +1445,0.4745,0.021 +1446,0.0642,0.0603 +1447,0.0,0.1623 +1448,0.0,0.1413 +1449,0.0005,0.1133 +1450,0.306,0.0419 +1451,0.697,0.0591 +1452,0.7584,0.0954 +1453,0.4743,0.1076 +1454,0.0625,0.1866 +1455,0.0,0.3309 +1456,0.0,0.2781 +1457,0.0004,0.2265 +1458,0.2972,0.1166 +1459,0.6854,0.2014 +1460,0.7482,0.1663 +1461,0.4653,0.142 +1462,0.0592,0.2161 +1463,0.0,0.2949 +1464,0.0,0.1917 +1465,0.0003,0.1237 +1466,0.2491,0.023 +1467,0.5813,0.0157 +1468,0.6883,0.008 +1469,0.4207,0.0081 +1470,0.0543,0.0645 +1471,0.0,0.127 +1472,0.0,0.107 +1473,0.0,0.0996 +1474,0.2681,0.0197 +1475,0.6532,0.0528 +1476,0.7045,0.0419 +1477,0.4078,0.0474 +1478,0.0379,0.123 +1479,0.0,0.1166 +1480,0.0,0.0814 +1481,0.0004,0.0554 +1482,0.2387,0.0057 +1483,0.5797,0.0081 +1484,0.5748,0.0135 +1485,0.3316,0.0515 +1486,0.0329,0.1231 +1487,0.0,0.239 +1488,0.0,0.2413 +1489,0.0003,0.2785 +1490,0.2161,0.2476 +1491,0.5958,0.2742 +1492,0.6691,0.2403 +1493,0.45,0.1424 +1494,0.0588,0.0458 +1495,0.0,0.0167 +1496,0.0,0.0298 +1497,0.0001,0.0721 +1498,0.2878,0.0282 +1499,0.6637,0.0456 +1500,0.6356,0.0877 +1501,0.3222,0.0876 +1502,0.0261,0.0973 +1503,0.0,0.1795 +1504,0.0,0.2224 +1505,0.0,0.2474 +1506,0.1458,0.373 +1507,0.403,0.5702 +1508,0.4087,0.6632 +1509,0.283,0.6523 +1510,0.0405,0.3891 +1511,0.0,0.3142 +1512,0.0,0.3073 +1513,0.0,0.2923 +1514,0.1655,0.4606 +1515,0.4102,0.5684 +1516,0.4345,0.6225 +1517,0.3401,0.6268 +1518,0.0458,0.475 +1519,0.0,0.3463 +1520,0.0,0.3127 +1521,0.0,0.3001 +1522,0.2544,0.3741 +1523,0.5744,0.3489 +1524,0.656,0.3081 +1525,0.4496,0.2455 +1526,0.059,0.099 +1527,0.0,0.0568 +1528,0.0,0.0645 +1529,0.0001,0.0669 +1530,0.2552,0.0107 +1531,0.5693,0.0184 +1532,0.6461,0.0241 +1533,0.4151,0.0351 +1534,0.051,0.0437 +1535,0.0,0.0924 +1536,0.0,0.0783 +1537,0.0,0.0775 +1538,0.1968,0.0434 +1539,0.4761,0.096 +1540,0.3949,0.1503 +1541,0.1875,0.1737 +1542,0.0172,0.0889 +1543,0.0,0.1255 +1544,0.0,0.1392 +1545,0.0,0.1138 +1546,0.0844,0.0981 +1547,0.2786,0.1539 +1548,0.23,0.2102 +1549,0.1365,0.1625 +1550,0.0223,0.0572 +1551,0.0,0.0884 +1552,0.0,0.0976 +1553,0.0,0.0952 +1554,0.0934,0.0944 +1555,0.276,0.1676 +1556,0.3489,0.2232 +1557,0.2679,0.2032 +1558,0.0354,0.0998 +1559,0.0,0.0956 +1560,0.0,0.1047 +1561,0.0,0.0876 +1562,0.197,0.0416 +1563,0.5164,0.0541 +1564,0.5619,0.0578 +1565,0.3584,0.0375 +1566,0.0406,0.012 +1567,0.0,0.0254 +1568,0.0,0.049 +1569,0.0,0.0501 +1570,0.1956,0.013 +1571,0.5491,0.0127 +1572,0.6,0.0181 +1573,0.4262,0.0158 +1574,0.0485,0.034 +1575,0.0,0.1028 +1576,0.0,0.1116 +1577,0.0,0.0954 +1578,0.1938,0.0659 +1579,0.4994,0.1665 +1580,0.6152,0.252 +1581,0.4017,0.2322 +1582,0.0458,0.1379 +1583,0.0,0.1056 +1584,0.0,0.1 +1585,0.0,0.0888 +1586,0.1878,0.0922 +1587,0.4884,0.1749 +1588,0.4789,0.2342 +1589,0.3529,0.1876 +1590,0.0439,0.0896 +1591,0.0,0.0676 +1592,0.0,0.0499 +1593,0.0,0.0585 +1594,0.1093,0.0534 +1595,0.2905,0.1808 +1596,0.3185,0.2765 +1597,0.2319,0.3105 +1598,0.0271,0.163 +1599,0.0,0.1587 +1600,0.0,0.2012 +1601,0.0,0.168 +1602,0.207,0.1454 +1603,0.4907,0.1494 +1604,0.4649,0.1232 +1605,0.3384,0.0654 +1606,0.0365,0.019 +1607,0.0,0.0573 +1608,0.0,0.1618 +1609,0.0,0.2072 +1610,0.1894,0.1911 +1611,0.4917,0.3089 +1612,0.5952,0.3554 +1613,0.4196,0.2722 +1614,0.0499,0.0826 +1615,0.0,0.0188 +1616,0.0,0.0145 +1617,0.0,0.0413 +1618,0.2097,0.022 +1619,0.5603,0.0646 +1620,0.5766,0.111 +1621,0.3128,0.1209 +1622,0.0284,0.1023 +1623,0.0,0.0898 +1624,0.0,0.0757 +1625,0.0,0.0805 +1626,0.158,0.0538 +1627,0.4581,0.083 +1628,0.4129,0.1117 +1629,0.3111,0.0908 +1630,0.0386,0.0534 +1631,0.0,0.0506 +1632,0.0,0.0407 +1633,0.0,0.0401 +1634,0.1909,0.0154 +1635,0.6016,0.0135 +1636,0.5822,0.0102 +1637,0.3524,0.0391 +1638,0.0279,0.1943 +1639,0.0,0.2767 +1640,0.0,0.2475 +1641,0.0,0.2438 +1642,0.1595,0.2285 +1643,0.4328,0.4075 +1644,0.4326,0.6074 +1645,0.2586,0.7132 +1646,0.0259,0.7053 +1647,0.0,0.7083 +1648,0.0,0.6672 +1649,0.0,0.522 +1650,0.2183,0.4305 +1651,0.5776,0.2779 +1652,0.5978,0.1815 +1653,0.3479,0.0952 +1654,0.0201,0.0279 +1655,0.0,0.1144 +1656,0.0,0.1606 +1657,0.0,0.1756 +1658,0.0963,0.2122 +1659,0.3008,0.3198 +1660,0.3332,0.3895 +1661,0.258,0.4142 +1662,0.0288,0.3197 +1663,0.0,0.3638 +1664,0.0,0.3578 +1665,0.0,0.3368 +1666,0.1376,0.498 +1667,0.3422,0.6197 +1668,0.3873,0.6185 +1669,0.2345,0.5797 +1670,0.0183,0.3592 +1671,0.0,0.2842 +1672,0.0,0.2899 +1673,0.0,0.2781 +1674,0.1461,0.3711 +1675,0.3634,0.4483 +1676,0.3459,0.5075 +1677,0.2751,0.5338 +1678,0.0302,0.4412 +1679,0.0,0.4816 +1680,0.0,0.4968 +1681,0.0,0.4367 +1682,0.1789,0.4962 +1683,0.4412,0.532 +1684,0.4205,0.5076 +1685,0.2843,0.4563 +1686,0.0287,0.3012 +1687,0.0,0.241 +1688,0.0,0.2171 +1689,0.0,0.178 +1690,0.2293,0.1531 +1691,0.5219,0.1354 +1692,0.5467,0.1055 +1693,0.3997,0.0583 +1694,0.0378,0.0515 +1695,0.0,0.1038 +1696,0.0,0.0687 +1697,0.0,0.0598 +1698,0.1751,0.0212 +1699,0.5457,0.0226 +1700,0.6396,0.0071 +1701,0.3259,0.0032 +1702,0.0256,0.0538 +1703,0.0,0.0949 +1704,0.0,0.0499 +1705,0.0,0.0352 +1706,0.2208,0.0094 +1707,0.5996,0.0045 +1708,0.6994,0.0068 +1709,0.411,0.0137 +1710,0.0275,0.0855 +1711,0.0,0.1543 +1712,0.0,0.1286 +1713,0.0,0.1246 +1714,0.2513,0.0456 +1715,0.6612,0.0777 +1716,0.7323,0.0654 +1717,0.4383,0.0425 +1718,0.0312,0.0941 +1719,0.0,0.1781 +1720,0.0,0.1876 +1721,0.0,0.1969 +1722,0.1982,0.0461 +1723,0.5156,0.0761 +1724,0.4412,0.1151 +1725,0.22,0.1481 +1726,0.0141,0.1637 +1727,0.0,0.1554 +1728,0.0,0.1204 +1729,0.0,0.0857 +1730,0.1985,0.0359 +1731,0.6264,0.0408 +1732,0.7171,0.0183 +1733,0.4357,0.0049 +1734,0.0246,0.0307 +1735,0.0,0.1718 +1736,0.0,0.1714 +1737,0.0,0.1506 +1738,0.2293,0.026 +1739,0.6286,0.0304 +1740,0.6919,0.0456 +1741,0.3952,0.0283 +1742,0.0241,0.0491 +1743,0.0,0.0974 +1744,0.0,0.0787 +1745,0.0,0.0488 +1746,0.1973,0.0071 +1747,0.5518,0.0106 +1748,0.6319,0.0076 +1749,0.382,0.0097 +1750,0.0239,0.0919 +1751,0.0,0.1192 +1752,0.0,0.0937 +1753,0.0,0.0976 +1754,0.159,0.057 +1755,0.4943,0.0564 +1756,0.5298,0.0487 +1757,0.3159,0.116 +1758,0.0176,0.2082 +1759,0.0,0.1426 +1760,0.0,0.0812 +1761,0.0,0.0631 +1762,0.1852,0.0464 +1763,0.5636,0.0258 +1764,0.6544,0.0178 +1765,0.3833,0.0251 +1766,0.0171,0.1292 +1767,0.0,0.141 +1768,0.0,0.0838 +1769,0.0,0.0695 +1770,0.156,0.0254 +1771,0.5177,0.0354 +1772,0.5905,0.0228 +1773,0.3269,0.0326 +1774,0.0141,0.1501 +1775,0.0,0.1246 +1776,0.0,0.0839 +1777,0.0,0.0685 +1778,0.1611,0.0265 +1779,0.5432,0.0211 +1780,0.6123,0.037 +1781,0.368,0.0705 +1782,0.0182,0.1333 +1783,0.0,0.1152 +1784,0.0,0.0746 +1785,0.0,0.053 +1786,0.1442,0.0372 +1787,0.4738,0.0656 +1788,0.5117,0.0485 +1789,0.3015,0.0552 +1790,0.011,0.1226 +1791,0.0,0.1204 +1792,0.0,0.108 +1793,0.0,0.1167 +1794,0.1883,0.1247 +1795,0.6261,0.1283 +1796,0.7034,0.1301 +1797,0.4004,0.1646 +1798,0.0153,0.2742 +1799,0.0,0.2591 +1800,0.0,0.2479 +1801,0.0,0.2438 +1802,0.1538,0.1839 +1803,0.5091,0.1736 +1804,0.5789,0.1704 +1805,0.2739,0.1027 +1806,0.0071,0.1425 +1807,0.0,0.1274 +1808,0.0,0.089 +1809,0.0,0.0668 +1810,0.1265,0.0405 +1811,0.3888,0.0367 +1812,0.3878,0.0292 +1813,0.2337,0.0259 +1814,0.0083,0.0439 +1815,0.0,0.0515 +1816,0.0,0.0456 +1817,0.0,0.0471 +1818,0.0522,0.0407 +1819,0.1952,0.0594 +1820,0.2223,0.074 +1821,0.1275,0.0866 +1822,0.0048,0.1073 +1823,0.0,0.1147 +1824,0.0,0.1209 +1825,0.0,0.1128 +1826,0.0418,0.0936 +1827,0.1473,0.1464 +1828,0.1853,0.1783 +1829,0.0904,0.1359 +1830,0.0029,0.1346 +1831,0.0,0.1472 +1832,0.0,0.1442 +1833,0.0,0.1193 +1834,0.0699,0.1074 +1835,0.2465,0.1233 +1836,0.256,0.081 +1837,0.1548,0.0364 +1838,0.0036,0.0336 +1839,0.0,0.0482 +1840,0.0,0.0452 +1841,0.0,0.0427 +1842,0.1241,0.0174 +1843,0.3774,0.0292 +1844,0.3482,0.0195 +1845,0.192,0.0095 +1846,0.0045,0.0205 +1847,0.0,0.0201 +1848,0.0,0.0203 +1849,0.0,0.0225 +1850,0.1168,0.0066 +1851,0.4309,0.0256 +1852,0.4406,0.0131 +1853,0.2633,0.0069 +1854,0.005,0.0278 +1855,0.0,0.0541 +1856,0.0,0.0497 +1857,0.0,0.0337 +1858,0.187,0.0093 +1859,0.6309,0.0315 +1860,0.6249,0.016 +1861,0.3725,0.0109 +1862,0.0065,0.0523 +1863,0.0,0.0982 +1864,0.0,0.0898 +1865,0.0,0.0683 +1866,0.2083,0.0158 +1867,0.6385,0.0511 +1868,0.6625,0.0584 +1869,0.383,0.0802 +1870,0.0053,0.2014 +1871,0.0,0.2441 +1872,0.0,0.2041 +1873,0.0,0.1929 +1874,0.1959,0.1011 +1875,0.6326,0.2687 +1876,0.611,0.2982 +1877,0.2874,0.3246 +1878,0.003,0.351 +1879,0.0,0.3178 +1880,0.0,0.2208 +1881,0.0,0.1513 +1882,0.1058,0.1022 +1883,0.0962,0.1906 +1884,0.0723,0.2286 +1885,0.0,0.1843 +1886,0.0,0.2887 +1887,0.0,0.3264 +1888,0.0,0.378 +1889,0.0,0.3687 +1890,0.0881,0.4315 +1891,0.3205,0.6155 +1892,0.3815,0.5585 +1893,0.2758,0.3574 +1894,0.0021,0.1842 +1895,0.0,0.2249 +1896,0.0,0.2686 +1897,0.0,0.2586 +1898,0.159,0.1431 +1899,0.5956,0.2544 +1900,0.667,0.2727 +1901,0.3601,0.1886 +1902,0.0019,0.2319 +1903,0.0,0.3577 +1904,0.0,0.2982 +1905,0.0,0.2487 +1906,0.0934,0.1608 +1907,0.356,0.1974 +1908,0.37,0.2063 +1909,0.1789,0.1187 +1910,0.0003,0.0972 +1911,0.0,0.1038 +1912,0.0,0.1013 +1913,0.0,0.0777 +1914,0.0811,0.0548 +1915,0.2917,0.0608 +1916,0.3377,0.0811 +1917,0.2352,0.0766 +1918,0.0008,0.0464 +1919,0.0,0.0476 +1920,0.0,0.0454 +1921,0.0,0.0338 +1922,0.1621,0.0147 +1923,0.5777,0.0159 +1924,0.6272,0.0121 +1925,0.357,0.0035 +1926,0.0013,0.0169 +1927,0.0,0.0803 +1928,0.0,0.0725 +1929,0.0,0.0567 +1930,0.1604,0.0237 +1931,0.5195,0.0277 +1932,0.6103,0.0279 +1933,0.3285,0.0224 +1934,0.0007,0.0616 +1935,0.0,0.0796 +1936,0.0,0.0602 +1937,0.0,0.0574 +1938,0.1489,0.0341 +1939,0.5697,0.0558 +1940,0.6408,0.0842 +1941,0.3502,0.0945 +1942,0.0008,0.1196 +1943,0.0,0.168 +1944,0.0,0.1863 +1945,0.0,0.1784 +1946,0.1351,0.1016 +1947,0.3864,0.1391 +1948,0.3151,0.2152 +1949,0.1395,0.2378 +1950,0.0,0.2563 +1951,0.0,0.2469 +1952,0.0,0.2063 +1953,0.0,0.1777 +1954,0.152,0.1728 +1955,0.4944,0.2383 +1956,0.4479,0.1968 +1957,0.2343,0.1288 +1958,0.0,0.073 +1959,0.0,0.0666 +1960,0.0,0.0659 +1961,0.0,0.0562 +1962,0.1028,0.0284 +1963,0.364,0.0529 +1964,0.3637,0.054 +1965,0.2299,0.0437 +1966,0.0,0.0416 +1967,0.0,0.0515 +1968,0.0,0.0866 +1969,0.0,0.1243 +1970,0.1313,0.0929 +1971,0.4642,0.1962 +1972,0.4027,0.2681 +1973,0.2412,0.2835 +1974,0.0,0.2396 +1975,0.0,0.2636 +1976,0.0,0.296 +1977,0.0,0.3157 +1978,0.1116,0.3281 +1979,0.3391,0.5081 +1980,0.3356,0.6031 +1981,0.1821,0.5933 +1982,0.0,0.4472 +1983,0.0,0.4449 +1984,0.0,0.4869 +1985,0.0,0.5203 +1986,0.0985,0.555 +1987,0.3123,0.6841 +1988,0.3213,0.662 +1989,0.2226,0.5743 +1990,0.0,0.4313 +1991,0.0,0.3044 +1992,0.0,0.2142 +1993,0.0,0.1829 +1994,0.095,0.1307 +1995,0.3466,0.1585 +1996,0.3926,0.1549 +1997,0.2228,0.1524 +1998,0.0,0.1366 +1999,0.0,0.0925 +2000,0.0,0.0756 +2001,0.0,0.0824 +2002,0.1228,0.0447 +2003,0.4416,0.0655 +2004,0.4173,0.0657 +2005,0.2409,0.0725 +2006,0.0,0.086 +2007,0.0,0.0737 +2008,0.0,0.057 +2009,0.0,0.053 +2010,0.1559,0.0207 +2011,0.5112,0.0621 +2012,0.5007,0.0615 +2013,0.289,0.082 +2014,0.0,0.1772 +2015,0.0,0.1692 +2016,0.0,0.1209 +2017,0.0,0.1028 +2018,0.15,0.0388 +2019,0.5521,0.1137 +2020,0.4872,0.1165 +2021,0.2668,0.1611 +2022,0.0,0.215 +2023,0.0,0.1976 +2024,0.0,0.1667 +2025,0.0,0.138 +2026,0.1241,0.0802 +2027,0.4593,0.1172 +2028,0.4971,0.1132 +2029,0.2555,0.0948 +2030,0.0,0.1559 +2031,0.0,0.1664 +2032,0.0,0.1273 +2033,0.0,0.0967 +2034,0.087,0.0445 +2035,0.4594,0.0866 +2036,0.6147,0.1183 +2037,0.2483,0.0721 +2038,0.0,0.2593 +2039,0.0,0.2149 +2040,0.0,0.1364 +2041,0.0,0.1018 +2042,0.0882,0.0524 +2043,0.3733,0.0548 +2044,0.3699,0.0636 +2045,0.1506,0.089 +2046,0.0,0.2233 +2047,0.0,0.24 +2048,0.0,0.2401 +2049,0.0,0.1895 +2050,0.062,0.1146 +2051,0.2301,0.1231 +2052,0.2511,0.1854 +2053,0.1643,0.2041 +2054,0.0,0.296 +2055,0.0,0.3353 +2056,0.0,0.3023 +2057,0.0,0.2806 +2058,0.0933,0.2061 +2059,0.3304,0.4669 +2060,0.3366,0.5251 +2061,0.1757,0.4402 +2062,0.0,0.3389 +2063,0.0,0.2685 +2064,0.0,0.1876 +2065,0.0,0.1589 +2066,0.0422,0.1627 +2067,0.2134,0.2944 +2068,0.229,0.3643 +2069,0.1155,0.3082 +2070,0.0,0.3904 +2071,0.0,0.3947 +2072,0.0,0.421 +2073,0.0,0.3771 +2074,0.0796,0.3476 +2075,0.2799,0.5142 +2076,0.2273,0.4211 +2077,0.0677,0.3835 +2078,0.0,0.3789 +2079,0.0,0.384 +2080,0.0,0.3191 +2081,0.0,0.2332 +2082,0.1007,0.1511 +2083,0.4348,0.1952 +2084,0.3981,0.1526 +2085,0.1934,0.1046 +2086,0.0,0.0882 +2087,0.0,0.1292 +2088,0.0,0.1356 +2089,0.0,0.1167 +2090,0.0953,0.0352 +2091,0.3522,0.0367 +2092,0.296,0.0549 +2093,0.1674,0.0604 +2094,0.0,0.1089 +2095,0.0,0.1267 +2096,0.0,0.1353 +2097,0.0,0.149 +2098,0.0694,0.1065 +2099,0.2916,0.1889 +2100,0.3135,0.2615 +2101,0.1774,0.2021 +2102,0.0,0.1747 +2103,0.0,0.1474 +2104,0.0,0.1148 +2105,0.0,0.0897 +2106,0.0964,0.0593 +2107,0.4408,0.0751 +2108,0.3971,0.0713 +2109,0.1958,0.0325 +2110,0.0,0.0574 +2111,0.0,0.1685 +2112,0.0,0.1621 +2113,0.0,0.1722 +2114,0.0556,0.1535 +2115,0.2309,0.1892 +2116,0.2287,0.19 +2117,0.0881,0.1029 +2118,0.0,0.1359 +2119,0.0,0.114 +2120,0.0,0.0939 +2121,0.0,0.0862 +2122,0.0444,0.0555 +2123,0.1855,0.0538 +2124,0.2536,0.0466 +2125,0.1227,0.0452 +2126,0.0,0.0618 +2127,0.0,0.1158 +2128,0.0,0.1384 +2129,0.0,0.1498 +2130,0.0793,0.108 +2131,0.3573,0.1793 +2132,0.3695,0.2346 +2133,0.1771,0.2109 +2134,0.0,0.186 +2135,0.0,0.166 +2136,0.0,0.1338 +2137,0.0,0.1015 +2138,0.0739,0.0515 +2139,0.3989,0.0331 +2140,0.326,0.0458 +2141,0.1452,0.0504 +2142,0.0,0.0682 +2143,0.0,0.0598 +2144,0.0,0.0622 +2145,0.0,0.0595 +2146,0.0751,0.0233 +2147,0.3895,0.0262 +2148,0.2618,0.0372 +2149,0.1343,0.0666 +2150,0.0,0.132 +2151,0.0,0.1273 +2152,0.0,0.0916 +2153,0.0,0.0751 +2154,0.0978,0.0447 +2155,0.5034,0.066 +2156,0.3747,0.0647 +2157,0.2092,0.0847 +2158,0.0,0.1286 +2159,0.0,0.1012 +2160,0.0,0.0697 +2161,0.0,0.0514 +2162,0.0969,0.028 +2163,0.496,0.0444 +2164,0.4422,0.051 +2165,0.2172,0.0897 +2166,0.0,0.1972 +2167,0.0,0.1502 +2168,0.0,0.1178 +2169,0.0,0.1048 +2170,0.1007,0.0583 +2171,0.5337,0.141 +2172,0.4134,0.1523 +2173,0.2041,0.1743 +2174,0.0,0.2708 +2175,0.0,0.2238 +2176,0.0,0.1633 +2177,0.0,0.1155 +2178,0.0979,0.055 +2179,0.5546,0.1205 +2180,0.5579,0.116 +2181,0.2353,0.134 +2182,0.0,0.1783 +2183,0.0,0.1323 +2184,0.0,0.0925 +2185,0.0,0.0781 +2186,0.116,0.0526 +2187,0.617,0.0855 +2188,0.6609,0.0853 +2189,0.2712,0.0586 +2190,0.0,0.0819 +2191,0.0,0.0683 +2192,0.0,0.054 +2193,0.0,0.0558 +2194,0.0997,0.0309 +2195,0.5647,0.0251 +2196,0.6442,0.0326 +2197,0.2559,0.0396 +2198,0.0,0.1189 +2199,0.0,0.1262 +2200,0.0,0.104 +2201,0.0,0.0764 +2202,0.0891,0.0484 +2203,0.5324,0.0264 +2204,0.5742,0.0353 +2205,0.1922,0.0208 +2206,0.0,0.0331 +2207,0.0,0.0474 +2208,0.0,0.0669 +2209,0.0,0.0984 +2210,0.0475,0.0829 +2211,0.3089,0.0509 +2212,0.3451,0.0479 +2213,0.1403,0.033 +2214,0.0,0.0413 +2215,0.0,0.044 +2216,0.0,0.0438 +2217,0.0,0.048 +2218,0.0591,0.0472 +2219,0.413,0.0475 +2220,0.477,0.0694 +2221,0.1524,0.0737 +2222,0.0,0.2305 +2223,0.0,0.2316 +2224,0.0,0.23 +2225,0.0,0.2386 +2226,0.0334,0.2307 +2227,0.2152,0.2815 +2228,0.2053,0.2487 +2229,0.0698,0.2046 +2230,0.0,0.2839 +2231,0.0,0.2389 +2232,0.0,0.2054 +2233,0.0,0.1951 +2234,0.0258,0.1839 +2235,0.1473,0.191 +2236,0.1297,0.1751 +2237,0.0555,0.1253 +2238,0.0,0.1595 +2239,0.0,0.1383 +2240,0.0,0.0998 +2241,0.0,0.073 +2242,0.0284,0.0595 +2243,0.153,0.0372 +2244,0.1234,0.022 +2245,0.0598,0.0132 +2246,0.0,0.0279 +2247,0.0,0.0292 +2248,0.0,0.0236 +2249,0.0,0.0246 +2250,0.0244,0.0253 +2251,0.189,0.0342 +2252,0.2068,0.0283 +2253,0.0932,0.0462 +2254,0.0,0.1186 +2255,0.0,0.1065 +2256,0.0,0.1018 +2257,0.0,0.1063 +2258,0.0369,0.0885 +2259,0.3341,0.1355 +2260,0.4116,0.1656 +2261,0.1426,0.2031 +2262,0.0,0.2993 +2263,0.0,0.2461 +2264,0.0,0.2149 +2265,0.0,0.1965 +2266,0.0584,0.1663 +2267,0.5077,0.3348 +2268,0.5618,0.3195 +2269,0.1919,0.2965 +2270,0.0,0.3161 +2271,0.0,0.24 +2272,0.0,0.1889 +2273,0.0,0.1571 +2274,0.0619,0.1167 +2275,0.5501,0.1786 +2276,0.5621,0.1333 +2277,0.1432,0.129 +2278,0.0,0.2229 +2279,0.0,0.1552 +2280,0.0,0.1163 +2281,0.0,0.1171 +2282,0.0266,0.0975 +2283,0.1835,0.1069 +2284,0.1296,0.0664 +2285,0.0427,0.0788 +2286,0.0,0.0793 +2287,0.0,0.0559 +2288,0.0,0.06 +2289,0.0,0.0737 +2290,0.0133,0.0848 +2291,0.0875,0.1384 +2292,0.0654,0.1352 +2293,0.0314,0.0923 +2294,0.0,0.1117 +2295,0.0,0.0858 +2296,0.0,0.0774 +2297,0.0,0.1005 +2298,0.0073,0.1296 +2299,0.0572,0.1637 +2300,0.0448,0.167 +2301,0.0297,0.1466 +2302,0.0,0.1404 +2303,0.0,0.0782 +2304,0.0,0.0585 +2305,0.0,0.0478 +2306,0.007,0.0377 +2307,0.0592,0.0285 +2308,0.0566,0.0249 +2309,0.0355,0.0278 +2310,0.0,0.0316 +2311,0.0,0.0264 +2312,0.0,0.0198 +2313,0.0,0.0151 +2314,0.0077,0.0159 +2315,0.1013,0.0078 +2316,0.0775,0.0045 +2317,0.0343,0.0052 +2318,0.0,0.0153 +2319,0.0,0.0201 +2320,0.0,0.0267 +2321,0.0,0.0338 +2322,0.0077,0.0293 +2323,0.1216,0.0184 +2324,0.1027,0.0135 +2325,0.0339,0.0119 +2326,0.0,0.0228 +2327,0.0,0.0263 +2328,0.0,0.0324 +2329,0.0,0.0413 +2330,0.0102,0.0415 +2331,0.1408,0.018 +2332,0.1243,0.0147 +2333,0.0501,0.0121 +2334,0.0,0.0219 +2335,0.0,0.0124 +2336,0.0,0.0075 +2337,0.0,0.0097 +2338,0.0075,0.0201 +2339,0.1299,0.0137 +2340,0.1023,0.0346 +2341,0.0356,0.0346 +2342,0.0,0.0592 +2343,0.0,0.0515 +2344,0.0,0.046 +2345,0.0,0.035 +2346,0.0062,0.0466 +2347,0.1517,0.0547 +2348,0.1858,0.0933 +2349,0.0554,0.1007 +2350,0.0,0.1542 +2351,0.0,0.1981 +2352,0.0,0.1965 +2353,0.0,0.1904 +2354,0.0111,0.1913 +2355,0.1569,0.1898 +2356,0.1308,0.2382 +2357,0.0299,0.2686 +2358,0.0,0.3059 +2359,0.0,0.2716 +2360,0.0,0.2582 +2361,0.0,0.2453 +2362,0.0093,0.2328 +2363,0.3221,0.2 +2364,0.2264,0.1673 +2365,0.0598,0.0799 +2366,0.0,0.0677 +2367,0.0,0.0581 +2368,0.0,0.0786 +2369,0.0,0.1042 +2370,0.0086,0.1032 +2371,0.2626,0.0538 +2372,0.3173,0.084 +2373,0.0618,0.0696 +2374,0.0,0.1384 +2375,0.0,0.1443 +2376,0.0,0.1404 +2377,0.0,0.1211 +2378,0.0042,0.1252 +2379,0.1522,0.0996 +2380,0.1594,0.1084 +2381,0.0462,0.0578 +2382,0.0,0.039 +2383,0.0,0.0323 +2384,0.0,0.0427 +2385,0.0,0.0561 +2386,0.0062,0.0628 +2387,0.3067,0.0705 +2388,0.4197,0.0865 +2389,0.0819,0.1085 +2390,0.0,0.246 +2391,0.0,0.196 +2392,0.0,0.1476 +2393,0.0,0.1112 +2394,0.0063,0.0881 +2395,0.3026,0.0668 +2396,0.3883,0.082 +2397,0.0673,0.1034 +2398,0.0,0.2404 +2399,0.0,0.163 +2400,0.0,0.1068 +2401,0.0,0.105 +2402,0.0043,0.1137 +2403,0.2208,0.1036 +2404,0.2877,0.1076 +2405,0.0487,0.1159 +2406,0.0,0.16 +2407,0.0,0.1083 +2408,0.0,0.0651 +2409,0.0,0.0379 +2410,0.0022,0.0259 +2411,0.1654,0.0048 +2412,0.1703,0.0081 +2413,0.0351,0.0027 +2414,0.0,0.0062 +2415,0.0,0.0137 +2416,0.0,0.0167 +2417,0.0,0.034 +2418,0.0006,0.0398 +2419,0.1152,0.0228 +2420,0.187,0.0258 +2421,0.0287,0.0586 +2422,0.0,0.1401 +2423,0.0,0.1445 +2424,0.0,0.1216 +2425,0.0,0.0954 +2426,0.0028,0.0943 +2427,0.2941,0.1258 +2428,0.4238,0.1167 +2429,0.0544,0.1573 +2430,0.0,0.2619 +2431,0.0,0.2005 +2432,0.0,0.1506 +2433,0.0,0.1044 +2434,0.0049,0.0848 +2435,0.4335,0.0246 +2436,0.4785,0.0192 +2437,0.0703,0.0191 +2438,0.0,0.0376 +2439,0.0,0.0687 +2440,0.0,0.0614 +2441,0.0,0.0373 +2442,0.005,0.029 +2443,0.3665,0.0042 +2444,0.3958,0.0036 +2445,0.0607,0.0129 +2446,0.0,0.0962 +2447,0.0,0.0952 +2448,0.0,0.0749 +2449,0.0,0.0532 +2450,0.0043,0.0445 +2451,0.3771,0.0095 +2452,0.4236,0.0024 +2453,0.0617,0.0042 +2454,0.0,0.026 +2455,0.0,0.0496 +2456,0.0,0.0745 +2457,0.0,0.0839 +2458,0.0025,0.0835 +2459,0.3239,0.0313 +2460,0.366,0.0375 +2461,0.0398,0.053 +2462,0.0,0.1328 +2463,0.0,0.1223 +2464,0.0,0.1021 +2465,0.0,0.0891 +2466,0.001,0.0937 +2467,0.336,0.0234 +2468,0.3682,0.0329 +2469,0.0554,0.0434 +2470,0.0,0.1419 +2471,0.0,0.1807 +2472,0.0,0.1563 +2473,0.0,0.1646 +2474,0.0015,0.213 +2475,0.153,0.1392 +2476,0.2002,0.1589 +2477,0.0278,0.171 +2478,0.0,0.2497 +2479,0.0,0.2809 +2480,0.0,0.3152 +2481,0.0,0.3067 +2482,0.0003,0.2332 +2483,0.1075,0.1101 +2484,0.1494,0.1381 +2485,0.0185,0.293 +2486,0.0,0.4957 +2487,0.0,0.6284 +2488,0.0,0.6653 +2489,0.0,0.4983 +2490,0.0014,0.3332 +2491,0.3745,0.166 +2492,0.4153,0.0825 +2493,0.029,0.0906 +2494,0.0,0.2273 +2495,0.0,0.328 +2496,0.0,0.4049 +2497,0.0,0.4322 +2498,0.0006,0.4361 +2499,0.1738,0.4534 +2500,0.1083,0.6216 +2501,0.0137,0.6628 +2502,0.0,0.6726 +2503,0.0,0.6895 +2504,0.0,0.7005 +2505,0.0,0.7349 +2506,0.0005,0.7311 +2507,0.1321,0.6722 +2508,0.1235,0.6353 +2509,0.0188,0.5373 +2510,0.0,0.4668 +2511,0.0,0.4586 +2512,0.0,0.4149 +2513,0.0,0.3672 +2514,0.0003,0.3454 +2515,0.1409,0.311 +2516,0.2065,0.3237 +2517,0.0262,0.2873 +2518,0.0,0.3064 +2519,0.0,0.3022 +2520,0.0,0.3057 +2521,0.0,0.3145 +2522,0.0002,0.3233 +2523,0.1689,0.268 +2524,0.2355,0.2847 +2525,0.0226,0.14 +2526,0.0,0.1218 +2527,0.0,0.1816 +2528,0.0,0.2143 +2529,0.0,0.2417 +2530,0.0001,0.3451 +2531,0.1614,0.3538 +2532,0.1764,0.4449 +2533,0.0089,0.4712 +2534,0.0,0.5178 +2535,0.0,0.6252 +2536,0.0,0.6428 +2537,0.0,0.6253 +2538,0.0,0.6188 +2539,0.2175,0.63 +2540,0.1745,0.6593 +2541,0.0094,0.5124 +2542,0.0,0.4084 +2543,0.0,0.3519 +2544,0.0,0.3658 +2545,0.0,0.4369 +2546,0.0043,0.5476 +2547,0.0503,0.6316 +2548,0.0994,0.6723 +2549,0.0104,0.6371 +2550,0.0,0.4828 +2551,0.0,0.3618 +2552,0.0,0.3126 +2553,0.0,0.3304 +2554,0.0001,0.4175 +2555,0.165,0.4302 +2556,0.1966,0.4325 +2557,0.012,0.3893 +2558,0.0,0.4007 +2559,0.0,0.338 +2560,0.0,0.3001 +2561,0.0,0.3047 +2562,0.0001,0.3343 +2563,0.0795,0.3789 +2564,0.0998,0.4059 +2565,0.003,0.4122 +2566,0.0,0.5423 +2567,0.0,0.797 +2568,0.0,0.9153 +2569,0.0,0.9194 +2570,0.0,0.9097 +2571,0.1345,0.7914 +2572,0.2048,0.7496 +2573,0.0114,0.5863 +2574,0.0,0.6716 +2575,0.0,0.7933 +2576,0.0,0.7982 +2577,0.0,0.8247 +2578,0.0,0.847 +2579,0.1481,0.8602 +2580,0.1475,0.855 +2581,0.0067,0.6944 +2582,0.0,0.5351 +2583,0.0,0.3958 +2584,0.0,0.3695 +2585,0.0,0.3559 +2586,0.0,0.3662 +2587,0.0882,0.2904 +2588,0.0938,0.2536 +2589,0.0016,0.1684 +2590,0.0,0.1643 +2591,0.0,0.1725 +2592,0.0,0.1778 +2593,0.0,0.1684 +2594,0.0,0.1378 +2595,0.1628,0.0942 +2596,0.1325,0.0877 +2597,0.0058,0.1074 +2598,0.0,0.1464 +2599,0.0,0.1469 +2600,0.0,0.159 +2601,0.0,0.1875 +2602,0.0,0.2067 +2603,0.1284,0.251 +2604,0.1424,0.2964 +2605,0.0066,0.2977 +2606,0.0,0.2655 +2607,0.0,0.2129 +2608,0.0,0.1499 +2609,0.0,0.1245 +2610,0.0,0.0932 +2611,0.1479,0.0393 +2612,0.191,0.0555 +2613,0.0056,0.1205 +2614,0.0,0.1502 +2615,0.0,0.174 +2616,0.0,0.2166 +2617,0.0,0.235 +2618,0.0,0.2691 +2619,0.161,0.3212 +2620,0.2165,0.4248 +2621,0.0042,0.4761 +2622,0.0,0.4315 +2623,0.0,0.3382 +2624,0.0,0.254 +2625,0.0,0.1855 +2626,0.0,0.1622 +2627,0.0566,0.1165 +2628,0.0618,0.0935 +2629,0.0013,0.1054 +2630,0.0,0.1089 +2631,0.0,0.1104 +2632,0.0,0.1122 +2633,0.0,0.1177 +2634,0.0,0.0944 +2635,0.1261,0.0424 +2636,0.1684,0.0348 +2637,0.0027,0.0629 +2638,0.0,0.0632 +2639,0.0,0.0603 +2640,0.0,0.0725 +2641,0.0,0.0702 +2642,0.0,0.0976 +2643,0.1545,0.1367 +2644,0.2331,0.1535 +2645,0.0022,0.2095 +2646,0.0,0.2941 +2647,0.0,0.3249 +2648,0.0,0.3427 +2649,0.0,0.3477 +2650,0.0,0.3586 +2651,0.0877,0.3502 +2652,0.1261,0.3765 +2653,0.0033,0.3884 +2654,0.0,0.4097 +2655,0.0,0.5028 +2656,0.0,0.6399 +2657,0.0,0.7049 +2658,0.0,0.6917 +2659,0.041,0.6622 +2660,0.0505,0.6358 +2661,0.0006,0.7317 +2662,0.0,0.8693 +2663,0.0,0.9318 +2664,0.0,0.8654 +2665,0.0,0.8077 +2666,0.0,0.7642 +2667,0.0946,0.6419 +2668,0.0625,0.5908 +2669,0.0006,0.4278 +2670,0.0,0.492 +2671,0.0,0.5742 +2672,0.0,0.6023 +2673,0.0,0.5154 +2674,0.0,0.3815 +2675,0.1282,0.3041 +2676,0.1317,0.2962 +2677,0.0022,0.2461 +2678,0.0,0.2108 +2679,0.0,0.2469 +2680,0.0,0.3772 +2681,0.0,0.411 +2682,0.0,0.3926 +2683,0.0417,0.2936 +2684,0.052,0.2271 +2685,0.0012,0.3216 +2686,0.0,0.325 +2687,0.0,0.3202 +2688,0.0,0.2959 +2689,0.0,0.2647 +2690,0.0,0.2377 +2691,0.1686,0.1408 +2692,0.2583,0.1236 +2693,0.0087,0.2242 +2694,0.0,0.2394 +2695,0.0,0.2518 +2696,0.0,0.2847 +2697,0.0,0.4138 +2698,0.0,0.5766 +2699,0.1063,0.5599 +2700,0.1264,0.4717 +2701,0.0025,0.4337 +2702,0.0,0.4208 +2703,0.0,0.4126 +2704,0.0,0.4081 +2705,0.0,0.4207 +2706,0.0,0.4748 +2707,0.185,0.5405 +2708,0.2001,0.6203 +2709,0.0045,0.6536 +2710,0.0,0.6789 +2711,0.0,0.6761 +2712,0.0,0.6641 +2713,0.0,0.6594 +2714,0.0,0.6529 +2715,0.1953,0.6454 +2716,0.2199,0.725 +2717,0.0054,0.6846 +2718,0.0,0.6151 +2719,0.0,0.4782 +2720,0.0,0.3031 +2721,0.0,0.1742 +2722,0.0,0.1045 +2723,0.178,0.048 +2724,0.2582,0.0184 +2725,0.0088,0.0388 +2726,0.0,0.083 +2727,0.0,0.1143 +2728,0.0,0.1241 +2729,0.0,0.1146 +2730,0.0,0.1158 +2731,0.2838,0.1133 +2732,0.3789,0.1042 +2733,0.007,0.2589 +2734,0.0,0.2881 +2735,0.0,0.2279 +2736,0.0,0.207 +2737,0.0,0.1859 +2738,0.0,0.2148 +2739,0.1235,0.2103 +2740,0.161,0.2208 +2741,0.0033,0.2353 +2742,0.0,0.2473 +2743,0.0,0.2064 +2744,0.0,0.2108 +2745,0.0,0.2552 +2746,0.0,0.2869 +2747,0.2271,0.3501 +2748,0.2802,0.3515 +2749,0.0039,0.4076 +2750,0.0,0.4094 +2751,0.0,0.3972 +2752,0.0,0.3696 +2753,0.0,0.3579 +2754,0.0,0.3851 +2755,0.092,0.421 +2756,0.0875,0.4863 +2757,0.0009,0.5104 +2758,0.0,0.4554 +2759,0.0,0.4853 +2760,0.0,0.4799 +2761,0.0,0.4107 +2762,0.0,0.3737 +2763,0.1511,0.2957 +2764,0.1779,0.2677 +2765,0.0023,0.2009 +2766,0.0,0.2195 +2767,0.0,0.3771 +2768,0.0,0.5956 +2769,0.0,0.7329 +2770,0.0,0.5797 +2771,0.1036,0.3663 +2772,0.1629,0.1971 +2773,0.0029,0.1221 +2774,0.0,0.0683 +2775,0.0,0.0344 +2776,0.0,0.0211 +2777,0.0,0.025 +2778,0.0,0.0519 +2779,0.1284,0.0537 +2780,0.1595,0.052 +2781,0.0033,0.1306 +2782,0.0,0.1561 +2783,0.0,0.1273 +2784,0.0,0.1038 +2785,0.0,0.0697 +2786,0.0,0.0525 +2787,0.1037,0.0247 +2788,0.1595,0.0111 +2789,0.0055,0.0363 +2790,0.0,0.0712 +2791,0.0,0.0827 +2792,0.0,0.099 +2793,0.0,0.1288 +2794,0.0,0.1691 +2795,0.0724,0.16 +2796,0.1213,0.1426 +2797,0.0009,0.2429 +2798,0.0,0.2579 +2799,0.0,0.2683 +2800,0.0,0.276 +2801,0.0,0.2743 +2802,0.0,0.2713 +2803,0.0625,0.2479 +2804,0.1099,0.2447 +2805,0.0027,0.3194 +2806,0.0,0.3394 +2807,0.0,0.3266 +2808,0.0,0.3126 +2809,0.0,0.302 +2810,0.0,0.3049 +2811,0.0742,0.2592 +2812,0.0986,0.229 +2813,0.0033,0.2538 +2814,0.0,0.2589 +2815,0.0,0.2703 +2816,0.0,0.2514 +2817,0.0,0.2238 +2818,0.0,0.2146 +2819,0.1239,0.1863 +2820,0.2425,0.1806 +2821,0.0043,0.2804 +2822,0.0,0.291 +2823,0.0,0.2571 +2824,0.0,0.235 +2825,0.0,0.2318 +2826,0.0,0.2668 +2827,0.1622,0.2727 +2828,0.1926,0.258 +2829,0.0035,0.3042 +2830,0.0,0.311 +2831,0.0,0.2922 +2832,0.0,0.271 +2833,0.0,0.303 +2834,0.0,0.3304 +2835,0.0963,0.3636 +2836,0.1227,0.5247 +2837,0.002,0.6305 +2838,0.0,0.7355 +2839,0.0,0.8302 +2840,0.0,0.7836 +2841,0.0,0.5837 +2842,0.0,0.4359 +2843,0.1033,0.5368 +2844,0.153,0.707 +2845,0.0053,0.7062 +2846,0.0,0.6754 +2847,0.0,0.6518 +2848,0.0,0.6582 +2849,0.0,0.6772 +2850,0.0,0.6732 +2851,0.1527,0.6539 +2852,0.1797,0.6557 +2853,0.002,0.4876 +2854,0.0,0.3403 +2855,0.0,0.2082 +2856,0.0,0.1171 +2857,0.0,0.1279 +2858,0.0,0.1835 +2859,0.1949,0.2419 +2860,0.2816,0.3181 +2861,0.0068,0.4694 +2862,0.0,0.5458 +2863,0.0,0.5761 +2864,0.0,0.4417 +2865,0.0,0.3397 +2866,0.0,0.2955 +2867,0.1802,0.2366 +2868,0.2232,0.2448 +2869,0.0055,0.2693 +2870,0.0,0.3089 +2871,0.0,0.4392 +2872,0.0,0.6203 +2873,0.0,0.6922 +2874,0.0,0.6868 +2875,0.2086,0.6469 +2876,0.3093,0.6586 +2877,0.009,0.6051 +2878,0.0,0.5453 +2879,0.0,0.4669 +2880,0.0,0.3904 +2881,0.0,0.3731 +2882,0.0,0.4027 +2883,0.1907,0.4155 +2884,0.243,0.4177 +2885,0.0087,0.4298 +2886,0.0,0.371 +2887,0.0,0.2675 +2888,0.0,0.154 +2889,0.0,0.0852 +2890,0.0,0.0526 +2891,0.1992,0.0368 +2892,0.2911,0.05 +2893,0.0116,0.1974 +2894,0.0,0.2762 +2895,0.0,0.2806 +2896,0.0,0.2664 +2897,0.0,0.2723 +2898,0.0,0.2802 +2899,0.1664,0.2775 +2900,0.2479,0.2066 +2901,0.0076,0.1964 +2902,0.0,0.1951 +2903,0.0,0.1624 +2904,0.0,0.1245 +2905,0.0,0.1327 +2906,0.0,0.1919 +2907,0.1319,0.2073 +2908,0.1998,0.2707 +2909,0.0051,0.3779 +2910,0.0,0.4312 +2911,0.0,0.4158 +2912,0.0,0.4145 +2913,0.0,0.404 +2914,0.0,0.3875 +2915,0.1067,0.317 +2916,0.1509,0.2319 +2917,0.006,0.2036 +2918,0.0,0.1543 +2919,0.0,0.1082 diff --git a/PyPSA/source/test/data/benchmark-sp/generators.csv b/PyPSA/source/test/data/benchmark-sp/generators.csv new file mode 100644 index 0000000000000000000000000000000000000000..d21d5e5d76566109e65e096f72a951e4c506d2ad --- /dev/null +++ b/PyPSA/source/test/data/benchmark-sp/generators.csv @@ -0,0 +1,5 @@ +name,bus,p_nom_extendable,marginal_cost,capital_cost,efficiency,p_nom_opt +solar,DE,True,0.01,87427.87103912777,1.0,1.5053655516424507 +wind,DE,True,0.02,174855.74207825554,1.0,1.1362372233947151 +gas,DE,True,66.66666666666667,61199.50972738944,0.6,0.9051023903737102 +lignite,DE,True,130.0,113656.2323508661,0.4,0.08785293884124255 diff --git a/PyPSA/source/test/data/benchmark-sp/investment_periods.csv b/PyPSA/source/test/data/benchmark-sp/investment_periods.csv new file mode 100644 index 0000000000000000000000000000000000000000..34dd42197768b92fdb696fcfe70396544f01bb62 --- /dev/null +++ b/PyPSA/source/test/data/benchmark-sp/investment_periods.csv @@ -0,0 +1 @@ +period,objective,years diff --git a/PyPSA/source/test/data/benchmark-sp/loads-p.csv b/PyPSA/source/test/data/benchmark-sp/loads-p.csv new file mode 100644 index 0000000000000000000000000000000000000000..5fbbcf34e842e3d824e4027ca4284ada423cf945 --- /dev/null +++ b/PyPSA/source/test/data/benchmark-sp/loads-p.csv @@ -0,0 +1,2921 @@ +,DE_load +0,1.0 +1,1.0 +2,1.0 +3,1.0 +4,1.0 +5,1.0 +6,1.0 +7,1.0 +8,1.0 +9,1.0 +10,1.0 +11,1.0 +12,1.0 +13,1.0 +14,1.0 +15,1.0 +16,1.0 +17,1.0 +18,1.0 +19,1.0 +20,1.0 +21,1.0 +22,1.0 +23,1.0 +24,1.0 +25,1.0 +26,1.0 +27,1.0 +28,1.0 +29,1.0 +30,1.0 +31,1.0 +32,1.0 +33,1.0 +34,1.0 +35,1.0 +36,1.0 +37,1.0 +38,1.0 +39,1.0 +40,1.0 +41,1.0 +42,1.0 +43,1.0 +44,1.0 +45,1.0 +46,1.0 +47,1.0 +48,1.0 +49,1.0 +50,1.0 +51,1.0 +52,1.0 +53,1.0 +54,1.0 +55,1.0 +56,1.0 +57,1.0 +58,1.0 +59,1.0 +60,1.0 +61,1.0 +62,1.0 +63,1.0 +64,1.0 +65,1.0 +66,1.0 +67,1.0 +68,1.0 +69,1.0 +70,1.0 +71,1.0 +72,1.0 +73,1.0 +74,1.0 +75,1.0 +76,1.0 +77,1.0 +78,1.0 +79,1.0 +80,1.0 +81,1.0 +82,1.0 +83,1.0 +84,1.0 +85,1.0 +86,1.0 +87,1.0 +88,1.0 +89,1.0 +90,1.0 +91,1.0 +92,1.0 +93,1.0 +94,1.0 +95,1.0 +96,1.0 +97,1.0 +98,1.0 +99,1.0 +100,1.0 +101,1.0 +102,1.0 +103,1.0 +104,1.0 +105,1.0 +106,1.0 +107,1.0 +108,1.0 +109,1.0 +110,1.0 +111,1.0 +112,1.0 +113,1.0 +114,1.0 +115,1.0 +116,1.0 +117,1.0 +118,1.0 +119,1.0 +120,1.0 +121,1.0 +122,1.0 +123,1.0 +124,1.0 +125,1.0 +126,1.0 +127,1.0 +128,1.0 +129,1.0 +130,1.0 +131,1.0 +132,1.0 +133,1.0 +134,1.0 +135,1.0 +136,1.0 +137,1.0 +138,1.0 +139,1.0 +140,1.0 +141,1.0 +142,1.0 +143,1.0 +144,1.0 +145,1.0 +146,1.0 +147,1.0 +148,1.0 +149,1.0 +150,1.0 +151,1.0 +152,1.0 +153,1.0 +154,1.0 +155,1.0 +156,1.0 +157,1.0 +158,1.0 +159,1.0 +160,1.0 +161,1.0 +162,1.0 +163,1.0 +164,1.0 +165,1.0 +166,1.0 +167,1.0 +168,1.0 +169,1.0 +170,1.0 +171,1.0 +172,1.0 +173,1.0 +174,1.0 +175,1.0 +176,1.0 +177,1.0 +178,1.0 +179,1.0 +180,1.0 +181,1.0 +182,1.0 +183,1.0 +184,1.0 +185,1.0 +186,1.0 +187,1.0 +188,1.0 +189,1.0 +190,1.0 +191,1.0 +192,1.0 +193,1.0 +194,1.0 +195,1.0 +196,1.0 +197,1.0 +198,1.0 +199,1.0 +200,1.0 +201,1.0 +202,1.0 +203,1.0 +204,1.0 +205,1.0 +206,1.0 +207,1.0 +208,1.0 +209,1.0 +210,1.0 +211,1.0 +212,1.0 +213,1.0 +214,1.0 +215,1.0 +216,1.0 +217,1.0 +218,1.0 +219,1.0 +220,1.0 +221,1.0 +222,1.0 +223,1.0 +224,1.0 +225,1.0 +226,1.0 +227,1.0 +228,1.0 +229,1.0 +230,1.0 +231,1.0 +232,1.0 +233,1.0 +234,1.0 +235,1.0 +236,1.0 +237,1.0 +238,1.0 +239,1.0 +240,1.0 +241,1.0 +242,1.0 +243,1.0 +244,1.0 +245,1.0 +246,1.0 +247,1.0 +248,1.0 +249,1.0 +250,1.0 +251,1.0 +252,1.0 +253,1.0 +254,1.0 +255,1.0 +256,1.0 +257,1.0 +258,1.0 +259,1.0 +260,1.0 +261,1.0 +262,1.0 +263,1.0 +264,1.0 +265,1.0 +266,1.0 +267,1.0 +268,1.0 +269,1.0 +270,1.0 +271,1.0 +272,1.0 +273,1.0 +274,1.0 +275,1.0 +276,1.0 +277,1.0 +278,1.0 +279,1.0 +280,1.0 +281,1.0 +282,1.0 +283,1.0 +284,1.0 +285,1.0 +286,1.0 +287,1.0 +288,1.0 +289,1.0 +290,1.0 +291,1.0 +292,1.0 +293,1.0 +294,1.0 +295,1.0 +296,1.0 +297,1.0 +298,1.0 +299,1.0 +300,1.0 +301,1.0 +302,1.0 +303,1.0 +304,1.0 +305,1.0 +306,1.0 +307,1.0 +308,1.0 +309,1.0 +310,1.0 +311,1.0 +312,1.0 +313,1.0 +314,1.0 +315,1.0 +316,1.0 +317,1.0 +318,1.0 +319,1.0 +320,1.0 +321,1.0 +322,1.0 +323,1.0 +324,1.0 +325,1.0 +326,1.0 +327,1.0 +328,1.0 +329,1.0 +330,1.0 +331,1.0 +332,1.0 +333,1.0 +334,1.0 +335,1.0 +336,1.0 +337,1.0 +338,1.0 +339,1.0 +340,1.0 +341,1.0 +342,1.0 +343,1.0 +344,1.0 +345,1.0 +346,1.0 +347,1.0 +348,1.0 +349,1.0 +350,1.0 +351,1.0 +352,1.0 +353,1.0 +354,1.0 +355,1.0 +356,1.0 +357,1.0 +358,1.0 +359,1.0 +360,1.0 +361,1.0 +362,1.0 +363,1.0 +364,1.0 +365,1.0 +366,1.0 +367,1.0 +368,1.0 +369,1.0 +370,1.0 +371,1.0 +372,1.0 +373,1.0 +374,1.0 +375,1.0 +376,1.0 +377,1.0 +378,1.0 +379,1.0 +380,1.0 +381,1.0 +382,1.0 +383,1.0 +384,1.0 +385,1.0 +386,1.0 +387,1.0 +388,1.0 +389,1.0 +390,1.0 +391,1.0 +392,1.0 +393,1.0 +394,1.0 +395,1.0 +396,1.0 +397,1.0 +398,1.0 +399,1.0 +400,1.0 +401,1.0 +402,1.0 +403,1.0 +404,1.0 +405,1.0 +406,1.0 +407,1.0 +408,1.0 +409,1.0 +410,1.0 +411,1.0 +412,1.0 +413,1.0 +414,1.0 +415,1.0 +416,1.0 +417,1.0 +418,1.0 +419,1.0 +420,1.0 +421,1.0 +422,1.0 +423,1.0 +424,1.0 +425,1.0 +426,1.0 +427,1.0 +428,1.0 +429,1.0 +430,1.0 +431,1.0 +432,1.0 +433,1.0 +434,1.0 +435,1.0 +436,1.0 +437,1.0 +438,1.0 +439,1.0 +440,1.0 +441,1.0 +442,1.0 +443,1.0 +444,1.0 +445,1.0 +446,1.0 +447,1.0 +448,1.0 +449,1.0 +450,1.0 +451,1.0 +452,1.0 +453,1.0 +454,1.0 +455,1.0 +456,1.0 +457,1.0 +458,1.0 +459,1.0 +460,1.0 +461,1.0 +462,1.0 +463,1.0 +464,1.0 +465,1.0 +466,1.0 +467,1.0 +468,1.0 +469,1.0 +470,1.0 +471,1.0 +472,1.0 +473,1.0 +474,1.0 +475,1.0 +476,1.0 +477,1.0 +478,1.0 +479,1.0 +480,1.0 +481,1.0 +482,1.0 +483,1.0 +484,1.0 +485,1.0 +486,1.0 +487,1.0 +488,1.0 +489,1.0 +490,1.0 +491,1.0 +492,1.0 +493,1.0 +494,1.0 +495,1.0 +496,1.0 +497,1.0 +498,1.0 +499,1.0 +500,1.0 +501,1.0 +502,1.0 +503,1.0 +504,1.0 +505,1.0 +506,1.0 +507,1.0 +508,1.0 +509,1.0 +510,1.0 +511,1.0 +512,1.0 +513,1.0 +514,1.0 +515,1.0 +516,1.0 +517,1.0 +518,1.0 +519,1.0 +520,1.0 +521,1.0 +522,1.0 +523,1.0 +524,1.0 +525,1.0 +526,1.0 +527,1.0 +528,1.0 +529,1.0 +530,1.0 +531,1.0 +532,1.0 +533,1.0 +534,1.0 +535,1.0 +536,1.0 +537,1.0 +538,1.0 +539,1.0 +540,1.0 +541,1.0 +542,1.0 +543,1.0 +544,1.0 +545,1.0 +546,1.0 +547,1.0 +548,1.0 +549,1.0 +550,1.0 +551,1.0 +552,1.0 +553,1.0 +554,1.0 +555,1.0 +556,1.0 +557,1.0 +558,1.0 +559,1.0 +560,1.0 +561,1.0 +562,1.0 +563,1.0 +564,1.0 +565,1.0 +566,1.0 +567,1.0 +568,1.0 +569,1.0 +570,1.0 +571,1.0 +572,1.0 +573,1.0 +574,1.0 +575,1.0 +576,1.0 +577,1.0 +578,1.0 +579,1.0 +580,1.0 +581,1.0 +582,1.0 +583,1.0 +584,1.0 +585,1.0 +586,1.0 +587,1.0 +588,1.0 +589,1.0 +590,1.0 +591,1.0 +592,1.0 +593,1.0 +594,1.0 +595,1.0 +596,1.0 +597,1.0 +598,1.0 +599,1.0 +600,1.0 +601,1.0 +602,1.0 +603,1.0 +604,1.0 +605,1.0 +606,1.0 +607,1.0 +608,1.0 +609,1.0 +610,1.0 +611,1.0 +612,1.0 +613,1.0 +614,1.0 +615,1.0 +616,1.0 +617,1.0 +618,1.0 +619,1.0 +620,1.0 +621,1.0 +622,1.0 +623,1.0 +624,1.0 +625,1.0 +626,1.0 +627,1.0 +628,1.0 +629,1.0 +630,1.0 +631,1.0 +632,1.0 +633,1.0 +634,1.0 +635,1.0 +636,1.0 +637,1.0 +638,1.0 +639,1.0 +640,1.0 +641,1.0 +642,1.0 +643,1.0 +644,1.0 +645,1.0 +646,1.0 +647,1.0 +648,1.0 +649,1.0 +650,1.0 +651,1.0 +652,1.0 +653,1.0 +654,1.0 +655,1.0 +656,1.0 +657,1.0 +658,1.0 +659,1.0 +660,1.0 +661,1.0 +662,1.0 +663,1.0 +664,1.0 +665,1.0 +666,1.0 +667,1.0 +668,1.0 +669,1.0 +670,1.0 +671,1.0 +672,1.0 +673,1.0 +674,1.0 +675,1.0 +676,1.0 +677,1.0 +678,1.0 +679,1.0 +680,1.0 +681,1.0 +682,1.0 +683,1.0 +684,1.0 +685,1.0 +686,1.0 +687,1.0 +688,1.0 +689,1.0 +690,1.0 +691,1.0 +692,1.0 +693,1.0 +694,1.0 +695,1.0 +696,1.0 +697,1.0 +698,1.0 +699,1.0 +700,1.0 +701,1.0 +702,1.0 +703,1.0 +704,1.0 +705,1.0 +706,1.0 +707,1.0 +708,1.0 +709,1.0 +710,1.0 +711,1.0 +712,1.0 +713,1.0 +714,1.0 +715,1.0 +716,1.0 +717,1.0 +718,1.0 +719,1.0 +720,1.0 +721,1.0 +722,1.0 +723,1.0 +724,1.0 +725,1.0 +726,1.0 +727,1.0 +728,1.0 +729,1.0 +730,1.0 +731,1.0 +732,1.0 +733,1.0 +734,1.0 +735,1.0 +736,1.0 +737,1.0 +738,1.0 +739,1.0 +740,1.0 +741,1.0 +742,1.0 +743,1.0 +744,1.0 +745,1.0 +746,1.0 +747,1.0 +748,1.0 +749,1.0 +750,1.0 +751,1.0 +752,1.0 +753,1.0 +754,1.0 +755,1.0 +756,1.0 +757,1.0 +758,1.0 +759,1.0 +760,1.0 +761,1.0 +762,1.0 +763,1.0 +764,1.0 +765,1.0 +766,1.0 +767,1.0 +768,1.0 +769,1.0 +770,1.0 +771,1.0 +772,1.0 +773,1.0 +774,1.0 +775,1.0 +776,1.0 +777,1.0 +778,1.0 +779,1.0 +780,1.0 +781,1.0 +782,1.0 +783,1.0 +784,1.0 +785,1.0 +786,1.0 +787,1.0 +788,1.0 +789,1.0 +790,1.0 +791,1.0 +792,1.0 +793,1.0 +794,1.0 +795,1.0 +796,1.0 +797,1.0 +798,1.0 +799,1.0 +800,1.0 +801,1.0 +802,1.0 +803,1.0 +804,1.0 +805,1.0 +806,1.0 +807,1.0 +808,1.0 +809,1.0 +810,1.0 +811,1.0 +812,1.0 +813,1.0 +814,1.0 +815,1.0 +816,1.0 +817,1.0 +818,1.0 +819,1.0 +820,1.0 +821,1.0 +822,1.0 +823,1.0 +824,1.0 +825,1.0 +826,1.0 +827,1.0 +828,1.0 +829,1.0 +830,1.0 +831,1.0 +832,1.0 +833,1.0 +834,1.0 +835,1.0 +836,1.0 +837,1.0 +838,1.0 +839,1.0 +840,1.0 +841,1.0 +842,1.0 +843,1.0 +844,1.0 +845,1.0 +846,1.0 +847,1.0 +848,1.0 +849,1.0 +850,1.0 +851,1.0 +852,1.0 +853,1.0 +854,1.0 +855,1.0 +856,1.0 +857,1.0 +858,1.0 +859,1.0 +860,1.0 +861,1.0 +862,1.0 +863,1.0 +864,1.0 +865,1.0 +866,1.0 +867,1.0 +868,1.0 +869,1.0 +870,1.0 +871,1.0 +872,1.0 +873,1.0 +874,1.0 +875,1.0 +876,1.0 +877,1.0 +878,1.0 +879,1.0 +880,1.0 +881,1.0 +882,1.0 +883,1.0 +884,1.0 +885,1.0 +886,1.0 +887,1.0 +888,1.0 +889,1.0 +890,1.0 +891,1.0 +892,1.0 +893,1.0 +894,1.0 +895,1.0 +896,1.0 +897,1.0 +898,1.0 +899,1.0 +900,1.0 +901,1.0 +902,1.0 +903,1.0 +904,1.0 +905,1.0 +906,1.0 +907,1.0 +908,1.0 +909,1.0 +910,1.0 +911,1.0 +912,1.0 +913,1.0 +914,1.0 +915,1.0 +916,1.0 +917,1.0 +918,1.0 +919,1.0 +920,1.0 +921,1.0 +922,1.0 +923,1.0 +924,1.0 +925,1.0 +926,1.0 +927,1.0 +928,1.0 +929,1.0 +930,1.0 +931,1.0 +932,1.0 +933,1.0 +934,1.0 +935,1.0 +936,1.0 +937,1.0 +938,1.0 +939,1.0 +940,1.0 +941,1.0 +942,1.0 +943,1.0 +944,1.0 +945,1.0 +946,1.0 +947,1.0 +948,1.0 +949,1.0 +950,1.0 +951,1.0 +952,1.0 +953,1.0 +954,1.0 +955,1.0 +956,1.0 +957,1.0 +958,1.0 +959,1.0 +960,1.0 +961,1.0 +962,1.0 +963,1.0 +964,1.0 +965,1.0 +966,1.0 +967,1.0 +968,1.0 +969,1.0 +970,1.0 +971,1.0 +972,1.0 +973,1.0 +974,1.0 +975,1.0 +976,1.0 +977,1.0 +978,1.0 +979,1.0 +980,1.0 +981,1.0 +982,1.0 +983,1.0 +984,1.0 +985,1.0 +986,1.0 +987,1.0 +988,1.0 +989,1.0 +990,1.0 +991,1.0 +992,1.0 +993,1.0 +994,1.0 +995,1.0 +996,1.0 +997,1.0 +998,1.0 +999,1.0 +1000,1.0 +1001,1.0 +1002,1.0 +1003,1.0 +1004,1.0 +1005,1.0 +1006,1.0 +1007,1.0 +1008,1.0 +1009,1.0 +1010,1.0 +1011,1.0 +1012,1.0 +1013,1.0 +1014,1.0 +1015,1.0 +1016,1.0 +1017,1.0 +1018,1.0 +1019,1.0 +1020,1.0 +1021,1.0 +1022,1.0 +1023,1.0 +1024,1.0 +1025,1.0 +1026,1.0 +1027,1.0 +1028,1.0 +1029,1.0 +1030,1.0 +1031,1.0 +1032,1.0 +1033,1.0 +1034,1.0 +1035,1.0 +1036,1.0 +1037,1.0 +1038,1.0 +1039,1.0 +1040,1.0 +1041,1.0 +1042,1.0 +1043,1.0 +1044,1.0 +1045,1.0 +1046,1.0 +1047,1.0 +1048,1.0 +1049,1.0 +1050,1.0 +1051,1.0 +1052,1.0 +1053,1.0 +1054,1.0 +1055,1.0 +1056,1.0 +1057,1.0 +1058,1.0 +1059,1.0 +1060,1.0 +1061,1.0 +1062,1.0 +1063,1.0 +1064,1.0 +1065,1.0 +1066,1.0 +1067,1.0 +1068,1.0 +1069,1.0 +1070,1.0 +1071,1.0 +1072,1.0 +1073,1.0 +1074,1.0 +1075,1.0 +1076,1.0 +1077,1.0 +1078,1.0 +1079,1.0 +1080,1.0 +1081,1.0 +1082,1.0 +1083,1.0 +1084,1.0 +1085,1.0 +1086,1.0 +1087,1.0 +1088,1.0 +1089,1.0 +1090,1.0 +1091,1.0 +1092,1.0 +1093,1.0 +1094,1.0 +1095,1.0 +1096,1.0 +1097,1.0 +1098,1.0 +1099,1.0 +1100,1.0 +1101,1.0 +1102,1.0 +1103,1.0 +1104,1.0 +1105,1.0 +1106,1.0 +1107,1.0 +1108,1.0 +1109,1.0 +1110,1.0 +1111,1.0 +1112,1.0 +1113,1.0 +1114,1.0 +1115,1.0 +1116,1.0 +1117,1.0 +1118,1.0 +1119,1.0 +1120,1.0 +1121,1.0 +1122,1.0 +1123,1.0 +1124,1.0 +1125,1.0 +1126,1.0 +1127,1.0 +1128,1.0 +1129,1.0 +1130,1.0 +1131,1.0 +1132,1.0 +1133,1.0 +1134,1.0 +1135,1.0 +1136,1.0 +1137,1.0 +1138,1.0 +1139,1.0 +1140,1.0 +1141,1.0 +1142,1.0 +1143,1.0 +1144,1.0 +1145,1.0 +1146,1.0 +1147,1.0 +1148,1.0 +1149,1.0 +1150,1.0 +1151,1.0 +1152,1.0 +1153,1.0 +1154,1.0 +1155,1.0 +1156,1.0 +1157,1.0 +1158,1.0 +1159,1.0 +1160,1.0 +1161,1.0 +1162,1.0 +1163,1.0 +1164,1.0 +1165,1.0 +1166,1.0 +1167,1.0 +1168,1.0 +1169,1.0 +1170,1.0 +1171,1.0 +1172,1.0 +1173,1.0 +1174,1.0 +1175,1.0 +1176,1.0 +1177,1.0 +1178,1.0 +1179,1.0 +1180,1.0 +1181,1.0 +1182,1.0 +1183,1.0 +1184,1.0 +1185,1.0 +1186,1.0 +1187,1.0 +1188,1.0 +1189,1.0 +1190,1.0 +1191,1.0 +1192,1.0 +1193,1.0 +1194,1.0 +1195,1.0 +1196,1.0 +1197,1.0 +1198,1.0 +1199,1.0 +1200,1.0 +1201,1.0 +1202,1.0 +1203,1.0 +1204,1.0 +1205,1.0 +1206,1.0 +1207,1.0 +1208,1.0 +1209,1.0 +1210,1.0 +1211,1.0 +1212,1.0 +1213,1.0 +1214,1.0 +1215,1.0 +1216,1.0 +1217,1.0 +1218,1.0 +1219,1.0 +1220,1.0 +1221,1.0 +1222,1.0 +1223,1.0 +1224,1.0 +1225,1.0 +1226,1.0 +1227,1.0 +1228,1.0 +1229,1.0 +1230,1.0 +1231,1.0 +1232,1.0 +1233,1.0 +1234,1.0 +1235,1.0 +1236,1.0 +1237,1.0 +1238,1.0 +1239,1.0 +1240,1.0 +1241,1.0 +1242,1.0 +1243,1.0 +1244,1.0 +1245,1.0 +1246,1.0 +1247,1.0 +1248,1.0 +1249,1.0 +1250,1.0 +1251,1.0 +1252,1.0 +1253,1.0 +1254,1.0 +1255,1.0 +1256,1.0 +1257,1.0 +1258,1.0 +1259,1.0 +1260,1.0 +1261,1.0 +1262,1.0 +1263,1.0 +1264,1.0 +1265,1.0 +1266,1.0 +1267,1.0 +1268,1.0 +1269,1.0 +1270,1.0 +1271,1.0 +1272,1.0 +1273,1.0 +1274,1.0 +1275,1.0 +1276,1.0 +1277,1.0 +1278,1.0 +1279,1.0 +1280,1.0 +1281,1.0 +1282,1.0 +1283,1.0 +1284,1.0 +1285,1.0 +1286,1.0 +1287,1.0 +1288,1.0 +1289,1.0 +1290,1.0 +1291,1.0 +1292,1.0 +1293,1.0 +1294,1.0 +1295,1.0 +1296,1.0 +1297,1.0 +1298,1.0 +1299,1.0 +1300,1.0 +1301,1.0 +1302,1.0 +1303,1.0 +1304,1.0 +1305,1.0 +1306,1.0 +1307,1.0 +1308,1.0 +1309,1.0 +1310,1.0 +1311,1.0 +1312,1.0 +1313,1.0 +1314,1.0 +1315,1.0 +1316,1.0 +1317,1.0 +1318,1.0 +1319,1.0 +1320,1.0 +1321,1.0 +1322,1.0 +1323,1.0 +1324,1.0 +1325,1.0 +1326,1.0 +1327,1.0 +1328,1.0 +1329,1.0 +1330,1.0 +1331,1.0 +1332,1.0 +1333,1.0 +1334,1.0 +1335,1.0 +1336,1.0 +1337,1.0 +1338,1.0 +1339,1.0 +1340,1.0 +1341,1.0 +1342,1.0 +1343,1.0 +1344,1.0 +1345,1.0 +1346,1.0 +1347,1.0 +1348,1.0 +1349,1.0 +1350,1.0 +1351,1.0 +1352,1.0 +1353,1.0 +1354,1.0 +1355,1.0 +1356,1.0 +1357,1.0 +1358,1.0 +1359,1.0 +1360,1.0 +1361,1.0 +1362,1.0 +1363,1.0 +1364,1.0 +1365,1.0 +1366,1.0 +1367,1.0 +1368,1.0 +1369,1.0 +1370,1.0 +1371,1.0 +1372,1.0 +1373,1.0 +1374,1.0 +1375,1.0 +1376,1.0 +1377,1.0 +1378,1.0 +1379,1.0 +1380,1.0 +1381,1.0 +1382,1.0 +1383,1.0 +1384,1.0 +1385,1.0 +1386,1.0 +1387,1.0 +1388,1.0 +1389,1.0 +1390,1.0 +1391,1.0 +1392,1.0 +1393,1.0 +1394,1.0 +1395,1.0 +1396,1.0 +1397,1.0 +1398,1.0 +1399,1.0 +1400,1.0 +1401,1.0 +1402,1.0 +1403,1.0 +1404,1.0 +1405,1.0 +1406,1.0 +1407,1.0 +1408,1.0 +1409,1.0 +1410,1.0 +1411,1.0 +1412,1.0 +1413,1.0 +1414,1.0 +1415,1.0 +1416,1.0 +1417,1.0 +1418,1.0 +1419,1.0 +1420,1.0 +1421,1.0 +1422,1.0 +1423,1.0 +1424,1.0 +1425,1.0 +1426,1.0 +1427,1.0 +1428,1.0 +1429,1.0 +1430,1.0 +1431,1.0 +1432,1.0 +1433,1.0 +1434,1.0 +1435,1.0 +1436,1.0 +1437,1.0 +1438,1.0 +1439,1.0 +1440,1.0 +1441,1.0 +1442,1.0 +1443,1.0 +1444,1.0 +1445,1.0 +1446,1.0 +1447,1.0 +1448,1.0 +1449,1.0 +1450,1.0 +1451,1.0 +1452,1.0 +1453,1.0 +1454,1.0 +1455,1.0 +1456,1.0 +1457,1.0 +1458,1.0 +1459,1.0 +1460,1.0 +1461,1.0 +1462,1.0 +1463,1.0 +1464,1.0 +1465,1.0 +1466,1.0 +1467,1.0 +1468,1.0 +1469,1.0 +1470,1.0 +1471,1.0 +1472,1.0 +1473,1.0 +1474,1.0 +1475,1.0 +1476,1.0 +1477,1.0 +1478,1.0 +1479,1.0 +1480,1.0 +1481,1.0 +1482,1.0 +1483,1.0 +1484,1.0 +1485,1.0 +1486,1.0 +1487,1.0 +1488,1.0 +1489,1.0 +1490,1.0 +1491,1.0 +1492,1.0 +1493,1.0 +1494,1.0 +1495,1.0 +1496,1.0 +1497,1.0 +1498,1.0 +1499,1.0 +1500,1.0 +1501,1.0 +1502,1.0 +1503,1.0 +1504,1.0 +1505,1.0 +1506,1.0 +1507,1.0 +1508,1.0 +1509,1.0 +1510,1.0 +1511,1.0 +1512,1.0 +1513,1.0 +1514,1.0 +1515,1.0 +1516,1.0 +1517,1.0 +1518,1.0 +1519,1.0 +1520,1.0 +1521,1.0 +1522,1.0 +1523,1.0 +1524,1.0 +1525,1.0 +1526,1.0 +1527,1.0 +1528,1.0 +1529,1.0 +1530,1.0 +1531,1.0 +1532,1.0 +1533,1.0 +1534,1.0 +1535,1.0 +1536,1.0 +1537,1.0 +1538,1.0 +1539,1.0 +1540,1.0 +1541,1.0 +1542,1.0 +1543,1.0 +1544,1.0 +1545,1.0 +1546,1.0 +1547,1.0 +1548,1.0 +1549,1.0 +1550,1.0 +1551,1.0 +1552,1.0 +1553,1.0 +1554,1.0 +1555,1.0 +1556,1.0 +1557,1.0 +1558,1.0 +1559,1.0 +1560,1.0 +1561,1.0 +1562,1.0 +1563,1.0 +1564,1.0 +1565,1.0 +1566,1.0 +1567,1.0 +1568,1.0 +1569,1.0 +1570,1.0 +1571,1.0 +1572,1.0 +1573,1.0 +1574,1.0 +1575,1.0 +1576,1.0 +1577,1.0 +1578,1.0 +1579,1.0 +1580,1.0 +1581,1.0 +1582,1.0 +1583,1.0 +1584,1.0 +1585,1.0 +1586,1.0 +1587,1.0 +1588,1.0 +1589,1.0 +1590,1.0 +1591,1.0 +1592,1.0 +1593,1.0 +1594,1.0 +1595,1.0 +1596,1.0 +1597,1.0 +1598,1.0 +1599,1.0 +1600,1.0 +1601,1.0 +1602,1.0 +1603,1.0 +1604,1.0 +1605,1.0 +1606,1.0 +1607,1.0 +1608,1.0 +1609,1.0 +1610,1.0 +1611,1.0 +1612,1.0 +1613,1.0 +1614,1.0 +1615,1.0 +1616,1.0 +1617,1.0 +1618,1.0 +1619,1.0 +1620,1.0 +1621,1.0 +1622,1.0 +1623,1.0 +1624,1.0 +1625,1.0 +1626,1.0 +1627,1.0 +1628,1.0 +1629,1.0 +1630,1.0 +1631,1.0 +1632,1.0 +1633,1.0 +1634,1.0 +1635,1.0 +1636,1.0 +1637,1.0 +1638,1.0 +1639,1.0 +1640,1.0 +1641,1.0 +1642,1.0 +1643,1.0 +1644,1.0 +1645,1.0 +1646,1.0 +1647,1.0 +1648,1.0 +1649,1.0 +1650,1.0 +1651,1.0 +1652,1.0 +1653,1.0 +1654,1.0 +1655,1.0 +1656,1.0 +1657,1.0 +1658,1.0 +1659,1.0 +1660,1.0 +1661,1.0 +1662,1.0 +1663,1.0 +1664,1.0 +1665,1.0 +1666,1.0 +1667,1.0 +1668,1.0 +1669,1.0 +1670,1.0 +1671,1.0 +1672,1.0 +1673,1.0 +1674,1.0 +1675,1.0 +1676,1.0 +1677,1.0 +1678,1.0 +1679,1.0 +1680,1.0 +1681,1.0 +1682,1.0 +1683,1.0 +1684,1.0 +1685,1.0 +1686,1.0 +1687,1.0 +1688,1.0 +1689,1.0 +1690,1.0 +1691,1.0 +1692,1.0 +1693,1.0 +1694,1.0 +1695,1.0 +1696,1.0 +1697,1.0 +1698,1.0 +1699,1.0 +1700,1.0 +1701,1.0 +1702,1.0 +1703,1.0 +1704,1.0 +1705,1.0 +1706,1.0 +1707,1.0 +1708,1.0 +1709,1.0 +1710,1.0 +1711,1.0 +1712,1.0 +1713,1.0 +1714,1.0 +1715,1.0 +1716,1.0 +1717,1.0 +1718,1.0 +1719,1.0 +1720,1.0 +1721,1.0 +1722,1.0 +1723,1.0 +1724,1.0 +1725,1.0 +1726,1.0 +1727,1.0 +1728,1.0 +1729,1.0 +1730,1.0 +1731,1.0 +1732,1.0 +1733,1.0 +1734,1.0 +1735,1.0 +1736,1.0 +1737,1.0 +1738,1.0 +1739,1.0 +1740,1.0 +1741,1.0 +1742,1.0 +1743,1.0 +1744,1.0 +1745,1.0 +1746,1.0 +1747,1.0 +1748,1.0 +1749,1.0 +1750,1.0 +1751,1.0 +1752,1.0 +1753,1.0 +1754,1.0 +1755,1.0 +1756,1.0 +1757,1.0 +1758,1.0 +1759,1.0 +1760,1.0 +1761,1.0 +1762,1.0 +1763,1.0 +1764,1.0 +1765,1.0 +1766,1.0 +1767,1.0 +1768,1.0 +1769,1.0 +1770,1.0 +1771,1.0 +1772,1.0 +1773,1.0 +1774,1.0 +1775,1.0 +1776,1.0 +1777,1.0 +1778,1.0 +1779,1.0 +1780,1.0 +1781,1.0 +1782,1.0 +1783,1.0 +1784,1.0 +1785,1.0 +1786,1.0 +1787,1.0 +1788,1.0 +1789,1.0 +1790,1.0 +1791,1.0 +1792,1.0 +1793,1.0 +1794,1.0 +1795,1.0 +1796,1.0 +1797,1.0 +1798,1.0 +1799,1.0 +1800,1.0 +1801,1.0 +1802,1.0 +1803,1.0 +1804,1.0 +1805,1.0 +1806,1.0 +1807,1.0 +1808,1.0 +1809,1.0 +1810,1.0 +1811,1.0 +1812,1.0 +1813,1.0 +1814,1.0 +1815,1.0 +1816,1.0 +1817,1.0 +1818,1.0 +1819,1.0 +1820,1.0 +1821,1.0 +1822,1.0 +1823,1.0 +1824,1.0 +1825,1.0 +1826,1.0 +1827,1.0 +1828,1.0 +1829,1.0 +1830,1.0 +1831,1.0 +1832,1.0 +1833,1.0 +1834,1.0 +1835,1.0 +1836,1.0 +1837,1.0 +1838,1.0 +1839,1.0 +1840,1.0 +1841,1.0 +1842,1.0 +1843,1.0 +1844,1.0 +1845,1.0 +1846,1.0 +1847,1.0 +1848,1.0 +1849,1.0 +1850,1.0 +1851,1.0 +1852,1.0 +1853,1.0 +1854,1.0 +1855,1.0 +1856,1.0 +1857,1.0 +1858,1.0 +1859,1.0 +1860,1.0 +1861,1.0 +1862,1.0 +1863,1.0 +1864,1.0 +1865,1.0 +1866,1.0 +1867,1.0 +1868,1.0 +1869,1.0 +1870,1.0 +1871,1.0 +1872,1.0 +1873,1.0 +1874,1.0 +1875,1.0 +1876,1.0 +1877,1.0 +1878,1.0 +1879,1.0 +1880,1.0 +1881,1.0 +1882,1.0 +1883,1.0 +1884,1.0 +1885,1.0 +1886,1.0 +1887,1.0 +1888,1.0 +1889,1.0 +1890,1.0 +1891,1.0 +1892,1.0 +1893,1.0 +1894,1.0 +1895,1.0 +1896,1.0 +1897,1.0 +1898,1.0 +1899,1.0 +1900,1.0 +1901,1.0 +1902,1.0 +1903,1.0 +1904,1.0 +1905,1.0 +1906,1.0 +1907,1.0 +1908,1.0 +1909,1.0 +1910,1.0 +1911,1.0 +1912,1.0 +1913,1.0 +1914,1.0 +1915,1.0 +1916,1.0 +1917,1.0 +1918,1.0 +1919,1.0 +1920,1.0 +1921,1.0 +1922,1.0 +1923,1.0 +1924,1.0 +1925,1.0 +1926,1.0 +1927,1.0 +1928,1.0 +1929,1.0 +1930,1.0 +1931,1.0 +1932,1.0 +1933,1.0 +1934,1.0 +1935,1.0 +1936,1.0 +1937,1.0 +1938,1.0 +1939,1.0 +1940,1.0 +1941,1.0 +1942,1.0 +1943,1.0 +1944,1.0 +1945,1.0 +1946,1.0 +1947,1.0 +1948,1.0 +1949,1.0 +1950,1.0 +1951,1.0 +1952,1.0 +1953,1.0 +1954,1.0 +1955,1.0 +1956,1.0 +1957,1.0 +1958,1.0 +1959,1.0 +1960,1.0 +1961,1.0 +1962,1.0 +1963,1.0 +1964,1.0 +1965,1.0 +1966,1.0 +1967,1.0 +1968,1.0 +1969,1.0 +1970,1.0 +1971,1.0 +1972,1.0 +1973,1.0 +1974,1.0 +1975,1.0 +1976,1.0 +1977,1.0 +1978,1.0 +1979,1.0 +1980,1.0 +1981,1.0 +1982,1.0 +1983,1.0 +1984,1.0 +1985,1.0 +1986,1.0 +1987,1.0 +1988,1.0 +1989,1.0 +1990,1.0 +1991,1.0 +1992,1.0 +1993,1.0 +1994,1.0 +1995,1.0 +1996,1.0 +1997,1.0 +1998,1.0 +1999,1.0 +2000,1.0 +2001,1.0 +2002,1.0 +2003,1.0 +2004,1.0 +2005,1.0 +2006,1.0 +2007,1.0 +2008,1.0 +2009,1.0 +2010,1.0 +2011,1.0 +2012,1.0 +2013,1.0 +2014,1.0 +2015,1.0 +2016,1.0 +2017,1.0 +2018,1.0 +2019,1.0 +2020,1.0 +2021,1.0 +2022,1.0 +2023,1.0 +2024,1.0 +2025,1.0 +2026,1.0 +2027,1.0 +2028,1.0 +2029,1.0 +2030,1.0 +2031,1.0 +2032,1.0 +2033,1.0 +2034,1.0 +2035,1.0 +2036,1.0 +2037,1.0 +2038,1.0 +2039,1.0 +2040,1.0 +2041,1.0 +2042,1.0 +2043,1.0 +2044,1.0 +2045,1.0 +2046,1.0 +2047,1.0 +2048,1.0 +2049,1.0 +2050,1.0 +2051,1.0 +2052,1.0 +2053,1.0 +2054,1.0 +2055,1.0 +2056,1.0 +2057,1.0 +2058,1.0 +2059,1.0 +2060,1.0 +2061,1.0 +2062,1.0 +2063,1.0 +2064,1.0 +2065,1.0 +2066,1.0 +2067,1.0 +2068,1.0 +2069,1.0 +2070,1.0 +2071,1.0 +2072,1.0 +2073,1.0 +2074,1.0 +2075,1.0 +2076,1.0 +2077,1.0 +2078,1.0 +2079,1.0 +2080,1.0 +2081,1.0 +2082,1.0 +2083,1.0 +2084,1.0 +2085,1.0 +2086,1.0 +2087,1.0 +2088,1.0 +2089,1.0 +2090,1.0 +2091,1.0 +2092,1.0 +2093,1.0 +2094,1.0 +2095,1.0 +2096,1.0 +2097,1.0 +2098,1.0 +2099,1.0 +2100,1.0 +2101,1.0 +2102,1.0 +2103,1.0 +2104,1.0 +2105,1.0 +2106,1.0 +2107,1.0 +2108,1.0 +2109,1.0 +2110,1.0 +2111,1.0 +2112,1.0 +2113,1.0 +2114,1.0 +2115,1.0 +2116,1.0 +2117,1.0 +2118,1.0 +2119,1.0 +2120,1.0 +2121,1.0 +2122,1.0 +2123,1.0 +2124,1.0 +2125,1.0 +2126,1.0 +2127,1.0 +2128,1.0 +2129,1.0 +2130,1.0 +2131,1.0 +2132,1.0 +2133,1.0 +2134,1.0 +2135,1.0 +2136,1.0 +2137,1.0 +2138,1.0 +2139,1.0 +2140,1.0 +2141,1.0 +2142,1.0 +2143,1.0 +2144,1.0 +2145,1.0 +2146,1.0 +2147,1.0 +2148,1.0 +2149,1.0 +2150,1.0 +2151,1.0 +2152,1.0 +2153,1.0 +2154,1.0 +2155,1.0 +2156,1.0 +2157,1.0 +2158,1.0 +2159,1.0 +2160,1.0 +2161,1.0 +2162,1.0 +2163,1.0 +2164,1.0 +2165,1.0 +2166,1.0 +2167,1.0 +2168,1.0 +2169,1.0 +2170,1.0 +2171,1.0 +2172,1.0 +2173,1.0 +2174,1.0 +2175,1.0 +2176,1.0 +2177,1.0 +2178,1.0 +2179,1.0 +2180,1.0 +2181,1.0 +2182,1.0 +2183,1.0 +2184,1.0 +2185,1.0 +2186,1.0 +2187,1.0 +2188,1.0 +2189,1.0 +2190,1.0 +2191,1.0 +2192,1.0 +2193,1.0 +2194,1.0 +2195,1.0 +2196,1.0 +2197,1.0 +2198,1.0 +2199,1.0 +2200,1.0 +2201,1.0 +2202,1.0 +2203,1.0 +2204,1.0 +2205,1.0 +2206,1.0 +2207,1.0 +2208,1.0 +2209,1.0 +2210,1.0 +2211,1.0 +2212,1.0 +2213,1.0 +2214,1.0 +2215,1.0 +2216,1.0 +2217,1.0 +2218,1.0 +2219,1.0 +2220,1.0 +2221,1.0 +2222,1.0 +2223,1.0 +2224,1.0 +2225,1.0 +2226,1.0 +2227,1.0 +2228,1.0 +2229,1.0 +2230,1.0 +2231,1.0 +2232,1.0 +2233,1.0 +2234,1.0 +2235,1.0 +2236,1.0 +2237,1.0 +2238,1.0 +2239,1.0 +2240,1.0 +2241,1.0 +2242,1.0 +2243,1.0 +2244,1.0 +2245,1.0 +2246,1.0 +2247,1.0 +2248,1.0 +2249,1.0 +2250,1.0 +2251,1.0 +2252,1.0 +2253,1.0 +2254,1.0 +2255,1.0 +2256,1.0 +2257,1.0 +2258,1.0 +2259,1.0 +2260,1.0 +2261,1.0 +2262,1.0 +2263,1.0 +2264,1.0 +2265,1.0 +2266,1.0 +2267,1.0 +2268,1.0 +2269,1.0 +2270,1.0 +2271,1.0 +2272,1.0 +2273,1.0 +2274,1.0 +2275,1.0 +2276,1.0 +2277,1.0 +2278,1.0 +2279,1.0 +2280,1.0 +2281,1.0 +2282,1.0 +2283,1.0 +2284,1.0 +2285,1.0 +2286,1.0 +2287,1.0 +2288,1.0 +2289,1.0 +2290,1.0 +2291,1.0 +2292,1.0 +2293,1.0 +2294,1.0 +2295,1.0 +2296,1.0 +2297,1.0 +2298,1.0 +2299,1.0 +2300,1.0 +2301,1.0 +2302,1.0 +2303,1.0 +2304,1.0 +2305,1.0 +2306,1.0 +2307,1.0 +2308,1.0 +2309,1.0 +2310,1.0 +2311,1.0 +2312,1.0 +2313,1.0 +2314,1.0 +2315,1.0 +2316,1.0 +2317,1.0 +2318,1.0 +2319,1.0 +2320,1.0 +2321,1.0 +2322,1.0 +2323,1.0 +2324,1.0 +2325,1.0 +2326,1.0 +2327,1.0 +2328,1.0 +2329,1.0 +2330,1.0 +2331,1.0 +2332,1.0 +2333,1.0 +2334,1.0 +2335,1.0 +2336,1.0 +2337,1.0 +2338,1.0 +2339,1.0 +2340,1.0 +2341,1.0 +2342,1.0 +2343,1.0 +2344,1.0 +2345,1.0 +2346,1.0 +2347,1.0 +2348,1.0 +2349,1.0 +2350,1.0 +2351,1.0 +2352,1.0 +2353,1.0 +2354,1.0 +2355,1.0 +2356,1.0 +2357,1.0 +2358,1.0 +2359,1.0 +2360,1.0 +2361,1.0 +2362,1.0 +2363,1.0 +2364,1.0 +2365,1.0 +2366,1.0 +2367,1.0 +2368,1.0 +2369,1.0 +2370,1.0 +2371,1.0 +2372,1.0 +2373,1.0 +2374,1.0 +2375,1.0 +2376,1.0 +2377,1.0 +2378,1.0 +2379,1.0 +2380,1.0 +2381,1.0 +2382,1.0 +2383,1.0 +2384,1.0 +2385,1.0 +2386,1.0 +2387,1.0 +2388,1.0 +2389,1.0 +2390,1.0 +2391,1.0 +2392,1.0 +2393,1.0 +2394,1.0 +2395,1.0 +2396,1.0 +2397,1.0 +2398,1.0 +2399,1.0 +2400,1.0 +2401,1.0 +2402,1.0 +2403,1.0 +2404,1.0 +2405,1.0 +2406,1.0 +2407,1.0 +2408,1.0 +2409,1.0 +2410,1.0 +2411,1.0 +2412,1.0 +2413,1.0 +2414,1.0 +2415,1.0 +2416,1.0 +2417,1.0 +2418,1.0 +2419,1.0 +2420,1.0 +2421,1.0 +2422,1.0 +2423,1.0 +2424,1.0 +2425,1.0 +2426,1.0 +2427,1.0 +2428,1.0 +2429,1.0 +2430,1.0 +2431,1.0 +2432,1.0 +2433,1.0 +2434,1.0 +2435,1.0 +2436,1.0 +2437,1.0 +2438,1.0 +2439,1.0 +2440,1.0 +2441,1.0 +2442,1.0 +2443,1.0 +2444,1.0 +2445,1.0 +2446,1.0 +2447,1.0 +2448,1.0 +2449,1.0 +2450,1.0 +2451,1.0 +2452,1.0 +2453,1.0 +2454,1.0 +2455,1.0 +2456,1.0 +2457,1.0 +2458,1.0 +2459,1.0 +2460,1.0 +2461,1.0 +2462,1.0 +2463,1.0 +2464,1.0 +2465,1.0 +2466,1.0 +2467,1.0 +2468,1.0 +2469,1.0 +2470,1.0 +2471,1.0 +2472,1.0 +2473,1.0 +2474,1.0 +2475,1.0 +2476,1.0 +2477,1.0 +2478,1.0 +2479,1.0 +2480,1.0 +2481,1.0 +2482,1.0 +2483,1.0 +2484,1.0 +2485,1.0 +2486,1.0 +2487,1.0 +2488,1.0 +2489,1.0 +2490,1.0 +2491,1.0 +2492,1.0 +2493,1.0 +2494,1.0 +2495,1.0 +2496,1.0 +2497,1.0 +2498,1.0 +2499,1.0 +2500,1.0 +2501,1.0 +2502,1.0 +2503,1.0 +2504,1.0 +2505,1.0 +2506,1.0 +2507,1.0 +2508,1.0 +2509,1.0 +2510,1.0 +2511,1.0 +2512,1.0 +2513,1.0 +2514,1.0 +2515,1.0 +2516,1.0 +2517,1.0 +2518,1.0 +2519,1.0 +2520,1.0 +2521,1.0 +2522,1.0 +2523,1.0 +2524,1.0 +2525,1.0 +2526,1.0 +2527,1.0 +2528,1.0 +2529,1.0 +2530,1.0 +2531,1.0 +2532,1.0 +2533,1.0 +2534,1.0 +2535,1.0 +2536,1.0 +2537,1.0 +2538,1.0 +2539,1.0 +2540,1.0 +2541,1.0 +2542,1.0 +2543,1.0 +2544,1.0 +2545,1.0 +2546,1.0 +2547,1.0 +2548,1.0 +2549,1.0 +2550,1.0 +2551,1.0 +2552,1.0 +2553,1.0 +2554,1.0 +2555,1.0 +2556,1.0 +2557,1.0 +2558,1.0 +2559,1.0 +2560,1.0 +2561,1.0 +2562,1.0 +2563,1.0 +2564,1.0 +2565,1.0 +2566,1.0 +2567,1.0 +2568,1.0 +2569,1.0 +2570,1.0 +2571,1.0 +2572,1.0 +2573,1.0 +2574,1.0 +2575,1.0 +2576,1.0 +2577,1.0 +2578,1.0 +2579,1.0 +2580,1.0 +2581,1.0 +2582,1.0 +2583,1.0 +2584,1.0 +2585,1.0 +2586,1.0 +2587,1.0 +2588,1.0 +2589,1.0 +2590,1.0 +2591,1.0 +2592,1.0 +2593,1.0 +2594,1.0 +2595,1.0 +2596,1.0 +2597,1.0 +2598,1.0 +2599,1.0 +2600,1.0 +2601,1.0 +2602,1.0 +2603,1.0 +2604,1.0 +2605,1.0 +2606,1.0 +2607,1.0 +2608,1.0 +2609,1.0 +2610,1.0 +2611,1.0 +2612,1.0 +2613,1.0 +2614,1.0 +2615,1.0 +2616,1.0 +2617,1.0 +2618,1.0 +2619,1.0 +2620,1.0 +2621,1.0 +2622,1.0 +2623,1.0 +2624,1.0 +2625,1.0 +2626,1.0 +2627,1.0 +2628,1.0 +2629,1.0 +2630,1.0 +2631,1.0 +2632,1.0 +2633,1.0 +2634,1.0 +2635,1.0 +2636,1.0 +2637,1.0 +2638,1.0 +2639,1.0 +2640,1.0 +2641,1.0 +2642,1.0 +2643,1.0 +2644,1.0 +2645,1.0 +2646,1.0 +2647,1.0 +2648,1.0 +2649,1.0 +2650,1.0 +2651,1.0 +2652,1.0 +2653,1.0 +2654,1.0 +2655,1.0 +2656,1.0 +2657,1.0 +2658,1.0 +2659,1.0 +2660,1.0 +2661,1.0 +2662,1.0 +2663,1.0 +2664,1.0 +2665,1.0 +2666,1.0 +2667,1.0 +2668,1.0 +2669,1.0 +2670,1.0 +2671,1.0 +2672,1.0 +2673,1.0 +2674,1.0 +2675,1.0 +2676,1.0 +2677,1.0 +2678,1.0 +2679,1.0 +2680,1.0 +2681,1.0 +2682,1.0 +2683,1.0 +2684,1.0 +2685,1.0 +2686,1.0 +2687,1.0 +2688,1.0 +2689,1.0 +2690,1.0 +2691,1.0 +2692,1.0 +2693,1.0 +2694,1.0 +2695,1.0 +2696,1.0 +2697,1.0 +2698,1.0 +2699,1.0 +2700,1.0 +2701,1.0 +2702,1.0 +2703,1.0 +2704,1.0 +2705,1.0 +2706,1.0 +2707,1.0 +2708,1.0 +2709,1.0 +2710,1.0 +2711,1.0 +2712,1.0 +2713,1.0 +2714,1.0 +2715,1.0 +2716,1.0 +2717,1.0 +2718,1.0 +2719,1.0 +2720,1.0 +2721,1.0 +2722,1.0 +2723,1.0 +2724,1.0 +2725,1.0 +2726,1.0 +2727,1.0 +2728,1.0 +2729,1.0 +2730,1.0 +2731,1.0 +2732,1.0 +2733,1.0 +2734,1.0 +2735,1.0 +2736,1.0 +2737,1.0 +2738,1.0 +2739,1.0 +2740,1.0 +2741,1.0 +2742,1.0 +2743,1.0 +2744,1.0 +2745,1.0 +2746,1.0 +2747,1.0 +2748,1.0 +2749,1.0 +2750,1.0 +2751,1.0 +2752,1.0 +2753,1.0 +2754,1.0 +2755,1.0 +2756,1.0 +2757,1.0 +2758,1.0 +2759,1.0 +2760,1.0 +2761,1.0 +2762,1.0 +2763,1.0 +2764,1.0 +2765,1.0 +2766,1.0 +2767,1.0 +2768,1.0 +2769,1.0 +2770,1.0 +2771,1.0 +2772,1.0 +2773,1.0 +2774,1.0 +2775,1.0 +2776,1.0 +2777,1.0 +2778,1.0 +2779,1.0 +2780,1.0 +2781,1.0 +2782,1.0 +2783,1.0 +2784,1.0 +2785,1.0 +2786,1.0 +2787,1.0 +2788,1.0 +2789,1.0 +2790,1.0 +2791,1.0 +2792,1.0 +2793,1.0 +2794,1.0 +2795,1.0 +2796,1.0 +2797,1.0 +2798,1.0 +2799,1.0 +2800,1.0 +2801,1.0 +2802,1.0 +2803,1.0 +2804,1.0 +2805,1.0 +2806,1.0 +2807,1.0 +2808,1.0 +2809,1.0 +2810,1.0 +2811,1.0 +2812,1.0 +2813,1.0 +2814,1.0 +2815,1.0 +2816,1.0 +2817,1.0 +2818,1.0 +2819,1.0 +2820,1.0 +2821,1.0 +2822,1.0 +2823,1.0 +2824,1.0 +2825,1.0 +2826,1.0 +2827,1.0 +2828,1.0 +2829,1.0 +2830,1.0 +2831,1.0 +2832,1.0 +2833,1.0 +2834,1.0 +2835,1.0 +2836,1.0 +2837,1.0 +2838,1.0 +2839,1.0 +2840,1.0 +2841,1.0 +2842,1.0 +2843,1.0 +2844,1.0 +2845,1.0 +2846,1.0 +2847,1.0 +2848,1.0 +2849,1.0 +2850,1.0 +2851,1.0 +2852,1.0 +2853,1.0 +2854,1.0 +2855,1.0 +2856,1.0 +2857,1.0 +2858,1.0 +2859,1.0 +2860,1.0 +2861,1.0 +2862,1.0 +2863,1.0 +2864,1.0 +2865,1.0 +2866,1.0 +2867,1.0 +2868,1.0 +2869,1.0 +2870,1.0 +2871,1.0 +2872,1.0 +2873,1.0 +2874,1.0 +2875,1.0 +2876,1.0 +2877,1.0 +2878,1.0 +2879,1.0 +2880,1.0 +2881,1.0 +2882,1.0 +2883,1.0 +2884,1.0 +2885,1.0 +2886,1.0 +2887,1.0 +2888,1.0 +2889,1.0 +2890,1.0 +2891,1.0 +2892,1.0 +2893,1.0 +2894,1.0 +2895,1.0 +2896,1.0 +2897,1.0 +2898,1.0 +2899,1.0 +2900,1.0 +2901,1.0 +2902,1.0 +2903,1.0 +2904,1.0 +2905,1.0 +2906,1.0 +2907,1.0 +2908,1.0 +2909,1.0 +2910,1.0 +2911,1.0 +2912,1.0 +2913,1.0 +2914,1.0 +2915,1.0 +2916,1.0 +2917,1.0 +2918,1.0 +2919,1.0 diff --git a/PyPSA/source/test/data/benchmark-sp/loads.csv b/PyPSA/source/test/data/benchmark-sp/loads.csv new file mode 100644 index 0000000000000000000000000000000000000000..f5cfa910ca4a9b60696571c59ead08cfffd85838 --- /dev/null +++ b/PyPSA/source/test/data/benchmark-sp/loads.csv @@ -0,0 +1,2 @@ +name,bus,p_set +DE_load,DE,1.0 diff --git a/PyPSA/source/test/data/benchmark-sp/meta.json b/PyPSA/source/test/data/benchmark-sp/meta.json new file mode 100644 index 0000000000000000000000000000000000000000..0967ef424bce6791893e9a57bb952f80fd536e93 --- /dev/null +++ b/PyPSA/source/test/data/benchmark-sp/meta.json @@ -0,0 +1 @@ +{} diff --git a/PyPSA/source/test/data/benchmark-sp/network.csv b/PyPSA/source/test/data/benchmark-sp/network.csv new file mode 100644 index 0000000000000000000000000000000000000000..2d0adee808cc8fd2a393b7058055ed094cf53263 --- /dev/null +++ b/PyPSA/source/test/data/benchmark-sp/network.csv @@ -0,0 +1,2 @@ +name,_linearized_uc,_multi_invest,objective,objective_constant,pypsa_version,srid +,0,0,968918.1583585878,0.0,0.34.0,4326 diff --git a/PyPSA/source/test/data/benchmark-sp/snapshots.csv b/PyPSA/source/test/data/benchmark-sp/snapshots.csv new file mode 100644 index 0000000000000000000000000000000000000000..3c9fc00d99fa584bb2d78e4903fe1b25cbd31461 --- /dev/null +++ b/PyPSA/source/test/data/benchmark-sp/snapshots.csv @@ -0,0 +1,2921 @@ +,snapshot,objective,stores,generators +0,2015-01-01 00:00:00,3,3,3 +1,2015-01-01 03:00:00,3,3,3 +2,2015-01-01 06:00:00,3,3,3 +3,2015-01-01 09:00:00,3,3,3 +4,2015-01-01 12:00:00,3,3,3 +5,2015-01-01 15:00:00,3,3,3 +6,2015-01-01 18:00:00,3,3,3 +7,2015-01-01 21:00:00,3,3,3 +8,2015-01-02 00:00:00,3,3,3 +9,2015-01-02 03:00:00,3,3,3 +10,2015-01-02 06:00:00,3,3,3 +11,2015-01-02 09:00:00,3,3,3 +12,2015-01-02 12:00:00,3,3,3 +13,2015-01-02 15:00:00,3,3,3 +14,2015-01-02 18:00:00,3,3,3 +15,2015-01-02 21:00:00,3,3,3 +16,2015-01-03 00:00:00,3,3,3 +17,2015-01-03 03:00:00,3,3,3 +18,2015-01-03 06:00:00,3,3,3 +19,2015-01-03 09:00:00,3,3,3 +20,2015-01-03 12:00:00,3,3,3 +21,2015-01-03 15:00:00,3,3,3 +22,2015-01-03 18:00:00,3,3,3 +23,2015-01-03 21:00:00,3,3,3 +24,2015-01-04 00:00:00,3,3,3 +25,2015-01-04 03:00:00,3,3,3 +26,2015-01-04 06:00:00,3,3,3 +27,2015-01-04 09:00:00,3,3,3 +28,2015-01-04 12:00:00,3,3,3 +29,2015-01-04 15:00:00,3,3,3 +30,2015-01-04 18:00:00,3,3,3 +31,2015-01-04 21:00:00,3,3,3 +32,2015-01-05 00:00:00,3,3,3 +33,2015-01-05 03:00:00,3,3,3 +34,2015-01-05 06:00:00,3,3,3 +35,2015-01-05 09:00:00,3,3,3 +36,2015-01-05 12:00:00,3,3,3 +37,2015-01-05 15:00:00,3,3,3 +38,2015-01-05 18:00:00,3,3,3 +39,2015-01-05 21:00:00,3,3,3 +40,2015-01-06 00:00:00,3,3,3 +41,2015-01-06 03:00:00,3,3,3 +42,2015-01-06 06:00:00,3,3,3 +43,2015-01-06 09:00:00,3,3,3 +44,2015-01-06 12:00:00,3,3,3 +45,2015-01-06 15:00:00,3,3,3 +46,2015-01-06 18:00:00,3,3,3 +47,2015-01-06 21:00:00,3,3,3 +48,2015-01-07 00:00:00,3,3,3 +49,2015-01-07 03:00:00,3,3,3 +50,2015-01-07 06:00:00,3,3,3 +51,2015-01-07 09:00:00,3,3,3 +52,2015-01-07 12:00:00,3,3,3 +53,2015-01-07 15:00:00,3,3,3 +54,2015-01-07 18:00:00,3,3,3 +55,2015-01-07 21:00:00,3,3,3 +56,2015-01-08 00:00:00,3,3,3 +57,2015-01-08 03:00:00,3,3,3 +58,2015-01-08 06:00:00,3,3,3 +59,2015-01-08 09:00:00,3,3,3 +60,2015-01-08 12:00:00,3,3,3 +61,2015-01-08 15:00:00,3,3,3 +62,2015-01-08 18:00:00,3,3,3 +63,2015-01-08 21:00:00,3,3,3 +64,2015-01-09 00:00:00,3,3,3 +65,2015-01-09 03:00:00,3,3,3 +66,2015-01-09 06:00:00,3,3,3 +67,2015-01-09 09:00:00,3,3,3 +68,2015-01-09 12:00:00,3,3,3 +69,2015-01-09 15:00:00,3,3,3 +70,2015-01-09 18:00:00,3,3,3 +71,2015-01-09 21:00:00,3,3,3 +72,2015-01-10 00:00:00,3,3,3 +73,2015-01-10 03:00:00,3,3,3 +74,2015-01-10 06:00:00,3,3,3 +75,2015-01-10 09:00:00,3,3,3 +76,2015-01-10 12:00:00,3,3,3 +77,2015-01-10 15:00:00,3,3,3 +78,2015-01-10 18:00:00,3,3,3 +79,2015-01-10 21:00:00,3,3,3 +80,2015-01-11 00:00:00,3,3,3 +81,2015-01-11 03:00:00,3,3,3 +82,2015-01-11 06:00:00,3,3,3 +83,2015-01-11 09:00:00,3,3,3 +84,2015-01-11 12:00:00,3,3,3 +85,2015-01-11 15:00:00,3,3,3 +86,2015-01-11 18:00:00,3,3,3 +87,2015-01-11 21:00:00,3,3,3 +88,2015-01-12 00:00:00,3,3,3 +89,2015-01-12 03:00:00,3,3,3 +90,2015-01-12 06:00:00,3,3,3 +91,2015-01-12 09:00:00,3,3,3 +92,2015-01-12 12:00:00,3,3,3 +93,2015-01-12 15:00:00,3,3,3 +94,2015-01-12 18:00:00,3,3,3 +95,2015-01-12 21:00:00,3,3,3 +96,2015-01-13 00:00:00,3,3,3 +97,2015-01-13 03:00:00,3,3,3 +98,2015-01-13 06:00:00,3,3,3 +99,2015-01-13 09:00:00,3,3,3 +100,2015-01-13 12:00:00,3,3,3 +101,2015-01-13 15:00:00,3,3,3 +102,2015-01-13 18:00:00,3,3,3 +103,2015-01-13 21:00:00,3,3,3 +104,2015-01-14 00:00:00,3,3,3 +105,2015-01-14 03:00:00,3,3,3 +106,2015-01-14 06:00:00,3,3,3 +107,2015-01-14 09:00:00,3,3,3 +108,2015-01-14 12:00:00,3,3,3 +109,2015-01-14 15:00:00,3,3,3 +110,2015-01-14 18:00:00,3,3,3 +111,2015-01-14 21:00:00,3,3,3 +112,2015-01-15 00:00:00,3,3,3 +113,2015-01-15 03:00:00,3,3,3 +114,2015-01-15 06:00:00,3,3,3 +115,2015-01-15 09:00:00,3,3,3 +116,2015-01-15 12:00:00,3,3,3 +117,2015-01-15 15:00:00,3,3,3 +118,2015-01-15 18:00:00,3,3,3 +119,2015-01-15 21:00:00,3,3,3 +120,2015-01-16 00:00:00,3,3,3 +121,2015-01-16 03:00:00,3,3,3 +122,2015-01-16 06:00:00,3,3,3 +123,2015-01-16 09:00:00,3,3,3 +124,2015-01-16 12:00:00,3,3,3 +125,2015-01-16 15:00:00,3,3,3 +126,2015-01-16 18:00:00,3,3,3 +127,2015-01-16 21:00:00,3,3,3 +128,2015-01-17 00:00:00,3,3,3 +129,2015-01-17 03:00:00,3,3,3 +130,2015-01-17 06:00:00,3,3,3 +131,2015-01-17 09:00:00,3,3,3 +132,2015-01-17 12:00:00,3,3,3 +133,2015-01-17 15:00:00,3,3,3 +134,2015-01-17 18:00:00,3,3,3 +135,2015-01-17 21:00:00,3,3,3 +136,2015-01-18 00:00:00,3,3,3 +137,2015-01-18 03:00:00,3,3,3 +138,2015-01-18 06:00:00,3,3,3 +139,2015-01-18 09:00:00,3,3,3 +140,2015-01-18 12:00:00,3,3,3 +141,2015-01-18 15:00:00,3,3,3 +142,2015-01-18 18:00:00,3,3,3 +143,2015-01-18 21:00:00,3,3,3 +144,2015-01-19 00:00:00,3,3,3 +145,2015-01-19 03:00:00,3,3,3 +146,2015-01-19 06:00:00,3,3,3 +147,2015-01-19 09:00:00,3,3,3 +148,2015-01-19 12:00:00,3,3,3 +149,2015-01-19 15:00:00,3,3,3 +150,2015-01-19 18:00:00,3,3,3 +151,2015-01-19 21:00:00,3,3,3 +152,2015-01-20 00:00:00,3,3,3 +153,2015-01-20 03:00:00,3,3,3 +154,2015-01-20 06:00:00,3,3,3 +155,2015-01-20 09:00:00,3,3,3 +156,2015-01-20 12:00:00,3,3,3 +157,2015-01-20 15:00:00,3,3,3 +158,2015-01-20 18:00:00,3,3,3 +159,2015-01-20 21:00:00,3,3,3 +160,2015-01-21 00:00:00,3,3,3 +161,2015-01-21 03:00:00,3,3,3 +162,2015-01-21 06:00:00,3,3,3 +163,2015-01-21 09:00:00,3,3,3 +164,2015-01-21 12:00:00,3,3,3 +165,2015-01-21 15:00:00,3,3,3 +166,2015-01-21 18:00:00,3,3,3 +167,2015-01-21 21:00:00,3,3,3 +168,2015-01-22 00:00:00,3,3,3 +169,2015-01-22 03:00:00,3,3,3 +170,2015-01-22 06:00:00,3,3,3 +171,2015-01-22 09:00:00,3,3,3 +172,2015-01-22 12:00:00,3,3,3 +173,2015-01-22 15:00:00,3,3,3 +174,2015-01-22 18:00:00,3,3,3 +175,2015-01-22 21:00:00,3,3,3 +176,2015-01-23 00:00:00,3,3,3 +177,2015-01-23 03:00:00,3,3,3 +178,2015-01-23 06:00:00,3,3,3 +179,2015-01-23 09:00:00,3,3,3 +180,2015-01-23 12:00:00,3,3,3 +181,2015-01-23 15:00:00,3,3,3 +182,2015-01-23 18:00:00,3,3,3 +183,2015-01-23 21:00:00,3,3,3 +184,2015-01-24 00:00:00,3,3,3 +185,2015-01-24 03:00:00,3,3,3 +186,2015-01-24 06:00:00,3,3,3 +187,2015-01-24 09:00:00,3,3,3 +188,2015-01-24 12:00:00,3,3,3 +189,2015-01-24 15:00:00,3,3,3 +190,2015-01-24 18:00:00,3,3,3 +191,2015-01-24 21:00:00,3,3,3 +192,2015-01-25 00:00:00,3,3,3 +193,2015-01-25 03:00:00,3,3,3 +194,2015-01-25 06:00:00,3,3,3 +195,2015-01-25 09:00:00,3,3,3 +196,2015-01-25 12:00:00,3,3,3 +197,2015-01-25 15:00:00,3,3,3 +198,2015-01-25 18:00:00,3,3,3 +199,2015-01-25 21:00:00,3,3,3 +200,2015-01-26 00:00:00,3,3,3 +201,2015-01-26 03:00:00,3,3,3 +202,2015-01-26 06:00:00,3,3,3 +203,2015-01-26 09:00:00,3,3,3 +204,2015-01-26 12:00:00,3,3,3 +205,2015-01-26 15:00:00,3,3,3 +206,2015-01-26 18:00:00,3,3,3 +207,2015-01-26 21:00:00,3,3,3 +208,2015-01-27 00:00:00,3,3,3 +209,2015-01-27 03:00:00,3,3,3 +210,2015-01-27 06:00:00,3,3,3 +211,2015-01-27 09:00:00,3,3,3 +212,2015-01-27 12:00:00,3,3,3 +213,2015-01-27 15:00:00,3,3,3 +214,2015-01-27 18:00:00,3,3,3 +215,2015-01-27 21:00:00,3,3,3 +216,2015-01-28 00:00:00,3,3,3 +217,2015-01-28 03:00:00,3,3,3 +218,2015-01-28 06:00:00,3,3,3 +219,2015-01-28 09:00:00,3,3,3 +220,2015-01-28 12:00:00,3,3,3 +221,2015-01-28 15:00:00,3,3,3 +222,2015-01-28 18:00:00,3,3,3 +223,2015-01-28 21:00:00,3,3,3 +224,2015-01-29 00:00:00,3,3,3 +225,2015-01-29 03:00:00,3,3,3 +226,2015-01-29 06:00:00,3,3,3 +227,2015-01-29 09:00:00,3,3,3 +228,2015-01-29 12:00:00,3,3,3 +229,2015-01-29 15:00:00,3,3,3 +230,2015-01-29 18:00:00,3,3,3 +231,2015-01-29 21:00:00,3,3,3 +232,2015-01-30 00:00:00,3,3,3 +233,2015-01-30 03:00:00,3,3,3 +234,2015-01-30 06:00:00,3,3,3 +235,2015-01-30 09:00:00,3,3,3 +236,2015-01-30 12:00:00,3,3,3 +237,2015-01-30 15:00:00,3,3,3 +238,2015-01-30 18:00:00,3,3,3 +239,2015-01-30 21:00:00,3,3,3 +240,2015-01-31 00:00:00,3,3,3 +241,2015-01-31 03:00:00,3,3,3 +242,2015-01-31 06:00:00,3,3,3 +243,2015-01-31 09:00:00,3,3,3 +244,2015-01-31 12:00:00,3,3,3 +245,2015-01-31 15:00:00,3,3,3 +246,2015-01-31 18:00:00,3,3,3 +247,2015-01-31 21:00:00,3,3,3 +248,2015-02-01 00:00:00,3,3,3 +249,2015-02-01 03:00:00,3,3,3 +250,2015-02-01 06:00:00,3,3,3 +251,2015-02-01 09:00:00,3,3,3 +252,2015-02-01 12:00:00,3,3,3 +253,2015-02-01 15:00:00,3,3,3 +254,2015-02-01 18:00:00,3,3,3 +255,2015-02-01 21:00:00,3,3,3 +256,2015-02-02 00:00:00,3,3,3 +257,2015-02-02 03:00:00,3,3,3 +258,2015-02-02 06:00:00,3,3,3 +259,2015-02-02 09:00:00,3,3,3 +260,2015-02-02 12:00:00,3,3,3 +261,2015-02-02 15:00:00,3,3,3 +262,2015-02-02 18:00:00,3,3,3 +263,2015-02-02 21:00:00,3,3,3 +264,2015-02-03 00:00:00,3,3,3 +265,2015-02-03 03:00:00,3,3,3 +266,2015-02-03 06:00:00,3,3,3 +267,2015-02-03 09:00:00,3,3,3 +268,2015-02-03 12:00:00,3,3,3 +269,2015-02-03 15:00:00,3,3,3 +270,2015-02-03 18:00:00,3,3,3 +271,2015-02-03 21:00:00,3,3,3 +272,2015-02-04 00:00:00,3,3,3 +273,2015-02-04 03:00:00,3,3,3 +274,2015-02-04 06:00:00,3,3,3 +275,2015-02-04 09:00:00,3,3,3 +276,2015-02-04 12:00:00,3,3,3 +277,2015-02-04 15:00:00,3,3,3 +278,2015-02-04 18:00:00,3,3,3 +279,2015-02-04 21:00:00,3,3,3 +280,2015-02-05 00:00:00,3,3,3 +281,2015-02-05 03:00:00,3,3,3 +282,2015-02-05 06:00:00,3,3,3 +283,2015-02-05 09:00:00,3,3,3 +284,2015-02-05 12:00:00,3,3,3 +285,2015-02-05 15:00:00,3,3,3 +286,2015-02-05 18:00:00,3,3,3 +287,2015-02-05 21:00:00,3,3,3 +288,2015-02-06 00:00:00,3,3,3 +289,2015-02-06 03:00:00,3,3,3 +290,2015-02-06 06:00:00,3,3,3 +291,2015-02-06 09:00:00,3,3,3 +292,2015-02-06 12:00:00,3,3,3 +293,2015-02-06 15:00:00,3,3,3 +294,2015-02-06 18:00:00,3,3,3 +295,2015-02-06 21:00:00,3,3,3 +296,2015-02-07 00:00:00,3,3,3 +297,2015-02-07 03:00:00,3,3,3 +298,2015-02-07 06:00:00,3,3,3 +299,2015-02-07 09:00:00,3,3,3 +300,2015-02-07 12:00:00,3,3,3 +301,2015-02-07 15:00:00,3,3,3 +302,2015-02-07 18:00:00,3,3,3 +303,2015-02-07 21:00:00,3,3,3 +304,2015-02-08 00:00:00,3,3,3 +305,2015-02-08 03:00:00,3,3,3 +306,2015-02-08 06:00:00,3,3,3 +307,2015-02-08 09:00:00,3,3,3 +308,2015-02-08 12:00:00,3,3,3 +309,2015-02-08 15:00:00,3,3,3 +310,2015-02-08 18:00:00,3,3,3 +311,2015-02-08 21:00:00,3,3,3 +312,2015-02-09 00:00:00,3,3,3 +313,2015-02-09 03:00:00,3,3,3 +314,2015-02-09 06:00:00,3,3,3 +315,2015-02-09 09:00:00,3,3,3 +316,2015-02-09 12:00:00,3,3,3 +317,2015-02-09 15:00:00,3,3,3 +318,2015-02-09 18:00:00,3,3,3 +319,2015-02-09 21:00:00,3,3,3 +320,2015-02-10 00:00:00,3,3,3 +321,2015-02-10 03:00:00,3,3,3 +322,2015-02-10 06:00:00,3,3,3 +323,2015-02-10 09:00:00,3,3,3 +324,2015-02-10 12:00:00,3,3,3 +325,2015-02-10 15:00:00,3,3,3 +326,2015-02-10 18:00:00,3,3,3 +327,2015-02-10 21:00:00,3,3,3 +328,2015-02-11 00:00:00,3,3,3 +329,2015-02-11 03:00:00,3,3,3 +330,2015-02-11 06:00:00,3,3,3 +331,2015-02-11 09:00:00,3,3,3 +332,2015-02-11 12:00:00,3,3,3 +333,2015-02-11 15:00:00,3,3,3 +334,2015-02-11 18:00:00,3,3,3 +335,2015-02-11 21:00:00,3,3,3 +336,2015-02-12 00:00:00,3,3,3 +337,2015-02-12 03:00:00,3,3,3 +338,2015-02-12 06:00:00,3,3,3 +339,2015-02-12 09:00:00,3,3,3 +340,2015-02-12 12:00:00,3,3,3 +341,2015-02-12 15:00:00,3,3,3 +342,2015-02-12 18:00:00,3,3,3 +343,2015-02-12 21:00:00,3,3,3 +344,2015-02-13 00:00:00,3,3,3 +345,2015-02-13 03:00:00,3,3,3 +346,2015-02-13 06:00:00,3,3,3 +347,2015-02-13 09:00:00,3,3,3 +348,2015-02-13 12:00:00,3,3,3 +349,2015-02-13 15:00:00,3,3,3 +350,2015-02-13 18:00:00,3,3,3 +351,2015-02-13 21:00:00,3,3,3 +352,2015-02-14 00:00:00,3,3,3 +353,2015-02-14 03:00:00,3,3,3 +354,2015-02-14 06:00:00,3,3,3 +355,2015-02-14 09:00:00,3,3,3 +356,2015-02-14 12:00:00,3,3,3 +357,2015-02-14 15:00:00,3,3,3 +358,2015-02-14 18:00:00,3,3,3 +359,2015-02-14 21:00:00,3,3,3 +360,2015-02-15 00:00:00,3,3,3 +361,2015-02-15 03:00:00,3,3,3 +362,2015-02-15 06:00:00,3,3,3 +363,2015-02-15 09:00:00,3,3,3 +364,2015-02-15 12:00:00,3,3,3 +365,2015-02-15 15:00:00,3,3,3 +366,2015-02-15 18:00:00,3,3,3 +367,2015-02-15 21:00:00,3,3,3 +368,2015-02-16 00:00:00,3,3,3 +369,2015-02-16 03:00:00,3,3,3 +370,2015-02-16 06:00:00,3,3,3 +371,2015-02-16 09:00:00,3,3,3 +372,2015-02-16 12:00:00,3,3,3 +373,2015-02-16 15:00:00,3,3,3 +374,2015-02-16 18:00:00,3,3,3 +375,2015-02-16 21:00:00,3,3,3 +376,2015-02-17 00:00:00,3,3,3 +377,2015-02-17 03:00:00,3,3,3 +378,2015-02-17 06:00:00,3,3,3 +379,2015-02-17 09:00:00,3,3,3 +380,2015-02-17 12:00:00,3,3,3 +381,2015-02-17 15:00:00,3,3,3 +382,2015-02-17 18:00:00,3,3,3 +383,2015-02-17 21:00:00,3,3,3 +384,2015-02-18 00:00:00,3,3,3 +385,2015-02-18 03:00:00,3,3,3 +386,2015-02-18 06:00:00,3,3,3 +387,2015-02-18 09:00:00,3,3,3 +388,2015-02-18 12:00:00,3,3,3 +389,2015-02-18 15:00:00,3,3,3 +390,2015-02-18 18:00:00,3,3,3 +391,2015-02-18 21:00:00,3,3,3 +392,2015-02-19 00:00:00,3,3,3 +393,2015-02-19 03:00:00,3,3,3 +394,2015-02-19 06:00:00,3,3,3 +395,2015-02-19 09:00:00,3,3,3 +396,2015-02-19 12:00:00,3,3,3 +397,2015-02-19 15:00:00,3,3,3 +398,2015-02-19 18:00:00,3,3,3 +399,2015-02-19 21:00:00,3,3,3 +400,2015-02-20 00:00:00,3,3,3 +401,2015-02-20 03:00:00,3,3,3 +402,2015-02-20 06:00:00,3,3,3 +403,2015-02-20 09:00:00,3,3,3 +404,2015-02-20 12:00:00,3,3,3 +405,2015-02-20 15:00:00,3,3,3 +406,2015-02-20 18:00:00,3,3,3 +407,2015-02-20 21:00:00,3,3,3 +408,2015-02-21 00:00:00,3,3,3 +409,2015-02-21 03:00:00,3,3,3 +410,2015-02-21 06:00:00,3,3,3 +411,2015-02-21 09:00:00,3,3,3 +412,2015-02-21 12:00:00,3,3,3 +413,2015-02-21 15:00:00,3,3,3 +414,2015-02-21 18:00:00,3,3,3 +415,2015-02-21 21:00:00,3,3,3 +416,2015-02-22 00:00:00,3,3,3 +417,2015-02-22 03:00:00,3,3,3 +418,2015-02-22 06:00:00,3,3,3 +419,2015-02-22 09:00:00,3,3,3 +420,2015-02-22 12:00:00,3,3,3 +421,2015-02-22 15:00:00,3,3,3 +422,2015-02-22 18:00:00,3,3,3 +423,2015-02-22 21:00:00,3,3,3 +424,2015-02-23 00:00:00,3,3,3 +425,2015-02-23 03:00:00,3,3,3 +426,2015-02-23 06:00:00,3,3,3 +427,2015-02-23 09:00:00,3,3,3 +428,2015-02-23 12:00:00,3,3,3 +429,2015-02-23 15:00:00,3,3,3 +430,2015-02-23 18:00:00,3,3,3 +431,2015-02-23 21:00:00,3,3,3 +432,2015-02-24 00:00:00,3,3,3 +433,2015-02-24 03:00:00,3,3,3 +434,2015-02-24 06:00:00,3,3,3 +435,2015-02-24 09:00:00,3,3,3 +436,2015-02-24 12:00:00,3,3,3 +437,2015-02-24 15:00:00,3,3,3 +438,2015-02-24 18:00:00,3,3,3 +439,2015-02-24 21:00:00,3,3,3 +440,2015-02-25 00:00:00,3,3,3 +441,2015-02-25 03:00:00,3,3,3 +442,2015-02-25 06:00:00,3,3,3 +443,2015-02-25 09:00:00,3,3,3 +444,2015-02-25 12:00:00,3,3,3 +445,2015-02-25 15:00:00,3,3,3 +446,2015-02-25 18:00:00,3,3,3 +447,2015-02-25 21:00:00,3,3,3 +448,2015-02-26 00:00:00,3,3,3 +449,2015-02-26 03:00:00,3,3,3 +450,2015-02-26 06:00:00,3,3,3 +451,2015-02-26 09:00:00,3,3,3 +452,2015-02-26 12:00:00,3,3,3 +453,2015-02-26 15:00:00,3,3,3 +454,2015-02-26 18:00:00,3,3,3 +455,2015-02-26 21:00:00,3,3,3 +456,2015-02-27 00:00:00,3,3,3 +457,2015-02-27 03:00:00,3,3,3 +458,2015-02-27 06:00:00,3,3,3 +459,2015-02-27 09:00:00,3,3,3 +460,2015-02-27 12:00:00,3,3,3 +461,2015-02-27 15:00:00,3,3,3 +462,2015-02-27 18:00:00,3,3,3 +463,2015-02-27 21:00:00,3,3,3 +464,2015-02-28 00:00:00,3,3,3 +465,2015-02-28 03:00:00,3,3,3 +466,2015-02-28 06:00:00,3,3,3 +467,2015-02-28 09:00:00,3,3,3 +468,2015-02-28 12:00:00,3,3,3 +469,2015-02-28 15:00:00,3,3,3 +470,2015-02-28 18:00:00,3,3,3 +471,2015-02-28 21:00:00,3,3,3 +472,2015-03-01 00:00:00,3,3,3 +473,2015-03-01 03:00:00,3,3,3 +474,2015-03-01 06:00:00,3,3,3 +475,2015-03-01 09:00:00,3,3,3 +476,2015-03-01 12:00:00,3,3,3 +477,2015-03-01 15:00:00,3,3,3 +478,2015-03-01 18:00:00,3,3,3 +479,2015-03-01 21:00:00,3,3,3 +480,2015-03-02 00:00:00,3,3,3 +481,2015-03-02 03:00:00,3,3,3 +482,2015-03-02 06:00:00,3,3,3 +483,2015-03-02 09:00:00,3,3,3 +484,2015-03-02 12:00:00,3,3,3 +485,2015-03-02 15:00:00,3,3,3 +486,2015-03-02 18:00:00,3,3,3 +487,2015-03-02 21:00:00,3,3,3 +488,2015-03-03 00:00:00,3,3,3 +489,2015-03-03 03:00:00,3,3,3 +490,2015-03-03 06:00:00,3,3,3 +491,2015-03-03 09:00:00,3,3,3 +492,2015-03-03 12:00:00,3,3,3 +493,2015-03-03 15:00:00,3,3,3 +494,2015-03-03 18:00:00,3,3,3 +495,2015-03-03 21:00:00,3,3,3 +496,2015-03-04 00:00:00,3,3,3 +497,2015-03-04 03:00:00,3,3,3 +498,2015-03-04 06:00:00,3,3,3 +499,2015-03-04 09:00:00,3,3,3 +500,2015-03-04 12:00:00,3,3,3 +501,2015-03-04 15:00:00,3,3,3 +502,2015-03-04 18:00:00,3,3,3 +503,2015-03-04 21:00:00,3,3,3 +504,2015-03-05 00:00:00,3,3,3 +505,2015-03-05 03:00:00,3,3,3 +506,2015-03-05 06:00:00,3,3,3 +507,2015-03-05 09:00:00,3,3,3 +508,2015-03-05 12:00:00,3,3,3 +509,2015-03-05 15:00:00,3,3,3 +510,2015-03-05 18:00:00,3,3,3 +511,2015-03-05 21:00:00,3,3,3 +512,2015-03-06 00:00:00,3,3,3 +513,2015-03-06 03:00:00,3,3,3 +514,2015-03-06 06:00:00,3,3,3 +515,2015-03-06 09:00:00,3,3,3 +516,2015-03-06 12:00:00,3,3,3 +517,2015-03-06 15:00:00,3,3,3 +518,2015-03-06 18:00:00,3,3,3 +519,2015-03-06 21:00:00,3,3,3 +520,2015-03-07 00:00:00,3,3,3 +521,2015-03-07 03:00:00,3,3,3 +522,2015-03-07 06:00:00,3,3,3 +523,2015-03-07 09:00:00,3,3,3 +524,2015-03-07 12:00:00,3,3,3 +525,2015-03-07 15:00:00,3,3,3 +526,2015-03-07 18:00:00,3,3,3 +527,2015-03-07 21:00:00,3,3,3 +528,2015-03-08 00:00:00,3,3,3 +529,2015-03-08 03:00:00,3,3,3 +530,2015-03-08 06:00:00,3,3,3 +531,2015-03-08 09:00:00,3,3,3 +532,2015-03-08 12:00:00,3,3,3 +533,2015-03-08 15:00:00,3,3,3 +534,2015-03-08 18:00:00,3,3,3 +535,2015-03-08 21:00:00,3,3,3 +536,2015-03-09 00:00:00,3,3,3 +537,2015-03-09 03:00:00,3,3,3 +538,2015-03-09 06:00:00,3,3,3 +539,2015-03-09 09:00:00,3,3,3 +540,2015-03-09 12:00:00,3,3,3 +541,2015-03-09 15:00:00,3,3,3 +542,2015-03-09 18:00:00,3,3,3 +543,2015-03-09 21:00:00,3,3,3 +544,2015-03-10 00:00:00,3,3,3 +545,2015-03-10 03:00:00,3,3,3 +546,2015-03-10 06:00:00,3,3,3 +547,2015-03-10 09:00:00,3,3,3 +548,2015-03-10 12:00:00,3,3,3 +549,2015-03-10 15:00:00,3,3,3 +550,2015-03-10 18:00:00,3,3,3 +551,2015-03-10 21:00:00,3,3,3 +552,2015-03-11 00:00:00,3,3,3 +553,2015-03-11 03:00:00,3,3,3 +554,2015-03-11 06:00:00,3,3,3 +555,2015-03-11 09:00:00,3,3,3 +556,2015-03-11 12:00:00,3,3,3 +557,2015-03-11 15:00:00,3,3,3 +558,2015-03-11 18:00:00,3,3,3 +559,2015-03-11 21:00:00,3,3,3 +560,2015-03-12 00:00:00,3,3,3 +561,2015-03-12 03:00:00,3,3,3 +562,2015-03-12 06:00:00,3,3,3 +563,2015-03-12 09:00:00,3,3,3 +564,2015-03-12 12:00:00,3,3,3 +565,2015-03-12 15:00:00,3,3,3 +566,2015-03-12 18:00:00,3,3,3 +567,2015-03-12 21:00:00,3,3,3 +568,2015-03-13 00:00:00,3,3,3 +569,2015-03-13 03:00:00,3,3,3 +570,2015-03-13 06:00:00,3,3,3 +571,2015-03-13 09:00:00,3,3,3 +572,2015-03-13 12:00:00,3,3,3 +573,2015-03-13 15:00:00,3,3,3 +574,2015-03-13 18:00:00,3,3,3 +575,2015-03-13 21:00:00,3,3,3 +576,2015-03-14 00:00:00,3,3,3 +577,2015-03-14 03:00:00,3,3,3 +578,2015-03-14 06:00:00,3,3,3 +579,2015-03-14 09:00:00,3,3,3 +580,2015-03-14 12:00:00,3,3,3 +581,2015-03-14 15:00:00,3,3,3 +582,2015-03-14 18:00:00,3,3,3 +583,2015-03-14 21:00:00,3,3,3 +584,2015-03-15 00:00:00,3,3,3 +585,2015-03-15 03:00:00,3,3,3 +586,2015-03-15 06:00:00,3,3,3 +587,2015-03-15 09:00:00,3,3,3 +588,2015-03-15 12:00:00,3,3,3 +589,2015-03-15 15:00:00,3,3,3 +590,2015-03-15 18:00:00,3,3,3 +591,2015-03-15 21:00:00,3,3,3 +592,2015-03-16 00:00:00,3,3,3 +593,2015-03-16 03:00:00,3,3,3 +594,2015-03-16 06:00:00,3,3,3 +595,2015-03-16 09:00:00,3,3,3 +596,2015-03-16 12:00:00,3,3,3 +597,2015-03-16 15:00:00,3,3,3 +598,2015-03-16 18:00:00,3,3,3 +599,2015-03-16 21:00:00,3,3,3 +600,2015-03-17 00:00:00,3,3,3 +601,2015-03-17 03:00:00,3,3,3 +602,2015-03-17 06:00:00,3,3,3 +603,2015-03-17 09:00:00,3,3,3 +604,2015-03-17 12:00:00,3,3,3 +605,2015-03-17 15:00:00,3,3,3 +606,2015-03-17 18:00:00,3,3,3 +607,2015-03-17 21:00:00,3,3,3 +608,2015-03-18 00:00:00,3,3,3 +609,2015-03-18 03:00:00,3,3,3 +610,2015-03-18 06:00:00,3,3,3 +611,2015-03-18 09:00:00,3,3,3 +612,2015-03-18 12:00:00,3,3,3 +613,2015-03-18 15:00:00,3,3,3 +614,2015-03-18 18:00:00,3,3,3 +615,2015-03-18 21:00:00,3,3,3 +616,2015-03-19 00:00:00,3,3,3 +617,2015-03-19 03:00:00,3,3,3 +618,2015-03-19 06:00:00,3,3,3 +619,2015-03-19 09:00:00,3,3,3 +620,2015-03-19 12:00:00,3,3,3 +621,2015-03-19 15:00:00,3,3,3 +622,2015-03-19 18:00:00,3,3,3 +623,2015-03-19 21:00:00,3,3,3 +624,2015-03-20 00:00:00,3,3,3 +625,2015-03-20 03:00:00,3,3,3 +626,2015-03-20 06:00:00,3,3,3 +627,2015-03-20 09:00:00,3,3,3 +628,2015-03-20 12:00:00,3,3,3 +629,2015-03-20 15:00:00,3,3,3 +630,2015-03-20 18:00:00,3,3,3 +631,2015-03-20 21:00:00,3,3,3 +632,2015-03-21 00:00:00,3,3,3 +633,2015-03-21 03:00:00,3,3,3 +634,2015-03-21 06:00:00,3,3,3 +635,2015-03-21 09:00:00,3,3,3 +636,2015-03-21 12:00:00,3,3,3 +637,2015-03-21 15:00:00,3,3,3 +638,2015-03-21 18:00:00,3,3,3 +639,2015-03-21 21:00:00,3,3,3 +640,2015-03-22 00:00:00,3,3,3 +641,2015-03-22 03:00:00,3,3,3 +642,2015-03-22 06:00:00,3,3,3 +643,2015-03-22 09:00:00,3,3,3 +644,2015-03-22 12:00:00,3,3,3 +645,2015-03-22 15:00:00,3,3,3 +646,2015-03-22 18:00:00,3,3,3 +647,2015-03-22 21:00:00,3,3,3 +648,2015-03-23 00:00:00,3,3,3 +649,2015-03-23 03:00:00,3,3,3 +650,2015-03-23 06:00:00,3,3,3 +651,2015-03-23 09:00:00,3,3,3 +652,2015-03-23 12:00:00,3,3,3 +653,2015-03-23 15:00:00,3,3,3 +654,2015-03-23 18:00:00,3,3,3 +655,2015-03-23 21:00:00,3,3,3 +656,2015-03-24 00:00:00,3,3,3 +657,2015-03-24 03:00:00,3,3,3 +658,2015-03-24 06:00:00,3,3,3 +659,2015-03-24 09:00:00,3,3,3 +660,2015-03-24 12:00:00,3,3,3 +661,2015-03-24 15:00:00,3,3,3 +662,2015-03-24 18:00:00,3,3,3 +663,2015-03-24 21:00:00,3,3,3 +664,2015-03-25 00:00:00,3,3,3 +665,2015-03-25 03:00:00,3,3,3 +666,2015-03-25 06:00:00,3,3,3 +667,2015-03-25 09:00:00,3,3,3 +668,2015-03-25 12:00:00,3,3,3 +669,2015-03-25 15:00:00,3,3,3 +670,2015-03-25 18:00:00,3,3,3 +671,2015-03-25 21:00:00,3,3,3 +672,2015-03-26 00:00:00,3,3,3 +673,2015-03-26 03:00:00,3,3,3 +674,2015-03-26 06:00:00,3,3,3 +675,2015-03-26 09:00:00,3,3,3 +676,2015-03-26 12:00:00,3,3,3 +677,2015-03-26 15:00:00,3,3,3 +678,2015-03-26 18:00:00,3,3,3 +679,2015-03-26 21:00:00,3,3,3 +680,2015-03-27 00:00:00,3,3,3 +681,2015-03-27 03:00:00,3,3,3 +682,2015-03-27 06:00:00,3,3,3 +683,2015-03-27 09:00:00,3,3,3 +684,2015-03-27 12:00:00,3,3,3 +685,2015-03-27 15:00:00,3,3,3 +686,2015-03-27 18:00:00,3,3,3 +687,2015-03-27 21:00:00,3,3,3 +688,2015-03-28 00:00:00,3,3,3 +689,2015-03-28 03:00:00,3,3,3 +690,2015-03-28 06:00:00,3,3,3 +691,2015-03-28 09:00:00,3,3,3 +692,2015-03-28 12:00:00,3,3,3 +693,2015-03-28 15:00:00,3,3,3 +694,2015-03-28 18:00:00,3,3,3 +695,2015-03-28 21:00:00,3,3,3 +696,2015-03-29 00:00:00,3,3,3 +697,2015-03-29 03:00:00,3,3,3 +698,2015-03-29 06:00:00,3,3,3 +699,2015-03-29 09:00:00,3,3,3 +700,2015-03-29 12:00:00,3,3,3 +701,2015-03-29 15:00:00,3,3,3 +702,2015-03-29 18:00:00,3,3,3 +703,2015-03-29 21:00:00,3,3,3 +704,2015-03-30 00:00:00,3,3,3 +705,2015-03-30 03:00:00,3,3,3 +706,2015-03-30 06:00:00,3,3,3 +707,2015-03-30 09:00:00,3,3,3 +708,2015-03-30 12:00:00,3,3,3 +709,2015-03-30 15:00:00,3,3,3 +710,2015-03-30 18:00:00,3,3,3 +711,2015-03-30 21:00:00,3,3,3 +712,2015-03-31 00:00:00,3,3,3 +713,2015-03-31 03:00:00,3,3,3 +714,2015-03-31 06:00:00,3,3,3 +715,2015-03-31 09:00:00,3,3,3 +716,2015-03-31 12:00:00,3,3,3 +717,2015-03-31 15:00:00,3,3,3 +718,2015-03-31 18:00:00,3,3,3 +719,2015-03-31 21:00:00,3,3,3 +720,2015-04-01 00:00:00,3,3,3 +721,2015-04-01 03:00:00,3,3,3 +722,2015-04-01 06:00:00,3,3,3 +723,2015-04-01 09:00:00,3,3,3 +724,2015-04-01 12:00:00,3,3,3 +725,2015-04-01 15:00:00,3,3,3 +726,2015-04-01 18:00:00,3,3,3 +727,2015-04-01 21:00:00,3,3,3 +728,2015-04-02 00:00:00,3,3,3 +729,2015-04-02 03:00:00,3,3,3 +730,2015-04-02 06:00:00,3,3,3 +731,2015-04-02 09:00:00,3,3,3 +732,2015-04-02 12:00:00,3,3,3 +733,2015-04-02 15:00:00,3,3,3 +734,2015-04-02 18:00:00,3,3,3 +735,2015-04-02 21:00:00,3,3,3 +736,2015-04-03 00:00:00,3,3,3 +737,2015-04-03 03:00:00,3,3,3 +738,2015-04-03 06:00:00,3,3,3 +739,2015-04-03 09:00:00,3,3,3 +740,2015-04-03 12:00:00,3,3,3 +741,2015-04-03 15:00:00,3,3,3 +742,2015-04-03 18:00:00,3,3,3 +743,2015-04-03 21:00:00,3,3,3 +744,2015-04-04 00:00:00,3,3,3 +745,2015-04-04 03:00:00,3,3,3 +746,2015-04-04 06:00:00,3,3,3 +747,2015-04-04 09:00:00,3,3,3 +748,2015-04-04 12:00:00,3,3,3 +749,2015-04-04 15:00:00,3,3,3 +750,2015-04-04 18:00:00,3,3,3 +751,2015-04-04 21:00:00,3,3,3 +752,2015-04-05 00:00:00,3,3,3 +753,2015-04-05 03:00:00,3,3,3 +754,2015-04-05 06:00:00,3,3,3 +755,2015-04-05 09:00:00,3,3,3 +756,2015-04-05 12:00:00,3,3,3 +757,2015-04-05 15:00:00,3,3,3 +758,2015-04-05 18:00:00,3,3,3 +759,2015-04-05 21:00:00,3,3,3 +760,2015-04-06 00:00:00,3,3,3 +761,2015-04-06 03:00:00,3,3,3 +762,2015-04-06 06:00:00,3,3,3 +763,2015-04-06 09:00:00,3,3,3 +764,2015-04-06 12:00:00,3,3,3 +765,2015-04-06 15:00:00,3,3,3 +766,2015-04-06 18:00:00,3,3,3 +767,2015-04-06 21:00:00,3,3,3 +768,2015-04-07 00:00:00,3,3,3 +769,2015-04-07 03:00:00,3,3,3 +770,2015-04-07 06:00:00,3,3,3 +771,2015-04-07 09:00:00,3,3,3 +772,2015-04-07 12:00:00,3,3,3 +773,2015-04-07 15:00:00,3,3,3 +774,2015-04-07 18:00:00,3,3,3 +775,2015-04-07 21:00:00,3,3,3 +776,2015-04-08 00:00:00,3,3,3 +777,2015-04-08 03:00:00,3,3,3 +778,2015-04-08 06:00:00,3,3,3 +779,2015-04-08 09:00:00,3,3,3 +780,2015-04-08 12:00:00,3,3,3 +781,2015-04-08 15:00:00,3,3,3 +782,2015-04-08 18:00:00,3,3,3 +783,2015-04-08 21:00:00,3,3,3 +784,2015-04-09 00:00:00,3,3,3 +785,2015-04-09 03:00:00,3,3,3 +786,2015-04-09 06:00:00,3,3,3 +787,2015-04-09 09:00:00,3,3,3 +788,2015-04-09 12:00:00,3,3,3 +789,2015-04-09 15:00:00,3,3,3 +790,2015-04-09 18:00:00,3,3,3 +791,2015-04-09 21:00:00,3,3,3 +792,2015-04-10 00:00:00,3,3,3 +793,2015-04-10 03:00:00,3,3,3 +794,2015-04-10 06:00:00,3,3,3 +795,2015-04-10 09:00:00,3,3,3 +796,2015-04-10 12:00:00,3,3,3 +797,2015-04-10 15:00:00,3,3,3 +798,2015-04-10 18:00:00,3,3,3 +799,2015-04-10 21:00:00,3,3,3 +800,2015-04-11 00:00:00,3,3,3 +801,2015-04-11 03:00:00,3,3,3 +802,2015-04-11 06:00:00,3,3,3 +803,2015-04-11 09:00:00,3,3,3 +804,2015-04-11 12:00:00,3,3,3 +805,2015-04-11 15:00:00,3,3,3 +806,2015-04-11 18:00:00,3,3,3 +807,2015-04-11 21:00:00,3,3,3 +808,2015-04-12 00:00:00,3,3,3 +809,2015-04-12 03:00:00,3,3,3 +810,2015-04-12 06:00:00,3,3,3 +811,2015-04-12 09:00:00,3,3,3 +812,2015-04-12 12:00:00,3,3,3 +813,2015-04-12 15:00:00,3,3,3 +814,2015-04-12 18:00:00,3,3,3 +815,2015-04-12 21:00:00,3,3,3 +816,2015-04-13 00:00:00,3,3,3 +817,2015-04-13 03:00:00,3,3,3 +818,2015-04-13 06:00:00,3,3,3 +819,2015-04-13 09:00:00,3,3,3 +820,2015-04-13 12:00:00,3,3,3 +821,2015-04-13 15:00:00,3,3,3 +822,2015-04-13 18:00:00,3,3,3 +823,2015-04-13 21:00:00,3,3,3 +824,2015-04-14 00:00:00,3,3,3 +825,2015-04-14 03:00:00,3,3,3 +826,2015-04-14 06:00:00,3,3,3 +827,2015-04-14 09:00:00,3,3,3 +828,2015-04-14 12:00:00,3,3,3 +829,2015-04-14 15:00:00,3,3,3 +830,2015-04-14 18:00:00,3,3,3 +831,2015-04-14 21:00:00,3,3,3 +832,2015-04-15 00:00:00,3,3,3 +833,2015-04-15 03:00:00,3,3,3 +834,2015-04-15 06:00:00,3,3,3 +835,2015-04-15 09:00:00,3,3,3 +836,2015-04-15 12:00:00,3,3,3 +837,2015-04-15 15:00:00,3,3,3 +838,2015-04-15 18:00:00,3,3,3 +839,2015-04-15 21:00:00,3,3,3 +840,2015-04-16 00:00:00,3,3,3 +841,2015-04-16 03:00:00,3,3,3 +842,2015-04-16 06:00:00,3,3,3 +843,2015-04-16 09:00:00,3,3,3 +844,2015-04-16 12:00:00,3,3,3 +845,2015-04-16 15:00:00,3,3,3 +846,2015-04-16 18:00:00,3,3,3 +847,2015-04-16 21:00:00,3,3,3 +848,2015-04-17 00:00:00,3,3,3 +849,2015-04-17 03:00:00,3,3,3 +850,2015-04-17 06:00:00,3,3,3 +851,2015-04-17 09:00:00,3,3,3 +852,2015-04-17 12:00:00,3,3,3 +853,2015-04-17 15:00:00,3,3,3 +854,2015-04-17 18:00:00,3,3,3 +855,2015-04-17 21:00:00,3,3,3 +856,2015-04-18 00:00:00,3,3,3 +857,2015-04-18 03:00:00,3,3,3 +858,2015-04-18 06:00:00,3,3,3 +859,2015-04-18 09:00:00,3,3,3 +860,2015-04-18 12:00:00,3,3,3 +861,2015-04-18 15:00:00,3,3,3 +862,2015-04-18 18:00:00,3,3,3 +863,2015-04-18 21:00:00,3,3,3 +864,2015-04-19 00:00:00,3,3,3 +865,2015-04-19 03:00:00,3,3,3 +866,2015-04-19 06:00:00,3,3,3 +867,2015-04-19 09:00:00,3,3,3 +868,2015-04-19 12:00:00,3,3,3 +869,2015-04-19 15:00:00,3,3,3 +870,2015-04-19 18:00:00,3,3,3 +871,2015-04-19 21:00:00,3,3,3 +872,2015-04-20 00:00:00,3,3,3 +873,2015-04-20 03:00:00,3,3,3 +874,2015-04-20 06:00:00,3,3,3 +875,2015-04-20 09:00:00,3,3,3 +876,2015-04-20 12:00:00,3,3,3 +877,2015-04-20 15:00:00,3,3,3 +878,2015-04-20 18:00:00,3,3,3 +879,2015-04-20 21:00:00,3,3,3 +880,2015-04-21 00:00:00,3,3,3 +881,2015-04-21 03:00:00,3,3,3 +882,2015-04-21 06:00:00,3,3,3 +883,2015-04-21 09:00:00,3,3,3 +884,2015-04-21 12:00:00,3,3,3 +885,2015-04-21 15:00:00,3,3,3 +886,2015-04-21 18:00:00,3,3,3 +887,2015-04-21 21:00:00,3,3,3 +888,2015-04-22 00:00:00,3,3,3 +889,2015-04-22 03:00:00,3,3,3 +890,2015-04-22 06:00:00,3,3,3 +891,2015-04-22 09:00:00,3,3,3 +892,2015-04-22 12:00:00,3,3,3 +893,2015-04-22 15:00:00,3,3,3 +894,2015-04-22 18:00:00,3,3,3 +895,2015-04-22 21:00:00,3,3,3 +896,2015-04-23 00:00:00,3,3,3 +897,2015-04-23 03:00:00,3,3,3 +898,2015-04-23 06:00:00,3,3,3 +899,2015-04-23 09:00:00,3,3,3 +900,2015-04-23 12:00:00,3,3,3 +901,2015-04-23 15:00:00,3,3,3 +902,2015-04-23 18:00:00,3,3,3 +903,2015-04-23 21:00:00,3,3,3 +904,2015-04-24 00:00:00,3,3,3 +905,2015-04-24 03:00:00,3,3,3 +906,2015-04-24 06:00:00,3,3,3 +907,2015-04-24 09:00:00,3,3,3 +908,2015-04-24 12:00:00,3,3,3 +909,2015-04-24 15:00:00,3,3,3 +910,2015-04-24 18:00:00,3,3,3 +911,2015-04-24 21:00:00,3,3,3 +912,2015-04-25 00:00:00,3,3,3 +913,2015-04-25 03:00:00,3,3,3 +914,2015-04-25 06:00:00,3,3,3 +915,2015-04-25 09:00:00,3,3,3 +916,2015-04-25 12:00:00,3,3,3 +917,2015-04-25 15:00:00,3,3,3 +918,2015-04-25 18:00:00,3,3,3 +919,2015-04-25 21:00:00,3,3,3 +920,2015-04-26 00:00:00,3,3,3 +921,2015-04-26 03:00:00,3,3,3 +922,2015-04-26 06:00:00,3,3,3 +923,2015-04-26 09:00:00,3,3,3 +924,2015-04-26 12:00:00,3,3,3 +925,2015-04-26 15:00:00,3,3,3 +926,2015-04-26 18:00:00,3,3,3 +927,2015-04-26 21:00:00,3,3,3 +928,2015-04-27 00:00:00,3,3,3 +929,2015-04-27 03:00:00,3,3,3 +930,2015-04-27 06:00:00,3,3,3 +931,2015-04-27 09:00:00,3,3,3 +932,2015-04-27 12:00:00,3,3,3 +933,2015-04-27 15:00:00,3,3,3 +934,2015-04-27 18:00:00,3,3,3 +935,2015-04-27 21:00:00,3,3,3 +936,2015-04-28 00:00:00,3,3,3 +937,2015-04-28 03:00:00,3,3,3 +938,2015-04-28 06:00:00,3,3,3 +939,2015-04-28 09:00:00,3,3,3 +940,2015-04-28 12:00:00,3,3,3 +941,2015-04-28 15:00:00,3,3,3 +942,2015-04-28 18:00:00,3,3,3 +943,2015-04-28 21:00:00,3,3,3 +944,2015-04-29 00:00:00,3,3,3 +945,2015-04-29 03:00:00,3,3,3 +946,2015-04-29 06:00:00,3,3,3 +947,2015-04-29 09:00:00,3,3,3 +948,2015-04-29 12:00:00,3,3,3 +949,2015-04-29 15:00:00,3,3,3 +950,2015-04-29 18:00:00,3,3,3 +951,2015-04-29 21:00:00,3,3,3 +952,2015-04-30 00:00:00,3,3,3 +953,2015-04-30 03:00:00,3,3,3 +954,2015-04-30 06:00:00,3,3,3 +955,2015-04-30 09:00:00,3,3,3 +956,2015-04-30 12:00:00,3,3,3 +957,2015-04-30 15:00:00,3,3,3 +958,2015-04-30 18:00:00,3,3,3 +959,2015-04-30 21:00:00,3,3,3 +960,2015-05-01 00:00:00,3,3,3 +961,2015-05-01 03:00:00,3,3,3 +962,2015-05-01 06:00:00,3,3,3 +963,2015-05-01 09:00:00,3,3,3 +964,2015-05-01 12:00:00,3,3,3 +965,2015-05-01 15:00:00,3,3,3 +966,2015-05-01 18:00:00,3,3,3 +967,2015-05-01 21:00:00,3,3,3 +968,2015-05-02 00:00:00,3,3,3 +969,2015-05-02 03:00:00,3,3,3 +970,2015-05-02 06:00:00,3,3,3 +971,2015-05-02 09:00:00,3,3,3 +972,2015-05-02 12:00:00,3,3,3 +973,2015-05-02 15:00:00,3,3,3 +974,2015-05-02 18:00:00,3,3,3 +975,2015-05-02 21:00:00,3,3,3 +976,2015-05-03 00:00:00,3,3,3 +977,2015-05-03 03:00:00,3,3,3 +978,2015-05-03 06:00:00,3,3,3 +979,2015-05-03 09:00:00,3,3,3 +980,2015-05-03 12:00:00,3,3,3 +981,2015-05-03 15:00:00,3,3,3 +982,2015-05-03 18:00:00,3,3,3 +983,2015-05-03 21:00:00,3,3,3 +984,2015-05-04 00:00:00,3,3,3 +985,2015-05-04 03:00:00,3,3,3 +986,2015-05-04 06:00:00,3,3,3 +987,2015-05-04 09:00:00,3,3,3 +988,2015-05-04 12:00:00,3,3,3 +989,2015-05-04 15:00:00,3,3,3 +990,2015-05-04 18:00:00,3,3,3 +991,2015-05-04 21:00:00,3,3,3 +992,2015-05-05 00:00:00,3,3,3 +993,2015-05-05 03:00:00,3,3,3 +994,2015-05-05 06:00:00,3,3,3 +995,2015-05-05 09:00:00,3,3,3 +996,2015-05-05 12:00:00,3,3,3 +997,2015-05-05 15:00:00,3,3,3 +998,2015-05-05 18:00:00,3,3,3 +999,2015-05-05 21:00:00,3,3,3 +1000,2015-05-06 00:00:00,3,3,3 +1001,2015-05-06 03:00:00,3,3,3 +1002,2015-05-06 06:00:00,3,3,3 +1003,2015-05-06 09:00:00,3,3,3 +1004,2015-05-06 12:00:00,3,3,3 +1005,2015-05-06 15:00:00,3,3,3 +1006,2015-05-06 18:00:00,3,3,3 +1007,2015-05-06 21:00:00,3,3,3 +1008,2015-05-07 00:00:00,3,3,3 +1009,2015-05-07 03:00:00,3,3,3 +1010,2015-05-07 06:00:00,3,3,3 +1011,2015-05-07 09:00:00,3,3,3 +1012,2015-05-07 12:00:00,3,3,3 +1013,2015-05-07 15:00:00,3,3,3 +1014,2015-05-07 18:00:00,3,3,3 +1015,2015-05-07 21:00:00,3,3,3 +1016,2015-05-08 00:00:00,3,3,3 +1017,2015-05-08 03:00:00,3,3,3 +1018,2015-05-08 06:00:00,3,3,3 +1019,2015-05-08 09:00:00,3,3,3 +1020,2015-05-08 12:00:00,3,3,3 +1021,2015-05-08 15:00:00,3,3,3 +1022,2015-05-08 18:00:00,3,3,3 +1023,2015-05-08 21:00:00,3,3,3 +1024,2015-05-09 00:00:00,3,3,3 +1025,2015-05-09 03:00:00,3,3,3 +1026,2015-05-09 06:00:00,3,3,3 +1027,2015-05-09 09:00:00,3,3,3 +1028,2015-05-09 12:00:00,3,3,3 +1029,2015-05-09 15:00:00,3,3,3 +1030,2015-05-09 18:00:00,3,3,3 +1031,2015-05-09 21:00:00,3,3,3 +1032,2015-05-10 00:00:00,3,3,3 +1033,2015-05-10 03:00:00,3,3,3 +1034,2015-05-10 06:00:00,3,3,3 +1035,2015-05-10 09:00:00,3,3,3 +1036,2015-05-10 12:00:00,3,3,3 +1037,2015-05-10 15:00:00,3,3,3 +1038,2015-05-10 18:00:00,3,3,3 +1039,2015-05-10 21:00:00,3,3,3 +1040,2015-05-11 00:00:00,3,3,3 +1041,2015-05-11 03:00:00,3,3,3 +1042,2015-05-11 06:00:00,3,3,3 +1043,2015-05-11 09:00:00,3,3,3 +1044,2015-05-11 12:00:00,3,3,3 +1045,2015-05-11 15:00:00,3,3,3 +1046,2015-05-11 18:00:00,3,3,3 +1047,2015-05-11 21:00:00,3,3,3 +1048,2015-05-12 00:00:00,3,3,3 +1049,2015-05-12 03:00:00,3,3,3 +1050,2015-05-12 06:00:00,3,3,3 +1051,2015-05-12 09:00:00,3,3,3 +1052,2015-05-12 12:00:00,3,3,3 +1053,2015-05-12 15:00:00,3,3,3 +1054,2015-05-12 18:00:00,3,3,3 +1055,2015-05-12 21:00:00,3,3,3 +1056,2015-05-13 00:00:00,3,3,3 +1057,2015-05-13 03:00:00,3,3,3 +1058,2015-05-13 06:00:00,3,3,3 +1059,2015-05-13 09:00:00,3,3,3 +1060,2015-05-13 12:00:00,3,3,3 +1061,2015-05-13 15:00:00,3,3,3 +1062,2015-05-13 18:00:00,3,3,3 +1063,2015-05-13 21:00:00,3,3,3 +1064,2015-05-14 00:00:00,3,3,3 +1065,2015-05-14 03:00:00,3,3,3 +1066,2015-05-14 06:00:00,3,3,3 +1067,2015-05-14 09:00:00,3,3,3 +1068,2015-05-14 12:00:00,3,3,3 +1069,2015-05-14 15:00:00,3,3,3 +1070,2015-05-14 18:00:00,3,3,3 +1071,2015-05-14 21:00:00,3,3,3 +1072,2015-05-15 00:00:00,3,3,3 +1073,2015-05-15 03:00:00,3,3,3 +1074,2015-05-15 06:00:00,3,3,3 +1075,2015-05-15 09:00:00,3,3,3 +1076,2015-05-15 12:00:00,3,3,3 +1077,2015-05-15 15:00:00,3,3,3 +1078,2015-05-15 18:00:00,3,3,3 +1079,2015-05-15 21:00:00,3,3,3 +1080,2015-05-16 00:00:00,3,3,3 +1081,2015-05-16 03:00:00,3,3,3 +1082,2015-05-16 06:00:00,3,3,3 +1083,2015-05-16 09:00:00,3,3,3 +1084,2015-05-16 12:00:00,3,3,3 +1085,2015-05-16 15:00:00,3,3,3 +1086,2015-05-16 18:00:00,3,3,3 +1087,2015-05-16 21:00:00,3,3,3 +1088,2015-05-17 00:00:00,3,3,3 +1089,2015-05-17 03:00:00,3,3,3 +1090,2015-05-17 06:00:00,3,3,3 +1091,2015-05-17 09:00:00,3,3,3 +1092,2015-05-17 12:00:00,3,3,3 +1093,2015-05-17 15:00:00,3,3,3 +1094,2015-05-17 18:00:00,3,3,3 +1095,2015-05-17 21:00:00,3,3,3 +1096,2015-05-18 00:00:00,3,3,3 +1097,2015-05-18 03:00:00,3,3,3 +1098,2015-05-18 06:00:00,3,3,3 +1099,2015-05-18 09:00:00,3,3,3 +1100,2015-05-18 12:00:00,3,3,3 +1101,2015-05-18 15:00:00,3,3,3 +1102,2015-05-18 18:00:00,3,3,3 +1103,2015-05-18 21:00:00,3,3,3 +1104,2015-05-19 00:00:00,3,3,3 +1105,2015-05-19 03:00:00,3,3,3 +1106,2015-05-19 06:00:00,3,3,3 +1107,2015-05-19 09:00:00,3,3,3 +1108,2015-05-19 12:00:00,3,3,3 +1109,2015-05-19 15:00:00,3,3,3 +1110,2015-05-19 18:00:00,3,3,3 +1111,2015-05-19 21:00:00,3,3,3 +1112,2015-05-20 00:00:00,3,3,3 +1113,2015-05-20 03:00:00,3,3,3 +1114,2015-05-20 06:00:00,3,3,3 +1115,2015-05-20 09:00:00,3,3,3 +1116,2015-05-20 12:00:00,3,3,3 +1117,2015-05-20 15:00:00,3,3,3 +1118,2015-05-20 18:00:00,3,3,3 +1119,2015-05-20 21:00:00,3,3,3 +1120,2015-05-21 00:00:00,3,3,3 +1121,2015-05-21 03:00:00,3,3,3 +1122,2015-05-21 06:00:00,3,3,3 +1123,2015-05-21 09:00:00,3,3,3 +1124,2015-05-21 12:00:00,3,3,3 +1125,2015-05-21 15:00:00,3,3,3 +1126,2015-05-21 18:00:00,3,3,3 +1127,2015-05-21 21:00:00,3,3,3 +1128,2015-05-22 00:00:00,3,3,3 +1129,2015-05-22 03:00:00,3,3,3 +1130,2015-05-22 06:00:00,3,3,3 +1131,2015-05-22 09:00:00,3,3,3 +1132,2015-05-22 12:00:00,3,3,3 +1133,2015-05-22 15:00:00,3,3,3 +1134,2015-05-22 18:00:00,3,3,3 +1135,2015-05-22 21:00:00,3,3,3 +1136,2015-05-23 00:00:00,3,3,3 +1137,2015-05-23 03:00:00,3,3,3 +1138,2015-05-23 06:00:00,3,3,3 +1139,2015-05-23 09:00:00,3,3,3 +1140,2015-05-23 12:00:00,3,3,3 +1141,2015-05-23 15:00:00,3,3,3 +1142,2015-05-23 18:00:00,3,3,3 +1143,2015-05-23 21:00:00,3,3,3 +1144,2015-05-24 00:00:00,3,3,3 +1145,2015-05-24 03:00:00,3,3,3 +1146,2015-05-24 06:00:00,3,3,3 +1147,2015-05-24 09:00:00,3,3,3 +1148,2015-05-24 12:00:00,3,3,3 +1149,2015-05-24 15:00:00,3,3,3 +1150,2015-05-24 18:00:00,3,3,3 +1151,2015-05-24 21:00:00,3,3,3 +1152,2015-05-25 00:00:00,3,3,3 +1153,2015-05-25 03:00:00,3,3,3 +1154,2015-05-25 06:00:00,3,3,3 +1155,2015-05-25 09:00:00,3,3,3 +1156,2015-05-25 12:00:00,3,3,3 +1157,2015-05-25 15:00:00,3,3,3 +1158,2015-05-25 18:00:00,3,3,3 +1159,2015-05-25 21:00:00,3,3,3 +1160,2015-05-26 00:00:00,3,3,3 +1161,2015-05-26 03:00:00,3,3,3 +1162,2015-05-26 06:00:00,3,3,3 +1163,2015-05-26 09:00:00,3,3,3 +1164,2015-05-26 12:00:00,3,3,3 +1165,2015-05-26 15:00:00,3,3,3 +1166,2015-05-26 18:00:00,3,3,3 +1167,2015-05-26 21:00:00,3,3,3 +1168,2015-05-27 00:00:00,3,3,3 +1169,2015-05-27 03:00:00,3,3,3 +1170,2015-05-27 06:00:00,3,3,3 +1171,2015-05-27 09:00:00,3,3,3 +1172,2015-05-27 12:00:00,3,3,3 +1173,2015-05-27 15:00:00,3,3,3 +1174,2015-05-27 18:00:00,3,3,3 +1175,2015-05-27 21:00:00,3,3,3 +1176,2015-05-28 00:00:00,3,3,3 +1177,2015-05-28 03:00:00,3,3,3 +1178,2015-05-28 06:00:00,3,3,3 +1179,2015-05-28 09:00:00,3,3,3 +1180,2015-05-28 12:00:00,3,3,3 +1181,2015-05-28 15:00:00,3,3,3 +1182,2015-05-28 18:00:00,3,3,3 +1183,2015-05-28 21:00:00,3,3,3 +1184,2015-05-29 00:00:00,3,3,3 +1185,2015-05-29 03:00:00,3,3,3 +1186,2015-05-29 06:00:00,3,3,3 +1187,2015-05-29 09:00:00,3,3,3 +1188,2015-05-29 12:00:00,3,3,3 +1189,2015-05-29 15:00:00,3,3,3 +1190,2015-05-29 18:00:00,3,3,3 +1191,2015-05-29 21:00:00,3,3,3 +1192,2015-05-30 00:00:00,3,3,3 +1193,2015-05-30 03:00:00,3,3,3 +1194,2015-05-30 06:00:00,3,3,3 +1195,2015-05-30 09:00:00,3,3,3 +1196,2015-05-30 12:00:00,3,3,3 +1197,2015-05-30 15:00:00,3,3,3 +1198,2015-05-30 18:00:00,3,3,3 +1199,2015-05-30 21:00:00,3,3,3 +1200,2015-05-31 00:00:00,3,3,3 +1201,2015-05-31 03:00:00,3,3,3 +1202,2015-05-31 06:00:00,3,3,3 +1203,2015-05-31 09:00:00,3,3,3 +1204,2015-05-31 12:00:00,3,3,3 +1205,2015-05-31 15:00:00,3,3,3 +1206,2015-05-31 18:00:00,3,3,3 +1207,2015-05-31 21:00:00,3,3,3 +1208,2015-06-01 00:00:00,3,3,3 +1209,2015-06-01 03:00:00,3,3,3 +1210,2015-06-01 06:00:00,3,3,3 +1211,2015-06-01 09:00:00,3,3,3 +1212,2015-06-01 12:00:00,3,3,3 +1213,2015-06-01 15:00:00,3,3,3 +1214,2015-06-01 18:00:00,3,3,3 +1215,2015-06-01 21:00:00,3,3,3 +1216,2015-06-02 00:00:00,3,3,3 +1217,2015-06-02 03:00:00,3,3,3 +1218,2015-06-02 06:00:00,3,3,3 +1219,2015-06-02 09:00:00,3,3,3 +1220,2015-06-02 12:00:00,3,3,3 +1221,2015-06-02 15:00:00,3,3,3 +1222,2015-06-02 18:00:00,3,3,3 +1223,2015-06-02 21:00:00,3,3,3 +1224,2015-06-03 00:00:00,3,3,3 +1225,2015-06-03 03:00:00,3,3,3 +1226,2015-06-03 06:00:00,3,3,3 +1227,2015-06-03 09:00:00,3,3,3 +1228,2015-06-03 12:00:00,3,3,3 +1229,2015-06-03 15:00:00,3,3,3 +1230,2015-06-03 18:00:00,3,3,3 +1231,2015-06-03 21:00:00,3,3,3 +1232,2015-06-04 00:00:00,3,3,3 +1233,2015-06-04 03:00:00,3,3,3 +1234,2015-06-04 06:00:00,3,3,3 +1235,2015-06-04 09:00:00,3,3,3 +1236,2015-06-04 12:00:00,3,3,3 +1237,2015-06-04 15:00:00,3,3,3 +1238,2015-06-04 18:00:00,3,3,3 +1239,2015-06-04 21:00:00,3,3,3 +1240,2015-06-05 00:00:00,3,3,3 +1241,2015-06-05 03:00:00,3,3,3 +1242,2015-06-05 06:00:00,3,3,3 +1243,2015-06-05 09:00:00,3,3,3 +1244,2015-06-05 12:00:00,3,3,3 +1245,2015-06-05 15:00:00,3,3,3 +1246,2015-06-05 18:00:00,3,3,3 +1247,2015-06-05 21:00:00,3,3,3 +1248,2015-06-06 00:00:00,3,3,3 +1249,2015-06-06 03:00:00,3,3,3 +1250,2015-06-06 06:00:00,3,3,3 +1251,2015-06-06 09:00:00,3,3,3 +1252,2015-06-06 12:00:00,3,3,3 +1253,2015-06-06 15:00:00,3,3,3 +1254,2015-06-06 18:00:00,3,3,3 +1255,2015-06-06 21:00:00,3,3,3 +1256,2015-06-07 00:00:00,3,3,3 +1257,2015-06-07 03:00:00,3,3,3 +1258,2015-06-07 06:00:00,3,3,3 +1259,2015-06-07 09:00:00,3,3,3 +1260,2015-06-07 12:00:00,3,3,3 +1261,2015-06-07 15:00:00,3,3,3 +1262,2015-06-07 18:00:00,3,3,3 +1263,2015-06-07 21:00:00,3,3,3 +1264,2015-06-08 00:00:00,3,3,3 +1265,2015-06-08 03:00:00,3,3,3 +1266,2015-06-08 06:00:00,3,3,3 +1267,2015-06-08 09:00:00,3,3,3 +1268,2015-06-08 12:00:00,3,3,3 +1269,2015-06-08 15:00:00,3,3,3 +1270,2015-06-08 18:00:00,3,3,3 +1271,2015-06-08 21:00:00,3,3,3 +1272,2015-06-09 00:00:00,3,3,3 +1273,2015-06-09 03:00:00,3,3,3 +1274,2015-06-09 06:00:00,3,3,3 +1275,2015-06-09 09:00:00,3,3,3 +1276,2015-06-09 12:00:00,3,3,3 +1277,2015-06-09 15:00:00,3,3,3 +1278,2015-06-09 18:00:00,3,3,3 +1279,2015-06-09 21:00:00,3,3,3 +1280,2015-06-10 00:00:00,3,3,3 +1281,2015-06-10 03:00:00,3,3,3 +1282,2015-06-10 06:00:00,3,3,3 +1283,2015-06-10 09:00:00,3,3,3 +1284,2015-06-10 12:00:00,3,3,3 +1285,2015-06-10 15:00:00,3,3,3 +1286,2015-06-10 18:00:00,3,3,3 +1287,2015-06-10 21:00:00,3,3,3 +1288,2015-06-11 00:00:00,3,3,3 +1289,2015-06-11 03:00:00,3,3,3 +1290,2015-06-11 06:00:00,3,3,3 +1291,2015-06-11 09:00:00,3,3,3 +1292,2015-06-11 12:00:00,3,3,3 +1293,2015-06-11 15:00:00,3,3,3 +1294,2015-06-11 18:00:00,3,3,3 +1295,2015-06-11 21:00:00,3,3,3 +1296,2015-06-12 00:00:00,3,3,3 +1297,2015-06-12 03:00:00,3,3,3 +1298,2015-06-12 06:00:00,3,3,3 +1299,2015-06-12 09:00:00,3,3,3 +1300,2015-06-12 12:00:00,3,3,3 +1301,2015-06-12 15:00:00,3,3,3 +1302,2015-06-12 18:00:00,3,3,3 +1303,2015-06-12 21:00:00,3,3,3 +1304,2015-06-13 00:00:00,3,3,3 +1305,2015-06-13 03:00:00,3,3,3 +1306,2015-06-13 06:00:00,3,3,3 +1307,2015-06-13 09:00:00,3,3,3 +1308,2015-06-13 12:00:00,3,3,3 +1309,2015-06-13 15:00:00,3,3,3 +1310,2015-06-13 18:00:00,3,3,3 +1311,2015-06-13 21:00:00,3,3,3 +1312,2015-06-14 00:00:00,3,3,3 +1313,2015-06-14 03:00:00,3,3,3 +1314,2015-06-14 06:00:00,3,3,3 +1315,2015-06-14 09:00:00,3,3,3 +1316,2015-06-14 12:00:00,3,3,3 +1317,2015-06-14 15:00:00,3,3,3 +1318,2015-06-14 18:00:00,3,3,3 +1319,2015-06-14 21:00:00,3,3,3 +1320,2015-06-15 00:00:00,3,3,3 +1321,2015-06-15 03:00:00,3,3,3 +1322,2015-06-15 06:00:00,3,3,3 +1323,2015-06-15 09:00:00,3,3,3 +1324,2015-06-15 12:00:00,3,3,3 +1325,2015-06-15 15:00:00,3,3,3 +1326,2015-06-15 18:00:00,3,3,3 +1327,2015-06-15 21:00:00,3,3,3 +1328,2015-06-16 00:00:00,3,3,3 +1329,2015-06-16 03:00:00,3,3,3 +1330,2015-06-16 06:00:00,3,3,3 +1331,2015-06-16 09:00:00,3,3,3 +1332,2015-06-16 12:00:00,3,3,3 +1333,2015-06-16 15:00:00,3,3,3 +1334,2015-06-16 18:00:00,3,3,3 +1335,2015-06-16 21:00:00,3,3,3 +1336,2015-06-17 00:00:00,3,3,3 +1337,2015-06-17 03:00:00,3,3,3 +1338,2015-06-17 06:00:00,3,3,3 +1339,2015-06-17 09:00:00,3,3,3 +1340,2015-06-17 12:00:00,3,3,3 +1341,2015-06-17 15:00:00,3,3,3 +1342,2015-06-17 18:00:00,3,3,3 +1343,2015-06-17 21:00:00,3,3,3 +1344,2015-06-18 00:00:00,3,3,3 +1345,2015-06-18 03:00:00,3,3,3 +1346,2015-06-18 06:00:00,3,3,3 +1347,2015-06-18 09:00:00,3,3,3 +1348,2015-06-18 12:00:00,3,3,3 +1349,2015-06-18 15:00:00,3,3,3 +1350,2015-06-18 18:00:00,3,3,3 +1351,2015-06-18 21:00:00,3,3,3 +1352,2015-06-19 00:00:00,3,3,3 +1353,2015-06-19 03:00:00,3,3,3 +1354,2015-06-19 06:00:00,3,3,3 +1355,2015-06-19 09:00:00,3,3,3 +1356,2015-06-19 12:00:00,3,3,3 +1357,2015-06-19 15:00:00,3,3,3 +1358,2015-06-19 18:00:00,3,3,3 +1359,2015-06-19 21:00:00,3,3,3 +1360,2015-06-20 00:00:00,3,3,3 +1361,2015-06-20 03:00:00,3,3,3 +1362,2015-06-20 06:00:00,3,3,3 +1363,2015-06-20 09:00:00,3,3,3 +1364,2015-06-20 12:00:00,3,3,3 +1365,2015-06-20 15:00:00,3,3,3 +1366,2015-06-20 18:00:00,3,3,3 +1367,2015-06-20 21:00:00,3,3,3 +1368,2015-06-21 00:00:00,3,3,3 +1369,2015-06-21 03:00:00,3,3,3 +1370,2015-06-21 06:00:00,3,3,3 +1371,2015-06-21 09:00:00,3,3,3 +1372,2015-06-21 12:00:00,3,3,3 +1373,2015-06-21 15:00:00,3,3,3 +1374,2015-06-21 18:00:00,3,3,3 +1375,2015-06-21 21:00:00,3,3,3 +1376,2015-06-22 00:00:00,3,3,3 +1377,2015-06-22 03:00:00,3,3,3 +1378,2015-06-22 06:00:00,3,3,3 +1379,2015-06-22 09:00:00,3,3,3 +1380,2015-06-22 12:00:00,3,3,3 +1381,2015-06-22 15:00:00,3,3,3 +1382,2015-06-22 18:00:00,3,3,3 +1383,2015-06-22 21:00:00,3,3,3 +1384,2015-06-23 00:00:00,3,3,3 +1385,2015-06-23 03:00:00,3,3,3 +1386,2015-06-23 06:00:00,3,3,3 +1387,2015-06-23 09:00:00,3,3,3 +1388,2015-06-23 12:00:00,3,3,3 +1389,2015-06-23 15:00:00,3,3,3 +1390,2015-06-23 18:00:00,3,3,3 +1391,2015-06-23 21:00:00,3,3,3 +1392,2015-06-24 00:00:00,3,3,3 +1393,2015-06-24 03:00:00,3,3,3 +1394,2015-06-24 06:00:00,3,3,3 +1395,2015-06-24 09:00:00,3,3,3 +1396,2015-06-24 12:00:00,3,3,3 +1397,2015-06-24 15:00:00,3,3,3 +1398,2015-06-24 18:00:00,3,3,3 +1399,2015-06-24 21:00:00,3,3,3 +1400,2015-06-25 00:00:00,3,3,3 +1401,2015-06-25 03:00:00,3,3,3 +1402,2015-06-25 06:00:00,3,3,3 +1403,2015-06-25 09:00:00,3,3,3 +1404,2015-06-25 12:00:00,3,3,3 +1405,2015-06-25 15:00:00,3,3,3 +1406,2015-06-25 18:00:00,3,3,3 +1407,2015-06-25 21:00:00,3,3,3 +1408,2015-06-26 00:00:00,3,3,3 +1409,2015-06-26 03:00:00,3,3,3 +1410,2015-06-26 06:00:00,3,3,3 +1411,2015-06-26 09:00:00,3,3,3 +1412,2015-06-26 12:00:00,3,3,3 +1413,2015-06-26 15:00:00,3,3,3 +1414,2015-06-26 18:00:00,3,3,3 +1415,2015-06-26 21:00:00,3,3,3 +1416,2015-06-27 00:00:00,3,3,3 +1417,2015-06-27 03:00:00,3,3,3 +1418,2015-06-27 06:00:00,3,3,3 +1419,2015-06-27 09:00:00,3,3,3 +1420,2015-06-27 12:00:00,3,3,3 +1421,2015-06-27 15:00:00,3,3,3 +1422,2015-06-27 18:00:00,3,3,3 +1423,2015-06-27 21:00:00,3,3,3 +1424,2015-06-28 00:00:00,3,3,3 +1425,2015-06-28 03:00:00,3,3,3 +1426,2015-06-28 06:00:00,3,3,3 +1427,2015-06-28 09:00:00,3,3,3 +1428,2015-06-28 12:00:00,3,3,3 +1429,2015-06-28 15:00:00,3,3,3 +1430,2015-06-28 18:00:00,3,3,3 +1431,2015-06-28 21:00:00,3,3,3 +1432,2015-06-29 00:00:00,3,3,3 +1433,2015-06-29 03:00:00,3,3,3 +1434,2015-06-29 06:00:00,3,3,3 +1435,2015-06-29 09:00:00,3,3,3 +1436,2015-06-29 12:00:00,3,3,3 +1437,2015-06-29 15:00:00,3,3,3 +1438,2015-06-29 18:00:00,3,3,3 +1439,2015-06-29 21:00:00,3,3,3 +1440,2015-06-30 00:00:00,3,3,3 +1441,2015-06-30 03:00:00,3,3,3 +1442,2015-06-30 06:00:00,3,3,3 +1443,2015-06-30 09:00:00,3,3,3 +1444,2015-06-30 12:00:00,3,3,3 +1445,2015-06-30 15:00:00,3,3,3 +1446,2015-06-30 18:00:00,3,3,3 +1447,2015-06-30 21:00:00,3,3,3 +1448,2015-07-01 00:00:00,3,3,3 +1449,2015-07-01 03:00:00,3,3,3 +1450,2015-07-01 06:00:00,3,3,3 +1451,2015-07-01 09:00:00,3,3,3 +1452,2015-07-01 12:00:00,3,3,3 +1453,2015-07-01 15:00:00,3,3,3 +1454,2015-07-01 18:00:00,3,3,3 +1455,2015-07-01 21:00:00,3,3,3 +1456,2015-07-02 00:00:00,3,3,3 +1457,2015-07-02 03:00:00,3,3,3 +1458,2015-07-02 06:00:00,3,3,3 +1459,2015-07-02 09:00:00,3,3,3 +1460,2015-07-02 12:00:00,3,3,3 +1461,2015-07-02 15:00:00,3,3,3 +1462,2015-07-02 18:00:00,3,3,3 +1463,2015-07-02 21:00:00,3,3,3 +1464,2015-07-03 00:00:00,3,3,3 +1465,2015-07-03 03:00:00,3,3,3 +1466,2015-07-03 06:00:00,3,3,3 +1467,2015-07-03 09:00:00,3,3,3 +1468,2015-07-03 12:00:00,3,3,3 +1469,2015-07-03 15:00:00,3,3,3 +1470,2015-07-03 18:00:00,3,3,3 +1471,2015-07-03 21:00:00,3,3,3 +1472,2015-07-04 00:00:00,3,3,3 +1473,2015-07-04 03:00:00,3,3,3 +1474,2015-07-04 06:00:00,3,3,3 +1475,2015-07-04 09:00:00,3,3,3 +1476,2015-07-04 12:00:00,3,3,3 +1477,2015-07-04 15:00:00,3,3,3 +1478,2015-07-04 18:00:00,3,3,3 +1479,2015-07-04 21:00:00,3,3,3 +1480,2015-07-05 00:00:00,3,3,3 +1481,2015-07-05 03:00:00,3,3,3 +1482,2015-07-05 06:00:00,3,3,3 +1483,2015-07-05 09:00:00,3,3,3 +1484,2015-07-05 12:00:00,3,3,3 +1485,2015-07-05 15:00:00,3,3,3 +1486,2015-07-05 18:00:00,3,3,3 +1487,2015-07-05 21:00:00,3,3,3 +1488,2015-07-06 00:00:00,3,3,3 +1489,2015-07-06 03:00:00,3,3,3 +1490,2015-07-06 06:00:00,3,3,3 +1491,2015-07-06 09:00:00,3,3,3 +1492,2015-07-06 12:00:00,3,3,3 +1493,2015-07-06 15:00:00,3,3,3 +1494,2015-07-06 18:00:00,3,3,3 +1495,2015-07-06 21:00:00,3,3,3 +1496,2015-07-07 00:00:00,3,3,3 +1497,2015-07-07 03:00:00,3,3,3 +1498,2015-07-07 06:00:00,3,3,3 +1499,2015-07-07 09:00:00,3,3,3 +1500,2015-07-07 12:00:00,3,3,3 +1501,2015-07-07 15:00:00,3,3,3 +1502,2015-07-07 18:00:00,3,3,3 +1503,2015-07-07 21:00:00,3,3,3 +1504,2015-07-08 00:00:00,3,3,3 +1505,2015-07-08 03:00:00,3,3,3 +1506,2015-07-08 06:00:00,3,3,3 +1507,2015-07-08 09:00:00,3,3,3 +1508,2015-07-08 12:00:00,3,3,3 +1509,2015-07-08 15:00:00,3,3,3 +1510,2015-07-08 18:00:00,3,3,3 +1511,2015-07-08 21:00:00,3,3,3 +1512,2015-07-09 00:00:00,3,3,3 +1513,2015-07-09 03:00:00,3,3,3 +1514,2015-07-09 06:00:00,3,3,3 +1515,2015-07-09 09:00:00,3,3,3 +1516,2015-07-09 12:00:00,3,3,3 +1517,2015-07-09 15:00:00,3,3,3 +1518,2015-07-09 18:00:00,3,3,3 +1519,2015-07-09 21:00:00,3,3,3 +1520,2015-07-10 00:00:00,3,3,3 +1521,2015-07-10 03:00:00,3,3,3 +1522,2015-07-10 06:00:00,3,3,3 +1523,2015-07-10 09:00:00,3,3,3 +1524,2015-07-10 12:00:00,3,3,3 +1525,2015-07-10 15:00:00,3,3,3 +1526,2015-07-10 18:00:00,3,3,3 +1527,2015-07-10 21:00:00,3,3,3 +1528,2015-07-11 00:00:00,3,3,3 +1529,2015-07-11 03:00:00,3,3,3 +1530,2015-07-11 06:00:00,3,3,3 +1531,2015-07-11 09:00:00,3,3,3 +1532,2015-07-11 12:00:00,3,3,3 +1533,2015-07-11 15:00:00,3,3,3 +1534,2015-07-11 18:00:00,3,3,3 +1535,2015-07-11 21:00:00,3,3,3 +1536,2015-07-12 00:00:00,3,3,3 +1537,2015-07-12 03:00:00,3,3,3 +1538,2015-07-12 06:00:00,3,3,3 +1539,2015-07-12 09:00:00,3,3,3 +1540,2015-07-12 12:00:00,3,3,3 +1541,2015-07-12 15:00:00,3,3,3 +1542,2015-07-12 18:00:00,3,3,3 +1543,2015-07-12 21:00:00,3,3,3 +1544,2015-07-13 00:00:00,3,3,3 +1545,2015-07-13 03:00:00,3,3,3 +1546,2015-07-13 06:00:00,3,3,3 +1547,2015-07-13 09:00:00,3,3,3 +1548,2015-07-13 12:00:00,3,3,3 +1549,2015-07-13 15:00:00,3,3,3 +1550,2015-07-13 18:00:00,3,3,3 +1551,2015-07-13 21:00:00,3,3,3 +1552,2015-07-14 00:00:00,3,3,3 +1553,2015-07-14 03:00:00,3,3,3 +1554,2015-07-14 06:00:00,3,3,3 +1555,2015-07-14 09:00:00,3,3,3 +1556,2015-07-14 12:00:00,3,3,3 +1557,2015-07-14 15:00:00,3,3,3 +1558,2015-07-14 18:00:00,3,3,3 +1559,2015-07-14 21:00:00,3,3,3 +1560,2015-07-15 00:00:00,3,3,3 +1561,2015-07-15 03:00:00,3,3,3 +1562,2015-07-15 06:00:00,3,3,3 +1563,2015-07-15 09:00:00,3,3,3 +1564,2015-07-15 12:00:00,3,3,3 +1565,2015-07-15 15:00:00,3,3,3 +1566,2015-07-15 18:00:00,3,3,3 +1567,2015-07-15 21:00:00,3,3,3 +1568,2015-07-16 00:00:00,3,3,3 +1569,2015-07-16 03:00:00,3,3,3 +1570,2015-07-16 06:00:00,3,3,3 +1571,2015-07-16 09:00:00,3,3,3 +1572,2015-07-16 12:00:00,3,3,3 +1573,2015-07-16 15:00:00,3,3,3 +1574,2015-07-16 18:00:00,3,3,3 +1575,2015-07-16 21:00:00,3,3,3 +1576,2015-07-17 00:00:00,3,3,3 +1577,2015-07-17 03:00:00,3,3,3 +1578,2015-07-17 06:00:00,3,3,3 +1579,2015-07-17 09:00:00,3,3,3 +1580,2015-07-17 12:00:00,3,3,3 +1581,2015-07-17 15:00:00,3,3,3 +1582,2015-07-17 18:00:00,3,3,3 +1583,2015-07-17 21:00:00,3,3,3 +1584,2015-07-18 00:00:00,3,3,3 +1585,2015-07-18 03:00:00,3,3,3 +1586,2015-07-18 06:00:00,3,3,3 +1587,2015-07-18 09:00:00,3,3,3 +1588,2015-07-18 12:00:00,3,3,3 +1589,2015-07-18 15:00:00,3,3,3 +1590,2015-07-18 18:00:00,3,3,3 +1591,2015-07-18 21:00:00,3,3,3 +1592,2015-07-19 00:00:00,3,3,3 +1593,2015-07-19 03:00:00,3,3,3 +1594,2015-07-19 06:00:00,3,3,3 +1595,2015-07-19 09:00:00,3,3,3 +1596,2015-07-19 12:00:00,3,3,3 +1597,2015-07-19 15:00:00,3,3,3 +1598,2015-07-19 18:00:00,3,3,3 +1599,2015-07-19 21:00:00,3,3,3 +1600,2015-07-20 00:00:00,3,3,3 +1601,2015-07-20 03:00:00,3,3,3 +1602,2015-07-20 06:00:00,3,3,3 +1603,2015-07-20 09:00:00,3,3,3 +1604,2015-07-20 12:00:00,3,3,3 +1605,2015-07-20 15:00:00,3,3,3 +1606,2015-07-20 18:00:00,3,3,3 +1607,2015-07-20 21:00:00,3,3,3 +1608,2015-07-21 00:00:00,3,3,3 +1609,2015-07-21 03:00:00,3,3,3 +1610,2015-07-21 06:00:00,3,3,3 +1611,2015-07-21 09:00:00,3,3,3 +1612,2015-07-21 12:00:00,3,3,3 +1613,2015-07-21 15:00:00,3,3,3 +1614,2015-07-21 18:00:00,3,3,3 +1615,2015-07-21 21:00:00,3,3,3 +1616,2015-07-22 00:00:00,3,3,3 +1617,2015-07-22 03:00:00,3,3,3 +1618,2015-07-22 06:00:00,3,3,3 +1619,2015-07-22 09:00:00,3,3,3 +1620,2015-07-22 12:00:00,3,3,3 +1621,2015-07-22 15:00:00,3,3,3 +1622,2015-07-22 18:00:00,3,3,3 +1623,2015-07-22 21:00:00,3,3,3 +1624,2015-07-23 00:00:00,3,3,3 +1625,2015-07-23 03:00:00,3,3,3 +1626,2015-07-23 06:00:00,3,3,3 +1627,2015-07-23 09:00:00,3,3,3 +1628,2015-07-23 12:00:00,3,3,3 +1629,2015-07-23 15:00:00,3,3,3 +1630,2015-07-23 18:00:00,3,3,3 +1631,2015-07-23 21:00:00,3,3,3 +1632,2015-07-24 00:00:00,3,3,3 +1633,2015-07-24 03:00:00,3,3,3 +1634,2015-07-24 06:00:00,3,3,3 +1635,2015-07-24 09:00:00,3,3,3 +1636,2015-07-24 12:00:00,3,3,3 +1637,2015-07-24 15:00:00,3,3,3 +1638,2015-07-24 18:00:00,3,3,3 +1639,2015-07-24 21:00:00,3,3,3 +1640,2015-07-25 00:00:00,3,3,3 +1641,2015-07-25 03:00:00,3,3,3 +1642,2015-07-25 06:00:00,3,3,3 +1643,2015-07-25 09:00:00,3,3,3 +1644,2015-07-25 12:00:00,3,3,3 +1645,2015-07-25 15:00:00,3,3,3 +1646,2015-07-25 18:00:00,3,3,3 +1647,2015-07-25 21:00:00,3,3,3 +1648,2015-07-26 00:00:00,3,3,3 +1649,2015-07-26 03:00:00,3,3,3 +1650,2015-07-26 06:00:00,3,3,3 +1651,2015-07-26 09:00:00,3,3,3 +1652,2015-07-26 12:00:00,3,3,3 +1653,2015-07-26 15:00:00,3,3,3 +1654,2015-07-26 18:00:00,3,3,3 +1655,2015-07-26 21:00:00,3,3,3 +1656,2015-07-27 00:00:00,3,3,3 +1657,2015-07-27 03:00:00,3,3,3 +1658,2015-07-27 06:00:00,3,3,3 +1659,2015-07-27 09:00:00,3,3,3 +1660,2015-07-27 12:00:00,3,3,3 +1661,2015-07-27 15:00:00,3,3,3 +1662,2015-07-27 18:00:00,3,3,3 +1663,2015-07-27 21:00:00,3,3,3 +1664,2015-07-28 00:00:00,3,3,3 +1665,2015-07-28 03:00:00,3,3,3 +1666,2015-07-28 06:00:00,3,3,3 +1667,2015-07-28 09:00:00,3,3,3 +1668,2015-07-28 12:00:00,3,3,3 +1669,2015-07-28 15:00:00,3,3,3 +1670,2015-07-28 18:00:00,3,3,3 +1671,2015-07-28 21:00:00,3,3,3 +1672,2015-07-29 00:00:00,3,3,3 +1673,2015-07-29 03:00:00,3,3,3 +1674,2015-07-29 06:00:00,3,3,3 +1675,2015-07-29 09:00:00,3,3,3 +1676,2015-07-29 12:00:00,3,3,3 +1677,2015-07-29 15:00:00,3,3,3 +1678,2015-07-29 18:00:00,3,3,3 +1679,2015-07-29 21:00:00,3,3,3 +1680,2015-07-30 00:00:00,3,3,3 +1681,2015-07-30 03:00:00,3,3,3 +1682,2015-07-30 06:00:00,3,3,3 +1683,2015-07-30 09:00:00,3,3,3 +1684,2015-07-30 12:00:00,3,3,3 +1685,2015-07-30 15:00:00,3,3,3 +1686,2015-07-30 18:00:00,3,3,3 +1687,2015-07-30 21:00:00,3,3,3 +1688,2015-07-31 00:00:00,3,3,3 +1689,2015-07-31 03:00:00,3,3,3 +1690,2015-07-31 06:00:00,3,3,3 +1691,2015-07-31 09:00:00,3,3,3 +1692,2015-07-31 12:00:00,3,3,3 +1693,2015-07-31 15:00:00,3,3,3 +1694,2015-07-31 18:00:00,3,3,3 +1695,2015-07-31 21:00:00,3,3,3 +1696,2015-08-01 00:00:00,3,3,3 +1697,2015-08-01 03:00:00,3,3,3 +1698,2015-08-01 06:00:00,3,3,3 +1699,2015-08-01 09:00:00,3,3,3 +1700,2015-08-01 12:00:00,3,3,3 +1701,2015-08-01 15:00:00,3,3,3 +1702,2015-08-01 18:00:00,3,3,3 +1703,2015-08-01 21:00:00,3,3,3 +1704,2015-08-02 00:00:00,3,3,3 +1705,2015-08-02 03:00:00,3,3,3 +1706,2015-08-02 06:00:00,3,3,3 +1707,2015-08-02 09:00:00,3,3,3 +1708,2015-08-02 12:00:00,3,3,3 +1709,2015-08-02 15:00:00,3,3,3 +1710,2015-08-02 18:00:00,3,3,3 +1711,2015-08-02 21:00:00,3,3,3 +1712,2015-08-03 00:00:00,3,3,3 +1713,2015-08-03 03:00:00,3,3,3 +1714,2015-08-03 06:00:00,3,3,3 +1715,2015-08-03 09:00:00,3,3,3 +1716,2015-08-03 12:00:00,3,3,3 +1717,2015-08-03 15:00:00,3,3,3 +1718,2015-08-03 18:00:00,3,3,3 +1719,2015-08-03 21:00:00,3,3,3 +1720,2015-08-04 00:00:00,3,3,3 +1721,2015-08-04 03:00:00,3,3,3 +1722,2015-08-04 06:00:00,3,3,3 +1723,2015-08-04 09:00:00,3,3,3 +1724,2015-08-04 12:00:00,3,3,3 +1725,2015-08-04 15:00:00,3,3,3 +1726,2015-08-04 18:00:00,3,3,3 +1727,2015-08-04 21:00:00,3,3,3 +1728,2015-08-05 00:00:00,3,3,3 +1729,2015-08-05 03:00:00,3,3,3 +1730,2015-08-05 06:00:00,3,3,3 +1731,2015-08-05 09:00:00,3,3,3 +1732,2015-08-05 12:00:00,3,3,3 +1733,2015-08-05 15:00:00,3,3,3 +1734,2015-08-05 18:00:00,3,3,3 +1735,2015-08-05 21:00:00,3,3,3 +1736,2015-08-06 00:00:00,3,3,3 +1737,2015-08-06 03:00:00,3,3,3 +1738,2015-08-06 06:00:00,3,3,3 +1739,2015-08-06 09:00:00,3,3,3 +1740,2015-08-06 12:00:00,3,3,3 +1741,2015-08-06 15:00:00,3,3,3 +1742,2015-08-06 18:00:00,3,3,3 +1743,2015-08-06 21:00:00,3,3,3 +1744,2015-08-07 00:00:00,3,3,3 +1745,2015-08-07 03:00:00,3,3,3 +1746,2015-08-07 06:00:00,3,3,3 +1747,2015-08-07 09:00:00,3,3,3 +1748,2015-08-07 12:00:00,3,3,3 +1749,2015-08-07 15:00:00,3,3,3 +1750,2015-08-07 18:00:00,3,3,3 +1751,2015-08-07 21:00:00,3,3,3 +1752,2015-08-08 00:00:00,3,3,3 +1753,2015-08-08 03:00:00,3,3,3 +1754,2015-08-08 06:00:00,3,3,3 +1755,2015-08-08 09:00:00,3,3,3 +1756,2015-08-08 12:00:00,3,3,3 +1757,2015-08-08 15:00:00,3,3,3 +1758,2015-08-08 18:00:00,3,3,3 +1759,2015-08-08 21:00:00,3,3,3 +1760,2015-08-09 00:00:00,3,3,3 +1761,2015-08-09 03:00:00,3,3,3 +1762,2015-08-09 06:00:00,3,3,3 +1763,2015-08-09 09:00:00,3,3,3 +1764,2015-08-09 12:00:00,3,3,3 +1765,2015-08-09 15:00:00,3,3,3 +1766,2015-08-09 18:00:00,3,3,3 +1767,2015-08-09 21:00:00,3,3,3 +1768,2015-08-10 00:00:00,3,3,3 +1769,2015-08-10 03:00:00,3,3,3 +1770,2015-08-10 06:00:00,3,3,3 +1771,2015-08-10 09:00:00,3,3,3 +1772,2015-08-10 12:00:00,3,3,3 +1773,2015-08-10 15:00:00,3,3,3 +1774,2015-08-10 18:00:00,3,3,3 +1775,2015-08-10 21:00:00,3,3,3 +1776,2015-08-11 00:00:00,3,3,3 +1777,2015-08-11 03:00:00,3,3,3 +1778,2015-08-11 06:00:00,3,3,3 +1779,2015-08-11 09:00:00,3,3,3 +1780,2015-08-11 12:00:00,3,3,3 +1781,2015-08-11 15:00:00,3,3,3 +1782,2015-08-11 18:00:00,3,3,3 +1783,2015-08-11 21:00:00,3,3,3 +1784,2015-08-12 00:00:00,3,3,3 +1785,2015-08-12 03:00:00,3,3,3 +1786,2015-08-12 06:00:00,3,3,3 +1787,2015-08-12 09:00:00,3,3,3 +1788,2015-08-12 12:00:00,3,3,3 +1789,2015-08-12 15:00:00,3,3,3 +1790,2015-08-12 18:00:00,3,3,3 +1791,2015-08-12 21:00:00,3,3,3 +1792,2015-08-13 00:00:00,3,3,3 +1793,2015-08-13 03:00:00,3,3,3 +1794,2015-08-13 06:00:00,3,3,3 +1795,2015-08-13 09:00:00,3,3,3 +1796,2015-08-13 12:00:00,3,3,3 +1797,2015-08-13 15:00:00,3,3,3 +1798,2015-08-13 18:00:00,3,3,3 +1799,2015-08-13 21:00:00,3,3,3 +1800,2015-08-14 00:00:00,3,3,3 +1801,2015-08-14 03:00:00,3,3,3 +1802,2015-08-14 06:00:00,3,3,3 +1803,2015-08-14 09:00:00,3,3,3 +1804,2015-08-14 12:00:00,3,3,3 +1805,2015-08-14 15:00:00,3,3,3 +1806,2015-08-14 18:00:00,3,3,3 +1807,2015-08-14 21:00:00,3,3,3 +1808,2015-08-15 00:00:00,3,3,3 +1809,2015-08-15 03:00:00,3,3,3 +1810,2015-08-15 06:00:00,3,3,3 +1811,2015-08-15 09:00:00,3,3,3 +1812,2015-08-15 12:00:00,3,3,3 +1813,2015-08-15 15:00:00,3,3,3 +1814,2015-08-15 18:00:00,3,3,3 +1815,2015-08-15 21:00:00,3,3,3 +1816,2015-08-16 00:00:00,3,3,3 +1817,2015-08-16 03:00:00,3,3,3 +1818,2015-08-16 06:00:00,3,3,3 +1819,2015-08-16 09:00:00,3,3,3 +1820,2015-08-16 12:00:00,3,3,3 +1821,2015-08-16 15:00:00,3,3,3 +1822,2015-08-16 18:00:00,3,3,3 +1823,2015-08-16 21:00:00,3,3,3 +1824,2015-08-17 00:00:00,3,3,3 +1825,2015-08-17 03:00:00,3,3,3 +1826,2015-08-17 06:00:00,3,3,3 +1827,2015-08-17 09:00:00,3,3,3 +1828,2015-08-17 12:00:00,3,3,3 +1829,2015-08-17 15:00:00,3,3,3 +1830,2015-08-17 18:00:00,3,3,3 +1831,2015-08-17 21:00:00,3,3,3 +1832,2015-08-18 00:00:00,3,3,3 +1833,2015-08-18 03:00:00,3,3,3 +1834,2015-08-18 06:00:00,3,3,3 +1835,2015-08-18 09:00:00,3,3,3 +1836,2015-08-18 12:00:00,3,3,3 +1837,2015-08-18 15:00:00,3,3,3 +1838,2015-08-18 18:00:00,3,3,3 +1839,2015-08-18 21:00:00,3,3,3 +1840,2015-08-19 00:00:00,3,3,3 +1841,2015-08-19 03:00:00,3,3,3 +1842,2015-08-19 06:00:00,3,3,3 +1843,2015-08-19 09:00:00,3,3,3 +1844,2015-08-19 12:00:00,3,3,3 +1845,2015-08-19 15:00:00,3,3,3 +1846,2015-08-19 18:00:00,3,3,3 +1847,2015-08-19 21:00:00,3,3,3 +1848,2015-08-20 00:00:00,3,3,3 +1849,2015-08-20 03:00:00,3,3,3 +1850,2015-08-20 06:00:00,3,3,3 +1851,2015-08-20 09:00:00,3,3,3 +1852,2015-08-20 12:00:00,3,3,3 +1853,2015-08-20 15:00:00,3,3,3 +1854,2015-08-20 18:00:00,3,3,3 +1855,2015-08-20 21:00:00,3,3,3 +1856,2015-08-21 00:00:00,3,3,3 +1857,2015-08-21 03:00:00,3,3,3 +1858,2015-08-21 06:00:00,3,3,3 +1859,2015-08-21 09:00:00,3,3,3 +1860,2015-08-21 12:00:00,3,3,3 +1861,2015-08-21 15:00:00,3,3,3 +1862,2015-08-21 18:00:00,3,3,3 +1863,2015-08-21 21:00:00,3,3,3 +1864,2015-08-22 00:00:00,3,3,3 +1865,2015-08-22 03:00:00,3,3,3 +1866,2015-08-22 06:00:00,3,3,3 +1867,2015-08-22 09:00:00,3,3,3 +1868,2015-08-22 12:00:00,3,3,3 +1869,2015-08-22 15:00:00,3,3,3 +1870,2015-08-22 18:00:00,3,3,3 +1871,2015-08-22 21:00:00,3,3,3 +1872,2015-08-23 00:00:00,3,3,3 +1873,2015-08-23 03:00:00,3,3,3 +1874,2015-08-23 06:00:00,3,3,3 +1875,2015-08-23 09:00:00,3,3,3 +1876,2015-08-23 12:00:00,3,3,3 +1877,2015-08-23 15:00:00,3,3,3 +1878,2015-08-23 18:00:00,3,3,3 +1879,2015-08-23 21:00:00,3,3,3 +1880,2015-08-24 00:00:00,3,3,3 +1881,2015-08-24 03:00:00,3,3,3 +1882,2015-08-24 06:00:00,3,3,3 +1883,2015-08-24 09:00:00,3,3,3 +1884,2015-08-24 12:00:00,3,3,3 +1885,2015-08-24 15:00:00,3,3,3 +1886,2015-08-24 18:00:00,3,3,3 +1887,2015-08-24 21:00:00,3,3,3 +1888,2015-08-25 00:00:00,3,3,3 +1889,2015-08-25 03:00:00,3,3,3 +1890,2015-08-25 06:00:00,3,3,3 +1891,2015-08-25 09:00:00,3,3,3 +1892,2015-08-25 12:00:00,3,3,3 +1893,2015-08-25 15:00:00,3,3,3 +1894,2015-08-25 18:00:00,3,3,3 +1895,2015-08-25 21:00:00,3,3,3 +1896,2015-08-26 00:00:00,3,3,3 +1897,2015-08-26 03:00:00,3,3,3 +1898,2015-08-26 06:00:00,3,3,3 +1899,2015-08-26 09:00:00,3,3,3 +1900,2015-08-26 12:00:00,3,3,3 +1901,2015-08-26 15:00:00,3,3,3 +1902,2015-08-26 18:00:00,3,3,3 +1903,2015-08-26 21:00:00,3,3,3 +1904,2015-08-27 00:00:00,3,3,3 +1905,2015-08-27 03:00:00,3,3,3 +1906,2015-08-27 06:00:00,3,3,3 +1907,2015-08-27 09:00:00,3,3,3 +1908,2015-08-27 12:00:00,3,3,3 +1909,2015-08-27 15:00:00,3,3,3 +1910,2015-08-27 18:00:00,3,3,3 +1911,2015-08-27 21:00:00,3,3,3 +1912,2015-08-28 00:00:00,3,3,3 +1913,2015-08-28 03:00:00,3,3,3 +1914,2015-08-28 06:00:00,3,3,3 +1915,2015-08-28 09:00:00,3,3,3 +1916,2015-08-28 12:00:00,3,3,3 +1917,2015-08-28 15:00:00,3,3,3 +1918,2015-08-28 18:00:00,3,3,3 +1919,2015-08-28 21:00:00,3,3,3 +1920,2015-08-29 00:00:00,3,3,3 +1921,2015-08-29 03:00:00,3,3,3 +1922,2015-08-29 06:00:00,3,3,3 +1923,2015-08-29 09:00:00,3,3,3 +1924,2015-08-29 12:00:00,3,3,3 +1925,2015-08-29 15:00:00,3,3,3 +1926,2015-08-29 18:00:00,3,3,3 +1927,2015-08-29 21:00:00,3,3,3 +1928,2015-08-30 00:00:00,3,3,3 +1929,2015-08-30 03:00:00,3,3,3 +1930,2015-08-30 06:00:00,3,3,3 +1931,2015-08-30 09:00:00,3,3,3 +1932,2015-08-30 12:00:00,3,3,3 +1933,2015-08-30 15:00:00,3,3,3 +1934,2015-08-30 18:00:00,3,3,3 +1935,2015-08-30 21:00:00,3,3,3 +1936,2015-08-31 00:00:00,3,3,3 +1937,2015-08-31 03:00:00,3,3,3 +1938,2015-08-31 06:00:00,3,3,3 +1939,2015-08-31 09:00:00,3,3,3 +1940,2015-08-31 12:00:00,3,3,3 +1941,2015-08-31 15:00:00,3,3,3 +1942,2015-08-31 18:00:00,3,3,3 +1943,2015-08-31 21:00:00,3,3,3 +1944,2015-09-01 00:00:00,3,3,3 +1945,2015-09-01 03:00:00,3,3,3 +1946,2015-09-01 06:00:00,3,3,3 +1947,2015-09-01 09:00:00,3,3,3 +1948,2015-09-01 12:00:00,3,3,3 +1949,2015-09-01 15:00:00,3,3,3 +1950,2015-09-01 18:00:00,3,3,3 +1951,2015-09-01 21:00:00,3,3,3 +1952,2015-09-02 00:00:00,3,3,3 +1953,2015-09-02 03:00:00,3,3,3 +1954,2015-09-02 06:00:00,3,3,3 +1955,2015-09-02 09:00:00,3,3,3 +1956,2015-09-02 12:00:00,3,3,3 +1957,2015-09-02 15:00:00,3,3,3 +1958,2015-09-02 18:00:00,3,3,3 +1959,2015-09-02 21:00:00,3,3,3 +1960,2015-09-03 00:00:00,3,3,3 +1961,2015-09-03 03:00:00,3,3,3 +1962,2015-09-03 06:00:00,3,3,3 +1963,2015-09-03 09:00:00,3,3,3 +1964,2015-09-03 12:00:00,3,3,3 +1965,2015-09-03 15:00:00,3,3,3 +1966,2015-09-03 18:00:00,3,3,3 +1967,2015-09-03 21:00:00,3,3,3 +1968,2015-09-04 00:00:00,3,3,3 +1969,2015-09-04 03:00:00,3,3,3 +1970,2015-09-04 06:00:00,3,3,3 +1971,2015-09-04 09:00:00,3,3,3 +1972,2015-09-04 12:00:00,3,3,3 +1973,2015-09-04 15:00:00,3,3,3 +1974,2015-09-04 18:00:00,3,3,3 +1975,2015-09-04 21:00:00,3,3,3 +1976,2015-09-05 00:00:00,3,3,3 +1977,2015-09-05 03:00:00,3,3,3 +1978,2015-09-05 06:00:00,3,3,3 +1979,2015-09-05 09:00:00,3,3,3 +1980,2015-09-05 12:00:00,3,3,3 +1981,2015-09-05 15:00:00,3,3,3 +1982,2015-09-05 18:00:00,3,3,3 +1983,2015-09-05 21:00:00,3,3,3 +1984,2015-09-06 00:00:00,3,3,3 +1985,2015-09-06 03:00:00,3,3,3 +1986,2015-09-06 06:00:00,3,3,3 +1987,2015-09-06 09:00:00,3,3,3 +1988,2015-09-06 12:00:00,3,3,3 +1989,2015-09-06 15:00:00,3,3,3 +1990,2015-09-06 18:00:00,3,3,3 +1991,2015-09-06 21:00:00,3,3,3 +1992,2015-09-07 00:00:00,3,3,3 +1993,2015-09-07 03:00:00,3,3,3 +1994,2015-09-07 06:00:00,3,3,3 +1995,2015-09-07 09:00:00,3,3,3 +1996,2015-09-07 12:00:00,3,3,3 +1997,2015-09-07 15:00:00,3,3,3 +1998,2015-09-07 18:00:00,3,3,3 +1999,2015-09-07 21:00:00,3,3,3 +2000,2015-09-08 00:00:00,3,3,3 +2001,2015-09-08 03:00:00,3,3,3 +2002,2015-09-08 06:00:00,3,3,3 +2003,2015-09-08 09:00:00,3,3,3 +2004,2015-09-08 12:00:00,3,3,3 +2005,2015-09-08 15:00:00,3,3,3 +2006,2015-09-08 18:00:00,3,3,3 +2007,2015-09-08 21:00:00,3,3,3 +2008,2015-09-09 00:00:00,3,3,3 +2009,2015-09-09 03:00:00,3,3,3 +2010,2015-09-09 06:00:00,3,3,3 +2011,2015-09-09 09:00:00,3,3,3 +2012,2015-09-09 12:00:00,3,3,3 +2013,2015-09-09 15:00:00,3,3,3 +2014,2015-09-09 18:00:00,3,3,3 +2015,2015-09-09 21:00:00,3,3,3 +2016,2015-09-10 00:00:00,3,3,3 +2017,2015-09-10 03:00:00,3,3,3 +2018,2015-09-10 06:00:00,3,3,3 +2019,2015-09-10 09:00:00,3,3,3 +2020,2015-09-10 12:00:00,3,3,3 +2021,2015-09-10 15:00:00,3,3,3 +2022,2015-09-10 18:00:00,3,3,3 +2023,2015-09-10 21:00:00,3,3,3 +2024,2015-09-11 00:00:00,3,3,3 +2025,2015-09-11 03:00:00,3,3,3 +2026,2015-09-11 06:00:00,3,3,3 +2027,2015-09-11 09:00:00,3,3,3 +2028,2015-09-11 12:00:00,3,3,3 +2029,2015-09-11 15:00:00,3,3,3 +2030,2015-09-11 18:00:00,3,3,3 +2031,2015-09-11 21:00:00,3,3,3 +2032,2015-09-12 00:00:00,3,3,3 +2033,2015-09-12 03:00:00,3,3,3 +2034,2015-09-12 06:00:00,3,3,3 +2035,2015-09-12 09:00:00,3,3,3 +2036,2015-09-12 12:00:00,3,3,3 +2037,2015-09-12 15:00:00,3,3,3 +2038,2015-09-12 18:00:00,3,3,3 +2039,2015-09-12 21:00:00,3,3,3 +2040,2015-09-13 00:00:00,3,3,3 +2041,2015-09-13 03:00:00,3,3,3 +2042,2015-09-13 06:00:00,3,3,3 +2043,2015-09-13 09:00:00,3,3,3 +2044,2015-09-13 12:00:00,3,3,3 +2045,2015-09-13 15:00:00,3,3,3 +2046,2015-09-13 18:00:00,3,3,3 +2047,2015-09-13 21:00:00,3,3,3 +2048,2015-09-14 00:00:00,3,3,3 +2049,2015-09-14 03:00:00,3,3,3 +2050,2015-09-14 06:00:00,3,3,3 +2051,2015-09-14 09:00:00,3,3,3 +2052,2015-09-14 12:00:00,3,3,3 +2053,2015-09-14 15:00:00,3,3,3 +2054,2015-09-14 18:00:00,3,3,3 +2055,2015-09-14 21:00:00,3,3,3 +2056,2015-09-15 00:00:00,3,3,3 +2057,2015-09-15 03:00:00,3,3,3 +2058,2015-09-15 06:00:00,3,3,3 +2059,2015-09-15 09:00:00,3,3,3 +2060,2015-09-15 12:00:00,3,3,3 +2061,2015-09-15 15:00:00,3,3,3 +2062,2015-09-15 18:00:00,3,3,3 +2063,2015-09-15 21:00:00,3,3,3 +2064,2015-09-16 00:00:00,3,3,3 +2065,2015-09-16 03:00:00,3,3,3 +2066,2015-09-16 06:00:00,3,3,3 +2067,2015-09-16 09:00:00,3,3,3 +2068,2015-09-16 12:00:00,3,3,3 +2069,2015-09-16 15:00:00,3,3,3 +2070,2015-09-16 18:00:00,3,3,3 +2071,2015-09-16 21:00:00,3,3,3 +2072,2015-09-17 00:00:00,3,3,3 +2073,2015-09-17 03:00:00,3,3,3 +2074,2015-09-17 06:00:00,3,3,3 +2075,2015-09-17 09:00:00,3,3,3 +2076,2015-09-17 12:00:00,3,3,3 +2077,2015-09-17 15:00:00,3,3,3 +2078,2015-09-17 18:00:00,3,3,3 +2079,2015-09-17 21:00:00,3,3,3 +2080,2015-09-18 00:00:00,3,3,3 +2081,2015-09-18 03:00:00,3,3,3 +2082,2015-09-18 06:00:00,3,3,3 +2083,2015-09-18 09:00:00,3,3,3 +2084,2015-09-18 12:00:00,3,3,3 +2085,2015-09-18 15:00:00,3,3,3 +2086,2015-09-18 18:00:00,3,3,3 +2087,2015-09-18 21:00:00,3,3,3 +2088,2015-09-19 00:00:00,3,3,3 +2089,2015-09-19 03:00:00,3,3,3 +2090,2015-09-19 06:00:00,3,3,3 +2091,2015-09-19 09:00:00,3,3,3 +2092,2015-09-19 12:00:00,3,3,3 +2093,2015-09-19 15:00:00,3,3,3 +2094,2015-09-19 18:00:00,3,3,3 +2095,2015-09-19 21:00:00,3,3,3 +2096,2015-09-20 00:00:00,3,3,3 +2097,2015-09-20 03:00:00,3,3,3 +2098,2015-09-20 06:00:00,3,3,3 +2099,2015-09-20 09:00:00,3,3,3 +2100,2015-09-20 12:00:00,3,3,3 +2101,2015-09-20 15:00:00,3,3,3 +2102,2015-09-20 18:00:00,3,3,3 +2103,2015-09-20 21:00:00,3,3,3 +2104,2015-09-21 00:00:00,3,3,3 +2105,2015-09-21 03:00:00,3,3,3 +2106,2015-09-21 06:00:00,3,3,3 +2107,2015-09-21 09:00:00,3,3,3 +2108,2015-09-21 12:00:00,3,3,3 +2109,2015-09-21 15:00:00,3,3,3 +2110,2015-09-21 18:00:00,3,3,3 +2111,2015-09-21 21:00:00,3,3,3 +2112,2015-09-22 00:00:00,3,3,3 +2113,2015-09-22 03:00:00,3,3,3 +2114,2015-09-22 06:00:00,3,3,3 +2115,2015-09-22 09:00:00,3,3,3 +2116,2015-09-22 12:00:00,3,3,3 +2117,2015-09-22 15:00:00,3,3,3 +2118,2015-09-22 18:00:00,3,3,3 +2119,2015-09-22 21:00:00,3,3,3 +2120,2015-09-23 00:00:00,3,3,3 +2121,2015-09-23 03:00:00,3,3,3 +2122,2015-09-23 06:00:00,3,3,3 +2123,2015-09-23 09:00:00,3,3,3 +2124,2015-09-23 12:00:00,3,3,3 +2125,2015-09-23 15:00:00,3,3,3 +2126,2015-09-23 18:00:00,3,3,3 +2127,2015-09-23 21:00:00,3,3,3 +2128,2015-09-24 00:00:00,3,3,3 +2129,2015-09-24 03:00:00,3,3,3 +2130,2015-09-24 06:00:00,3,3,3 +2131,2015-09-24 09:00:00,3,3,3 +2132,2015-09-24 12:00:00,3,3,3 +2133,2015-09-24 15:00:00,3,3,3 +2134,2015-09-24 18:00:00,3,3,3 +2135,2015-09-24 21:00:00,3,3,3 +2136,2015-09-25 00:00:00,3,3,3 +2137,2015-09-25 03:00:00,3,3,3 +2138,2015-09-25 06:00:00,3,3,3 +2139,2015-09-25 09:00:00,3,3,3 +2140,2015-09-25 12:00:00,3,3,3 +2141,2015-09-25 15:00:00,3,3,3 +2142,2015-09-25 18:00:00,3,3,3 +2143,2015-09-25 21:00:00,3,3,3 +2144,2015-09-26 00:00:00,3,3,3 +2145,2015-09-26 03:00:00,3,3,3 +2146,2015-09-26 06:00:00,3,3,3 +2147,2015-09-26 09:00:00,3,3,3 +2148,2015-09-26 12:00:00,3,3,3 +2149,2015-09-26 15:00:00,3,3,3 +2150,2015-09-26 18:00:00,3,3,3 +2151,2015-09-26 21:00:00,3,3,3 +2152,2015-09-27 00:00:00,3,3,3 +2153,2015-09-27 03:00:00,3,3,3 +2154,2015-09-27 06:00:00,3,3,3 +2155,2015-09-27 09:00:00,3,3,3 +2156,2015-09-27 12:00:00,3,3,3 +2157,2015-09-27 15:00:00,3,3,3 +2158,2015-09-27 18:00:00,3,3,3 +2159,2015-09-27 21:00:00,3,3,3 +2160,2015-09-28 00:00:00,3,3,3 +2161,2015-09-28 03:00:00,3,3,3 +2162,2015-09-28 06:00:00,3,3,3 +2163,2015-09-28 09:00:00,3,3,3 +2164,2015-09-28 12:00:00,3,3,3 +2165,2015-09-28 15:00:00,3,3,3 +2166,2015-09-28 18:00:00,3,3,3 +2167,2015-09-28 21:00:00,3,3,3 +2168,2015-09-29 00:00:00,3,3,3 +2169,2015-09-29 03:00:00,3,3,3 +2170,2015-09-29 06:00:00,3,3,3 +2171,2015-09-29 09:00:00,3,3,3 +2172,2015-09-29 12:00:00,3,3,3 +2173,2015-09-29 15:00:00,3,3,3 +2174,2015-09-29 18:00:00,3,3,3 +2175,2015-09-29 21:00:00,3,3,3 +2176,2015-09-30 00:00:00,3,3,3 +2177,2015-09-30 03:00:00,3,3,3 +2178,2015-09-30 06:00:00,3,3,3 +2179,2015-09-30 09:00:00,3,3,3 +2180,2015-09-30 12:00:00,3,3,3 +2181,2015-09-30 15:00:00,3,3,3 +2182,2015-09-30 18:00:00,3,3,3 +2183,2015-09-30 21:00:00,3,3,3 +2184,2015-10-01 00:00:00,3,3,3 +2185,2015-10-01 03:00:00,3,3,3 +2186,2015-10-01 06:00:00,3,3,3 +2187,2015-10-01 09:00:00,3,3,3 +2188,2015-10-01 12:00:00,3,3,3 +2189,2015-10-01 15:00:00,3,3,3 +2190,2015-10-01 18:00:00,3,3,3 +2191,2015-10-01 21:00:00,3,3,3 +2192,2015-10-02 00:00:00,3,3,3 +2193,2015-10-02 03:00:00,3,3,3 +2194,2015-10-02 06:00:00,3,3,3 +2195,2015-10-02 09:00:00,3,3,3 +2196,2015-10-02 12:00:00,3,3,3 +2197,2015-10-02 15:00:00,3,3,3 +2198,2015-10-02 18:00:00,3,3,3 +2199,2015-10-02 21:00:00,3,3,3 +2200,2015-10-03 00:00:00,3,3,3 +2201,2015-10-03 03:00:00,3,3,3 +2202,2015-10-03 06:00:00,3,3,3 +2203,2015-10-03 09:00:00,3,3,3 +2204,2015-10-03 12:00:00,3,3,3 +2205,2015-10-03 15:00:00,3,3,3 +2206,2015-10-03 18:00:00,3,3,3 +2207,2015-10-03 21:00:00,3,3,3 +2208,2015-10-04 00:00:00,3,3,3 +2209,2015-10-04 03:00:00,3,3,3 +2210,2015-10-04 06:00:00,3,3,3 +2211,2015-10-04 09:00:00,3,3,3 +2212,2015-10-04 12:00:00,3,3,3 +2213,2015-10-04 15:00:00,3,3,3 +2214,2015-10-04 18:00:00,3,3,3 +2215,2015-10-04 21:00:00,3,3,3 +2216,2015-10-05 00:00:00,3,3,3 +2217,2015-10-05 03:00:00,3,3,3 +2218,2015-10-05 06:00:00,3,3,3 +2219,2015-10-05 09:00:00,3,3,3 +2220,2015-10-05 12:00:00,3,3,3 +2221,2015-10-05 15:00:00,3,3,3 +2222,2015-10-05 18:00:00,3,3,3 +2223,2015-10-05 21:00:00,3,3,3 +2224,2015-10-06 00:00:00,3,3,3 +2225,2015-10-06 03:00:00,3,3,3 +2226,2015-10-06 06:00:00,3,3,3 +2227,2015-10-06 09:00:00,3,3,3 +2228,2015-10-06 12:00:00,3,3,3 +2229,2015-10-06 15:00:00,3,3,3 +2230,2015-10-06 18:00:00,3,3,3 +2231,2015-10-06 21:00:00,3,3,3 +2232,2015-10-07 00:00:00,3,3,3 +2233,2015-10-07 03:00:00,3,3,3 +2234,2015-10-07 06:00:00,3,3,3 +2235,2015-10-07 09:00:00,3,3,3 +2236,2015-10-07 12:00:00,3,3,3 +2237,2015-10-07 15:00:00,3,3,3 +2238,2015-10-07 18:00:00,3,3,3 +2239,2015-10-07 21:00:00,3,3,3 +2240,2015-10-08 00:00:00,3,3,3 +2241,2015-10-08 03:00:00,3,3,3 +2242,2015-10-08 06:00:00,3,3,3 +2243,2015-10-08 09:00:00,3,3,3 +2244,2015-10-08 12:00:00,3,3,3 +2245,2015-10-08 15:00:00,3,3,3 +2246,2015-10-08 18:00:00,3,3,3 +2247,2015-10-08 21:00:00,3,3,3 +2248,2015-10-09 00:00:00,3,3,3 +2249,2015-10-09 03:00:00,3,3,3 +2250,2015-10-09 06:00:00,3,3,3 +2251,2015-10-09 09:00:00,3,3,3 +2252,2015-10-09 12:00:00,3,3,3 +2253,2015-10-09 15:00:00,3,3,3 +2254,2015-10-09 18:00:00,3,3,3 +2255,2015-10-09 21:00:00,3,3,3 +2256,2015-10-10 00:00:00,3,3,3 +2257,2015-10-10 03:00:00,3,3,3 +2258,2015-10-10 06:00:00,3,3,3 +2259,2015-10-10 09:00:00,3,3,3 +2260,2015-10-10 12:00:00,3,3,3 +2261,2015-10-10 15:00:00,3,3,3 +2262,2015-10-10 18:00:00,3,3,3 +2263,2015-10-10 21:00:00,3,3,3 +2264,2015-10-11 00:00:00,3,3,3 +2265,2015-10-11 03:00:00,3,3,3 +2266,2015-10-11 06:00:00,3,3,3 +2267,2015-10-11 09:00:00,3,3,3 +2268,2015-10-11 12:00:00,3,3,3 +2269,2015-10-11 15:00:00,3,3,3 +2270,2015-10-11 18:00:00,3,3,3 +2271,2015-10-11 21:00:00,3,3,3 +2272,2015-10-12 00:00:00,3,3,3 +2273,2015-10-12 03:00:00,3,3,3 +2274,2015-10-12 06:00:00,3,3,3 +2275,2015-10-12 09:00:00,3,3,3 +2276,2015-10-12 12:00:00,3,3,3 +2277,2015-10-12 15:00:00,3,3,3 +2278,2015-10-12 18:00:00,3,3,3 +2279,2015-10-12 21:00:00,3,3,3 +2280,2015-10-13 00:00:00,3,3,3 +2281,2015-10-13 03:00:00,3,3,3 +2282,2015-10-13 06:00:00,3,3,3 +2283,2015-10-13 09:00:00,3,3,3 +2284,2015-10-13 12:00:00,3,3,3 +2285,2015-10-13 15:00:00,3,3,3 +2286,2015-10-13 18:00:00,3,3,3 +2287,2015-10-13 21:00:00,3,3,3 +2288,2015-10-14 00:00:00,3,3,3 +2289,2015-10-14 03:00:00,3,3,3 +2290,2015-10-14 06:00:00,3,3,3 +2291,2015-10-14 09:00:00,3,3,3 +2292,2015-10-14 12:00:00,3,3,3 +2293,2015-10-14 15:00:00,3,3,3 +2294,2015-10-14 18:00:00,3,3,3 +2295,2015-10-14 21:00:00,3,3,3 +2296,2015-10-15 00:00:00,3,3,3 +2297,2015-10-15 03:00:00,3,3,3 +2298,2015-10-15 06:00:00,3,3,3 +2299,2015-10-15 09:00:00,3,3,3 +2300,2015-10-15 12:00:00,3,3,3 +2301,2015-10-15 15:00:00,3,3,3 +2302,2015-10-15 18:00:00,3,3,3 +2303,2015-10-15 21:00:00,3,3,3 +2304,2015-10-16 00:00:00,3,3,3 +2305,2015-10-16 03:00:00,3,3,3 +2306,2015-10-16 06:00:00,3,3,3 +2307,2015-10-16 09:00:00,3,3,3 +2308,2015-10-16 12:00:00,3,3,3 +2309,2015-10-16 15:00:00,3,3,3 +2310,2015-10-16 18:00:00,3,3,3 +2311,2015-10-16 21:00:00,3,3,3 +2312,2015-10-17 00:00:00,3,3,3 +2313,2015-10-17 03:00:00,3,3,3 +2314,2015-10-17 06:00:00,3,3,3 +2315,2015-10-17 09:00:00,3,3,3 +2316,2015-10-17 12:00:00,3,3,3 +2317,2015-10-17 15:00:00,3,3,3 +2318,2015-10-17 18:00:00,3,3,3 +2319,2015-10-17 21:00:00,3,3,3 +2320,2015-10-18 00:00:00,3,3,3 +2321,2015-10-18 03:00:00,3,3,3 +2322,2015-10-18 06:00:00,3,3,3 +2323,2015-10-18 09:00:00,3,3,3 +2324,2015-10-18 12:00:00,3,3,3 +2325,2015-10-18 15:00:00,3,3,3 +2326,2015-10-18 18:00:00,3,3,3 +2327,2015-10-18 21:00:00,3,3,3 +2328,2015-10-19 00:00:00,3,3,3 +2329,2015-10-19 03:00:00,3,3,3 +2330,2015-10-19 06:00:00,3,3,3 +2331,2015-10-19 09:00:00,3,3,3 +2332,2015-10-19 12:00:00,3,3,3 +2333,2015-10-19 15:00:00,3,3,3 +2334,2015-10-19 18:00:00,3,3,3 +2335,2015-10-19 21:00:00,3,3,3 +2336,2015-10-20 00:00:00,3,3,3 +2337,2015-10-20 03:00:00,3,3,3 +2338,2015-10-20 06:00:00,3,3,3 +2339,2015-10-20 09:00:00,3,3,3 +2340,2015-10-20 12:00:00,3,3,3 +2341,2015-10-20 15:00:00,3,3,3 +2342,2015-10-20 18:00:00,3,3,3 +2343,2015-10-20 21:00:00,3,3,3 +2344,2015-10-21 00:00:00,3,3,3 +2345,2015-10-21 03:00:00,3,3,3 +2346,2015-10-21 06:00:00,3,3,3 +2347,2015-10-21 09:00:00,3,3,3 +2348,2015-10-21 12:00:00,3,3,3 +2349,2015-10-21 15:00:00,3,3,3 +2350,2015-10-21 18:00:00,3,3,3 +2351,2015-10-21 21:00:00,3,3,3 +2352,2015-10-22 00:00:00,3,3,3 +2353,2015-10-22 03:00:00,3,3,3 +2354,2015-10-22 06:00:00,3,3,3 +2355,2015-10-22 09:00:00,3,3,3 +2356,2015-10-22 12:00:00,3,3,3 +2357,2015-10-22 15:00:00,3,3,3 +2358,2015-10-22 18:00:00,3,3,3 +2359,2015-10-22 21:00:00,3,3,3 +2360,2015-10-23 00:00:00,3,3,3 +2361,2015-10-23 03:00:00,3,3,3 +2362,2015-10-23 06:00:00,3,3,3 +2363,2015-10-23 09:00:00,3,3,3 +2364,2015-10-23 12:00:00,3,3,3 +2365,2015-10-23 15:00:00,3,3,3 +2366,2015-10-23 18:00:00,3,3,3 +2367,2015-10-23 21:00:00,3,3,3 +2368,2015-10-24 00:00:00,3,3,3 +2369,2015-10-24 03:00:00,3,3,3 +2370,2015-10-24 06:00:00,3,3,3 +2371,2015-10-24 09:00:00,3,3,3 +2372,2015-10-24 12:00:00,3,3,3 +2373,2015-10-24 15:00:00,3,3,3 +2374,2015-10-24 18:00:00,3,3,3 +2375,2015-10-24 21:00:00,3,3,3 +2376,2015-10-25 00:00:00,3,3,3 +2377,2015-10-25 03:00:00,3,3,3 +2378,2015-10-25 06:00:00,3,3,3 +2379,2015-10-25 09:00:00,3,3,3 +2380,2015-10-25 12:00:00,3,3,3 +2381,2015-10-25 15:00:00,3,3,3 +2382,2015-10-25 18:00:00,3,3,3 +2383,2015-10-25 21:00:00,3,3,3 +2384,2015-10-26 00:00:00,3,3,3 +2385,2015-10-26 03:00:00,3,3,3 +2386,2015-10-26 06:00:00,3,3,3 +2387,2015-10-26 09:00:00,3,3,3 +2388,2015-10-26 12:00:00,3,3,3 +2389,2015-10-26 15:00:00,3,3,3 +2390,2015-10-26 18:00:00,3,3,3 +2391,2015-10-26 21:00:00,3,3,3 +2392,2015-10-27 00:00:00,3,3,3 +2393,2015-10-27 03:00:00,3,3,3 +2394,2015-10-27 06:00:00,3,3,3 +2395,2015-10-27 09:00:00,3,3,3 +2396,2015-10-27 12:00:00,3,3,3 +2397,2015-10-27 15:00:00,3,3,3 +2398,2015-10-27 18:00:00,3,3,3 +2399,2015-10-27 21:00:00,3,3,3 +2400,2015-10-28 00:00:00,3,3,3 +2401,2015-10-28 03:00:00,3,3,3 +2402,2015-10-28 06:00:00,3,3,3 +2403,2015-10-28 09:00:00,3,3,3 +2404,2015-10-28 12:00:00,3,3,3 +2405,2015-10-28 15:00:00,3,3,3 +2406,2015-10-28 18:00:00,3,3,3 +2407,2015-10-28 21:00:00,3,3,3 +2408,2015-10-29 00:00:00,3,3,3 +2409,2015-10-29 03:00:00,3,3,3 +2410,2015-10-29 06:00:00,3,3,3 +2411,2015-10-29 09:00:00,3,3,3 +2412,2015-10-29 12:00:00,3,3,3 +2413,2015-10-29 15:00:00,3,3,3 +2414,2015-10-29 18:00:00,3,3,3 +2415,2015-10-29 21:00:00,3,3,3 +2416,2015-10-30 00:00:00,3,3,3 +2417,2015-10-30 03:00:00,3,3,3 +2418,2015-10-30 06:00:00,3,3,3 +2419,2015-10-30 09:00:00,3,3,3 +2420,2015-10-30 12:00:00,3,3,3 +2421,2015-10-30 15:00:00,3,3,3 +2422,2015-10-30 18:00:00,3,3,3 +2423,2015-10-30 21:00:00,3,3,3 +2424,2015-10-31 00:00:00,3,3,3 +2425,2015-10-31 03:00:00,3,3,3 +2426,2015-10-31 06:00:00,3,3,3 +2427,2015-10-31 09:00:00,3,3,3 +2428,2015-10-31 12:00:00,3,3,3 +2429,2015-10-31 15:00:00,3,3,3 +2430,2015-10-31 18:00:00,3,3,3 +2431,2015-10-31 21:00:00,3,3,3 +2432,2015-11-01 00:00:00,3,3,3 +2433,2015-11-01 03:00:00,3,3,3 +2434,2015-11-01 06:00:00,3,3,3 +2435,2015-11-01 09:00:00,3,3,3 +2436,2015-11-01 12:00:00,3,3,3 +2437,2015-11-01 15:00:00,3,3,3 +2438,2015-11-01 18:00:00,3,3,3 +2439,2015-11-01 21:00:00,3,3,3 +2440,2015-11-02 00:00:00,3,3,3 +2441,2015-11-02 03:00:00,3,3,3 +2442,2015-11-02 06:00:00,3,3,3 +2443,2015-11-02 09:00:00,3,3,3 +2444,2015-11-02 12:00:00,3,3,3 +2445,2015-11-02 15:00:00,3,3,3 +2446,2015-11-02 18:00:00,3,3,3 +2447,2015-11-02 21:00:00,3,3,3 +2448,2015-11-03 00:00:00,3,3,3 +2449,2015-11-03 03:00:00,3,3,3 +2450,2015-11-03 06:00:00,3,3,3 +2451,2015-11-03 09:00:00,3,3,3 +2452,2015-11-03 12:00:00,3,3,3 +2453,2015-11-03 15:00:00,3,3,3 +2454,2015-11-03 18:00:00,3,3,3 +2455,2015-11-03 21:00:00,3,3,3 +2456,2015-11-04 00:00:00,3,3,3 +2457,2015-11-04 03:00:00,3,3,3 +2458,2015-11-04 06:00:00,3,3,3 +2459,2015-11-04 09:00:00,3,3,3 +2460,2015-11-04 12:00:00,3,3,3 +2461,2015-11-04 15:00:00,3,3,3 +2462,2015-11-04 18:00:00,3,3,3 +2463,2015-11-04 21:00:00,3,3,3 +2464,2015-11-05 00:00:00,3,3,3 +2465,2015-11-05 03:00:00,3,3,3 +2466,2015-11-05 06:00:00,3,3,3 +2467,2015-11-05 09:00:00,3,3,3 +2468,2015-11-05 12:00:00,3,3,3 +2469,2015-11-05 15:00:00,3,3,3 +2470,2015-11-05 18:00:00,3,3,3 +2471,2015-11-05 21:00:00,3,3,3 +2472,2015-11-06 00:00:00,3,3,3 +2473,2015-11-06 03:00:00,3,3,3 +2474,2015-11-06 06:00:00,3,3,3 +2475,2015-11-06 09:00:00,3,3,3 +2476,2015-11-06 12:00:00,3,3,3 +2477,2015-11-06 15:00:00,3,3,3 +2478,2015-11-06 18:00:00,3,3,3 +2479,2015-11-06 21:00:00,3,3,3 +2480,2015-11-07 00:00:00,3,3,3 +2481,2015-11-07 03:00:00,3,3,3 +2482,2015-11-07 06:00:00,3,3,3 +2483,2015-11-07 09:00:00,3,3,3 +2484,2015-11-07 12:00:00,3,3,3 +2485,2015-11-07 15:00:00,3,3,3 +2486,2015-11-07 18:00:00,3,3,3 +2487,2015-11-07 21:00:00,3,3,3 +2488,2015-11-08 00:00:00,3,3,3 +2489,2015-11-08 03:00:00,3,3,3 +2490,2015-11-08 06:00:00,3,3,3 +2491,2015-11-08 09:00:00,3,3,3 +2492,2015-11-08 12:00:00,3,3,3 +2493,2015-11-08 15:00:00,3,3,3 +2494,2015-11-08 18:00:00,3,3,3 +2495,2015-11-08 21:00:00,3,3,3 +2496,2015-11-09 00:00:00,3,3,3 +2497,2015-11-09 03:00:00,3,3,3 +2498,2015-11-09 06:00:00,3,3,3 +2499,2015-11-09 09:00:00,3,3,3 +2500,2015-11-09 12:00:00,3,3,3 +2501,2015-11-09 15:00:00,3,3,3 +2502,2015-11-09 18:00:00,3,3,3 +2503,2015-11-09 21:00:00,3,3,3 +2504,2015-11-10 00:00:00,3,3,3 +2505,2015-11-10 03:00:00,3,3,3 +2506,2015-11-10 06:00:00,3,3,3 +2507,2015-11-10 09:00:00,3,3,3 +2508,2015-11-10 12:00:00,3,3,3 +2509,2015-11-10 15:00:00,3,3,3 +2510,2015-11-10 18:00:00,3,3,3 +2511,2015-11-10 21:00:00,3,3,3 +2512,2015-11-11 00:00:00,3,3,3 +2513,2015-11-11 03:00:00,3,3,3 +2514,2015-11-11 06:00:00,3,3,3 +2515,2015-11-11 09:00:00,3,3,3 +2516,2015-11-11 12:00:00,3,3,3 +2517,2015-11-11 15:00:00,3,3,3 +2518,2015-11-11 18:00:00,3,3,3 +2519,2015-11-11 21:00:00,3,3,3 +2520,2015-11-12 00:00:00,3,3,3 +2521,2015-11-12 03:00:00,3,3,3 +2522,2015-11-12 06:00:00,3,3,3 +2523,2015-11-12 09:00:00,3,3,3 +2524,2015-11-12 12:00:00,3,3,3 +2525,2015-11-12 15:00:00,3,3,3 +2526,2015-11-12 18:00:00,3,3,3 +2527,2015-11-12 21:00:00,3,3,3 +2528,2015-11-13 00:00:00,3,3,3 +2529,2015-11-13 03:00:00,3,3,3 +2530,2015-11-13 06:00:00,3,3,3 +2531,2015-11-13 09:00:00,3,3,3 +2532,2015-11-13 12:00:00,3,3,3 +2533,2015-11-13 15:00:00,3,3,3 +2534,2015-11-13 18:00:00,3,3,3 +2535,2015-11-13 21:00:00,3,3,3 +2536,2015-11-14 00:00:00,3,3,3 +2537,2015-11-14 03:00:00,3,3,3 +2538,2015-11-14 06:00:00,3,3,3 +2539,2015-11-14 09:00:00,3,3,3 +2540,2015-11-14 12:00:00,3,3,3 +2541,2015-11-14 15:00:00,3,3,3 +2542,2015-11-14 18:00:00,3,3,3 +2543,2015-11-14 21:00:00,3,3,3 +2544,2015-11-15 00:00:00,3,3,3 +2545,2015-11-15 03:00:00,3,3,3 +2546,2015-11-15 06:00:00,3,3,3 +2547,2015-11-15 09:00:00,3,3,3 +2548,2015-11-15 12:00:00,3,3,3 +2549,2015-11-15 15:00:00,3,3,3 +2550,2015-11-15 18:00:00,3,3,3 +2551,2015-11-15 21:00:00,3,3,3 +2552,2015-11-16 00:00:00,3,3,3 +2553,2015-11-16 03:00:00,3,3,3 +2554,2015-11-16 06:00:00,3,3,3 +2555,2015-11-16 09:00:00,3,3,3 +2556,2015-11-16 12:00:00,3,3,3 +2557,2015-11-16 15:00:00,3,3,3 +2558,2015-11-16 18:00:00,3,3,3 +2559,2015-11-16 21:00:00,3,3,3 +2560,2015-11-17 00:00:00,3,3,3 +2561,2015-11-17 03:00:00,3,3,3 +2562,2015-11-17 06:00:00,3,3,3 +2563,2015-11-17 09:00:00,3,3,3 +2564,2015-11-17 12:00:00,3,3,3 +2565,2015-11-17 15:00:00,3,3,3 +2566,2015-11-17 18:00:00,3,3,3 +2567,2015-11-17 21:00:00,3,3,3 +2568,2015-11-18 00:00:00,3,3,3 +2569,2015-11-18 03:00:00,3,3,3 +2570,2015-11-18 06:00:00,3,3,3 +2571,2015-11-18 09:00:00,3,3,3 +2572,2015-11-18 12:00:00,3,3,3 +2573,2015-11-18 15:00:00,3,3,3 +2574,2015-11-18 18:00:00,3,3,3 +2575,2015-11-18 21:00:00,3,3,3 +2576,2015-11-19 00:00:00,3,3,3 +2577,2015-11-19 03:00:00,3,3,3 +2578,2015-11-19 06:00:00,3,3,3 +2579,2015-11-19 09:00:00,3,3,3 +2580,2015-11-19 12:00:00,3,3,3 +2581,2015-11-19 15:00:00,3,3,3 +2582,2015-11-19 18:00:00,3,3,3 +2583,2015-11-19 21:00:00,3,3,3 +2584,2015-11-20 00:00:00,3,3,3 +2585,2015-11-20 03:00:00,3,3,3 +2586,2015-11-20 06:00:00,3,3,3 +2587,2015-11-20 09:00:00,3,3,3 +2588,2015-11-20 12:00:00,3,3,3 +2589,2015-11-20 15:00:00,3,3,3 +2590,2015-11-20 18:00:00,3,3,3 +2591,2015-11-20 21:00:00,3,3,3 +2592,2015-11-21 00:00:00,3,3,3 +2593,2015-11-21 03:00:00,3,3,3 +2594,2015-11-21 06:00:00,3,3,3 +2595,2015-11-21 09:00:00,3,3,3 +2596,2015-11-21 12:00:00,3,3,3 +2597,2015-11-21 15:00:00,3,3,3 +2598,2015-11-21 18:00:00,3,3,3 +2599,2015-11-21 21:00:00,3,3,3 +2600,2015-11-22 00:00:00,3,3,3 +2601,2015-11-22 03:00:00,3,3,3 +2602,2015-11-22 06:00:00,3,3,3 +2603,2015-11-22 09:00:00,3,3,3 +2604,2015-11-22 12:00:00,3,3,3 +2605,2015-11-22 15:00:00,3,3,3 +2606,2015-11-22 18:00:00,3,3,3 +2607,2015-11-22 21:00:00,3,3,3 +2608,2015-11-23 00:00:00,3,3,3 +2609,2015-11-23 03:00:00,3,3,3 +2610,2015-11-23 06:00:00,3,3,3 +2611,2015-11-23 09:00:00,3,3,3 +2612,2015-11-23 12:00:00,3,3,3 +2613,2015-11-23 15:00:00,3,3,3 +2614,2015-11-23 18:00:00,3,3,3 +2615,2015-11-23 21:00:00,3,3,3 +2616,2015-11-24 00:00:00,3,3,3 +2617,2015-11-24 03:00:00,3,3,3 +2618,2015-11-24 06:00:00,3,3,3 +2619,2015-11-24 09:00:00,3,3,3 +2620,2015-11-24 12:00:00,3,3,3 +2621,2015-11-24 15:00:00,3,3,3 +2622,2015-11-24 18:00:00,3,3,3 +2623,2015-11-24 21:00:00,3,3,3 +2624,2015-11-25 00:00:00,3,3,3 +2625,2015-11-25 03:00:00,3,3,3 +2626,2015-11-25 06:00:00,3,3,3 +2627,2015-11-25 09:00:00,3,3,3 +2628,2015-11-25 12:00:00,3,3,3 +2629,2015-11-25 15:00:00,3,3,3 +2630,2015-11-25 18:00:00,3,3,3 +2631,2015-11-25 21:00:00,3,3,3 +2632,2015-11-26 00:00:00,3,3,3 +2633,2015-11-26 03:00:00,3,3,3 +2634,2015-11-26 06:00:00,3,3,3 +2635,2015-11-26 09:00:00,3,3,3 +2636,2015-11-26 12:00:00,3,3,3 +2637,2015-11-26 15:00:00,3,3,3 +2638,2015-11-26 18:00:00,3,3,3 +2639,2015-11-26 21:00:00,3,3,3 +2640,2015-11-27 00:00:00,3,3,3 +2641,2015-11-27 03:00:00,3,3,3 +2642,2015-11-27 06:00:00,3,3,3 +2643,2015-11-27 09:00:00,3,3,3 +2644,2015-11-27 12:00:00,3,3,3 +2645,2015-11-27 15:00:00,3,3,3 +2646,2015-11-27 18:00:00,3,3,3 +2647,2015-11-27 21:00:00,3,3,3 +2648,2015-11-28 00:00:00,3,3,3 +2649,2015-11-28 03:00:00,3,3,3 +2650,2015-11-28 06:00:00,3,3,3 +2651,2015-11-28 09:00:00,3,3,3 +2652,2015-11-28 12:00:00,3,3,3 +2653,2015-11-28 15:00:00,3,3,3 +2654,2015-11-28 18:00:00,3,3,3 +2655,2015-11-28 21:00:00,3,3,3 +2656,2015-11-29 00:00:00,3,3,3 +2657,2015-11-29 03:00:00,3,3,3 +2658,2015-11-29 06:00:00,3,3,3 +2659,2015-11-29 09:00:00,3,3,3 +2660,2015-11-29 12:00:00,3,3,3 +2661,2015-11-29 15:00:00,3,3,3 +2662,2015-11-29 18:00:00,3,3,3 +2663,2015-11-29 21:00:00,3,3,3 +2664,2015-11-30 00:00:00,3,3,3 +2665,2015-11-30 03:00:00,3,3,3 +2666,2015-11-30 06:00:00,3,3,3 +2667,2015-11-30 09:00:00,3,3,3 +2668,2015-11-30 12:00:00,3,3,3 +2669,2015-11-30 15:00:00,3,3,3 +2670,2015-11-30 18:00:00,3,3,3 +2671,2015-11-30 21:00:00,3,3,3 +2672,2015-12-01 00:00:00,3,3,3 +2673,2015-12-01 03:00:00,3,3,3 +2674,2015-12-01 06:00:00,3,3,3 +2675,2015-12-01 09:00:00,3,3,3 +2676,2015-12-01 12:00:00,3,3,3 +2677,2015-12-01 15:00:00,3,3,3 +2678,2015-12-01 18:00:00,3,3,3 +2679,2015-12-01 21:00:00,3,3,3 +2680,2015-12-02 00:00:00,3,3,3 +2681,2015-12-02 03:00:00,3,3,3 +2682,2015-12-02 06:00:00,3,3,3 +2683,2015-12-02 09:00:00,3,3,3 +2684,2015-12-02 12:00:00,3,3,3 +2685,2015-12-02 15:00:00,3,3,3 +2686,2015-12-02 18:00:00,3,3,3 +2687,2015-12-02 21:00:00,3,3,3 +2688,2015-12-03 00:00:00,3,3,3 +2689,2015-12-03 03:00:00,3,3,3 +2690,2015-12-03 06:00:00,3,3,3 +2691,2015-12-03 09:00:00,3,3,3 +2692,2015-12-03 12:00:00,3,3,3 +2693,2015-12-03 15:00:00,3,3,3 +2694,2015-12-03 18:00:00,3,3,3 +2695,2015-12-03 21:00:00,3,3,3 +2696,2015-12-04 00:00:00,3,3,3 +2697,2015-12-04 03:00:00,3,3,3 +2698,2015-12-04 06:00:00,3,3,3 +2699,2015-12-04 09:00:00,3,3,3 +2700,2015-12-04 12:00:00,3,3,3 +2701,2015-12-04 15:00:00,3,3,3 +2702,2015-12-04 18:00:00,3,3,3 +2703,2015-12-04 21:00:00,3,3,3 +2704,2015-12-05 00:00:00,3,3,3 +2705,2015-12-05 03:00:00,3,3,3 +2706,2015-12-05 06:00:00,3,3,3 +2707,2015-12-05 09:00:00,3,3,3 +2708,2015-12-05 12:00:00,3,3,3 +2709,2015-12-05 15:00:00,3,3,3 +2710,2015-12-05 18:00:00,3,3,3 +2711,2015-12-05 21:00:00,3,3,3 +2712,2015-12-06 00:00:00,3,3,3 +2713,2015-12-06 03:00:00,3,3,3 +2714,2015-12-06 06:00:00,3,3,3 +2715,2015-12-06 09:00:00,3,3,3 +2716,2015-12-06 12:00:00,3,3,3 +2717,2015-12-06 15:00:00,3,3,3 +2718,2015-12-06 18:00:00,3,3,3 +2719,2015-12-06 21:00:00,3,3,3 +2720,2015-12-07 00:00:00,3,3,3 +2721,2015-12-07 03:00:00,3,3,3 +2722,2015-12-07 06:00:00,3,3,3 +2723,2015-12-07 09:00:00,3,3,3 +2724,2015-12-07 12:00:00,3,3,3 +2725,2015-12-07 15:00:00,3,3,3 +2726,2015-12-07 18:00:00,3,3,3 +2727,2015-12-07 21:00:00,3,3,3 +2728,2015-12-08 00:00:00,3,3,3 +2729,2015-12-08 03:00:00,3,3,3 +2730,2015-12-08 06:00:00,3,3,3 +2731,2015-12-08 09:00:00,3,3,3 +2732,2015-12-08 12:00:00,3,3,3 +2733,2015-12-08 15:00:00,3,3,3 +2734,2015-12-08 18:00:00,3,3,3 +2735,2015-12-08 21:00:00,3,3,3 +2736,2015-12-09 00:00:00,3,3,3 +2737,2015-12-09 03:00:00,3,3,3 +2738,2015-12-09 06:00:00,3,3,3 +2739,2015-12-09 09:00:00,3,3,3 +2740,2015-12-09 12:00:00,3,3,3 +2741,2015-12-09 15:00:00,3,3,3 +2742,2015-12-09 18:00:00,3,3,3 +2743,2015-12-09 21:00:00,3,3,3 +2744,2015-12-10 00:00:00,3,3,3 +2745,2015-12-10 03:00:00,3,3,3 +2746,2015-12-10 06:00:00,3,3,3 +2747,2015-12-10 09:00:00,3,3,3 +2748,2015-12-10 12:00:00,3,3,3 +2749,2015-12-10 15:00:00,3,3,3 +2750,2015-12-10 18:00:00,3,3,3 +2751,2015-12-10 21:00:00,3,3,3 +2752,2015-12-11 00:00:00,3,3,3 +2753,2015-12-11 03:00:00,3,3,3 +2754,2015-12-11 06:00:00,3,3,3 +2755,2015-12-11 09:00:00,3,3,3 +2756,2015-12-11 12:00:00,3,3,3 +2757,2015-12-11 15:00:00,3,3,3 +2758,2015-12-11 18:00:00,3,3,3 +2759,2015-12-11 21:00:00,3,3,3 +2760,2015-12-12 00:00:00,3,3,3 +2761,2015-12-12 03:00:00,3,3,3 +2762,2015-12-12 06:00:00,3,3,3 +2763,2015-12-12 09:00:00,3,3,3 +2764,2015-12-12 12:00:00,3,3,3 +2765,2015-12-12 15:00:00,3,3,3 +2766,2015-12-12 18:00:00,3,3,3 +2767,2015-12-12 21:00:00,3,3,3 +2768,2015-12-13 00:00:00,3,3,3 +2769,2015-12-13 03:00:00,3,3,3 +2770,2015-12-13 06:00:00,3,3,3 +2771,2015-12-13 09:00:00,3,3,3 +2772,2015-12-13 12:00:00,3,3,3 +2773,2015-12-13 15:00:00,3,3,3 +2774,2015-12-13 18:00:00,3,3,3 +2775,2015-12-13 21:00:00,3,3,3 +2776,2015-12-14 00:00:00,3,3,3 +2777,2015-12-14 03:00:00,3,3,3 +2778,2015-12-14 06:00:00,3,3,3 +2779,2015-12-14 09:00:00,3,3,3 +2780,2015-12-14 12:00:00,3,3,3 +2781,2015-12-14 15:00:00,3,3,3 +2782,2015-12-14 18:00:00,3,3,3 +2783,2015-12-14 21:00:00,3,3,3 +2784,2015-12-15 00:00:00,3,3,3 +2785,2015-12-15 03:00:00,3,3,3 +2786,2015-12-15 06:00:00,3,3,3 +2787,2015-12-15 09:00:00,3,3,3 +2788,2015-12-15 12:00:00,3,3,3 +2789,2015-12-15 15:00:00,3,3,3 +2790,2015-12-15 18:00:00,3,3,3 +2791,2015-12-15 21:00:00,3,3,3 +2792,2015-12-16 00:00:00,3,3,3 +2793,2015-12-16 03:00:00,3,3,3 +2794,2015-12-16 06:00:00,3,3,3 +2795,2015-12-16 09:00:00,3,3,3 +2796,2015-12-16 12:00:00,3,3,3 +2797,2015-12-16 15:00:00,3,3,3 +2798,2015-12-16 18:00:00,3,3,3 +2799,2015-12-16 21:00:00,3,3,3 +2800,2015-12-17 00:00:00,3,3,3 +2801,2015-12-17 03:00:00,3,3,3 +2802,2015-12-17 06:00:00,3,3,3 +2803,2015-12-17 09:00:00,3,3,3 +2804,2015-12-17 12:00:00,3,3,3 +2805,2015-12-17 15:00:00,3,3,3 +2806,2015-12-17 18:00:00,3,3,3 +2807,2015-12-17 21:00:00,3,3,3 +2808,2015-12-18 00:00:00,3,3,3 +2809,2015-12-18 03:00:00,3,3,3 +2810,2015-12-18 06:00:00,3,3,3 +2811,2015-12-18 09:00:00,3,3,3 +2812,2015-12-18 12:00:00,3,3,3 +2813,2015-12-18 15:00:00,3,3,3 +2814,2015-12-18 18:00:00,3,3,3 +2815,2015-12-18 21:00:00,3,3,3 +2816,2015-12-19 00:00:00,3,3,3 +2817,2015-12-19 03:00:00,3,3,3 +2818,2015-12-19 06:00:00,3,3,3 +2819,2015-12-19 09:00:00,3,3,3 +2820,2015-12-19 12:00:00,3,3,3 +2821,2015-12-19 15:00:00,3,3,3 +2822,2015-12-19 18:00:00,3,3,3 +2823,2015-12-19 21:00:00,3,3,3 +2824,2015-12-20 00:00:00,3,3,3 +2825,2015-12-20 03:00:00,3,3,3 +2826,2015-12-20 06:00:00,3,3,3 +2827,2015-12-20 09:00:00,3,3,3 +2828,2015-12-20 12:00:00,3,3,3 +2829,2015-12-20 15:00:00,3,3,3 +2830,2015-12-20 18:00:00,3,3,3 +2831,2015-12-20 21:00:00,3,3,3 +2832,2015-12-21 00:00:00,3,3,3 +2833,2015-12-21 03:00:00,3,3,3 +2834,2015-12-21 06:00:00,3,3,3 +2835,2015-12-21 09:00:00,3,3,3 +2836,2015-12-21 12:00:00,3,3,3 +2837,2015-12-21 15:00:00,3,3,3 +2838,2015-12-21 18:00:00,3,3,3 +2839,2015-12-21 21:00:00,3,3,3 +2840,2015-12-22 00:00:00,3,3,3 +2841,2015-12-22 03:00:00,3,3,3 +2842,2015-12-22 06:00:00,3,3,3 +2843,2015-12-22 09:00:00,3,3,3 +2844,2015-12-22 12:00:00,3,3,3 +2845,2015-12-22 15:00:00,3,3,3 +2846,2015-12-22 18:00:00,3,3,3 +2847,2015-12-22 21:00:00,3,3,3 +2848,2015-12-23 00:00:00,3,3,3 +2849,2015-12-23 03:00:00,3,3,3 +2850,2015-12-23 06:00:00,3,3,3 +2851,2015-12-23 09:00:00,3,3,3 +2852,2015-12-23 12:00:00,3,3,3 +2853,2015-12-23 15:00:00,3,3,3 +2854,2015-12-23 18:00:00,3,3,3 +2855,2015-12-23 21:00:00,3,3,3 +2856,2015-12-24 00:00:00,3,3,3 +2857,2015-12-24 03:00:00,3,3,3 +2858,2015-12-24 06:00:00,3,3,3 +2859,2015-12-24 09:00:00,3,3,3 +2860,2015-12-24 12:00:00,3,3,3 +2861,2015-12-24 15:00:00,3,3,3 +2862,2015-12-24 18:00:00,3,3,3 +2863,2015-12-24 21:00:00,3,3,3 +2864,2015-12-25 00:00:00,3,3,3 +2865,2015-12-25 03:00:00,3,3,3 +2866,2015-12-25 06:00:00,3,3,3 +2867,2015-12-25 09:00:00,3,3,3 +2868,2015-12-25 12:00:00,3,3,3 +2869,2015-12-25 15:00:00,3,3,3 +2870,2015-12-25 18:00:00,3,3,3 +2871,2015-12-25 21:00:00,3,3,3 +2872,2015-12-26 00:00:00,3,3,3 +2873,2015-12-26 03:00:00,3,3,3 +2874,2015-12-26 06:00:00,3,3,3 +2875,2015-12-26 09:00:00,3,3,3 +2876,2015-12-26 12:00:00,3,3,3 +2877,2015-12-26 15:00:00,3,3,3 +2878,2015-12-26 18:00:00,3,3,3 +2879,2015-12-26 21:00:00,3,3,3 +2880,2015-12-27 00:00:00,3,3,3 +2881,2015-12-27 03:00:00,3,3,3 +2882,2015-12-27 06:00:00,3,3,3 +2883,2015-12-27 09:00:00,3,3,3 +2884,2015-12-27 12:00:00,3,3,3 +2885,2015-12-27 15:00:00,3,3,3 +2886,2015-12-27 18:00:00,3,3,3 +2887,2015-12-27 21:00:00,3,3,3 +2888,2015-12-28 00:00:00,3,3,3 +2889,2015-12-28 03:00:00,3,3,3 +2890,2015-12-28 06:00:00,3,3,3 +2891,2015-12-28 09:00:00,3,3,3 +2892,2015-12-28 12:00:00,3,3,3 +2893,2015-12-28 15:00:00,3,3,3 +2894,2015-12-28 18:00:00,3,3,3 +2895,2015-12-28 21:00:00,3,3,3 +2896,2015-12-29 00:00:00,3,3,3 +2897,2015-12-29 03:00:00,3,3,3 +2898,2015-12-29 06:00:00,3,3,3 +2899,2015-12-29 09:00:00,3,3,3 +2900,2015-12-29 12:00:00,3,3,3 +2901,2015-12-29 15:00:00,3,3,3 +2902,2015-12-29 18:00:00,3,3,3 +2903,2015-12-29 21:00:00,3,3,3 +2904,2015-12-30 00:00:00,3,3,3 +2905,2015-12-30 03:00:00,3,3,3 +2906,2015-12-30 06:00:00,3,3,3 +2907,2015-12-30 09:00:00,3,3,3 +2908,2015-12-30 12:00:00,3,3,3 +2909,2015-12-30 15:00:00,3,3,3 +2910,2015-12-30 18:00:00,3,3,3 +2911,2015-12-30 21:00:00,3,3,3 +2912,2015-12-31 00:00:00,3,3,3 +2913,2015-12-31 03:00:00,3,3,3 +2914,2015-12-31 06:00:00,3,3,3 +2915,2015-12-31 09:00:00,3,3,3 +2916,2015-12-31 12:00:00,3,3,3 +2917,2015-12-31 15:00:00,3,3,3 +2918,2015-12-31 18:00:00,3,3,3 +2919,2015-12-31 21:00:00,3,3,3 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/buses-marginal_price.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/buses-marginal_price.csv new file mode 100644 index 0000000000000000000000000000000000000000..6430335dda2989b4991ad869736888035ca1a26c --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/buses-marginal_price.csv @@ -0,0 +1,13 @@ +name,0,1,2,3,4,5 +2015-01-01 00:00:00,184.05054124,184.05054124,184.05054124,184.05054124,184.031785292,184.040905508 +2015-01-01 01:00:00,161.674262143,189.676676206,189.684356307,161.674262143,161.674262143,161.674262143 +2015-01-01 02:00:00,195.416119604,340.478835762,340.518621484,195.400974993,195.400974993,195.400974993 +2015-01-01 03:00:00,114.8350693,214.859887685,214.887321058,114.8350693,114.8350693,114.8350693 +2015-01-01 04:00:00,138.941387085,259.669665783,259.702777405,138.941387085,138.941387085,138.941387085 +2015-01-01 05:00:00,121.972905508,265.767567541,265.807005479,121.972905508,121.972905508,121.972905508 +2015-01-01 06:00:00,147.545277681,275.767818468,275.802972338,147.545277681,147.545277681,147.545277681 +2015-01-01 07:00:00,129.554410239,129.554410239,129.554410239,129.554410239,129.554410239,129.554410239 +2015-01-01 08:00:00,163.139615386,163.139615386,163.139615386,163.139615387,163.139615386,163.139615386 +2015-01-01 09:00:00,36.3648042764,197.282373282,197.326507446,36.3648042764,36.3648042764,36.3648042764 +2015-01-01 10:00:00,31.6207175756,173.241089459,173.279931064,31.6207175756,31.6122278329,31.5941431666 +2015-01-01 11:00:00,38.6184659646,38.6251418042,38.64529914,38.6184659646,38.6251418042,38.6034414176 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/buses-p.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/buses-p.csv new file mode 100644 index 0000000000000000000000000000000000000000..54a762dad58988ce44dacaa54bf993d6129d9d22 --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/buses-p.csv @@ -0,0 +1,13 @@ +name,0,1,2,3,4,5 +2015-01-01 00:00:00,144.526100563,448.145283245,-592.671383808,-1283.02515973,1617.18977521,-334.164615477 +2015-01-01 01:00:00,270.329634057,123.534536107,-393.864170164,-1364.11019936,-847.440459764,2211.55065912 +2015-01-01 02:00:00,270.34438284,69.7442492969,-340.088632137,-1261.06660426,1214.28067641,46.7859278505 +2015-01-01 03:00:00,270.091748311,991.127603023,-1261.21935133,-974.0412465,-1803.86367431,2777.90492081 +2015-01-01 04:00:00,270.390287113,-97.6732163405,-172.717070772,-1345.4020887,-520.384943654,1865.78703235 +2015-01-01 05:00:00,270.265565552,357.198768393,-627.464333946,-1147.6735053,-1454.47524549,2602.14875079 +2015-01-01 06:00:00,270.392216954,-104.711539938,-165.680677016,-861.404967748,-1112.7465744,1974.15154214 +2015-01-01 07:00:00,-0.21776933728,794.226517946,-794.008748609,-433.960301757,-1659.93556481,2093.89586657 +2015-01-01 08:00:00,33.7369510196,-21.1306727195,-12.6062783001,-1733.44429321,1088.20745201,645.236841206 +2015-01-01 09:00:00,270.297749093,239.822180715,-510.119929809,-957.70540718,-1026.50089973,1984.20630691 +2015-01-01 10:00:00,270.227652116,495.472855338,-765.700507454,-1063.31166257,-2202.50615633,3265.8178189 +2015-01-01 11:00:00,270.050322816,1142.21051964,-1412.26084246,-696.970081317,-2540.38786789,3237.35794921 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/buses-v_ang.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/buses-v_ang.csv new file mode 100644 index 0000000000000000000000000000000000000000..099dfbb2f2d559dfde62096005f782ab6ae4246a --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/buses-v_ang.csv @@ -0,0 +1,13 @@ +name,1,2,4,5 +2015-01-01 00:00:00,-0.000108364410437,-0.000108749093228,0.00151803384138,0.000308168900436 +2015-01-01 01:00:00,-0.0002031094528,-0.000203263006672,0.000303212447291,0.00327761881522 +2015-01-01 02:00:00,-0.000203152046757,-0.000203263006672,0.00130044055319,0.000734135293981 +2015-01-01 03:00:00,-0.000202422447326,-0.000203263006672,-0.000395635383588,0.00371806182725 +2015-01-01 04:00:00,-0.000203284616644,-0.000203263006672,0.000460130635998,0.00287015121344 +2015-01-01 05:00:00,-0.000202924425265,-0.000203263006672,-0.000123542110164,0.00360974870339 +2015-01-01 06:00:00,-0.000203290189956,-0.000203263006672,-0.00010348422557,0.00273356655067 +2015-01-01 07:00:00,6.28910011028e-07,0.0,-0.00061352062878,0.0026405712827 +2015-01-01 08:00:00,-2.53693274671e-05,-2.53595484438e-05,0.0014909164275,0.00167676980095 +2015-01-01 09:00:00,-0.000203017370174,-0.000203263006672,-7.48706405319e-06,0.0027970777917 +2015-01-01 10:00:00,-0.000202814932626,-0.000203263006672,-0.000551040086452,0.00432696621477 +2015-01-01 11:00:00,-0.000202302811987,-0.000203263006672,-0.000921227229747,0.00409661105385 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/buses.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/buses.csv new file mode 100644 index 0000000000000000000000000000000000000000..c388d040f1b7eb802f65d0d4a6f00978546dbfeb --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/buses.csv @@ -0,0 +1,7 @@ +name,v_nom +0,380.0 +1,380.0 +2,380.0 +3,380.0 +4,380.0 +5,380.0 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/carriers.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/carriers.csv new file mode 100644 index 0000000000000000000000000000000000000000..4ab33a26381f0666d16973afc234eba24d70abec --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/carriers.csv @@ -0,0 +1,4 @@ +name,co2_emissions +gas,0.24 +wind,0.0 +battery,0.0 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/generators-p.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/generators-p.csv new file mode 100644 index 0000000000000000000000000000000000000000..2122623e0c7cb9782e680691405ed91e24c8e3fc --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/generators-p.csv @@ -0,0 +1,13 @@ +name,Wind 0,Gas 0,Wind 1,Gas 1,Wind 2,Gas 2,Wind 3,Wind 4,Wind 5 +2015-01-01 00:00:00,12.417331512,7.0,345.344279927,4.52957822415,87.8638907187,-2.57309682275e-14,96.959574832,56.1030219256,79.8913723426 +2015-01-01 01:00:00,80.0632672681,7.0,288.18422741,4.52957822415,121.065779343,-2.57309682275e-14,38.7860644064,86.3541854559,3200.92492541 +2015-01-01 02:00:00,55.624023399,7.0,168.792215273,192.507074526,8.98765342683,-1.09356614967e-12,12.1343455741,4.45518785448,460.541654141 +2015-01-01 03:00:00,22.5709338608,7.0,881.952221916,4.52957822415,83.9575473081,-2.57309682275e-14,1.25559801591,7.19742796895,4144.14298469 +2015-01-01 04:00:00,56.810046192,7.0,251.469412491,4.52957822415,37.8488616855,-2.57309682275e-14,74.3825854075,19.5429481109,2490.74069666 +2015-01-01 05:00:00,97.0019989088,7.0,1047.50923732,4.52957822415,35.9771638722,-2.57309682275e-14,49.3769714269,61.9955718381,3551.63800949 +2015-01-01 06:00:00,15.6791394646,7.0,22.9783674585,166.637849802,10.5033215579,8.60304681255e-13,48.6345110937,60.6329461653,2437.15432624 +2015-01-01 07:00:00,31.7362409322,7.0,1222.89718074,4.52957822415,86.9617828869,-2.57309682275e-14,38.0141172624,55.0948219118,3193.26789801 +2015-01-01 08:00:00,66.9232893453,7.0,327.699864848,4.52957822415,9.95022516426,-2.57309682275e-14,37.0084197914,62.9717507022,900.457420222 +2015-01-01 09:00:00,75.2755553739,7.0,82.3042139266,4.52957822415,39.0472647869,-2.57309682275e-14,80.4754563743,19.3434282623,2739.66935951 +2015-01-01 10:00:00,52.4670309124,7.0,1143.96781266,4.52957822415,39.4945155731,-2.57309682275e-14,6.5961090684,73.5065963289,3308.27506558 +2015-01-01 11:00:00,90.7815852504,7.0,1152.82965068,4.52957822415,2.09273594634,-2.57309682275e-14,23.4362086121,61.6778357002,4065.91085273 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/generators-p_max_pu.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/generators-p_max_pu.csv new file mode 100644 index 0000000000000000000000000000000000000000..45023b569dc88448e60d2aa79ef2d226669bafcb --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/generators-p_max_pu.csv @@ -0,0 +1,13 @@ +name,Wind 0,Wind 1,Wind 2,Wind 3,Wind 4,Wind 5 +2015-01-01 00:00:00,0.12417331512,0.279183679011,0.585759271458,0.96959574832,0.561030219256,0.0186472893729 +2015-01-01 01:00:00,0.800632672681,0.232974273841,0.807105195619,0.387860644064,0.863541854559,0.747121642737 +2015-01-01 02:00:00,0.55624023399,0.136455225661,0.0599176895122,0.121343455741,0.0445518785448,0.107494129106 +2015-01-01 03:00:00,0.225709338608,0.712988980383,0.559716982054,0.0125559801591,0.0719742796895,0.96727633 +2015-01-01 04:00:00,0.56810046192,0.203293234661,0.25232574457,0.743825854075,0.195429481109,0.581358927273 +2015-01-01 05:00:00,0.970019989088,0.84682880149,0.239847759148,0.493769714269,0.619955718381,0.82898089955 +2015-01-01 06:00:00,0.156791394646,0.0185762021774,0.0700221437192,0.486345110937,0.606329461653,0.568851437086 +2015-01-01 07:00:00,0.317362409322,0.988616154412,0.579745219246,0.380141172624,0.550948219118,0.745334430907 +2015-01-01 08:00:00,0.669232893453,0.264919557663,0.0663348344284,0.370084197914,0.629717507022,0.210174009915 +2015-01-01 09:00:00,0.752755553739,0.0665364813541,0.260315098579,0.804754563743,0.193434282623,0.63946088088 +2015-01-01 10:00:00,0.524670309124,0.924807970399,0.263296770487,0.065961090684,0.735065963289,0.772178029543 +2015-01-01 11:00:00,0.907815852504,0.931972069197,0.0139515729756,0.234362086121,0.616778357002,0.949016320688 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/generators.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/generators.csv new file mode 100644 index 0000000000000000000000000000000000000000..ea2180b77ec53f5be633b2bbeede58cd8c143c51 --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/generators.csv @@ -0,0 +1,13 @@ +name,bus,capital_cost,carrier,control,efficiency,marginal_cost,p_max_pu,p_min_pu,p_nom,p_nom_extendable,p_nom_min,p_nom_opt +Wind 0,0,2558.68982845,wind,Slack,1.0,0.00417304802367,1.0,0.0,100.0,True,100.0,100.0 +Gas 0,0,119.810148908,gas,PQ,0.358007445687,2.56154775438,0.85,0.02,350.0,False,0.0,350.0 +Wind 1,1,2876.3891523,wind,PQ,1.0,0.00692322615669,1.0,0.0,100.0,True,100.0,1236.97875589 +Gas 1,1,108.504421137,gas,PQ,0.350390547832,5.57842665402,0.85,0.02,0.0,True,0.0,226.478911207 +Wind 2,2,2421.10762501,wind,PQ,1.0,0.000983468338331,1.0,0.0,150.0,False,100.0,150.0 +Gas 2,2,153.316528497,gas,PQ,0.35691877114,5.8315581206,0.85,0.02,0.0,True,0.0,-1.28654841138e-12 +Wind 3,3,2018.28827734,wind,Slack,1.0,0.00834625671897,1.0,0.0,100.0,True,100.0,100.0 +Gas 3,3,198.886108891,gas,PQ,0.357481656544,5.00057725978,0.85,0.02,0.0,True,0.0,0.0 +Wind 4,4,2447.89352618,wind,PQ,1.0,0.00103226006578,1.0,0.0,100.0,True,100.0,100.0 +Gas 4,4,129.361414837,gas,PQ,0.352877753386,5.63438201237,0.85,0.02,0.0,True,0.0,0.0 +Wind 5,5,2211.628116,wind,PQ,1.0,0.0067883553294,1.0,0.0,100.0,True,100.0,4284.34239127 +Gas 5,5,149.157315928,gas,PQ,0.350533625451,3.06218663749,0.85,0.02,0.0,True,0.0,0.0 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/global_constraints.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/global_constraints.csv new file mode 100644 index 0000000000000000000000000000000000000000..1db0653580334d5de5748c8b7d285bbb3d8de1ca --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/global_constraints.csv @@ -0,0 +1,2 @@ +name,sense,constant,mu +co2_limit,<=,1000.0,394.4658709 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/lines-p0.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/lines-p0.csv new file mode 100644 index 0000000000000000000000000000000000000000..a016842e55c73de1cb53faea49c0579c2cecff78 --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/lines-p0.csv @@ -0,0 +1,13 @@ +name,0,1,2,3,4,5 +2015-01-01 00:00:00,37.5227702392,485.668053485,-107.003330324,-1176.87017036,440.319604853,106.154989376 +2015-01-01 01:00:00,70.3296340567,193.864170164,-200.0,-235.068332979,-1082.50879274,1129.04186638 +2015-01-01 02:00:00,70.3443828404,140.088632137,-200.0,-1008.17890461,206.101771794,252.887699644 +2015-01-01 03:00:00,70.0917483113,1061.21935133,-200.0,306.720093183,-1497.14358113,1280.76133968 +2015-01-01 04:00:00,70.3902871129,-27.2829292275,-200.0,-356.720650892,-877.105594546,988.681437805 +2015-01-01 05:00:00,70.2655655525,427.464333946,-200.0,95.7771956536,-1358.69804984,1243.45070095 +2015-01-01 06:00:00,70.3922169541,-34.3193229843,-200.0,80.2271298943,-1032.5194445,941.632097642 +2015-01-01 07:00:00,-0.217769337273,794.008748609,0.0,475.637701366,-1184.29786345,909.598003123 +2015-01-01 08:00:00,8.78450260401,-12.3461701155,-24.9524484156,-1155.84713739,-67.6396853805,577.597155825 +2015-01-01 09:00:00,70.2977490935,310.119929809,-200.0,5.80441760096,-1020.69648213,963.509824781 +2015-01-01 10:00:00,70.2276521165,565.700507454,-200.0,427.199066806,-1775.30708953,1490.51072937 +2015-01-01 11:00:00,70.0503228164,1212.26084246,-200.0,714.190169716,-1826.19769818,1411.16025103 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/lines-p1.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/lines-p1.csv new file mode 100644 index 0000000000000000000000000000000000000000..4e0c6117cccd99e992102734042f7bbeb301c240 --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/lines-p1.csv @@ -0,0 +1,13 @@ +name,0,1,2,3,4,5 +2015-01-01 00:00:00,-37.5227702392,-485.668053485,107.003330324,1176.87017036,-440.319604853,-106.154989376 +2015-01-01 01:00:00,-70.3296340567,-193.864170164,200.0,235.068332979,1082.50879274,-1129.04186638 +2015-01-01 02:00:00,-70.3443828404,-140.088632137,200.0,1008.17890461,-206.101771794,-252.887699644 +2015-01-01 03:00:00,-70.0917483113,-1061.21935133,200.0,-306.720093183,1497.14358113,-1280.76133968 +2015-01-01 04:00:00,-70.3902871129,27.2829292275,200.0,356.720650892,877.105594546,-988.681437805 +2015-01-01 05:00:00,-70.2655655525,-427.464333946,200.0,-95.7771956536,1358.69804984,-1243.45070095 +2015-01-01 06:00:00,-70.3922169541,34.3193229843,200.0,-80.2271298943,1032.5194445,-941.632097642 +2015-01-01 07:00:00,0.217769337273,-794.008748609,-0.0,-475.637701366,1184.29786345,-909.598003123 +2015-01-01 08:00:00,-8.78450260401,12.3461701155,24.9524484156,1155.84713739,67.6396853805,-577.597155825 +2015-01-01 09:00:00,-70.2977490935,-310.119929809,200.0,-5.80441760096,1020.69648213,-963.509824781 +2015-01-01 10:00:00,-70.2276521165,-565.700507454,200.0,-427.199066806,1775.30708953,-1490.51072937 +2015-01-01 11:00:00,-70.0503228164,-1212.26084246,200.0,-714.190169716,1826.19769818,-1411.16025103 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/lines.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/lines.csv new file mode 100644 index 0000000000000000000000000000000000000000..3918945ea013a5c00f7778b303b2cdab8ad672dd --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/lines.csv @@ -0,0 +1,7 @@ +name,bus0,bus1,capital_cost,s_nom,s_nom_extendable,s_nom_opt,x,v_nom +0,0,1,0.144064898688,0.0,True,70.3922169541,0.417022004703,380.0 +1,1,2,0.0604665145264,0.0,True,1212.26084246,0.000114374817345,380.0 +2,2,0,0.0184677189538,200.0,False,200.0,0.146755890817,380.0 +3,3,4,0.0691121454086,0.0,True,1176.87017036,0.186260211378,380.0 +4,4,5,0.107763346801,0.0,True,1826.19769818,0.396767474231,380.0 +5,5,3,0.137043900079,0.0,True,1490.51072937,0.419194514403,380.0 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/links-p0.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/links-p0.csv new file mode 100644 index 0000000000000000000000000000000000000000..35567b60c21f2126edd2e731c06d10c25f15f9fe --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/links-p0.csv @@ -0,0 +1,13 @@ +name,TL 0,TL 1 +2015-01-01 00:00:00,-680.226374543,-270.0 +2015-01-01 01:00:00,-858.666524517,-270.0 +2015-01-01 02:00:00,-1133.31511543,-270.0 +2015-01-01 03:00:00,-619.197651437,-270.0 +2015-01-01 04:00:00,-1070.88633213,-270.0 +2015-01-01 05:00:00,-575.354756515,-270.0 +2015-01-01 06:00:00,-678.62521097,-270.0 +2015-01-01 07:00:00,55.0836832383,-208.944672608 +2015-01-01 08:00:00,-849.348812594,-270.0 +2015-01-01 09:00:00,-341.362702065,-270.0 +2015-01-01 10:00:00,-945.636809665,-270.0 +2015-01-01 11:00:00,-181.575225587,-231.062358345 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/links-p1.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/links-p1.csv new file mode 100644 index 0000000000000000000000000000000000000000..2f23ad4c1587d82eab3fda3c0673075596bfab41 --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/links-p1.csv @@ -0,0 +1,13 @@ +name,TL 0,TL 1 +2015-01-01 00:00:00,680.226374543,270.0 +2015-01-01 01:00:00,858.666524517,270.0 +2015-01-01 02:00:00,1133.31511543,270.0 +2015-01-01 03:00:00,619.197651437,270.0 +2015-01-01 04:00:00,1070.88633213,270.0 +2015-01-01 05:00:00,575.354756515,270.0 +2015-01-01 06:00:00,678.62521097,270.0 +2015-01-01 07:00:00,-55.0836832383,208.944672608 +2015-01-01 08:00:00,849.348812594,270.0 +2015-01-01 09:00:00,341.362702065,270.0 +2015-01-01 10:00:00,945.636809665,270.0 +2015-01-01 11:00:00,181.575225587,231.062358345 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/links.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/links.csv new file mode 100644 index 0000000000000000000000000000000000000000..8ae9e4c3b55b7ebf2313b3ce2a52b1dd94de2b35 --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/links.csv @@ -0,0 +1,3 @@ +name,bus0,bus1,capital_cost,p_max_pu,p_min_pu,p_nom,p_nom_extendable,p_nom_opt +TL 0,0,3,0.0408904499463,0.9,-0.9,1000.0,True,1259.23901715 +TL 1,1,4,0.175623487278,0.9,-0.9,300.0,False,300.0 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/loads-p.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/loads-p.csv new file mode 100644 index 0000000000000000000000000000000000000000..e2cf39e284aac70af88bc3cffebb74238c43b68a --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/loads-p.csv @@ -0,0 +1,13 @@ +name,0,1,2,3,4,5 +2015-01-01 00:00:00,574.117605492,146.728574906,589.305536903,699.758360021,102.334428828,414.05598782 +2015-01-01 01:00:00,694.400157728,414.179269527,49.9534589461,535.896405916,663.79464522,514.889112058 +2015-01-01 02:00:00,944.594755991,586.555040502,903.401915288,137.474704146,139.276347251,807.39128871 +2015-01-01 03:00:00,397.676836986,165.354197117,927.508580396,347.765859746,750.812103136,725.99798535 +2015-01-01 04:00:00,883.306091206,623.672207056,750.942434027,348.898341978,269.927891765,895.886218196 +2015-01-01 05:00:00,428.091189871,964.840047148,663.441497818,621.695720209,114.745972953,949.489258707 +2015-01-01 06:00:00,449.91213348,578.389614387,408.136802761,237.026980243,903.379520562,573.679486672 +2015-01-01 07:00:00,2.87032703116,617.144913621,326.644901772,527.058102258,885.942099311,357.269760002 +2015-01-01 08:00:00,908.53515092,623.360115792,15.8212428466,929.437233744,690.896917517,997.322850451 +2015-01-01 09:00:00,172.340508345,137.135749629,932.595463037,696.81816149,66.0001727221,755.463052602 +2015-01-01 10:00:00,753.876188461,923.024535546,711.524758628,124.270961972,19.8801338398,26.2109868777 +2015-01-01 11:00:00,28.3064880208,246.211067603,860.027948683,538.831064342,552.821978686,842.03089236 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/loads-p_set.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/loads-p_set.csv new file mode 100644 index 0000000000000000000000000000000000000000..e2cf39e284aac70af88bc3cffebb74238c43b68a --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/loads-p_set.csv @@ -0,0 +1,13 @@ +name,0,1,2,3,4,5 +2015-01-01 00:00:00,574.117605492,146.728574906,589.305536903,699.758360021,102.334428828,414.05598782 +2015-01-01 01:00:00,694.400157728,414.179269527,49.9534589461,535.896405916,663.79464522,514.889112058 +2015-01-01 02:00:00,944.594755991,586.555040502,903.401915288,137.474704146,139.276347251,807.39128871 +2015-01-01 03:00:00,397.676836986,165.354197117,927.508580396,347.765859746,750.812103136,725.99798535 +2015-01-01 04:00:00,883.306091206,623.672207056,750.942434027,348.898341978,269.927891765,895.886218196 +2015-01-01 05:00:00,428.091189871,964.840047148,663.441497818,621.695720209,114.745972953,949.489258707 +2015-01-01 06:00:00,449.91213348,578.389614387,408.136802761,237.026980243,903.379520562,573.679486672 +2015-01-01 07:00:00,2.87032703116,617.144913621,326.644901772,527.058102258,885.942099311,357.269760002 +2015-01-01 08:00:00,908.53515092,623.360115792,15.8212428466,929.437233744,690.896917517,997.322850451 +2015-01-01 09:00:00,172.340508345,137.135749629,932.595463037,696.81816149,66.0001727221,755.463052602 +2015-01-01 10:00:00,753.876188461,923.024535546,711.524758628,124.270961972,19.8801338398,26.2109868777 +2015-01-01 11:00:00,28.3064880208,246.211067603,860.027948683,538.831064342,552.821978686,842.03089236 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/loads.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/loads.csv new file mode 100644 index 0000000000000000000000000000000000000000..1e67ea670df061397822e2e9d46e1a497a740431 --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/loads.csv @@ -0,0 +1,7 @@ +name,bus +0,0 +1,1 +2,2 +3,3 +4,4 +5,5 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/network.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/network.csv new file mode 100644 index 0000000000000000000000000000000000000000..2c9ac956b7dcf41703a7fe7cc43bd1569cc28b05 --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/network.csv @@ -0,0 +1,2 @@ +name,max_memusage,now,objective,opf_keep_files,pypsa_version,srid +Test 6 bus,157124,now,14670508.8254,True,0.10.0,4326 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/snapshots.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/snapshots.csv new file mode 100644 index 0000000000000000000000000000000000000000..9520251d71eba52611b0736e52ac1f814d417f5b --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/snapshots.csv @@ -0,0 +1,13 @@ +name,weightings +2015-01-01 00:00:00,3.0 +2015-01-01 01:00:00,3.0 +2015-01-01 02:00:00,3.0 +2015-01-01 03:00:00,3.0 +2015-01-01 04:00:00,3.0 +2015-01-01 05:00:00,3.0 +2015-01-01 06:00:00,3.0 +2015-01-01 07:00:00,3.0 +2015-01-01 08:00:00,3.0 +2015-01-01 09:00:00,3.0 +2015-01-01 10:00:00,3.0 +2015-01-01 11:00:00,3.0 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units-inflow.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units-inflow.csv new file mode 100644 index 0000000000000000000000000000000000000000..b05a2c657d705b9f898c79bfd0cc4b7b1a0ddb22 --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units-inflow.csv @@ -0,0 +1,13 @@ +name,Storage 0 +2015-01-01 00:00:00,20.0 +2015-01-01 01:00:00,20.0 +2015-01-01 02:00:00,20.0 +2015-01-01 03:00:00,20.0 +2015-01-01 04:00:00,20.0 +2015-01-01 05:00:00,20.0 +2015-01-01 06:00:00,20.0 +2015-01-01 07:00:00,20.0 +2015-01-01 08:00:00,20.0 +2015-01-01 09:00:00,20.0 +2015-01-01 10:00:00,20.0 +2015-01-01 11:00:00,20.0 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units-p.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units-p.csv new file mode 100644 index 0000000000000000000000000000000000000000..2396d96bae82fcd9c283ae1102b5afd67146371a --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units-p.csv @@ -0,0 +1,13 @@ +name,Storage 0,Storage 1,Storage 2,Storage 3,Storage 4,Storage 5 +2015-01-01 00:00:00,19.0,-25.0,-91.2297376241,0.0,1933.42118211,0.0 +2015-01-01 01:00:00,19.0,-25.0,-464.97649056,-8.33333333333,0.0,-474.485154231 +2015-01-01 02:00:00,19.0,25.0,554.325629724,-2.41113025316,1619.1018358,393.635562419 +2015-01-01 03:00:00,19.0,0.0,-417.668318247,-8.33333333333,-790.248999142,-640.240078532 +2015-01-01 04:00:00,19.0,0.0,540.376501569,0.0,0.0,270.932553889 +2015-01-01 05:00:00,19.0,0.0,0.0,0.0,-1131.72484438,0.0 +2015-01-01 06:00:00,19.0,14.0618571886,231.952804187,5.61271237124,0.0,110.676702574 +2015-01-01 07:00:00,19.0,-25.0,-554.325629724,0.0,-620.143614806,-742.102271435 +2015-01-01 08:00:00,19.0,0.0,-6.73526061775,8.33333333333,1986.13261882,742.102271435 +2015-01-01 09:00:00,19.0,20.1241381934,383.428268442,0.0,-709.844155271,0.0 +2015-01-01 10:00:00,19.0,0.0,-93.6702643991,0.0,-1986.13261882,-16.2462597984 +2015-01-01 11:00:00,19.0,0.0,-554.325629724,0.0,-1818.18136656,13.4779888389 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units-spill.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units-spill.csv new file mode 100644 index 0000000000000000000000000000000000000000..5471db54008aec1d971f56547240cfdd9bd1ac09 --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units-spill.csv @@ -0,0 +1,13 @@ +name,Storage 0 +2015-01-01 00:00:00,0.0 +2015-01-01 01:00:00,0.0 +2015-01-01 02:00:00,0.0 +2015-01-01 03:00:00,0.0 +2015-01-01 04:00:00,0.0 +2015-01-01 05:00:00,0.0 +2015-01-01 06:00:00,0.0 +2015-01-01 07:00:00,0.0 +2015-01-01 08:00:00,0.0 +2015-01-01 09:00:00,0.0 +2015-01-01 10:00:00,0.0 +2015-01-01 11:00:00,1.17776221233e-11 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units-state_of_charge.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units-state_of_charge.csv new file mode 100644 index 0000000000000000000000000000000000000000..caeca8d0e7224d6a84ecc0a42a47570074eddfae --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units-state_of_charge.csv @@ -0,0 +1,13 @@ +name,Storage 0,Storage 1,Storage 2,Storage 3,Storage 4,Storage 5 +2015-01-01 00:00:00,0.0,67.5,2133.89610195,0.0,5457.31438837,0.0 +2015-01-01 01:00:00,-1.24770075047e-12,132.9951825,3325.95377834,22.5,5295.22669373,1281.10991642 +2015-01-01 02:00:00,0.0,50.0977241635,1476.66763657,28.3417791835,25.0,0.0 +2015-01-01 03:00:00,-2.15020568677e-11,48.6097716581,2560.51359036,50.0,2157.92977268,1728.64821204 +2015-01-01 04:00:00,-4.99421777219e-12,47.1660128301,778.011665998,48.51495,2093.8371005,821.729145525 +2015-01-01 05:00:00,1.54073411388e-11,45.765135083,754.903941506,47.07400747,5087.3051246,797.322968174 +2015-01-01 06:00:00,2.20463920539e-12,0.0,0.0,27.9515075176,4936.20707509,424.136302146 +2015-01-01 07:00:00,-2.97987307221e-12,67.5,1496.67920025,27.1213197929,6463.98454873,2415.21516271 +2015-01-01 08:00:00,0.0,65.4951825,1470.411535,0.0,0.0,0.0 +2015-01-01 09:00:00,0.0,0.0,215.912731127,0.0,1916.57921923,0.0 +2015-01-01 10:00:00,2.30866862472e-11,0.0,462.409620977,0.0,7222.21297066,43.8649014557 +2015-01-01 11:00:00,0.0,0.0,1945.35479308,0.0,11916.7957129,0.0 diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units-state_of_charge_set.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units-state_of_charge_set.csv new file mode 100644 index 0000000000000000000000000000000000000000..e8abbcaecbb3d4472946bc2df0858a96ff75d269 --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units-state_of_charge_set.csv @@ -0,0 +1,13 @@ +name,Storage 3,Storage 4 +2015-01-01 00:00:00,, +2015-01-01 01:00:00,, +2015-01-01 02:00:00,,25.0 +2015-01-01 03:00:00,50.0, +2015-01-01 04:00:00,, +2015-01-01 05:00:00,, +2015-01-01 06:00:00,, +2015-01-01 07:00:00,, +2015-01-01 08:00:00,, +2015-01-01 09:00:00,, +2015-01-01 10:00:00,, +2015-01-01 11:00:00,, diff --git a/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units.csv b/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units.csv new file mode 100644 index 0000000000000000000000000000000000000000..7e0bac150533b7744a7469e1cb0b66fc4a4a830a --- /dev/null +++ b/PyPSA/source/test/data/storage-hvdc/results-lopf/storage_units.csv @@ -0,0 +1,7 @@ +name,bus,capital_cost,carrier,cyclic_state_of_charge,efficiency_dispatch,efficiency_store,marginal_cost,max_hours,p_nom,p_nom_extendable,p_nom_opt,standing_loss +Storage 0,0,670.467510178,battery,False,0.95,0.9,0.109550372792,6.0,0.0,True,19.0,0.01 +Storage 1,1,313.424178159,battery,False,0.95,0.9,3.87304630288,6.0,25.0,False,25.0,0.01 +Storage 2,2,878.142503429,battery,True,0.95,0.9,0.679321678258,6.0,0.0,True,554.325629724,0.01 +Storage 3,3,686.500927682,battery,False,0.95,0.9,1.26206252402,6.0,0.0,True,8.33333333333,0.01 +Storage 4,4,789.279328451,battery,True,0.95,0.9,1.12177596826,6.0,0.0,True,1986.13261882,0.01 +Storage 5,5,19.3669578703,battery,False,0.95,0.9,0.520114288473,6.0,0.0,True,742.102271435,0.01 diff --git a/PyPSA/source/test/test_bugs.py b/PyPSA/source/test/test_bugs.py new file mode 100644 index 0000000000000000000000000000000000000000..5e9172fed21a9ddbcfaf570aa2311bf336a6d410 --- /dev/null +++ b/PyPSA/source/test/test_bugs.py @@ -0,0 +1,263 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pickle +from pathlib import Path + +import numpy as np +import pandas as pd +import pytest +from numpy.testing import assert_array_almost_equal as almost_equal + +import pypsa + +try: + import openpyxl # noqa: F401 + import python_calamine # noqa: F401 + + excel_installed = True +except ImportError: + excel_installed = False + + +def test_1144(): + """ + See https://github.com/PyPSA/PyPSA/issues/1144. + """ + n = pypsa.examples.ac_dc_meshed() + n.c.generators.static["build_year"] = [2020, 2020, 2030, 2030, 2040, 2040] + n.investment_periods = [2020, 2030, 2040] + capacity = n.statistics.installed_capacity(components="Generator") + assert capacity[2020].sum() < capacity[2030].sum() < capacity[2040].sum() + + +def test_890(): + """ + See https://github.com/PyPSA/PyPSA/issues/890. + """ + n = pypsa.examples.scigrid_de() + n.calculate_dependent_values() + + n.c.lines.static = n.c.lines.static.reindex( + columns=n.components["Line"]["defaults"].index[1:] + ) + n.c.lines.static["type"] = np.nan + n.c.buses.static = n.c.buses.static.reindex( + columns=n.components["Bus"]["defaults"].index[1:] + ) + n.c.buses.static["frequency"] = 50 + + n.set_investment_periods([2020, 2030]) + + weighting = pd.Series(1, n.c.buses.static.index) + busmap = n.cluster.busmap_by_kmeans(bus_weightings=weighting, n_clusters=50) + nc = n.cluster.cluster_by_busmap(busmap) + + C = n.cluster.get_clustering_from_busmap(busmap) + nc = C.n + + almost_equal(n.investment_periods, nc.investment_periods) + almost_equal(n.investment_period_weightings, nc.investment_period_weightings) + + +def test_331(): + """ + See https://github.com/PyPSA/PyPSA/issues/331. + """ + n = pypsa.Network() + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=10) + n.add("Generator", "generator1", bus="bus", p_nom=15, marginal_cost=10) + n.optimize() + n.add("Generator", "generator2", bus="bus", p_nom=5, marginal_cost=5) + n.optimize() + assert "generator2" in n.c.generators.dynamic.p + + +def test_nomansland_bus(caplog): + n = pypsa.Network() + n.add("Bus", "bus") + + n.add("Load", "load", bus="bus", p_set=10) + n.add("Generator", "generator1", bus="bus", p_nom=15, marginal_cost=10) + + n.consistency_check() + assert "The following buses have no attached components" not in caplog.text, ( + "warning should not trigger..." + ) + + n.add("Bus", "extrabus") + + n.consistency_check() + assert "The following buses have no attached components" in caplog.text, ( + "warning is not working..." + ) + + n.optimize() + + +def test_515(): + """ + Time-varying marginal costs removed. + See https://github.com/PyPSA/PyPSA/issues/515. + """ + marginal_costs = [0, 10] + + n = pypsa.Network() + n.set_snapshots(range(2)) + + n.add("Bus", "bus") + n.add("Generator", "gen", bus="bus", p_nom=1, marginal_cost=marginal_costs) + n.add("Load", "load", bus="bus", p_set=1) + + n.optimize() + + assert n.objective == 10 + + +def test_779(): + """ + Importing from xarray dataset. + See https://github.com/PyPSA/PyPSA/issues/779. + """ + n1 = pypsa.Network() + n1.add("Bus", "bus") + xarr = n1.export_to_netcdf() + n2 = pypsa.Network() + n2.import_from_netcdf(xarr) + + +def test_multiport_assignment_defaults_single_add(): + """ + Add a single link to a network, then add a second link with additional + ports. + + Check that the default values are assigned to the first link. + """ + n = pypsa.Network() + n.add("Bus", "bus") + n.add("Bus", "bus2") + n.add("Link", "link", bus0="bus", bus1="bus2") + n.add("Link", "link2", bus0="bus", bus1="bus2", bus2="bus") + assert n.c.links.static.loc["link", "bus2"] == "" + + +def test_multiport_assignment_defaults_multiple_add(): + """ + Add a single link to a network, then add a second link with additional + ports. + + Check that the default values are assigned to the first link. + """ + n = pypsa.Network() + n.add("Bus", "bus") + n.add("Bus", "bus2") + n.add("Link", ["link"], bus0="bus", bus1="bus2") + n.add("Link", ["link2"], bus0="bus", bus1="bus2", bus2="bus") + assert n.c.links.static.loc["link", "bus2"] == "" + + +@pytest.mark.skipif(not excel_installed, reason="openpyxl not installed") +def test_1268(tmpdir): + """ + Excel import without snapshots sheet should not raise KeyError. + See https://github.com/PyPSA/PyPSA/issues/1268. + """ + fn = str(tmpdir / "no_snapshots.xlsx") + + buses = pd.DataFrame({"v_nom": [132]}, index=["bus1"]) + with pd.ExcelWriter(fn, engine="openpyxl") as writer: + buses.to_excel(writer, sheet_name="buses") + + n = pypsa.Network() + n.import_from_excel(fn) + assert len(n.c.buses.static) == 1 + + +def test_1319(): + """ + Copying a solved network should work after setting solver_model to None. + See https://github.com/PyPSA/PyPSA/issues/1319. + """ + n = pypsa.examples.ac_dc_meshed() + n.optimize() + + # Should raise error when trying to copy with solver_model attached + with pytest.raises( + ValueError, match="Copying a solved network with an attached solver model" + ): + n.copy() + + # Should work after setting solver_model to None + n.model.solver_model = None + n_copy = n.copy() # Should not raise an error + assert n_copy is not n + assert len(n_copy.buses) == len(n.c.buses.static) + + +def test_1411(): + """ + Investment periods should sync when setting snapshots with MultiIndex. + See https://github.com/PyPSA/PyPSA/issues/1411. + """ + # Test 1: Setting MultiIndex snapshots directly on fresh network + n = pypsa.Network() + n.add("Bus", "bus0") + timesteps = pd.date_range("2013-03-01", periods=3, freq="D") + snapshots = pd.MultiIndex.from_product( + [[2020, 2030], timesteps], names=["period", "timestep"] + ) + n.set_snapshots(snapshots) + + # Check that _investment_periods_data is synchronized with both periods + assert n._investment_periods_data.index.tolist() == [2020, 2030] + + # Test 2: Extending snapshots to add new period + n2 = pypsa.Network() + n2.set_snapshots(pd.date_range("2013-03-01", periods=3, freq="D")) + n2.add("Bus", "bus0") + + # Convert to multi-period with period 0 + snapshots_multi = pd.MultiIndex.from_product( + [[0], n2.snapshots], names=["period", "timestep"] + ) + n2.set_snapshots(snapshots_multi) + n2.set_investment_periods([0]) + + # Check that after set_investment_periods, data is synchronized + assert n2._investment_periods_data.index.tolist() == [0] + + # Extend snapshots to add period 2040 + new_snapshots = pd.date_range("2013-03-01", periods=3, freq="D") + extended = pd.MultiIndex.from_tuples( + list(n2.snapshots) + [(2040, t) for t in new_snapshots], + names=["period", "timestep"], + ) + n2.set_snapshots(extended) + + # Check that _investment_periods_data is synchronized + assert n2._investment_periods_data.index.tolist() == [0, 2040] + + +def test_1420(tmp_path): + """ + Network pickling should not cause RecursionError in xarray accessor. + See https://github.com/PyPSA/PyPSA/issues/1420. + """ + n = pypsa.Network() + n.add("Bus", "bus") + n.add("Generator", "gen", bus="bus", p_nom=100) + + pickle_file = tmp_path / "network.pkl" + + with Path(pickle_file).open("wb") as out: + pickle.dump(n, out) + + with Path(pickle_file).open("rb") as inp: + n_loaded = pickle.load(inp) + + # Verify network was loaded correctly + assert len(n_loaded.c.buses.static) == 1 + assert len(n_loaded.c.generators.static) == 1 + # tmp_path is automatically cleaned up by pytest diff --git a/PyPSA/source/test/test_collection.py b/PyPSA/source/test/test_collection.py new file mode 100644 index 0000000000000000000000000000000000000000..2eeed0fd4a86ea47492e2d654740ffe995b00dab --- /dev/null +++ b/PyPSA/source/test/test_collection.py @@ -0,0 +1,394 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pandas as pd +import pytest + +import pypsa + + +@pytest.fixture +def network1(): + n = pypsa.Network() + n.add("Carrier", "wind") + n.add("Carrier", "gas") # Add gas carrier for consistency if needed later + n.add("Bus", "bus1") + n.add( + "Generator", + "gen_wind", + bus="bus1", + carrier="wind", + p_nom=100, + capital_cost=1000, + ) # Added capital_cost + return n + + +@pytest.fixture +def network2(): + n = pypsa.Network() + n.add("Carrier", "solar") + n.add("Carrier", "gas", co2_emissions=0.2) + n.add("Bus", "bus2") + n.add( + "Generator", + "gen_solar", + bus="bus2", + carrier="solar", + p_nom=50, + capital_cost=800, + ) # Added capital_cost + n.add( + "Generator", "gen_gas", bus="bus2", carrier="gas", p_nom=200, capital_cost=500 + ) # Added capital_cost + return n + + +@pytest.fixture +def network3(): + n = pypsa.Network() + n.add("Carrier", "hydro") + n.add("Bus", "bus3") + n.add("StorageUnit", "storage_hydro", bus="bus3", carrier="hydro", p_nom=80) + return n + + +@pytest.fixture +def collection(network1, network2): + """Fixture to create a NetworkCollection with two networks.""" + # Give networks unique names + network1.name = "net1" + network2.name = "net2" + return pypsa.NetworkCollection([network1, network2], index=["net1", "net2"]) + + +class TestNetworkCollectionInit: + def test_collection_init_list(self, network1, network2): + """Test initialization with a list of networks.""" + # Give networks unique names + network1.name = "net1" + network2.name = "net2" + networks = [network1, network2] + collection = pypsa.NetworkCollection(networks) + assert len(collection) == 2 + assert isinstance(collection.networks, pd.Series) + assert collection.networks.index.equals( + pd.Index(["net1", "net2"], name="network") + ) + assert collection["net1"] == network1 + assert collection["net2"] == network2 + + def test_collection_init_list_with_index(self, network1, network2): + """Test initialization with a list and custom index.""" + networks = [network1, network2] + custom_index = pd.Index(["net_A", "net_B"], name="scenario") + collection = pypsa.NetworkCollection(networks, index=custom_index) + assert len(collection) == 2 + assert collection.networks.index.equals(custom_index) + assert collection["net_A"] == network1 + assert collection["net_B"] == network2 + + def test_collection_initi_list_with_multiindex(self, network1, network2): + """Test initialization with a list and MultiIndex.""" + networks = [network1, network2] + multi_index = pd.MultiIndex.from_tuples( + [("base", 2030), ("high_renewables", 2030)], names=["scenario", "year"] + ) + collection = pypsa.NetworkCollection(networks, index=multi_index) + assert len(collection) == 2 + assert collection.networks.index.equals(multi_index) + assert "network" not in collection.networks.index.names + assert collection[("base", 2030)] == network1 + assert collection[("high_renewables", 2030)] == network2 + + def test_collection_init_series(self, network1, network2): + """Test initialization with a pandas Series.""" + networks_series = pd.Series([network1, network2], index=["net_A", "net_B"]) + collection = pypsa.NetworkCollection(networks_series) + assert len(collection) == 2 + assert isinstance(collection.networks, pd.Series) + assert collection.networks.index.equals( + pd.Index(["net_A", "net_B"], name="network") + ) + assert collection["net_A"] == network1 + assert collection["net_B"] == network2 + + def test_collection_init_series_with_multiindex(self, network1, network2): + """Test initialization with a pandas Series.""" + index = pd.MultiIndex.from_tuples( + [("base", 2030), ("high_renewables", 2030)], names=["scenario", "year"] + ) + networks_series = pd.Series([network1, network2], index=index) + collection = pypsa.NetworkCollection(networks_series) + assert len(collection) == 2 + assert collection.networks.index.equals(index) + assert collection[("base", 2030)] == network1 + assert collection[("high_renewables", 2030)] == network2 + + def test_collection_init_invalid_type(self): + """Test initialization with invalid types.""" + with pytest.raises(TypeError): + pypsa.NetworkCollection([pypsa.Network(), 123]) + with pytest.raises(TypeError): + pypsa.NetworkCollection(pd.Series([pypsa.Network(), 5])) + with pytest.raises(TypeError): + pypsa.NetworkCollection("single_string") + + def test_collection_init_duplicate_names(self): + """Test that duplicate network names raise an error.""" + # Create networks with duplicate names + n1 = pypsa.Network(name="base") + n2 = pypsa.Network(name="base") + n3 = pypsa.Network(name="scenario") + + with pytest.raises( + ValueError, match="Duplicate network names found: \\['base'\\]" + ): + pypsa.NetworkCollection([n1, n2, n3]) + + # Test with default names (empty name) + n1 = pypsa.Network() + n2 = pypsa.Network() + + with pytest.raises( + ValueError, match="Duplicate network names found: \\['Unnamed Network'\\]" + ): + pypsa.NetworkCollection([n1, n2]) + + # Should work with custom index even if names are duplicated + collection = pypsa.NetworkCollection([n1, n2], index=["net_A", "net_B"]) + assert len(collection) == 2 + + def test_collection_init_index_mismatch(self, network1, network2): + """Test initialization with mismatched index length.""" + with pytest.raises(ValueError): + pypsa.NetworkCollection([network1, network2], index=pd.Index(["A"])) + + +def test_collection_index_names(network1, network2): + """Test the index names of the NetworkCollection object.""" + # Give networks unique names + network1.name = "net1" + network2.name = "net2" + networks = [network1, network2] + collection = pypsa.NetworkCollection(networks) + assert collection.index.names == ["network"] + assert collection.networks.index.name == "network" + assert collection._index_names == ["network"] + + # Test custom single index overwrite + custom_index = pd.Index(["net_A", "net_B"], name="scenario") + collection_custom = pypsa.NetworkCollection(networks, index=custom_index) + assert collection_custom.index.names == ["scenario"] + assert collection_custom.networks.index.name == "scenario" + assert collection_custom._index_names == ["scenario"] + + # test with multiindex + multi_index = pd.MultiIndex.from_tuples( + [("base", 2030), ("high_renewables", 2030)], names=["scenario", "year"] + ) + collection_multi = pypsa.NetworkCollection(networks, index=multi_index) + assert collection_multi.index.names == ["scenario", "year"] + assert collection_multi._index_names == ["scenario", "year"] + + +def test_collection_not_implemented_members(network1, network2): + """Test that not implemented members raise NotImplementedError.""" + # Give networks unique names + network1.name = "net1" + network2.name = "net2" + collection = pypsa.NetworkCollection([network1, network2]) + with pytest.raises(NotImplementedError): + collection.add("Generator", "gen", bus="bus1", p_nom=100) + with pytest.raises(NotImplementedError): + collection.remove("Generator", "gen") + with pytest.raises(NotImplementedError): + collection.set_snapshots([0, 1, 2]) + + +def test_collection_getitem_slice(network1, network2, network3): + """Test slicing the NetworkCollection object.""" + # Give networks unique names + network1.name = "net1" + network2.name = "net2" + network3.name = "net3" + networks = [network1, network2, network3] + collection = pypsa.NetworkCollection(networks) + sliced_networks = collection[1:] + assert isinstance(sliced_networks, pypsa.NetworkCollection) + assert len(sliced_networks) == 2 + assert sliced_networks["net2"] == network2 # Original index 1 + assert sliced_networks["net3"] == network3 # Original index 2 + pd.testing.assert_index_equal( + sliced_networks.networks.index, + pd.Index(["net2", "net3"], name="network"), + ) + + +def test_collection_iteration(network1, network2): + """Test iterating over the NetworkCollection object.""" + # Give networks unique names + network1.name = "net1" + network2.name = "net2" + networks = [network1, network2] + collection = pypsa.NetworkCollection(networks) + iterated_list = list(collection) + assert iterated_list == networks + + +def test_collection_dynamic_data(network1, network2): + """Test dynamic data access.""" + # Give networks unique names + network1.name = "net1" + network2.name = "net2" + network1.snapshots = [1, 2] + network2.snapshots = [1, 2] + network1.add("Generator", "dyn-gen", bus="bus1", p_min_pu=[0, 1]) + network2.add("Generator", "dyn-gen", bus="bus1", p_min_pu=[0, 1]) + collection = pypsa.NetworkCollection( + [network1, network2], index=pd.Index(["net1", "net2"], name="scenario") + ) + + assert collection.c.generators.dynamic.p_min_pu["net1"].equals( + network1.c.generators.dynamic.p_min_pu + ) + assert "scenario" in collection.c.generators.dynamic.p_min_pu.columns.names + + +def test_collection_statistics_nonexistent_method(network1): + """Test calling a method that doesn't exist on the accessor.""" + # Give network a unique name + network1.name = "net1" + collection = pypsa.NetworkCollection([network1]) + with pytest.raises( + AttributeError, + match="Only members as they are defined in any Network class can be accessed.", + ): + collection.nonexistent_method() + + +def test_collection_repr(network1, network2, network3): + """Test the string representation of NetworkCollection.""" + # Test with networks having unique names + network1.name = "net1" + network2.name = "net2" + network3.name = "net3" + + collection = pypsa.NetworkCollection([network1, network2]) + repr_str = repr(collection) + assert "Networks: 2" in repr_str + assert "Index name: 'network'" in repr_str + assert "Entries: ['net1', 'net2']" in repr_str + + # Test with custom index + custom_index = pd.Index(["net_A", "net_B", "net_C"], name="scenario") + collection = pypsa.NetworkCollection( + [network1, network2, network3], index=custom_index + ) + repr_str = repr(collection) + assert "Networks: 3" in repr_str + assert "Index name: 'scenario'" in repr_str + assert "Entries: ['net_A', 'net_B', 'net_C']" in repr_str + + # Test with MultiIndex + multi_index = pd.MultiIndex.from_tuples( + [("base", 2030), ("high_renewables", 2030)], names=["scenario", "year"] + ) + collection = pypsa.NetworkCollection([network1, network2], index=multi_index) + repr_str = repr(collection) + assert "Networks: 2" in repr_str + assert "MultiIndex with 2 levels: ['scenario', 'year']" in repr_str + assert "First 2 entries: [('base', 2030), ('high_renewables', 2030)]" in repr_str + + # Test with many networks (to check truncation) + many_networks = [pypsa.Network(name=f"net_{i}") for i in range(10)] + collection = pypsa.NetworkCollection(many_networks) + repr_str = repr(collection) + assert "Networks: 10" in repr_str + assert "... and 5 more" in repr_str + + +def test_collection_init_with_strings(): + """Test initialization with string paths.""" + # Use example networks from the examples directory + example_path1 = "examples/networks/ac-dc-meshed/ac-dc-meshed.nc" + example_path2 = "examples/networks/scigrid-de/scigrid-de.nc" + + # Test with list of strings + collection = pypsa.NetworkCollection([example_path1, example_path2]) + assert len(collection) == 2 + assert all(isinstance(n, pypsa.Network) for n in collection.networks) + + # Test with pandas Series containing strings + networks_series = pd.Series([example_path1, example_path2], index=["net1", "net2"]) + collection_series = pypsa.NetworkCollection(networks_series) + assert len(collection_series) == 2 + assert all(isinstance(n, pypsa.Network) for n in collection_series.networks) + + +def test_collection_init_mixed_networks_and_strings(network1): + """Test initialization with mixed Network objects and strings.""" + network1.name = "manual_net" + example_path = "examples/networks/ac-dc-meshed/ac-dc-meshed.nc" + + # Test with mixed list + collection = pypsa.NetworkCollection([network1, example_path]) + assert len(collection) == 2 + assert all(isinstance(n, pypsa.Network) for n in collection.networks) + assert collection["manual_net"] == network1 + + # Test with custom index + custom_index = pd.Index(["net_A", "net_B"], name="scenario") + collection_custom = pypsa.NetworkCollection( + [network1, example_path], index=custom_index + ) + assert len(collection_custom) == 2 + assert collection_custom["net_A"] == network1 + + +def test_collection_init_empty_string(): + """Test initialization with empty string (creates empty network).""" + collection = pypsa.NetworkCollection([""]) + assert len(collection) == 1 + assert isinstance(collection.networks.iloc[0], pypsa.Network) + + +class TestCollectionComponents: + """Test the components property of NetworkCollection.""" + + def test_static_data(self, component_name): + new_api = pypsa.options.api.new_components_api + + # Give networks unique names + network1 = pypsa.examples.ac_dc_meshed() + network2 = pypsa.examples.ac_dc_meshed() + network1.name = "net1" + network2.name = "net2" + collection = pypsa.NetworkCollection([network1, network2]) + static_data = ( + getattr(collection, component_name) + if not new_api + else getattr(collection, component_name).static + ) + static_network_data = ( + getattr(network1, component_name) + if not new_api + else getattr(network1, component_name).static + ) + + if not static_data.empty: + assert static_data.loc["net1"].equals(static_network_data) + assert "name" in static_data.index.names + assert "network" in static_data.index.names + + assert static_data.equals( + getattr(collection.components, component_name).static + ) + dynamic_data = ( + getattr(collection, component_name + "_t") + if not new_api + else getattr(collection, component_name).dynamic + ) + for key, value in dynamic_data.items(): + assert dynamic_data[key].equals(value) diff --git a/PyPSA/source/test/test_collection_statistics.py b/PyPSA/source/test/test_collection_statistics.py new file mode 100644 index 0000000000000000000000000000000000000000..8867fc01fa4d56cf1b79e3c64a6a9253094e3423 --- /dev/null +++ b/PyPSA/source/test/test_collection_statistics.py @@ -0,0 +1,495 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Tests for NetworkCollection statistics with various groupers.""" + +import pandas as pd +import pytest + +import pypsa +from pypsa.statistics.grouping import groupers + + +@pytest.fixture +def optimized_network_collection_from_ac_dc(ac_dc_network_r): + """Create a NetworkCollection from the already optimized ac_dc_network_r fixture.""" + # Create two scenarios using the same optimized network + return pypsa.NetworkCollection( + [ac_dc_network_r, ac_dc_network_r], + index=pd.Index(["base", "variant"], name="scenario"), + ) + + +@pytest.fixture +def simple_network(): + """Create a simple network with different bus carriers for tests that don't need optimization.""" + n = pypsa.Network() + n.snapshots = pd.date_range("2023-01-01", periods=3, freq="h") + + # Add carriers + n.add("Carrier", ["AC", "DC", "gas", "wind", "load"]) + + # Add buses with different carriers + n.add("Bus", ["bus_ac1", "bus_ac2", "bus_dc1"], carrier=["AC", "AC", "DC"]) + + # Add generators on different buses with costs + n.add( + "Generator", + "gas_gen", + bus="bus_ac1", + carrier="gas", + p_nom=100, + marginal_cost=50, + capital_cost=1000, + ) + n.add( + "Generator", + "wind_gen", + bus="bus_ac2", + carrier="wind", + p_nom=50, + marginal_cost=0, + capital_cost=2000, + ) + + # Add load + n.add("Load", "load1", bus="bus_ac1", p_set=30) + n.add("Load", "load2", bus="bus_dc1", p_set=20) + + # Add a link between AC and DC buses + n.add("Link", "ac_dc_link", bus0="bus_ac1", bus1="bus_dc1", p_nom=40) + + return n + + +class TestNetworkCollectionIndexValidation: + """Test index validation for NetworkCollection.""" + + def test_single_index_without_name_gets_default(self, simple_network): + """Test that single index without name gets default name.""" + n1 = simple_network.copy() + n2 = simple_network.copy() + + index = pd.Index(["a", "b"]) # No name + nc = pypsa.NetworkCollection([n1, n2], index=index) + assert nc.networks.index.name == "network" # Should get default name + + def test_multiindex_without_names_raises_error(self, simple_network): + """Test that MultiIndex without names raises ValueError.""" + networks = [simple_network.copy() for _ in range(4)] + + # Create MultiIndex without names + index = pd.MultiIndex.from_product([["a", "b"], ["1", "2"]]) + with pytest.raises( + ValueError, match="All levels of MultiIndex must have names" + ): + pypsa.NetworkCollection(networks, index=index) + + def test_multiindex_with_partial_names_raises_error(self, simple_network): + """Test that MultiIndex with partial names raises ValueError.""" + networks = [simple_network.copy() for _ in range(4)] + + # Create MultiIndex with only one name + index = pd.MultiIndex.from_product([["a", "b"], ["1", "2"]]) + index = index.set_names(["scenario", None]) + with pytest.raises( + ValueError, match="All levels of MultiIndex must have names" + ): + pypsa.NetworkCollection(networks, index=index) + + +def test_network_collection_statistics_basic(optimized_network_collection_from_ac_dc): + """Test basic statistics functionality on NetworkCollection.""" + nc = optimized_network_collection_from_ac_dc + + # Test that statistics accessor exists + assert hasattr(nc, "statistics") + + # Test basic energy balance (using the optimized network) + result = nc.statistics.energy_balance(groupby="carrier") + assert isinstance(result, pd.Series) + assert not result.empty + assert "scenario" in result.index.names + assert "carrier" in result.index.names + + +def test_network_collection_bus_carrier_grouper( + optimized_network_collection_from_ac_dc, +): + """Test bus_carrier grouper with NetworkCollection.""" + nc = optimized_network_collection_from_ac_dc + + # Test energy balance with bus_carrier grouping + result = nc.statistics.energy_balance(groupby="bus_carrier") + assert not result.empty + assert "bus_carrier" in result.index.names + assert "AC" in result.index.get_level_values("bus_carrier") + + # Energy balance should sum to approximately zero for each scenario + for scenario in ["base", "variant"]: + scenario_result = result.xs(scenario, level="scenario") + balance = scenario_result.sum() + assert abs(balance) < 1e-6, f"Energy balance not zero for {scenario}" + + +def test_network_collection_carrier_bus_carrier_grouper( + optimized_network_collection_from_ac_dc, +): + """Test combined carrier and bus_carrier grouping with NetworkCollection.""" + nc = optimized_network_collection_from_ac_dc + + # Test with list groupby + result = nc.statistics.energy_balance(groupby=["carrier", "bus_carrier"]) + assert not result.empty + assert "carrier" in result.index.names + assert "bus_carrier" in result.index.names + assert "scenario" in result.index.names + + # Check that gas generators are only on AC buses + if "gas" in result.index.get_level_values("carrier"): + gas_results = result.xs("gas", level="carrier", drop_level=False) + assert all(gas_results.index.get_level_values("bus_carrier") == "AC") + + # Check that wind generators are only on AC buses + if "wind" in result.index.get_level_values("carrier"): + wind_results = result.xs("wind", level="carrier", drop_level=False) + assert all(wind_results.index.get_level_values("bus_carrier") == "AC") + + +def test_network_collection_country_grouper(simple_network): + """Test country grouper with NetworkCollection.""" + # Add country information to buses + simple_network.c.buses.static["country"] = ["DE", "DE", "FR"] + + nc = pypsa.NetworkCollection( + [simple_network, simple_network.copy()], + index=pd.Index(["s1", "s2"], name="scenario"), + ) + + # No need to optimize for installed_capacity + # Test grouping by country with installed_capacity + result = nc.statistics.installed_capacity( + groupby=["carrier", "bus_carrier", "country"] + ) + assert not result.empty + assert "country" in result.index.names + assert "DE" in result.index.get_level_values("country") + + +def test_network_collection_location_grouper(simple_network): + """Test location grouper with NetworkCollection.""" + # Add location information to buses + simple_network.c.buses.static["location"] = ["Berlin", "Munich", "Paris"] + + nc = pypsa.NetworkCollection( + [simple_network, simple_network.copy()], + index=pd.Index(["s1", "s2"], name="scenario"), + ) + + # No need to optimize for installed_capacity + # Test grouping by location with installed_capacity + result = nc.statistics.installed_capacity( + groupby=["carrier", "bus_carrier", "location"] + ) + assert not result.empty + assert "location" in result.index.names + # Check that we have the expected locations (only those with components) + locations = result.index.get_level_values("location").unique() + assert "Berlin" in locations # bus_ac1 has gas_gen + assert "Munich" in locations # bus_ac2 has wind_gen + # Note: Paris (bus_dc1) has no generators, only loads, so won't appear in installed_capacity + + +def test_network_collection_unit_grouper(simple_network): + """Test unit grouper with NetworkCollection.""" + # Add unit information to buses + simple_network.c.buses.static["unit"] = ["MW", "MW", "MVA"] + + nc = pypsa.NetworkCollection( + [simple_network, simple_network.copy()], + index=pd.Index(["s1", "s2"], name="scenario"), + ) + + # No need to optimize for installed_capacity + # Test grouping by unit with installed_capacity + result = nc.statistics.installed_capacity(groupby=["unit"]) + assert not result.empty + assert "unit" in result.index.names + units = result.index.get_level_values("unit").unique() + assert "MW" in units # Generators are on MW buses + # Note: MVA bus has no generators, only loads, so won't appear in installed_capacity + + +def test_network_collection_multiple_groupers(optimized_network_collection_from_ac_dc): + """Test multiple groupers simultaneously with NetworkCollection.""" + nc = optimized_network_collection_from_ac_dc + + # Test with callable grouper from groupers module + result = nc.statistics.energy_balance(groupby=groupers[["carrier", "bus_carrier"]]) + assert not result.empty + assert "carrier" in result.index.names + assert "bus_carrier" in result.index.names + + # Test mixing string and callable groupers with installed_capacity (doesn't need optimization) + result2 = nc.statistics.installed_capacity( + groupby=["carrier", groupers.bus_carrier] + ) + assert not result2.empty + assert "carrier" in result2.index.names + assert "bus_carrier" in result2.index.names + + +def test_network_collection_multiindex_scenarios(ac_dc_network_r): + """Test NetworkCollection with MultiIndex scenarios.""" + # Create multiple copies of the already optimized network + networks = [ac_dc_network_r.copy() for _ in range(4)] + + # Create MultiIndex for scenarios + index = pd.MultiIndex.from_product( + [["low", "high"], [2030, 2040]], names=["demand", "year"] + ) + + nc = pypsa.NetworkCollection(networks, index=index) + + # Test statistics with MultiIndex using energy_balance (since networks are already optimized) + result = nc.statistics.energy_balance(groupby=["carrier", "bus_carrier"]) + assert not result.empty + assert result.index.names == [ + "component", + "demand", + "year", + "carrier", + "bus_carrier", + ] + + # Check we can access specific scenarios + high_2040 = result.xs(("high", 2040), level=["demand", "year"]) + assert not high_2040.empty + + +def test_network_collection_vs_single_network(ac_dc_network_r): + """Test that NetworkCollection statistics match single network statistics.""" + # Get single network results using installed_capacity (no optimization needed) + single_result = ac_dc_network_r.statistics.installed_capacity( + groupby=["carrier", "bus_carrier"] + ) + + # Create NetworkCollection with one network + nc = pypsa.NetworkCollection([ac_dc_network_r], index=pd.Index(["s1"])) + + # Get collection results + collection_result = nc.statistics.installed_capacity( + groupby=["carrier", "bus_carrier"] + ) + + # Results should match (except for the network index level) + collection_values = collection_result.xs("s1", level="network") + pd.testing.assert_series_equal( + single_result.sort_index(), collection_values.sort_index(), check_names=False + ) + + +def test_network_collection_empty_results(ac_dc_network_r): + """Test NetworkCollection behavior with filters that produce empty results.""" + nc = pypsa.NetworkCollection([ac_dc_network_r], index=pd.Index(["s1"])) + + # Filter for non-existent bus_carrier should raise ValueError for now + # (bus_carrier_unit function doesn't handle non-existent carriers well) + with pytest.raises(ValueError, match="Bus carriers.*not in network"): + nc.statistics.energy_balance( + bus_carrier="NonExistent", groupby=["carrier", "bus_carrier"] + ) + + +def test_network_collection_custom_grouper(ac_dc_network_r): + """Test NetworkCollection with custom grouper function.""" + nc = pypsa.NetworkCollection([ac_dc_network_r], index=pd.Index(["s1"])) + + # Define custom grouper that groups by first letter of generator name + def first_letter_grouper(n, c, **kwargs): + idx = n.c[c].static.index + # Handle MultiIndex case (NetworkCollection) + if isinstance(idx, pd.MultiIndex): + # Get the last level (component names) + component_names = idx.get_level_values(-1) + first_letters = component_names.str[0] + # Recreate the full index + return pd.Series(first_letters.values, index=idx, name="first_letter") + else: + # Single network case + return idx.str[0].rename("first_letter") + + # Register the custom grouper + groupers.add_grouper("first_letter", first_letter_grouper) + + # Test with custom grouper using installed_capacity (no optimization needed) + result = nc.statistics.installed_capacity( + components=["Generator"], groupby=["carrier", "bus_carrier", "first_letter"] + ) + assert not result.empty + assert "first_letter" in result.index.names + + +def test_network_collection_get_transmission_branches(simple_network): + """Test get_transmission_branches function with NetworkCollection.""" + from pypsa.statistics.expressions import get_transmission_branches + + # Add a line between AC buses to test transmission branch detection + simple_network.add("Line", "ac_line", bus0="bus_ac1", bus1="bus_ac2", r=0.1, x=0.1) + + # Create a NetworkCollection with different bus carriers + nc = pypsa.NetworkCollection( + [simple_network, simple_network.copy()], + index=pd.Index(["s1", "s2"], name="scenario"), + ) + + # Test getting transmission branches for AC buses + branches = get_transmission_branches(nc, bus_carrier="AC") + assert isinstance(branches, pd.MultiIndex) + # Should include scenario level in the index + assert branches.names == ["scenario", "component", "name"] + + # Should find the line between AC buses for both scenarios + assert "Line" in branches.get_level_values("component") + assert "ac_line" in branches.get_level_values("name") + assert "s1" in branches.get_level_values("scenario") + assert "s2" in branches.get_level_values("scenario") + + # Should have 2 entries (one for each scenario) + assert len(branches) == 2 + + # Should NOT find the ac_dc_link since it connects different carriers + assert "ac_dc_link" not in branches.get_level_values("name") + + # Test with DC buses - should be empty since we only have one DC bus + branches_dc = get_transmission_branches(nc, bus_carrier="DC") + assert len(branches_dc) == 0 + # Should still have correct structure even when empty + assert branches_dc.names == ["scenario", "component", "name"] + + # Test with multiple bus carriers + branches_multi = get_transmission_branches(nc, bus_carrier=["AC", "DC"]) + assert isinstance(branches_multi, pd.MultiIndex) + # Should only find AC line since DC has no transmission branches + assert "ac_line" in branches_multi.get_level_values("name") + assert len(branches_multi) == 2 # Two scenarios + + # Test with None (all bus carriers) + branches_all = get_transmission_branches(nc, bus_carrier=None) + assert isinstance(branches_all, pd.MultiIndex) + assert "ac_line" in branches_all.get_level_values("name") + assert len(branches_all) == 2 # Two scenarios + + +def test_network_collection_get_transmission_carriers(simple_network): + """Test get_transmission_carriers function with NetworkCollection.""" + from pypsa.statistics.expressions import get_transmission_carriers + + # Add carriers for transmission components + simple_network.add("Carrier", "transmission") + + # Add a line between AC buses with transmission carrier + simple_network.add( + "Line", + "ac_line", + bus0="bus_ac1", + bus1="bus_ac2", + r=0.1, + x=0.1, + carrier="transmission", + ) + + # Create a NetworkCollection + nc = pypsa.NetworkCollection( + [simple_network, simple_network.copy()], + index=pd.Index(["s1", "s2"], name="scenario"), + ) + + # Test getting transmission carriers for AC buses + carriers = get_transmission_carriers(nc, bus_carrier="AC") + assert isinstance(carriers, pd.MultiIndex) + # Should include scenario level in the index + assert carriers.names == ["scenario", "component", "carrier"] + + # Should find the transmission carrier for lines in both scenarios + assert "Line" in carriers.get_level_values("component") + assert "transmission" in carriers.get_level_values("carrier") + assert "s1" in carriers.get_level_values("scenario") + assert "s2" in carriers.get_level_values("scenario") + + # Should have 2 entries (one for each scenario) + assert len(carriers) == 2 + + # Test with DC buses - should be empty + carriers_dc = get_transmission_carriers(nc, bus_carrier="DC") + assert len(carriers_dc) == 0 + # Should still have correct structure even when empty + assert carriers_dc.names == ["scenario", "component", "carrier"] + + +def test_network_collection_default_energy_balance_groupby( + optimized_network_collection_from_ac_dc, +): + """Test that default groupby for energy_balance works with NetworkCollection.""" + nc = optimized_network_collection_from_ac_dc + + # Test with default groupby (should be ["carrier", "bus_carrier"]) + result = nc.statistics.energy_balance() + assert not result.empty + assert "carrier" in result.index.names + assert "bus_carrier" in result.index.names + assert "scenario" in result.index.names # Uses 'scenario' as index name + + +def test_network_collection_opex_and_capex( + optimized_network_collection_from_ac_dc, +): + """Test OPEX and CAPEX statistics with NetworkCollection.""" + nc = optimized_network_collection_from_ac_dc + + # Test OPEX calculation + opex_result = nc.statistics.opex() + assert isinstance(opex_result, pd.Series) + assert not opex_result.empty + assert "scenario" in opex_result.index.names + + # Test CAPEX calculation + capex_result = nc.statistics.capex() + assert isinstance(capex_result, pd.Series) + assert not capex_result.empty + assert "scenario" in capex_result.index.names + + +def test_network_collection_transmission( + optimized_network_collection_from_ac_dc, +): + """Test transmission statistics with NetworkCollection.""" + nc = optimized_network_collection_from_ac_dc + + # Test transmission statistics + transmission_result = nc.statistics.transmission() + assert isinstance(transmission_result, pd.Series) + assert not transmission_result.empty + assert "scenario" in transmission_result.index.names + + +def test_network_collection_revenue( + optimized_network_collection_from_ac_dc, +): + """Test revenue statistics with NetworkCollection.""" + nc = optimized_network_collection_from_ac_dc + + # Test revenue calculation + revenue_result = nc.statistics.revenue() + assert isinstance(revenue_result, pd.Series) + assert not revenue_result.empty + assert "scenario" in revenue_result.index.names + + # Check that revenue is non-negative for each scenario (typical for generators) + for scenario in nc.networks.index: + scenario_revenue = revenue_result.xs(scenario, level="scenario") + network_revenue = nc.networks.loc[scenario].statistics.revenue() + assert (scenario_revenue == network_revenue).all(), ( + f"Revenue mismatch for scenario {scenario}" + ) diff --git a/PyPSA/source/test/test_common.py b/PyPSA/source/test/test_common.py new file mode 100644 index 0000000000000000000000000000000000000000..2f1ef7a49d055d47ec8bd4a31d7abde5a3a4594a --- /dev/null +++ b/PyPSA/source/test/test_common.py @@ -0,0 +1,439 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import copy +import warnings +from unittest.mock import patch + +import numpy as np +import pandas as pd +import pytest + +from pypsa.common import ( + MethodHandlerWrapper, + UnexpectedError, + _check_for_update, + as_index, + equals, + list_as_string, +) +from pypsa.definitions.structures import Dict + + +def test_unexpected_error_message_formatting(): + """Test that UnexpectedError correctly formats the error message with the issue tracker URL.""" + # Test with a custom message + custom_message = "Something unexpected happened" + error = UnexpectedError(custom_message) + + # Verify the error message contains both the custom message and the URL + assert custom_message in str(error) + assert UnexpectedError.URL_CREATE_ISSUE in str(error) + assert "Please track this issue in our issue tracker:" in str(error) + + # Test with an empty message + empty_error = UnexpectedError() + assert "Please track this issue in our issue tracker:" in str(empty_error) + assert UnexpectedError.URL_CREATE_ISSUE in str(empty_error) + + +def test_decorator_with_arguments(): + """Test the decorator when used with arguments: @MethodHandlerWrapper(handler_class=...)""" + + class ResultHandler: + def __init__(self, method): + self.method = method + + def __call__(self, *args, **kwargs): + result = self.method(*args, **kwargs) + return f"Processed: {result}" + + class TestClass: + def __init__(self, value=10): + self.value = value + + @MethodHandlerWrapper(handler_class=ResultHandler) + def method_with_decorator_args(self, x): + """Test method with decorator args""" + return self.value + x + + test_instance = TestClass() + result = test_instance.method_with_decorator_args(5) + assert result == "Processed: 15" + + +def test_decorator_without_arguments(): + """Test the decorator when used without arguments: @MethodHandlerWrapper""" + + class ResultHandler: + def __init__(self, method): + self.method = method + + def __call__(self, *args, **kwargs): + result = self.method(*args, **kwargs) + return f"Processed: {result}" + + wrapper = MethodHandlerWrapper(handler_class=ResultHandler) + + class TestClass: + def __init__(self, value=10): + self.value = value + + @wrapper + def method_with_simple_decorator(self, x): + """Test method with simple decorator""" + return self.value + x + + test_instance = TestClass() + result = test_instance.method_with_simple_decorator(5) + assert result == "Processed: 15" + + +def test_class_method_access(): + """Test accessing the decorated method at the class level""" + + class ResultHandler: + def __init__(self, method): + self.method = method + + def __call__(self, *args, **kwargs): + result = self.method(*args, **kwargs) + return f"Processed: {result}" + + class TestClass: + def __init__(self, value=10): + self.value = value + + @MethodHandlerWrapper(handler_class=ResultHandler) + def method(self, x): + return self.value + x + + # Should return the wrapper itself, not the handler instance + assert isinstance(TestClass.method, MethodHandlerWrapper) + + +@pytest.mark.parametrize( + ("attr", "expected_name"), + [ + ("snapshots", "snapshot"), + ("investment_periods", "period"), + ], +) +def test_as_index(ac_dc_periods, attr, expected_name): + n = ac_dc_periods + + # Test with None values + result = as_index(n, None, attr) + assert isinstance(result, pd.Index) + assert result.equals(getattr(n, attr)) + assert result.name == expected_name + + # Test with valid values + values = getattr(n, attr)[:3] + result = as_index(n, values, attr) + assert isinstance(result, pd.Index) + assert result.equals(pd.Index(values)) + assert result.name == expected_name + + # Test with different levels + # with pytest.raises(ValueError): + # as_index(n, n.snapshots, attr) + + # Test with invalid values + with pytest.raises(ValueError): + as_index(n, ["invalid"], attr) + + # # Test with scalar value + # scalar_result = as_index(n, getattr(n, attr)[0], attr, expected_name) + # assert isinstance(scalar_result, pd.Index) + # assert scalar_result.equals(pd.Index([getattr(n, attr)[0]])) + # assert scalar_result.name == expected_name + + +# Tests for the Comparator class +class TestEquals: + @pytest.mark.parametrize( + ("a", "b", "expected"), + [ + (1, 1, True), + (1, 2, False), + (np.array([1, 2, 3]), np.array([1, 2, 3]), True), + (np.array([1, 2, 3]), np.array([1, 2, 4]), False), + (pd.DataFrame({"A": [1, 2]}), pd.DataFrame({"A": [1, 2]}), True), + (pd.DataFrame({"A": [1, 2]}), pd.DataFrame({"A": [1, 4]}), False), + ({"a": 1, "b": 2}, {"a": 1, "b": 3}, False), + ([1, 2, 3], [1, 2, 3], True), + (np.nan, np.nan, True), + # Additional test cases + ("string", "string", True), + ("string", "different", False), + (None, None, True), + (True, True, True), + (True, False, False), + ([], [], True), + ({}, {}, True), + ((1, 2), (1, 2), True), + ((1, 2), (1, 3), False), + ({1, 2}, {1, 2}, True), + ({1, 2}, {1, 3}, False), + # Same object identity + (lambda x: x, lambda x: x, False), # Functions with different identity + ], + ) + def test_equals(self, a, b, expected): + assert equals(a, b) == expected + + @pytest.mark.parametrize( + ("a", "b"), + [ + (1, 2), + ("a", "b"), + (np.array([1, 2, 3]), np.array([1, 2, 4])), + (pd.DataFrame({"A": [1, 3]}), pd.DataFrame({"A": [1, 2]})), + ], + ) + def test_equals_logs(self, a, b, caplog): + assert equals(a, b, log_mode="silent") is False + assert caplog.text == "" + + assert equals(a, b, log_mode="verbose") is False + assert caplog.text != "" + + with pytest.raises(ValueError): + equals(a, b, log_mode="strict") + + with pytest.raises(ValueError): + equals(a, b, log_mode="invalid") + + def test_equals_ignored_classes(self): + class IgnoredClass: + def __init__(self, value=1): + self.value = value + + assert equals( + IgnoredClass(value=1), IgnoredClass(value=2), ignored_classes=[IgnoredClass] + ) + + def test_equals_type_mismatch(self): + with pytest.raises(ValueError): + equals(1, "1", log_mode="strict") + + def test_invalid_log_mode_type(self): + with pytest.raises(ValueError, match="'log_mode' must be one of"): + equals(1, 1, log_mode=123) + + def test_nested_structures(self): + a = {"level1": {"level2": [1, 2, {"level3": "value"}]}} + b = {"level1": {"level2": [1, 2, {"level3": "different"}]}} + + assert equals(a, a) is True + assert equals(a, b) is False + + def test_pandas_series(self): + a = pd.Series([1, 2, 3]) + b = pd.Series([1, 2, 3]) + c = pd.Series([1, 2, 4]) + + assert equals(a, b) is True + assert equals(a, c) is False + + def test_pandas_empty_dataframes(self): + a = pd.DataFrame() + b = pd.DataFrame() + + assert equals(a, b) is True + + def test_numpy_arrays_with_nan(self): + a = np.array([1, 2, np.nan]) + b = np.array([1, 2, np.nan]) + + assert equals(a, b) is True + + def test_same_object_identity(self): + obj = {"complex": "object"} + assert equals(obj, obj) is True + + def test_dict_with_missing_keys(self): + a = {"key1": 1, "key2": 2} + b = {"key1": 1} + c = {"key1": 1, "key2": 2, "key3": 3} + + assert equals(a, b) is False + assert equals(a, c) is False + + +@pytest.fixture +def warning_catcher(): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + yield w + + +def test_list_as_string(): + # Test comma-separated (default) + assert list_as_string(["a", "b", "c"]) == "a, b, c" + # Test bullet-list + expected_bullet = " - x\n - y\n - z" + assert ( + list_as_string(["x", "y", "z"], prefix=" ", style="bullet-list") + == expected_bullet + ) + # Test dict input + assert list_as_string({"a": 1, "b": 2, "c": 3}) == "a, b, c" + # Test empty list + assert list_as_string([]) == "" + # Test single item + assert list_as_string(["a"]) == "a" + # Test invalid style + with pytest.raises(ValueError): + list_as_string(["a", "b"], style="invalid") + # Test prefix + assert list_as_string(["a", "b"], prefix="-> ") == "-> a, b" + # Test empty lists + assert list_as_string([]) == "" + assert list_as_string([], style="bullet-list") == "" + + +@pytest.fixture +def mock_response(): + """Create a mock response object.""" + + class MockResponse: + def __init__(self, data): + self._data = data + + def read(self): + return f'{{"tag_name": "v{self._data}"}}'.encode() + + return MockResponse + + +@pytest.mark.parametrize( + ("current", "latest", "expected_message"), + [ + # Test case format: (current_version, latest_version, expected_message) + ( + "1.0.0", + "2.0.0", + "New version 2.0.0 available! (Current: 1.0.0)", + ), # newer version + ("1.0.0", "1.0.0", ""), # same version + ("2.0.0", "1.0.0", ""), # current is newer + ( + "1.2.2", + "1.2.3", + "New version 1.2.3 available! (Current: 1.2.2)", + ), # minor update + ], +) +def test_check_for_update(mock_response, current, latest, expected_message): + """Test version comparison scenarios.""" + with patch("urllib.request.urlopen") as mock_urlopen: + mock_urlopen.return_value = mock_response(latest) + _check_for_update.cache_clear() + result = _check_for_update(current, "test_owner", "test_repo") + assert result == expected_message + + +def test_check_for_update_error_handling(): + """Test error handling scenarios.""" + with patch("urllib.request.urlopen") as mock_urlopen: + mock_urlopen.side_effect = Exception("Connection failed") + + _check_for_update.cache_clear() + result = _check_for_update("1.0.0", "test_owner", "test_repo") + assert result == "" + + +def test_check_for_update_respects_network_option(mock_response): + """Test that version check respects the allow_network_requests option.""" + import pypsa + + # Test that version check is skipped when network requests are disabled + with pypsa.option_context("general.allow_network_requests", False): + result = _check_for_update("1.0.0", "test_owner", "test_repo") + assert result == "" + + # Test that version check works when network requests are allowed + with patch("urllib.request.urlopen") as mock_urlopen: + mock_urlopen.return_value = mock_response("2.0.0") + + _check_for_update.cache_clear() + with pypsa.option_context("general.allow_network_requests", True): + result = _check_for_update("1.0.0", "test_owner", "test_repo") + assert "New version 2.0.0 available!" in result + + +def test_url_loading_respects_network_option(): + """Test that URL loading respects the allow_network_requests option.""" + import pypsa + from pypsa.network.io import _retrieve_from_url + + # Test that URL loading is blocked when network requests are disabled + def dummy_function(x): + return None + + with pypsa.option_context("general.allow_network_requests", False): + with pytest.raises(ValueError, match="Network requests are disabled"): + _retrieve_from_url("https://example.com/test.nc", dummy_function) + + # Test that URL loading works when network requests are allowed + with pypsa.option_context("general.allow_network_requests", True): + with patch("pypsa.network.io.urlretrieve") as mock_urlretrieve: + with patch("tempfile.NamedTemporaryFile") as mock_temp: + mock_temp.return_value.__enter__.return_value.name = "/tmp/test" + + def mock_io_function(x): + return "test_result" + + result = _retrieve_from_url( + "https://example.com/test.nc", mock_io_function + ) + assert result == "test_result" + mock_urlretrieve.assert_called_once() + + +def test_network_constructor_respects_network_option(): + """Test that Network constructor respects the allow_network_requests option when loading from URLs.""" + import pypsa + + # Test that Network loading is blocked when network requests are disabled + with pypsa.option_context("general.allow_network_requests", False): + with pytest.raises(ValueError, match="Network requests are disabled"): + pypsa.Network("https://example.com/test.nc") + + +class TestDict: + """Test Dict class copy functionality.""" + + def test_dict_copy_methods(self): + """Test that all copy methods preserve Dict type and values.""" + d = Dict({"a": 1, "b": [2, 3]}) + + # Test .copy() - shallow copy + d_copy = d.copy() + assert type(d_copy) is Dict + assert d_copy == d + assert d_copy is not d + d_copy["b"].append(4) + assert d["b"] == [2, 3, 4] + + # Test copy.copy() - shallow copy + d["b"] = [2, 3] # Reset + d_shallow = copy.copy(d) + assert type(d_shallow) is Dict + assert d_shallow == d + assert d_shallow is not d + d_shallow["b"].append(5) + assert d["b"] == [2, 3, 5] + + # Test copy.deepcopy() - deep copy + d["b"] = [2, 3] # Reset + d_deep = copy.deepcopy(d) + assert type(d_deep) is Dict + assert d_deep == d + assert d_deep is not d + d_deep["b"].append(6) + assert d["b"] == [2, 3] + assert d_deep["b"] == [2, 3, 6] diff --git a/PyPSA/source/test/test_common_deprecations.py b/PyPSA/source/test/test_common_deprecations.py new file mode 100644 index 0000000000000000000000000000000000000000..97a8639ae874d1cc0e759490332db20b2f255496 --- /dev/null +++ b/PyPSA/source/test/test_common_deprecations.py @@ -0,0 +1,209 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import warnings +from unittest.mock import patch + +import pytest + +from pypsa.common import ( + deprecated_common_kwargs, + deprecated_in_next_major, + deprecated_kwargs, + deprecated_namespace, + rename_deprecated_kwargs, +) + + +@pytest.fixture +def mock_version_semver(): + with patch("pypsa.common.__version_base__", "1.0.0"): + yield + + +def test_rename_deprecated_kwargs_basic(mock_version_semver): + """Test basic functionality of rename_deprecated_kwargs.""" + func_name = "test_func" + kwargs = {"old_arg": "value"} + aliases = {"old_arg": "new_arg"} + deprecated_in = "0.9.0" + removed_in = "1.1.0" + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + rename_deprecated_kwargs(func_name, kwargs, aliases, deprecated_in, removed_in) + + # Check that a warning was raised + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "`old_arg` is deprecated as an argument to `test_func`" in str( + w[0].message + ) + assert "Deprecated in version 0.9.0" in str(w[0].message) + assert "Will be removed in version 1.1.0" in str(w[0].message) + + # Check that the argument was renamed + assert "new_arg" in kwargs + assert "old_arg" not in kwargs + assert kwargs["new_arg"] == "value" + + +def test_rename_deprecated_kwargs_both_args_present(mock_version_semver): + """Test that an error is raised when both old and new arguments are provided.""" + func_name = "test_func" + kwargs = {"old_arg": "old_value", "new_arg": "new_value"} + aliases = {"old_arg": "new_arg"} + deprecated_in = "0.9.0" + removed_in = "1.1.0" + + with pytest.raises(DeprecationWarning) as excinfo: + rename_deprecated_kwargs(func_name, kwargs, aliases, deprecated_in, removed_in) + + assert "received both old_arg and new_arg as arguments" in str(excinfo.value) + + +def test_rename_deprecated_kwargs_no_deprecated_args(mock_version_semver): + """Test that no warnings are raised when no deprecated arguments are used.""" + func_name = "test_func" + kwargs = {"new_arg": "value"} + aliases = {"old_arg": "new_arg"} + deprecated_in = "0.9.0" + removed_in = "1.1.0" + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + rename_deprecated_kwargs(func_name, kwargs, aliases, deprecated_in, removed_in) + + # Check that no warnings were raised + assert len(w) == 0 + + # Check that the arguments remain unchanged + assert "new_arg" in kwargs + assert kwargs["new_arg"] == "value" + + +def test_rename_deprecated_kwargs_multiple_aliases(mock_version_semver): + """Test handling multiple aliases at once.""" + func_name = "test_func" + kwargs = {"old_arg1": "value1", "old_arg2": "value2"} + aliases = {"old_arg1": "new_arg1", "old_arg2": "new_arg2"} + deprecated_in = "0.9.0" + removed_in = "1.1.0" + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + rename_deprecated_kwargs(func_name, kwargs, aliases, deprecated_in, removed_in) + + # Check that warnings were raised + assert len(w) == 2 + + # Check that the arguments were renamed + assert "new_arg1" in kwargs + assert "new_arg2" in kwargs + assert "old_arg1" not in kwargs + assert "old_arg2" not in kwargs + assert kwargs["new_arg1"] == "value1" + assert kwargs["new_arg2"] == "value2" + + +def test_deprecated_kwargs_decorator(mock_version_semver): + """Test the deprecated_kwargs decorator.""" + + @deprecated_kwargs(deprecated_in="0.9.0", removed_in="1.1.0", old_arg="new_arg") + def test_func(new_arg): + return new_arg + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + result = test_func(old_arg="value") + + # Check that a warning was raised + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "`old_arg` is deprecated as an argument to `test_func`" in str( + w[0].message + ) + + # Check that the function worked correctly + assert result == "value" + + +def test_deprecated_common_kwargs(mock_version_semver): + """Test the deprecated_common_kwargs decorator.""" + + @deprecated_common_kwargs + def test_func(n): + return n + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + result = test_func(network="value") + + # Check that a warning was raised + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "`network` is deprecated as an argument to `test_func`" in str( + w[0].message + ) + + # Check that the function worked correctly + assert result == "value" + + +def test_deprecated_in_next_major(mock_version_semver): + """Test the deprecated_in_next_major decorator.""" + + @deprecated_in_next_major("This function will be removed in version 2.0") + def test_func(): + return "value" + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + result = test_func() + + # Check that a warning was raised + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "is deprecated as of 1.0" in str(w[0].message).lower() + assert "will be removed in 2.0" in str(w[0].message).lower() + + # Check that the function worked correctly + assert result == "value" + + +def test_deprecated_namespace(mock_version_semver): + """Test the deprecated_namespace decorator.""" + + def test_func(): + return "value" + + decorated_func = deprecated_namespace( + test_func, + previous_module="old.module", + deprecated_in="0.9.0", + removed_in="1.1.0", + ) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + result = decorated_func() + + # Check that a warning was raised + assert len(w) == 1 + assert issubclass(w[0].category, DeprecationWarning) + assert "old.module" in str(w[0].message) + assert "Deprecated since version 0.9.0" in str(w[0].message) + assert "Will be removed in version 1.1.0" in str(w[0].message) + + # Check that the function worked correctly + assert result == "value" + + +def test_component_names_deprecation(ac_dc_network): + """Test component_names deprecation.""" + with pytest.warns(DeprecationWarning, match="component_names is deprecated"): + assert ( + ac_dc_network.c.generators.component_names + == ac_dc_network.c.generators.names + ).all() diff --git a/PyPSA/source/test/test_components.py b/PyPSA/source/test/test_components.py new file mode 100644 index 0000000000000000000000000000000000000000..a4fdc9f824da00c6eb3455f458ce226733e48314 --- /dev/null +++ b/PyPSA/source/test/test_components.py @@ -0,0 +1,88 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import warnings + +import pandas as pd +import pytest + +from pypsa import Components, Network +from pypsa.components.legacy import Component +from pypsa.components.types import get as get_component_type + + +def test_components_non_implemented(): + """Test that the components module raises an ImportError if imported directly.""" + ct = get_component_type("Generator") + with pytest.raises(NotImplementedError): + Components(ctype=ct, names=["Generator"]) + n = Network() + with pytest.raises(NotImplementedError): + Components(ctype=ct, n=n) + + +@pytest.fixture +def legacy_component(): + n = Network() + # Create a sample component object + data = {"active": [True, False, True], "other_attr": [1, 2, 3]} + static = pd.DataFrame(data, index=["asset1", "asset2", "asset3"]) + dynamic = {"time_series": pd.DataFrame({"value": [0.1, 0.2, 0.3]})} + + component = Component( + name="Generator", + n=n, + static=static, + dynamic=dynamic, + ) + return component + + +def test_component_initialization(legacy_component): + component = legacy_component + assert component.name == "Generator" + assert component.list_name == "generators" + assert component.static.shape == (3, 2) + assert "time_series" in component.dynamic + + +def test_active_assets(legacy_component): + component = legacy_component + active_assets = component.static.query("active").index + assert len(active_assets) == 2 + assert "asset1" in active_assets + assert "asset3" in active_assets + + +def test_components_iteration_equivalence(): + """Test that self.components and self.iterate_components yield the same results.""" + n = Network() + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=10) + n.add("Line", "line", bus0="bus", bus1="bus", x=0.1, r=0.01) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + old_components = sorted([c.name for c in n.iterate_components()]) + + new_components = sorted([c.name for c in n.components]) + + assert old_components == new_components + assert all(not c.empty for c in n.components) + + +def test_active_in_investment_period(legacy_component): + component = legacy_component + active_assets = component.get_active_assets() + assert active_assets.sum() == 2 + assert active_assets["asset1"] + assert not active_assets["asset2"] + assert active_assets["asset3"] + + +def test_imports(): + with pytest.raises(ImportError): + from pypsa.components import Network # noqa: F401 + with pytest.raises(ImportError): + from pypsa.components import SubNetwork # noqa: F401 diff --git a/PyPSA/source/test/test_components_array.py b/PyPSA/source/test/test_components_array.py new file mode 100644 index 0000000000000000000000000000000000000000..d57a57af796d87b38c26c2adacba6355f7b5803c --- /dev/null +++ b/PyPSA/source/test/test_components_array.py @@ -0,0 +1,279 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import numpy as np +import pandas as pd +import xarray + +from pypsa.components.array import _from_xarray + + +def test_as_xarray_static(ac_dc_network): + n = ac_dc_network + da = n.c.generators._as_xarray("bus") + + assert isinstance(da, xarray.DataArray) + + # Check coords + assert list(da.coords) == ["name"] + assert np.array_equal(da.coords["name"], n.c.generators.static.index) + + # Check data + assert np.array_equal(da.values, n.c.generators.static["bus"].values) + + +def test_as_xarray_dynamic(ac_dc_network): + n = ac_dc_network + da = n.c.generators._as_xarray("p_max_pu") + + assert isinstance(da, xarray.DataArray) + + # Check coords + assert list(da.coords) == ["snapshot", "name"] + assert np.array_equal(da.snapshot, n.snapshots) + assert np.array_equal(da.coords["name"], n.c.generators.static.index) + + # Check data + non_dynamic_index = n.c.generators.static.index.difference( + n.c.generators.dynamic.p_max_pu.columns + ) + assert np.array_equal( + da.sel(name=non_dynamic_index), + np.ones((10, 3)) + * n.c.generators.static.loc[non_dynamic_index, "p_max_pu"].values, + ) + assert np.array_equal( + da.sel(name=n.c.generators.dynamic["p_max_pu"].columns), + n.c.generators.dynamic["p_max_pu"].values, + ) + + +def test_as_xarray_static_with_periods(ac_dc_network): + """ + This is the same test as test_as_xarray_static, since static data is not + affected by periods. + """ + n = ac_dc_network + # Add investment periods to the network + n.investment_periods = [2000, 2010] + + da = n.c.generators._as_xarray("bus") + + assert isinstance(da, xarray.DataArray) + + # Check coords + assert list(da.coords) == ["name"] + assert np.array_equal(da.coords["name"], n.c.generators.static.index) + + # Check data + assert np.array_equal(da.values, n.c.generators.static["bus"].values) + + +def test_as_xarray_dynamic_with_periods(ac_dc_network): + n = ac_dc_network + # Add investment periods to the network + n.investment_periods = [2000, 2010] + + da = n.c.generators._as_xarray("p_max_pu") + + assert isinstance(da, xarray.DataArray) + + # Check coords + assert list(da.coords) == ["snapshot", "period", "timestep", "name"] + assert np.array_equal(da.snapshot, n.snapshots) + assert np.array_equal(da.period.to_index().unique(), n.periods) + assert np.array_equal(da.timestep.to_index().unique(), n.timesteps) + assert np.array_equal(da.coords["name"], n.c.generators.static.index) + + # Check data + non_dynamic_index = n.c.generators.static.index.difference( + n.c.generators.dynamic.p_max_pu.columns + ) + assert np.array_equal( + da.sel(name=non_dynamic_index), + np.ones((20, 3)) + * n.c.generators.static.loc[non_dynamic_index, "p_max_pu"].values, + ) + assert np.array_equal( + da.sel(name=n.c.generators.dynamic["p_max_pu"].columns), + n.c.generators.dynamic["p_max_pu"].values, + ) + + +def test_as_xarray_static_with_scenarios(ac_dc_network): + n = ac_dc_network + # Add scenarios to the network + scenarios = ["scenario1", "scenario2"] + n.scenarios = scenarios + da = n.c.generators._as_xarray("bus") + + assert isinstance(da, xarray.DataArray) + + # Check coords + assert list(da.coords) == ["scenario", "name"] + assert np.array_equal( + da.coords["name"], n.c.generators.static.index.get_level_values("name").unique() + ) + assert np.array_equal(da.scenario, scenarios) + + # Check data + assert np.array_equal(da.values.flatten(), n.c.generators.static["bus"].values) + + +def test_as_xarray_dynamic_with_scenarios(ac_dc_network): + n = ac_dc_network + # Add scenarios to the network + scenarios = ["scenario1", "scenario2"] + n.scenarios = scenarios + + da = n.c.generators._as_xarray("p_max_pu") + + assert isinstance(da, xarray.DataArray) + + # Check coords + assert np.array_equal(da.snapshot, n.snapshots) + # assert np.array_equal( + # da.coords["name"], n.c.generators.static.index.get_level_values("component").unique() + # ) # TODO sorting + assert np.array_equal(da.scenario, scenarios) + + # Check data + # non_dynamic_index = n.c.generators.static.index.difference(n.c.generators.dynamic.p_max_pu.columns) + assert np.array_equal( + da.sel( + scenario=scenarios[0], + name=n.c.generators.dynamic.p_max_pu.columns.get_level_values(1).unique(), + ).values, + n.c.generators.dynamic.p_max_pu[scenarios[0]].values, + ) + + # TODO add test for non_dynamic_index + + +def test_ds_property_consistency(ac_dc_network): + n = ac_dc_network + """Test that ds property returns the same data as individual da calls.""" + ds = n.c.generators.ds + + # Test all attributes match individual _as_xarray calls + for attr in ds.data_vars: + da_individual = n.c.generators._as_xarray(attr) + da_from_ds = ds[attr] + assert set(da_individual.coords) == set(da_from_ds.coords) + assert da_individual.equals(da_from_ds) + + +def test_from_xarray(ac_dc_types): + """Test that _from_xarray works with the new signature requiring Components parameter.""" + n = ac_dc_types + for c in n.components: + # Test with a few representative attributes + for attr in list(c.static.columns)[ + :2 + ]: # Only test first 2 attributes per component + da = c.da[attr] + # Main test: ensure function works with Components parameter + result = _from_xarray(da, c) + + # Basic checks + assert isinstance(result, pd.DataFrame | pd.Series) + + # For dynamic data, check round-trip consistency + if attr in c.dynamic and not c.dynamic[attr].empty: + dynamic_data = c._as_dynamic(attr) + # Check basic shape compatibility + if isinstance(result, pd.DataFrame) and isinstance( + dynamic_data, pd.DataFrame + ): + assert result.shape == dynamic_data.shape + elif isinstance(result, pd.Series) and isinstance( + dynamic_data, pd.Series + ): + assert len(result) == len(dynamic_data) + + +def test_from_xarray_auxiliary_dimensions(): + """Test _from_xarray with auxiliary dimensions like contingency scenarios.""" + import pandas as pd + import xarray as xr + + from pypsa.components.array import _from_xarray + + # Create mock component for testing + class MockComponent: + def __init__(self): + self.component_names = ["gen1", "gen2"] + self.scenarios = ["s1", "s2"] + self.has_scenarios = True + + c = MockComponent() + + # Test case: name + scenario + auxiliary dimension (3+ dimensions) + data_with_scenario = xr.DataArray( + [[[1, 2], [3, 4]], [[5, 6], [7, 8]]], + dims=["scenario", "name", "cycle"], + coords={ + "scenario": ["s1", "s2"], + "name": ["gen1", "gen2"], + "cycle": ["c1", "c2"], + }, + ) + result_with_scenario = _from_xarray(data_with_scenario, c) + assert isinstance(result_with_scenario, pd.DataFrame | pd.Series) + + # Test case: name + snapshot + auxiliary dimension (3+ dimensions) + data_with_snapshot = xr.DataArray( + [[[1, 2], [3, 4]], [[5, 6], [7, 8]]], + dims=["snapshot", "name", "cycle"], + coords={ + "snapshot": pd.date_range("2020-01-01", periods=2, freq="h"), + "name": ["gen1", "gen2"], + "cycle": ["c1", "c2"], + }, + ) + c.has_scenarios = False + result_with_snapshot = _from_xarray(data_with_snapshot, c) + assert isinstance(result_with_snapshot, pd.DataFrame | pd.Series) + + +def test_from_xarray_edge_cases(): + """Test _from_xarray edge cases and dimension handling.""" + import pandas as pd + import xarray as xr + + from pypsa.components.array import _from_xarray + + # Create mock component for testing + class MockComponent: + def __init__(self): + self.names = ["gen1", "gen2"] + self.scenarios = ["s1", "s2"] + self.has_scenarios = False + + c = MockComponent() + + # Test case 1: Missing name dimension (should be expanded) + data_no_name = xr.DataArray( + [1, 2, 3], + dims=["snapshot"], + coords={"snapshot": pd.date_range("2020-01-01", periods=3, freq="h")}, + ) + result = _from_xarray(data_no_name, c) + assert isinstance(result, pd.DataFrame | pd.Series) + # After expansion, should have both name and snapshot dimensions + assert result.shape[1] == 2 # Should have 2 components (gen1, gen2) + + # Test case 2: 2D case with snapshot + auxiliary dim (no name, expanded) + data_2d_no_name = xr.DataArray( + [[1, 2], [3, 4], [5, 6]], + dims=["snapshot", "cycle"], + coords={ + "snapshot": pd.date_range("2020-01-01", periods=3, freq="h"), + "cycle": ["c1", "c2"], + }, + ) + result_2d = _from_xarray(data_2d_no_name, c) + assert isinstance(result_2d, pd.DataFrame) + # After name expansion, we have 3+ dimensions, so combined index is created + assert len(result_2d.columns) == 4 # gen1*c1, gen1*c2, gen2*c1, gen2*c2 diff --git a/PyPSA/source/test/test_components_custom.py b/PyPSA/source/test/test_components_custom.py new file mode 100644 index 0000000000000000000000000000000000000000..28afc3960633c26b90faee1ed3317539ff74c984 --- /dev/null +++ b/PyPSA/source/test/test_components_custom.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +from pathlib import Path + +import pandas as pd + +import pypsa +from pypsa.components.types import component_types_df, default_components, get + + +def test_custom_components(): + df = pd.read_csv( + Path(__file__).parent.parent / "pypsa" / "data" / "components.csv", index_col=0 + ) + + assert component_types_df.equals(df) + + for component in df.index: + get(component) + + assert default_components == df.index.to_list() + + +def test_custom_component_registration(): + defaults_data = { + "attribute": ["name", "attribute_a"], + "type": ["string", "float"], + "unit": ["n/a", "n/a"], + "default": ["n/a", 1], + "description": ["Unique name", "Some custom attribute"], + "status": ["Input (required)", "Input (optional)"], + } + + defaults_df = pd.DataFrame(defaults_data) + pypsa.components.types.add_component_type( + name="CustomComponent", + list_name="custom_components", + description="A custom component example", + category="custom", + defaults_df=defaults_df, + ) + + custom_component = get("custom_components") + assert custom_component.name == "CustomComponent" + assert custom_component.list_name == "custom_components" + assert custom_component.description == "A custom component example" + assert custom_component.category == "custom" + assert custom_component.defaults.equals(defaults_df) + + +# def test_unregistered_custom_components(): +# import pypsa + +# with pytest.raises(ValueError, match="Component type 'MyComponent' not found."): +# pypsa.Network(custom_components=["MyComponent"]) diff --git a/PyPSA/source/test/test_components_store.py b/PyPSA/source/test/test_components_store.py new file mode 100644 index 0000000000000000000000000000000000000000..332f83b4506d3819111e81bb1f348156e2d68684 --- /dev/null +++ b/PyPSA/source/test/test_components_store.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +from pypsa.components.store import ComponentsStore + + +def test_init(): + # Dict like initialization + components = ComponentsStore() + components["a"] = "b" + assert components["a"] == "b" + components = ComponentsStore({"a": "b", "c": "d"}) + assert components["a"] == "b" + assert components["c"] == "d" diff --git a/PyPSA/source/test/test_components_utils.py b/PyPSA/source/test/test_components_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..79b09251fbf3094b12aae49d5a1705f66cf1c7f5 --- /dev/null +++ b/PyPSA/source/test/test_components_utils.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pytest + +from pypsa.components.common import as_components + + +def test_as_components(ac_dc_network): + n = ac_dc_network + + assert as_components(n, "Generator") == n.c.generators + assert as_components(n, "generators") == n.c.generators + assert as_components(n, n.c.generators) == n.c.generators + with pytest.raises(TypeError): + assert as_components(n, 10) diff --git a/PyPSA/source/test/test_consistency_check.py b/PyPSA/source/test/test_consistency_check.py new file mode 100644 index 0000000000000000000000000000000000000000..7a1de0fca712a3bf4dad09c1d4f0268db4bcc0dd --- /dev/null +++ b/PyPSA/source/test/test_consistency_check.py @@ -0,0 +1,442 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import os + +import numpy as np +import pytest + +import pypsa +import pypsa.consistency + + +def assert_log_or_error_in_consistency(n, caplog, strict=False): + if strict: + with pytest.raises(pypsa.consistency.ConsistencyError): + n.consistency_check(strict=strict) + else: + n.consistency_check(strict=strict) + assert caplog.records[-1].levelname == "WARNING" + + +@pytest.fixture +def consistent_n(): + n = pypsa.Network() + n.add("Bus", "one") + n.add("Bus", "two") + n.add("Generator", "gen_one", bus="one", p_nom_max=10) + n.add("Line", "line_one", bus0="one", bus1="two", x=0.01, r=0.01) + n.add("Carrier", "AC") + return n + + +@pytest.mark.parametrize("strict", [[], ["unknown_buses"]]) +@pytest.mark.skipif(os.name == "nt", reason="dtype confusing on Windows") +def test_consistency(consistent_n, caplog, strict): + if strict: + consistent_n.consistency_check(strict=strict) + else: + consistent_n.consistency_check() + assert not caplog.records + + +@pytest.mark.parametrize("strict", [[], ["disconnected_buses"]]) +def test_missing_bus(consistent_n, caplog, strict): + consistent_n.add("Bus", "three") + assert_log_or_error_in_consistency(consistent_n, caplog, strict=strict) + + +@pytest.mark.parametrize("strict", [[], ["assets"]]) +def test_infeasible_capacity_limits(consistent_n, caplog, strict): + consistent_n.c.generators.static.loc[ + "gen_one", ["p_nom_extendable", "committable"] + ] = ( + True, + True, + ) + assert_log_or_error_in_consistency(consistent_n, caplog, strict=strict) + + +@pytest.mark.parametrize("strict", [[], ["static_power_attrs"]]) +def test_nans_in_capacity_limits(consistent_n, caplog, strict): + consistent_n.c.generators.static.loc["gen_one", "p_nom_extendable"] = True + consistent_n.c.generators.static.loc["gen_one", "p_nom_max"] = np.nan + assert_log_or_error_in_consistency(consistent_n, caplog, strict=strict) + + +@pytest.mark.parametrize("strict", [[], ["shapes"]]) +def test_shapes_with_missing_idx(ac_dc_shapes, caplog, strict): + n = ac_dc_shapes + with pytest.warns(UserWarning, match="CRS not set"): # Userwarning from pyproj + n.add( + "Shape", + "missing_idx", + geometry=n.c.shapes.static.geometry.iloc[0], + component="Bus", + idx="missing_idx", + ) + assert_log_or_error_in_consistency(ac_dc_shapes, caplog, strict=strict) + if not strict: + assert any( + "have idx values that are not included" in r.message for r in caplog.records + ) + + +@pytest.mark.parametrize("strict", [[], ["unknown_buses"]]) +def test_unknown_carriers(consistent_n, caplog, strict): + consistent_n.add("Generator", "wind", bus="hub", carrier="wind") + assert_log_or_error_in_consistency(consistent_n, caplog, strict=strict) + + +@pytest.mark.parametrize("strict", [[], ["generators"]]) +def test_inconsistent_e_sum_values(consistent_n, caplog, strict): + """ + Test that the consistency check raises a warning if the e_sum_min is greater than e_sum_max. + """ + consistent_n.add( + "Generator", "gen_two", bus="one", p_nom_max=10, e_sum_min=10, e_sum_max=5 + ) + assert_log_or_error_in_consistency(consistent_n, caplog, strict=strict) + + +@pytest.mark.parametrize("strict", [[], ["scenarios_sum"]]) +def test_scenarios_sum_to_one(consistent_n, caplog, strict): + """ + Test that the consistency check raises a warning if scenarios don't sum to 1. + """ + # Set up scenarios that sum to 1 (should pass) + consistent_n.set_scenarios({"low": 0.4, "high": 0.6}) + + # Manually modify scenarios to break sum=1 constraint + consistent_n._scenarios_data.iloc[0, 0] = 0.2 # Sum becomes 0.8 + + assert_log_or_error_in_consistency(consistent_n, caplog, strict=strict) + + +def test_unknown_check(): + n = pypsa.Network() + with pytest.raises(ValueError): + n.consistency_check(strict=["some_check"]) + + +@pytest.mark.parametrize("strict", [[], ["scenario_invariant_attrs"]]) +def test_scenario_invariant_attributes(consistent_n, caplog, strict): + """ + Test that the consistency check raises an error if invariant attributes vary across scenarios. + """ + # Set up scenarios + consistent_n.set_scenarios({"s1": 0.5, "s2": 0.5}) + + # Modify an invariant attribute (carrier) across scenarios - this should always fail + # regardless of strict mode + consistent_n.c.generators.static.loc[("s1", "gen_one"), "carrier"] = ( + "different_carrier" + ) + + # This check always raises an error + with pytest.raises(pypsa.consistency.ConsistencyError): + consistent_n.consistency_check(strict=strict) + + +def test_scenario_invariant_attributes_comprehensive(): + """ + Comprehensive test covering all invariant attributes and edge cases. + + This test verifies that the following attributes are not changed across scenarios + of a stochastic network by user modifications. Applies *exclusively* to stochastic + networks. + + Invariant attributes (must be identical across all scenarios): + - name + - bus + - type + - p_nom_extendable + - committable + - sign + - carrier + - weight + - p_nom_opt + - build_year + - lifetime + - active + + Note: "control" is not included as an invariant attribute because different + scenarios can have different control types (PQ, PV, Slack). However, slack bus + consistency is enforced separately via check_stochastic_slack_bus_consistency. + """ + n = pypsa.Network() + + n.add("Bus", "bus1") + n.add("Bus", "bus2") + n.add("Carrier", "gas") + n.add("Carrier", "wind") + n.add("Carrier", "AC") + + # Let's add multiple components to test different cases with invariant attributes + n.add( + "Generator", + "gen1", + bus="bus1", + carrier="gas", + p_nom_extendable=True, + committable=True, + ) + n.add("Line", "line1", bus0="bus1", bus1="bus2", x=0.1, r=0.01, carrier="AC") + n.add("Load", "load1", bus="bus1", p_set=50, carrier="AC") + n.add("Link", "link1", bus0="bus1", bus1="bus2", p_nom=75, carrier="AC") + + # Set up scenarios + n.set_scenarios({"scenario1": 0.4, "scenario2": 0.6}) + + # Test 1: Network with consistent invariant attributes should pass + n.consistency_check() + + # Test 2: Test invariant attributes for generators (only test attributes that exist) + # Note: "control" is no longer tested as invariant because different scenarios + # can have different control types (PQ, PV, Slack) but slack bus consistency + # is enforced separately + generator_invariant_tests = [ + ("generators", "gen1", "carrier", "wind"), + ("generators", "gen1", "bus", "bus2"), + ("generators", "gen1", "p_nom_extendable", False), + ("generators", "gen1", "committable", False), + ("generators", "gen1", "sign", -1.0), + ("generators", "gen1", "weight", 5.0), + ("generators", "gen1", "type", "solar"), + ("generators", "gen1", "active", False), + ] + + for component_name, element_name, attr, new_value in generator_invariant_tests: + n_test = n.copy() + + # Modify the invariant attribute in one scenario + component = getattr(n_test.c, component_name) + component.static.loc[("scenario1", element_name), attr] = new_value + + # Should always raise an error regardless of strict mode + with pytest.raises( + pypsa.consistency.ConsistencyError, + match=f"Component '{element_name}' .* has attribute '{attr}' that varies across scenarios", + ): + n_test.consistency_check() + + # Test 3: Test invariant attributes for lines + line_invariant_tests = [ + ("lines", "line1", "carrier", "gas"), + ("lines", "line1", "active", False), + ] + + for component_name, element_name, attr, new_value in line_invariant_tests: + n_test = n.copy() + + # Modify the invariant attribute in one scenario + component = getattr(n_test.c, component_name) + component.static.loc[("scenario1", element_name), attr] = new_value + + # Should always raise an error + with pytest.raises( + pypsa.consistency.ConsistencyError, + match=f"Component '{element_name}' .* has attribute '{attr}' that varies across scenarios", + ): + n_test.consistency_check() + + # Test 4: Test invariant attributes for links + link_invariant_tests = [ + ("links", "link1", "carrier", "gas"), + ("links", "link1", "active", False), + ("links", "link1", "committable", True), + ("links", "link1", "p_nom_extendable", True), + ("links", "link1", "type", "HVDC"), + ] + + for component_name, element_name, attr, new_value in link_invariant_tests: + n_test = n.copy() + + # Modify the invariant attribute in one scenario + component = getattr(n_test.c, component_name) + component.static.loc[("scenario1", element_name), attr] = new_value + + # Should always raise an error + with pytest.raises( + pypsa.consistency.ConsistencyError, + match=f"Component '{element_name}' .* has attribute '{attr}' that varies across scenarios", + ): + n_test.consistency_check() + + # Test 5: Test with NaN values - should not raise error if all scenarios have NaN + n_nan = n.copy() + n_nan.c.generators.static["build_year"] = n_nan.c.generators.static[ + "build_year" + ].astype(float) + n_nan.c.generators.static.loc[:, "build_year"] = np.nan + n_nan.consistency_check() # Should pass + + # Test 6: Test with mixed NaN and non-NaN values - should raise error (strict behavior) + n_mixed_nan = n.copy() + n_mixed_nan.c.generators.static["lifetime"] = n_mixed_nan.c.generators.static[ + "lifetime" + ].astype(float) + n_mixed_nan.c.generators.static.loc[("scenario1", "gen1"), "lifetime"] = 25.0 + n_mixed_nan.c.generators.static.loc[("scenario2", "gen1"), "lifetime"] = np.nan + + with pytest.raises( + pypsa.consistency.ConsistencyError, + match="Component 'gen1' .* has attribute 'lifetime' that varies across scenarios", + ): + n_mixed_nan.consistency_check() # Should raise error (any difference is not allowed) + + # Test 7: Test that non-invariant attributes can vary (should not raise error) + n_varying = n.copy() + n_varying.c.generators.static.loc[("scenario1", "gen1"), "p_nom"] = ( + 150 # p_nom is not invariant + ) + n_varying.c.generators.static.loc[("scenario1", "gen1"), "p_set"] = ( + 80 # p_set is not invariant + ) + n_varying.c.lines.static.loc[("scenario1", "line1"), "s_nom"] = ( + 200 # s_nom is not invariant + ) + n_varying.c.links.static.loc[("scenario1", "link1"), "p_nom"] = ( + 100 # p_nom is not invariant + ) + n_varying.consistency_check() # Should pass + + # Test 8: Test with non-stochastic network (should skip check) + n_non_stoch = pypsa.Network() + n_non_stoch.add("Bus", "bus") + n_non_stoch.add("Generator", "gen", bus="bus", carrier="test") + n_non_stoch.consistency_check() # Should pass + + +@pytest.mark.parametrize("strict", [[], ["line_types"]]) +def test_line_types_consistency(caplog, strict): + """ + Test that the consistency check raises an error if line_types vary across scenarios. + """ + n = pypsa.Network() + n.add("Bus", "bus1", v_nom=20) + n.add("Bus", "bus2", v_nom=20) + + n.set_scenarios({"s1": 0.5, "s2": 0.5}) + + # Create line_types with MultiIndex (different across scenarios) + import pandas as pd + + line_types_data = { + ("s1", "type1"): {"r": 0.1, "x": 0.2, "c": 0.0, "i_nom": 100}, + ("s1", "type2"): {"r": 0.15, "x": 0.25, "c": 0.0, "i_nom": 150}, + ("s2", "type1"): {"r": 0.1, "x": 0.2, "c": 0.0, "i_nom": 100}, # Same as s1 + ("s2", "type2"): { + "r": 0.2, + "x": 0.3, + "c": 0.0, + "i_nom": 200, + }, # Different from s1 + } + + line_types_df = pd.DataFrame.from_dict(line_types_data, orient="index") + line_types_df.index = pd.MultiIndex.from_tuples( + line_types_df.index, names=["scenario", "type"] + ) + + # Manually set line_types to simulate stochastic network + n.c.line_types.static = line_types_df + + # Test only the line_types consistency check directly to avoid calculate_dependent_values + if strict and "line_types" in strict: + with pytest.raises(pypsa.consistency.ConsistencyError): + pypsa.consistency.check_line_types_consistency(n, strict=True) + else: + # For non-strict mode, check that it logs a warning + pypsa.consistency.check_line_types_consistency(n, strict=False) + assert caplog.records[-1].levelname == "WARNING" + + +def test_line_types_consistency_pass(): + """ + Test that the consistency check passes when line_types are identical across scenarios. + """ + n = pypsa.Network() + n.add("Bus", "bus1", v_nom=20) + n.add("Bus", "bus2", v_nom=20) + + n.set_scenarios({"s1": 0.5, "s2": 0.5}) + + # Create identical line_types across scenarios + import pandas as pd + + line_types_data = { + ("s1", "type1"): {"r": 0.1, "x": 0.2, "c": 0.0, "i_nom": 100}, + ("s1", "type2"): {"r": 0.15, "x": 0.25, "c": 0.0, "i_nom": 150}, + ("s2", "type1"): {"r": 0.1, "x": 0.2, "c": 0.0, "i_nom": 100}, # Same as s1 + ("s2", "type2"): {"r": 0.15, "x": 0.25, "c": 0.0, "i_nom": 150}, # Same as s1 + } + + line_types_df = pd.DataFrame.from_dict(line_types_data, orient="index") + line_types_df.index = pd.MultiIndex.from_tuples( + line_types_df.index, names=["scenario", "type"] + ) + + # Manually set line_types to simulate stochastic network + n.c.line_types.static = line_types_df + + # This should pass because line_types are identical across scenarios + pypsa.consistency.check_line_types_consistency(n, strict=True) + + +def test_line_types_consistency_non_stochastic(): + """ + Test that the consistency check is skipped for non-stochastic networks. + """ + n = pypsa.Network() + n.add("Bus", "bus1", v_nom=20) + n.add("Bus", "bus2", v_nom=20) + + # Add line_types without scenarios (normal operation) + n.add("LineType", "type1", r=0.1, x=0.2, c=0.0, i_nom=100) + n.add("LineType", "type2", r=0.15, x=0.25, c=0.0, i_nom=150) + + # This should pass because it's not a stochastic network + pypsa.consistency.check_line_types_consistency(n, strict=True) + + +@pytest.mark.parametrize("strict", [[], ["unknown_buses"]]) +def test_check_for_unknown_buses(caplog, strict): + """Test check_for_unknown_buses via consistency_check(): GlobalConstraint/Link empty buses OK, invalid warns.""" + n = pypsa.Network() + n.add("Bus", "bus0") + n.add("Bus", "bus1") + + # Add components with empty buses (should be OK) + n.add("GlobalConstraint", "gc1") + n.add("Link", "link1", bus0="bus0", bus1="bus1") + caplog.clear() + n.consistency_check(strict=strict) + assert not any("buses which are not defined" in r.message for r in caplog.records) + + # Add component with invalid bus (should warn/error) + n.add("Generator", "gen1", bus="invalid_bus") + assert_log_or_error_in_consistency(n, caplog, strict=strict) + + +def test_check_for_unknown_buses_when_adding(caplog): + """Test check_for_unknown_buses: empty buses in GlobalConstraint/Links OK, invalid buses warn.""" + n = pypsa.Network() + n.add("Bus", "bus0") + n.add("Bus", "bus1") + + # GlobalConstraint with empty bus - no warning + caplog.clear() + n.add("GlobalConstraint", "gc1") + assert not any("buses which are not defined" in r.message for r in caplog.records) + + # Link with empty bus2/bus3 - no warning + caplog.clear() + n.add("Link", "link1", bus0="bus0", bus1="bus1") + assert not any("buses which are not defined" in r.message for r in caplog.records) + + # Invalid bus - should warn + caplog.clear() + n.add("Generator", "gen1", bus="invalid") + assert any("buses which are not defined" in r.message for r in caplog.records) diff --git a/PyPSA/source/test/test_constants.py b/PyPSA/source/test/test_constants.py new file mode 100644 index 0000000000000000000000000000000000000000..3f6691e57d48701996220216cdfb3dcbf5cdc578 --- /dev/null +++ b/PyPSA/source/test/test_constants.py @@ -0,0 +1,167 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Test constants module.""" + +import re + +import pytest + +from pypsa.constants import ( + DEFAULT_EPSG, + DEFAULT_TIMESTAMP, + RE_PORTS, + RE_PORTS_FILTER, + RE_PORTS_GE_2, +) + + +class TestConstants: + """Test constants values.""" + + def test_default_epsg(self): + """Test DEFAULT_EPSG constant.""" + assert DEFAULT_EPSG == 4326 + assert isinstance(DEFAULT_EPSG, int) + + def test_default_timestamp(self): + """Test DEFAULT_TIMESTAMP constant.""" + assert DEFAULT_TIMESTAMP == "now" + assert isinstance(DEFAULT_TIMESTAMP, str) + + +class TestRegexPatterns: + """Test regex pattern constants.""" + + def test_re_ports_basic_matches(self): + """Test RE_PORTS pattern matches basic bus columns.""" + assert RE_PORTS.match("bus") + assert RE_PORTS.match("bus0") + assert RE_PORTS.match("bus1") + assert RE_PORTS.match("bus2") + assert RE_PORTS.match("bus10") + assert RE_PORTS.match("bus123") + + def test_re_ports_capture_groups(self): + """Test RE_PORTS pattern capture groups.""" + match = RE_PORTS.match("bus") + assert match.group(1) == "" + + match = RE_PORTS.match("bus0") + assert match.group(1) == "0" + + match = RE_PORTS.match("bus123") + assert match.group(1) == "123" + + def test_re_ports_non_matches(self): + """Test RE_PORTS pattern non-matches.""" + assert not RE_PORTS.match("Bus") + assert not RE_PORTS.match("bus_") + assert not RE_PORTS.match("bus-1") + assert not RE_PORTS.match("busx") + assert not RE_PORTS.match("load") + assert not RE_PORTS.match("generator") + assert not RE_PORTS.match("") + + def test_re_ports_filter_matches(self): + """Test RE_PORTS_FILTER pattern matches.""" + assert RE_PORTS_FILTER.match("bus") + assert RE_PORTS_FILTER.match("bus0") + assert RE_PORTS_FILTER.match("bus1") + assert RE_PORTS_FILTER.match("bus2") + assert RE_PORTS_FILTER.match("bus10") + assert RE_PORTS_FILTER.match("bus123") + + def test_re_ports_filter_non_matches(self): + """Test RE_PORTS_FILTER pattern non-matches.""" + assert not RE_PORTS_FILTER.match("Bus") + assert not RE_PORTS_FILTER.match("bus_") + assert not RE_PORTS_FILTER.match("bus-1") + assert not RE_PORTS_FILTER.match("busx") + assert not RE_PORTS_FILTER.match("load") + assert not RE_PORTS_FILTER.match("") + + def test_re_ports_ge_2_matches(self): + """Test RE_PORTS_GE_2 pattern matches ports >= 2.""" + assert RE_PORTS_GE_2.match("bus2") + assert RE_PORTS_GE_2.match("bus3") + assert RE_PORTS_GE_2.match("bus9") + assert RE_PORTS_GE_2.match("bus10") + assert RE_PORTS_GE_2.match("bus11") + assert RE_PORTS_GE_2.match("bus23") + assert RE_PORTS_GE_2.match("bus100") + assert RE_PORTS_GE_2.match("bus999") + + def test_re_ports_ge_2_capture_groups(self): + """Test RE_PORTS_GE_2 pattern capture groups.""" + match = RE_PORTS_GE_2.match("bus2") + assert match.group(1) == "2" + + match = RE_PORTS_GE_2.match("bus10") + assert match.group(1) == "10" + + match = RE_PORTS_GE_2.match("bus123") + assert match.group(1) == "123" + + def test_re_ports_ge_2_non_matches(self): + """Test RE_PORTS_GE_2 pattern non-matches for ports < 2.""" + assert not RE_PORTS_GE_2.match("bus") + assert not RE_PORTS_GE_2.match("bus0") + assert not RE_PORTS_GE_2.match("bus1") + assert not RE_PORTS_GE_2.match("Bus2") + assert not RE_PORTS_GE_2.match("bus_2") + assert not RE_PORTS_GE_2.match("bus-2") + assert not RE_PORTS_GE_2.match("busx") + assert not RE_PORTS_GE_2.match("") + + def test_regex_pattern_types(self): + """Test that regex patterns are compiled Pattern objects.""" + assert isinstance(RE_PORTS, re.Pattern) + assert isinstance(RE_PORTS_FILTER, re.Pattern) + assert isinstance(RE_PORTS_GE_2, re.Pattern) + + @pytest.mark.parametrize( + ("pattern", "test_string", "expected"), + [ + (RE_PORTS, "bus", True), + (RE_PORTS, "bus0", True), + (RE_PORTS, "bus123", True), + (RE_PORTS, "Bus", False), + (RE_PORTS_FILTER, "bus", True), + (RE_PORTS_FILTER, "bus0", True), + (RE_PORTS_FILTER, "Bus", False), + (RE_PORTS_GE_2, "bus2", True), + (RE_PORTS_GE_2, "bus1", False), + (RE_PORTS_GE_2, "bus0", False), + ], + ) + def test_regex_patterns_parametrized(self, pattern, test_string, expected): + """Parametrized test for regex patterns.""" + result = pattern.match(test_string) is not None + assert result == expected + + def test_regex_patterns_edge_cases(self): + """Test regex patterns with edge cases.""" + # Test with leading zeros - should not match for GE_2 + assert RE_PORTS.match("bus00") + assert RE_PORTS_FILTER.match("bus00") + assert not RE_PORTS_GE_2.match("bus00") # 00 < 2 + assert not RE_PORTS_GE_2.match("bus01") # 01 < 2 + assert not RE_PORTS_GE_2.match("bus001") # Leading zeros should not match + + # Test with very long numbers + long_num = "2" * 100 # Use 2 instead of 1 since 111...1 >= 2 + assert RE_PORTS.match(f"bus{long_num}") + assert RE_PORTS_FILTER.match(f"bus{long_num}") + assert RE_PORTS_GE_2.match(f"bus{long_num}") + + # Test multi-digit numbers starting with 1 that are >= 2 + assert RE_PORTS_GE_2.match("bus10") + assert RE_PORTS_GE_2.match("bus15") + assert RE_PORTS_GE_2.match("bus100") + + # Test partial matches (should not match) + assert not RE_PORTS.match("bus123extra") + assert not RE_PORTS_FILTER.match("bus123extra") + assert not RE_PORTS_GE_2.match("bus123extra") diff --git a/PyPSA/source/test/test_deprecations.py b/PyPSA/source/test/test_deprecations.py new file mode 100644 index 0000000000000000000000000000000000000000..e0748467db545f386edcf80e5bbb0ebfe83e85ab --- /dev/null +++ b/PyPSA/source/test/test_deprecations.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import warnings + +import pytest + + +def test_statistics_deprecated_kwargs(ac_dc_solved): + """Test that old parameter names raise deprecation warnings for all statistics methods.""" + from pypsa.statistics.expressions import StatisticsAccessor + + n = ac_dc_solved + + # Test each method for deprecated 'comps' parameter + for method_name in StatisticsAccessor._methods: + if method_name in ["prices"]: + continue + with pytest.raises(DeprecationWarning) as excinfo: + getattr(n.statistics, method_name)(comps="Generator") + assert "`comps` is deprecated" in str(excinfo.value) + assert "use `components` instead" in str(excinfo.value) + + # Test a few methods for deprecated 'aggregate_groups' parameter + for method_name in ["supply", "energy_balance", "capex"]: + with pytest.raises(DeprecationWarning) as excinfo: + getattr(n.statistics, method_name)(aggregate_groups="sum") + assert "`aggregate_groups` is deprecated" in str(excinfo.value) + assert "use `groupby_method` instead" in str(excinfo.value) + + # Test a few methods for deprecated 'aggregate_time' parameter + for method_name in ["supply", "withdrawal", "transmission"]: + with pytest.raises(DeprecationWarning) as excinfo: + getattr(n.statistics, method_name)(aggregate_time="mean") + assert "`aggregate_time` is deprecated" in str(excinfo.value) + assert "use `groupby_time` instead" in str(excinfo.value) + + # Test the __call__ method also has deprecated parameters + with pytest.raises(DeprecationWarning) as excinfo: + n.statistics(comps="Generator") + assert "`comps` is deprecated" in str(excinfo.value) + + with pytest.raises(DeprecationWarning) as excinfo: + n.statistics(aggregate_groups="sum") + assert "`aggregate_groups` is deprecated" in str(excinfo.value) + + with pytest.raises(DeprecationWarning) as excinfo: + n.statistics(aggregate_time="mean") + assert "`aggregate_time` is deprecated" in str(excinfo.value) + + # Test that both old and new parameters raise error + with pytest.raises(DeprecationWarning) as excinfo: + n.statistics.installed_capacity(comps="Generator", components="Generator") + assert "received both comps and components" in str(excinfo.value) + + # Test equivalence: old and new params produce same results + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + old_result = n.statistics.installed_capacity(comps="Generator") + new_result = n.statistics.installed_capacity(components="Generator") + assert old_result.equals(new_result) diff --git a/PyPSA/source/test/test_descriptors.py b/PyPSA/source/test/test_descriptors.py new file mode 100644 index 0000000000000000000000000000000000000000..b0f53034f678a740589fcebc1106b55f5df1123d --- /dev/null +++ b/PyPSA/source/test/test_descriptors.py @@ -0,0 +1,154 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import warnings + +import pandas as pd +import pytest + +import pypsa +from pypsa.common import expand_series +from pypsa.descriptors import ( + _additional_linkports, + get_bounds_pu, + get_extendable_i, + get_non_extendable_i, +) +from pypsa.network.power_flow import allocate_series_dataframes + + +@pytest.fixture +def network(): + n = pypsa.Network() + n.snapshots = pd.date_range("2019-01-01", "2019-01-02", freq="h") + return n + + +def test_get_switchable_as_dense(network): + n = network + n.add("Bus", "bus0") + n.add("Generator", "gen0", bus="bus0", p_nom=100) + + for attr, val in [("p_max_pu", 1.0), ("p_nom", 100)]: + df = n.get_switchable_as_dense("Generator", attr) + assert isinstance(df, pd.DataFrame) + assert df.index.equals(n.snapshots) + assert df.columns.equals(pd.Index(["gen0"])) + assert (df == val).all().all() + + +def test_get_switchable_as_iter(network): + n = network + n.add("Bus", "bus0") + n.add("Generator", "gen0", bus="bus0", p_nom=100) + + iter_df = n.get_switchable_as_iter("Generator", "p_max_pu", n.snapshots) + df = pd.concat(iter_df, axis=1).T + assert isinstance(df, pd.DataFrame) + assert len(df) == len(n.snapshots) + assert (df["gen0"] == 1.0).all() + + +def test_allocate_series_dataframes(network): + n = network + n.add("Bus", "bus0") + n.add("Generator", "gen0", bus="bus0") + n.add("Load", "load0", bus="bus0") + + allocate_series_dataframes(n, {"Generator": ["p"], "Load": ["p"]}) + + assert "p" in n.c.generators.dynamic + assert "p" in n.c.loads.dynamic + assert n.c.generators.dynamic.p.shape == (len(n.snapshots), 1) + assert n.c.loads.dynamic.p.shape == (len(n.snapshots), 1) + + +def test_get_extendable_i(network): + n = network + n.add("Bus", "bus0") + n.add("Generator", "gen0", bus="bus0", p_nom_extendable=True) + n.add("Generator", "gen1", bus="bus0", p_nom_extendable=False) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + ext_i = get_extendable_i(n, "Generator") + assert len(ext_i) == 1 + assert "gen0" in ext_i + + +def test_get_non_extendable_i(network): + n = network + n.add("Bus", "bus0") + n.add("Generator", "gen0", bus="bus0", p_nom_extendable=True) + n.add("Generator", "gen1", bus="bus0", p_nom_extendable=False) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + nonext_i = get_non_extendable_i(n, "Generator") + assert len(nonext_i) == 1 + assert "gen1" in nonext_i + + +def test_expand_series(): + s = pd.Series([1, 2, 3]) + cols = ["a", "b", "c"] + df = expand_series(s, cols) + + assert isinstance(df, pd.DataFrame) + assert list(df.columns) == cols + assert (df["a"] == df["b"]).all() + assert (df["b"] == df["c"]).all() + + +def test_additional_linkports(): + n = pypsa.Network() + n.add("Bus", "bus0") + n.add("Bus", "bus1") + n.add("Bus", "bus2") + n.add("Link", "link0", bus0="bus0", bus1="bus1", bus2="bus2") + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + + ports = _additional_linkports(n, n.c.links.static.columns) + assert ports == ["2"] + assert ports == n.c.links.additional_ports + + +def test_get_bounds_pu(): + n = pypsa.Network() + n.snapshots = pd.date_range("2019-01-01", "2019-01-02", freq="h") + n.add("Bus", "bus0") + n.add("Generator", "gen0", bus="bus0", p_nom=100, p_min_pu=0.2, p_max_pu=0.8) + n.add("Generator", "gen1", bus="bus0", p_nom=200, p_min_pu=0.1, p_max_pu=0.9) + + # Test deprecated function vs component method consistency + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + deprecated_result = get_bounds_pu(n, "Generator", n.snapshots, attr="p") + + # Get bounds from component method directly + component_bounds = n.components["Generator"].get_bounds_pu(attr="p") + component_result = ( + component_bounds[0].sel(snapshot=n.snapshots).to_dataframe().unstack(level=0), + component_bounds[1].sel(snapshot=n.snapshots).to_dataframe().unstack(level=0), + ) + + # Test that both methods return the same type and structure + assert isinstance(deprecated_result, tuple) + assert len(deprecated_result) == 2 + assert isinstance(deprecated_result[0], pd.DataFrame) + assert isinstance(deprecated_result[1], pd.DataFrame) + + # Check that the values are the same - this is the key test + pd.testing.assert_frame_equal(deprecated_result[0], component_result[0]) + pd.testing.assert_frame_equal(deprecated_result[1], component_result[1]) + + # Basic sanity checks on data shape and values + assert deprecated_result[0].shape == (2, 25) # 2 generators, 25 snapshots + assert deprecated_result[1].shape == (2, 25) + + # Check some values are correct (not all, just to verify functionality) + assert 0.2 in deprecated_result[0].values # gen0 min_pu + assert 0.8 in deprecated_result[1].values # gen0 max_pu + assert 0.1 in deprecated_result[0].values # gen1 min_pu + assert 0.9 in deprecated_result[1].values # gen1 max_pu diff --git a/PyPSA/source/test/test_docs.py b/PyPSA/source/test/test_docs.py new file mode 100644 index 0000000000000000000000000000000000000000..96b4a9d6b36b8d0df61d8b6738a1728c5b184fd8 --- /dev/null +++ b/PyPSA/source/test/test_docs.py @@ -0,0 +1,241 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import doctest +import importlib +import json +import pkgutil +import sys +from pathlib import Path + +import numpy as np +import pandas as pd +import pytest + +import pypsa + +try: + import cartopy # noqa + + cartopy_available = True +except ImportError: + cartopy_available = False + +new_api = pypsa.options.api.new_components_api + + +rng = np.random.default_rng(42) + + +# Create a pytest fixture to check for the test-docs flag +@pytest.fixture(scope="session") +def test_docs_flag(pytestconfig): + """Check if --test-docs flag is provided.""" + return pytestconfig.getoption("--test-docs", default=False) + + +sub_network_parent = pypsa.examples.ac_dc_meshed().determine_network_topology() +# Warning: Keep in sync with settings in doc/conf.py +n = pypsa.examples.ac_dc_meshed() +n.optimize() + +# Create another network with shuffled load time series for collection comparisons +n_shuffled_load = pypsa.examples.ac_dc_meshed() +df = n.loads_t.p_set +flat_values = df.values.ravel() +shuffled_series = pd.Series(flat_values).sample(frac=1).values +df_shuffled = pd.DataFrame( + shuffled_series.reshape(df.shape), index=df.index, columns=df.columns +) +n_shuffled_load.loads_t.p_set = df_shuffled +n_shuffled_load.name = "AC-DC-Meshed-Shuffled-Load" +n_shuffled_load.optimize() + +# Remove solver model to allow copying +n.model.solver_model = None +n_shuffled_load.model.solver_model = None + +# Create a network collection +nc = pypsa.NetworkCollection([n.copy(), n_shuffled_load.copy()]) + + +doctest_globals = { + "np": np, + "pd": pd, + "pypsa": pypsa, + "n": n, + "n_shuffled_load": n_shuffled_load, + "n_stochastic": pypsa.examples.stochastic_network(), + "nc": nc, + "network_collection": nc, + "c": pypsa.examples.ac_dc_meshed().components.generators, + "sub_network_parent": pypsa.examples.ac_dc_meshed().determine_network_topology(), + "sub_network": sub_network_parent.c.sub_networks.static.loc["0", "obj"], +} + +modules = [ + importlib.import_module(name) + for _, name, _ in pkgutil.walk_packages(pypsa.__path__, pypsa.__name__ + ".") + if name not in ["pypsa.utils", "pypsa.components.utils", "pypsa.typing"] +] + + +@pytest.mark.skipif( + sys.version_info[:2] == (3, 10), + reason="Doctest fail until linopy supports numpy 2 on all python versions", +) +@pytest.mark.skipif(new_api, reason="New components API not yet shown in docs") +@pytest.mark.skipif(not cartopy_available, reason="Cartopy not available") +@pytest.mark.parametrize("module", modules) +def test_doctest_code(module, close_matplotlib_figures, test_docs_flag): + if not test_docs_flag: + pytest.skip("Need --test-docs option to run documentation tests") + finder = doctest.DocTestFinder() + + runner = doctest.DocTestRunner(optionflags=doctest.NORMALIZE_WHITESPACE) + + tests = finder.find(module) + + failures = 0 + + for test in tests: + # Create a fresh copy of the globals for each test + + test_globals = dict(doctest_globals) + + test.globs.update(test_globals) + + # Run the test + + failures += runner.run(test).failed + + assert failures == 0, f"{failures} doctest(s) failed in module {module.__name__}" + + +@pytest.mark.skipif( + sys.version_info[:2] == (3, 10), + reason="Doctest fail until linopy supports numpy 2 on all python versions", +) +@pytest.mark.skipif(new_api, reason="New components API not yet shown in docs") +@pytest.mark.skipif(not cartopy_available, reason="Cartopy not available") +@pytest.mark.parametrize( + "fpath", [*Path("docs").glob("**/*.md"), Path("README.md")], ids=str +) +def test_doctest_docs(fpath, test_docs_flag): + """Test Python code blocks in markdown files using doctest.""" + if not test_docs_flag: + pytest.skip("Need --test-docs option to run documentation tests") + import re + + # Read the markdown file + content = fpath.read_text() + + # Extract Python code blocks + python_blocks = re.findall(r"``` py\n(.*?)\n```", content, re.DOTALL) + + if not python_blocks: + return # No Python code blocks to test + + # Combine all Python blocks into one docstring-like content + combined_content = "\n\n".join(python_blocks) + + # Create a pseudo-module with the combined content as docstring + class PseudoModule: + def __init__(self, content): + self.__doc__ = content + self.__name__ = str(fpath) + + module = PseudoModule(combined_content) + + finder = doctest.DocTestFinder() + runner = doctest.DocTestRunner(optionflags=doctest.NORMALIZE_WHITESPACE) + tests = finder.find(module) + failures = 0 + + for test in tests: + # Create a fresh copy of the globals for each test + test_globals = dict(doctest_globals) + + # For docs files, use a fresh network to avoid optimization artifacts + if str(fpath).startswith("docs/"): + test_globals["n"] = pypsa.examples.ac_dc_meshed() + + test.globs.update(test_globals) + + # Run the test + failures += runner.run(test).failed + + assert failures == 0, f"{failures} doctest(s) failed in {fpath}" + + +def test_notebooks(test_docs_flag, pytestconfig): + """Test and manage warning filter injection in Jupyter notebooks. + + This test validates that all notebooks have the correct warning filter cell + injected as the first cell. When run with --fix-notebooks flag, it will + automatically inject or update the warning filters for self-healing. + """ + if not test_docs_flag: + pytest.skip("Need --test-docs option to run documentation tests") + + fix_notebooks = pytestconfig.getoption("--fix-notebooks", default=False) + expected_tags = ["injected-warnings", "hide-cell"] + expected_source = [ + "# General notebook settings\n", + "import warnings\n", + "\n", + 'warnings.filterwarnings("error", category=DeprecationWarning)', + ] + expected_cell = { + "cell_type": "code", + "execution_count": None, + "metadata": {"tags": expected_tags}, + "outputs": [], + "source": expected_source, + } + + notebook_paths = list(Path("docs").glob("**/*.ipynb")) + if not notebook_paths: + pytest.skip("No notebooks found to test") + + failed_notebooks = [] + injection_count = 0 + + for notebook_path in notebook_paths: + with notebook_path.open(encoding="utf-8") as f: + notebook = json.load(f) + + cells = notebook.get("cells", []) + if not cells: + continue + + # Check if first cell matches expected warning filter + first_cell = cells[0] + is_correct = ( + first_cell.get("metadata", {}).get("tags") == expected_tags + and first_cell.get("source") == expected_source + ) + + if not is_correct: + if fix_notebooks: + # Replace or insert warning cell + if first_cell.get("metadata", {}).get("tags") == expected_tags: + cells[0] = expected_cell.copy() # Update existing + else: + cells.insert(0, expected_cell.copy()) # Insert new + + with notebook_path.open("w", encoding="utf-8") as f: + json.dump(notebook, f, indent=1, ensure_ascii=False) + injection_count += 1 + else: + failed_notebooks.append(notebook_path) + + if fix_notebooks and injection_count > 0: + print(f"Fixed {injection_count} notebooks") + + if failed_notebooks and not fix_notebooks: + pytest.fail( + f"{len(failed_notebooks)} notebook(s) have missing or incorrect warning filters. " + f"Run `pytest test/test_docs.py::test_notebooks --test-docs --fix-notebooks` and commit the changes." + ) diff --git a/PyPSA/source/test/test_examples.py b/PyPSA/source/test/test_examples.py new file mode 100644 index 0000000000000000000000000000000000000000..4e31ed7eeb7d69cc5788d0e3ad38a7f8321f0e62 --- /dev/null +++ b/PyPSA/source/test/test_examples.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import logging + +import pytest + +import pypsa + +logger = logging.getLogger(__name__) + + +def test_ac_dc_meshed(): + n = pypsa.examples.ac_dc_meshed() + assert not n.c.buses.static.empty + + +def test_storage_hvdc(): + n = pypsa.examples.storage_hvdc() + assert not n.c.buses.static.empty + + +def test_scigrid_de(): + n = pypsa.examples.scigrid_de() + assert not n.c.buses.static.empty + + +def test_model_energy(): + n = pypsa.examples.model_energy() + assert not n.c.buses.static.empty + + +def test_carbon_management(): + try: + n = pypsa.examples.carbon_management() + assert not n.c.buses.static.empty + except RuntimeError as e: + logger.warning("Test would have failed: %s", e) + pytest.skip("Test failed but converted to warning") + + +def test_check_url_availability(): + """Test _check_url_availability function.""" + from pypsa.examples import _check_url_availability + + # Test invalid URL formats + assert not _check_url_availability("invalid-url") + assert not _check_url_availability("ftp://example.com") + assert not _check_url_availability("") + assert not _check_url_availability("https://google.com/invalid-url") + + # Test valid URL format (should return True for valid URLs) + assert _check_url_availability("https://google.com") + assert _check_url_availability("https://google.com/search?q=pypsa") diff --git a/PyPSA/source/test/test_graph.py b/PyPSA/source/test/test_graph.py new file mode 100644 index 0000000000000000000000000000000000000000..c949c411fd1d89f248caf29529bf42d9b31beac4 --- /dev/null +++ b/PyPSA/source/test/test_graph.py @@ -0,0 +1,210 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +""" +Created on Wed May 6 12:00:00 2025 + +Tests for the graph module +""" + +import warnings + +import pandas as pd +import scipy.sparse as sp + +from pypsa import Network + + +def test_adjacency_matrix_returns_dataframe(): + """Test that adjacency_matrix returns a pandas DataFrame""" + # Create a simple network with 3 buses and 2 lines + n = Network() + n.add("Bus", "bus0") + n.add("Bus", "bus1") + n.add("Bus", "bus2") + n.add("Line", "line0", bus0="bus0", bus1="bus1") + n.add("Line", "line1", bus0="bus1", bus1="bus2") + + # Get the adjacency matrix + adj = n.adjacency_matrix(return_dataframe=True) + + # Check that the result is a DataFrame + assert isinstance(adj, pd.DataFrame) + + # Check dimensions + assert adj.shape == (3, 3) + + # Check indices + assert list(adj.index) == ["bus0", "bus1", "bus2"] + assert list(adj.columns) == ["bus0", "bus1", "bus2"] + + # Check values + assert adj.at["bus0", "bus1"] == 1 + assert adj.at["bus1", "bus2"] == 1 + assert adj.at["bus0", "bus0"] == 0 + assert adj.at["bus0", "bus2"] == 0 + assert adj.at["bus1", "bus0"] == 0 + assert adj.at["bus1", "bus1"] == 0 + assert adj.at["bus2", "bus0"] == 0 + assert adj.at["bus2", "bus1"] == 0 + assert adj.at["bus2", "bus2"] == 0 + + +def test_adjacency_matrix_with_weights(): + """Test adjacency_matrix with custom weights""" + # Create a simple network with 3 buses and 2 lines + n = Network() + n.add("Bus", "bus0") + n.add("Bus", "bus1") + n.add("Bus", "bus2") + n.add("Line", "line0", bus0="bus0", bus1="bus1") + n.add("Line", "line1", bus0="bus1", bus1="bus2") + + # Create weights + weights = pd.Series( + [2.5, 3.5], + index=pd.MultiIndex.from_tuples( + [("Line", "line0"), ("Line", "line1")], names=["component", "name"] + ), + ) + + # Get the adjacency matrix with weights + adj = n.adjacency_matrix(weights=weights, return_dataframe=True) + + # Check values + assert adj.at["bus0", "bus1"] == 2.5 + assert adj.at["bus1", "bus2"] == 3.5 + + +def test_adjacency_matrix_returns_sparse(): + """Test that adjacency_matrix returns a sparse matrix when return_dataframe=False""" + # Create a simple network with 3 buses and 2 lines + n = Network() + n.add("Bus", "bus0") + n.add("Bus", "bus1") + n.add("Bus", "bus2") + n.add("Line", "line0", bus0="bus0", bus1="bus1") + n.add("Line", "line1", bus0="bus1", bus1="bus2") + + # Get the adjacency matrix as sparse + adj = n.adjacency_matrix(return_dataframe=False) + + # Check that the result is a sparse matrix + assert isinstance(adj, sp.coo_matrix) + + # Check dimensions + assert adj.shape == (3, 3) + + # Convert to dense for easier checking + adj_dense = adj.toarray() + + # Check values (bus indices are in order: bus0=0, bus1=1, bus2=2) + assert adj_dense[0, 1] == 1 # bus0 -> bus1 + assert adj_dense[1, 2] == 1 # bus1 -> bus2 + assert adj_dense[0, 0] == 0 + assert adj_dense[0, 2] == 0 + assert adj_dense[1, 0] == 0 + assert adj_dense[1, 1] == 0 + assert adj_dense[2, 0] == 0 + assert adj_dense[2, 1] == 0 + assert adj_dense[2, 2] == 0 + + +def test_adjacency_matrix_sparse_with_weights(): + """Test adjacency_matrix with custom weights returns sparse matrix correctly""" + # Create a simple network with 3 buses and 2 lines + n = Network() + n.add("Bus", "bus0") + n.add("Bus", "bus1") + n.add("Bus", "bus2") + n.add("Line", "line0", bus0="bus0", bus1="bus1") + n.add("Line", "line1", bus0="bus1", bus1="bus2") + + # Create weights + weights = pd.Series( + [2.5, 3.5], + index=pd.MultiIndex.from_tuples( + [("Line", "line0"), ("Line", "line1")], names=["component", "name"] + ), + ) + + # Get the adjacency matrix with weights as sparse + adj = n.adjacency_matrix(weights=weights, return_dataframe=False) + + # Check that the result is a sparse matrix + assert isinstance(adj, sp.coo_matrix) + + # Convert to dense for easier checking + adj_dense = adj.toarray() + + # Check values + assert adj_dense[0, 1] == 2.5 # bus0 -> bus1 + assert adj_dense[1, 2] == 3.5 # bus1 -> bus2 + + +def test_adjacency_matrix_compatibility(): + """Test that both formats contain the same information""" + # Create a simple network with 3 buses and 2 lines + n = Network() + n.add("Bus", "bus0") + n.add("Bus", "bus1") + n.add("Bus", "bus2") + n.add("Line", "line0", bus0="bus0", bus1="bus1") + n.add("Line", "line1", bus0="bus1", bus1="bus2") + + # Get both formats + adj_df = n.adjacency_matrix(return_dataframe=True) + adj_sparse = n.adjacency_matrix(return_dataframe=False) + + # Convert sparse to dense + adj_sparse_dense = adj_sparse.toarray() + + # Check that they contain the same information + for i, bus_i in enumerate(["bus0", "bus1", "bus2"]): + for j, bus_j in enumerate(["bus0", "bus1", "bus2"]): + assert adj_df.at[bus_i, bus_j] == adj_sparse_dense[i, j] + + +def test_adjacency_matrix_deprecation_warning(): + """Test that adjacency_matrix shows deprecation warning when return_dataframe is not specified""" + # Create a simple network with 3 buses and 2 lines + n = Network() + n.add("Bus", "bus0") + n.add("Bus", "bus1") + n.add("Bus", "bus2") + n.add("Line", "line0", bus0="bus0", bus1="bus1") + n.add("Line", "line1", bus0="bus1", bus1="bus2") + + # Check that calling without return_dataframe parameter raises FutureWarning + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + adj = n.adjacency_matrix() + + # Check that a warning was raised + assert len(w) == 1 + assert issubclass(w[0].category, FutureWarning) + assert "adjacency_matrix will return a pandas DataFrame by default" in str( + w[0].message + ) + + # Check that it still returns sparse matrix + assert isinstance(adj, sp.coo_matrix) + + # Check that calling with explicit return_dataframe=False doesn't raise warning + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + adj = n.adjacency_matrix(return_dataframe=False) + + # Check that no warning was raised + assert len(w) == 0 + assert isinstance(adj, sp.coo_matrix) + + # Check that calling with explicit return_dataframe=True doesn't raise warning + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + adj = n.adjacency_matrix(return_dataframe=True) + + # Check that no warning was raised + assert len(w) == 0 + assert isinstance(adj, pd.DataFrame) diff --git a/PyPSA/source/test/test_guards.py b/PyPSA/source/test/test_guards.py new file mode 100644 index 0000000000000000000000000000000000000000..907e6747aacbf7db34672ea3cae855fe175407a5 --- /dev/null +++ b/PyPSA/source/test/test_guards.py @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Tests for runtime verification guards.""" + +import subprocess +import sys + +import pypsa + + +def test_runtime_verification_enabled(): + """Test that runtime verification is enabled during tests.""" + assert pypsa.options.debug.runtime_verification is True, ( + "Runtime verification should be enabled during tests" + ) + + +def test_runtime_verification_disabled_by_default(): + """Test that runtime verification is disabled by default without conftest.""" + code = "import pypsa; print(pypsa.options.debug.runtime_verification)" + result = subprocess.run( + [sys.executable, "-c", code], capture_output=True, text=True + ) + assert result.stdout.strip() == "False", ( + "Runtime verification should be False by default" + ) diff --git a/PyPSA/source/test/test_indices.py b/PyPSA/source/test/test_indices.py new file mode 100644 index 0000000000000000000000000000000000000000..5605e3104deafd54e2bf8d46c4128e38a8f50617 --- /dev/null +++ b/PyPSA/source/test/test_indices.py @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pandas as pd +import pytest + + +@pytest.fixture +def network(ac_dc_network): + return ac_dc_network # Change scope of existing fixture to function + + +@pytest.fixture +def network_add_snapshots(network): + # Single dimension + n = network.copy() + snapshots = pd.date_range("2015-01-01", "2015-01-02", freq="h") + n.set_snapshots(snapshots) + assert n.snapshots.equals(snapshots) + assert n.snapshots.names == ["snapshot"] # TODO: Should be changed + return n + + +@pytest.fixture +def network_add_snapshots_multiindex(network): + # Multi dimension + n = network.copy() + snapshots = pd.MultiIndex.from_product( + [[2015], pd.date_range("2015-01-01", "2015-01-02", freq="h")] + ) + n.set_snapshots(snapshots) + assert n.snapshots.equals(snapshots) + assert n.snapshots.names == ["period", "timestep"] + return n + + +@pytest.mark.parametrize( + "network_fixture", + ["network", "network_add_snapshots", "network_add_snapshots_multiindex"], +) +def test_snapshot_index_consistency(request, network_fixture): + n = request.getfixturevalue(network_fixture) + for component in n.all_components: + dynamic = n.c[component].dynamic + for k in dynamic.keys(): + assert dynamic[k].index.equals(n.snapshots) + + +@pytest.mark.parametrize( + "network_fixture", + ["network_add_snapshots", "network_add_snapshots_multiindex"], +) +def test_existing_value_casting(request, network_fixture): + n = request.getfixturevalue(network_fixture) + base_network = request.getfixturevalue("network") + assert not isinstance(base_network.snapshots, pd.MultiIndex) + snapshots = base_network.snapshots + if isinstance(n.snapshots, pd.MultiIndex): + vals = n.c.generators.dynamic.p_max_pu.xs(2015).loc[snapshots, :] + else: + vals = n.c.generators.dynamic.p_max_pu.loc[snapshots, :] + assert vals.equals(base_network.c.generators.dynamic.p_max_pu) + + +# @pytest.mark.parametrize("meta", [{"test": "test"}, {"test": {"test": "test"}}]) +def test_set_snapshots_checks(network): + # Don't allow time zone aware snapshots + snapshots_tz = pd.date_range("2020-01-01", "2020-01-02", freq="h", tz="UTC") + with pytest.raises(ValueError): + network.set_snapshots(snapshots_tz) + + # Don't allow more than two dimensions + snapshots_more_dims = pd.MultiIndex.from_product([[2020], snapshots_tz, ["test"]]) + with pytest.raises(ValueError): + network.set_snapshots(snapshots_more_dims) + + # Don't allow empty snapshots + with pytest.raises(ValueError): + network.set_snapshots(pd.Index([])) + with pytest.raises(ValueError): + network.set_snapshots(pd.MultiIndex.from_arrays([[], []])) diff --git a/PyPSA/source/test/test_io.py b/PyPSA/source/test/test_io.py new file mode 100644 index 0000000000000000000000000000000000000000..fb4b7f9f4bda919b5854ab3e2906f0c38d442585 --- /dev/null +++ b/PyPSA/source/test/test_io.py @@ -0,0 +1,721 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import sys + +import pandas as pd +import pytest +from geopandas.testing import assert_geodataframe_equal +from numpy.testing import assert_array_almost_equal as equal + +import pypsa + +try: + import tables # noqa: F401 + + tables_installed = True +except ImportError: + tables_installed = False + +try: + import openpyxl # noqa: F401 + import python_calamine # noqa: F401 + + excel_installed = True +except ImportError: + excel_installed = False + + +def custom_equals(n1, n2, ignore_attrs=None): + """ + Custom equality check that allows certain attributes to be different. + Parameters + ---------- + n1, n2 : pypsa.Network + Networks to compare + ignore_attrs : list of str, optional + List of attribute names that are allowed to be different. + """ + if not ignore_attrs: + return n1.equals(n2, log_mode="strict") + + # Copy networks to avoid modifying originals + n1 = n1.copy() + n2 = n2.copy() + + for attr in ignore_attrs: + for net in (n1, n2): + obj = net + parts = attr.split(".") + for part in parts[:-1]: + if hasattr(obj, part): + obj = getattr(obj, part) + else: + break + else: + if hasattr(obj, parts[-1]): + setattr(obj, parts[-1], None) + + return n1.equals(n2, log_mode="strict") + + +# TODO classes could be further parametrized +class TestCSVDir: + @pytest.mark.parametrize( + "meta", + [ + {"test": "test"}, + {"test": "test", "test2": "test2"}, + {"test": {"test": "test", "test2": "test2"}}, + ], + ) + def test_csv_io(self, scipy_network, tmpdir, meta): + fn = tmpdir / "csv_export" + scipy_network.meta = meta + scipy_network.export_to_csv_folder(fn) + pypsa.Network(fn) + reloaded = pypsa.Network(fn) + assert reloaded.meta == scipy_network.meta + + @pytest.mark.parametrize( + "meta", + [ + {"test": "test"}, + {"test": "test", "test2": "test2"}, + {"test": {"test": "test", "test2": "test2"}}, + ], + ) + def test_csv_io_quotes(self, scipy_network, tmpdir, meta): + fn = tmpdir / "csv_export" + scipy_network.meta = meta + scipy_network.export_to_csv_folder(fn, quotechar="'") + imported = pypsa.Network() + imported.import_from_csv_folder(fn, quotechar="'") + assert imported.meta == scipy_network.meta + + def test_csv_io_Path(self, scipy_network, tmpdir): + fn = tmpdir / "csv_export" + scipy_network.export_to_csv_folder(fn) + pypsa.Network(fn) + + def test_csv_io_multiindexed(self, ac_dc_periods, tmpdir): + fn = tmpdir / "csv_export" + ac_dc_periods.export_to_csv_folder(fn) + m = pypsa.Network(fn) + pd.testing.assert_frame_equal( + m.c.generators.dynamic.p, + ac_dc_periods.c.generators.dynamic.p, + ) + + def test_csv_io_shapes(self, ac_dc_shapes, tmpdir): + fn = tmpdir / "csv_export" + ac_dc_shapes.export_to_csv_folder(fn) + m = pypsa.Network(fn) + assert_geodataframe_equal( + m.c.shapes.static, + ac_dc_shapes.c.shapes.static, + check_less_precise=True, + ) + + def test_csv_io_shapes_with_missing(self, ac_dc_shapes, tmpdir): + fn = tmpdir / "csv_export" + n = ac_dc_shapes.copy() + n.c.shapes.static.loc["Manchester", "geometry"] = None + n.export_to_csv_folder(fn) + m = pypsa.Network(fn) + assert_geodataframe_equal( + m.c.shapes.static, + n.c.shapes.static, + check_less_precise=True, + ) + + @pytest.mark.skipif( + sys.version_info < (3, 13) or sys.platform not in ["linux", "darwin"], + reason="Unstable test in CI. Remove with 1.0", + ) + def test_io_equality(self, networks_including_solved, tmp_path): + """ + Test if the network is equal after export and import using CSV format. + """ + n = networks_including_solved + if n.has_scenarios: + with pytest.raises( + NotImplementedError, + match="Stochastic networks are not supported*", + ): + n.export_to_csv_folder(tmp_path / "network") + return + n.export_to_csv_folder(tmp_path / "network") + n3 = pypsa.Network(tmp_path / "network") + # Allow difference for solved networks + # TODO: Remove _components.links with #1128 + ignore = ( + [ + "_components.sub_networks.static.obj", + "_components.links", + "_components.lines", + ] + if n.model is not None + else [] + ) + assert custom_equals(n, n3, ignore_attrs=ignore) + + +class TestNetcdf: + @pytest.mark.parametrize( + "meta", + [ + {"test": "test"}, + {"test": "test", "test2": "test2"}, + {"test": {"test": "test", "test2": "test2"}}, + ], + ) + def test_netcdf_io(self, scipy_network, tmpdir, meta): + fn = tmpdir / "netcdf_export.nc" + scipy_network.meta = meta + scipy_network.export_to_netcdf(fn) + reloaded = pypsa.Network(fn) + assert reloaded.meta == scipy_network.meta + + def test_netcdf_io_Path(self, scipy_network, tmpdir): + fn = tmpdir / "netcdf_export.nc" + scipy_network.export_to_netcdf(fn) + pypsa.Network(fn) + + def test_netcdf_io_datetime(self, tmpdir): + fn = tmpdir / "temp.nc" + exported_sns = pd.date_range(start="2013-03-01", end="2013-03-02", freq="h") + n = pypsa.Network() + n.set_snapshots(exported_sns) + n.export_to_netcdf(fn) + imported_sns = pypsa.Network(fn).snapshots + + assert (imported_sns == exported_sns).all() + + def test_netcdf_io_multiindexed(self, ac_dc_periods, tmpdir): + fn = tmpdir / "netcdf_export.nc" + ac_dc_periods.export_to_netcdf(fn) + m = pypsa.Network(fn) + pd.testing.assert_frame_equal( + m.c.generators.dynamic.p, + ac_dc_periods.c.generators.dynamic.p, + ) + pd.testing.assert_frame_equal( + m.snapshot_weightings, + ac_dc_periods.snapshot_weightings[ + m.snapshot_weightings.columns + ], # reset order + ) + + def test_netcdf_io_shapes(self, ac_dc_shapes, tmpdir): + fn = tmpdir / "netcdf_export.nc" + ac_dc_shapes.export_to_netcdf(fn) + m = pypsa.Network(fn) + assert_geodataframe_equal( + m.c.shapes.static, + ac_dc_shapes.c.shapes.static, + check_less_precise=True, + ) + + def test_netcdf_io_shapes_with_missing(self, ac_dc_shapes, tmpdir): + fn = tmpdir / "netcdf_export.nc" + n = ac_dc_shapes.copy() + n.c.shapes.static.loc["Manchester", "geometry"] = None + n.export_to_netcdf(fn) + m = pypsa.Network(fn) + assert_geodataframe_equal( + m.c.shapes.static, + n.c.shapes.static, + check_less_precise=True, + ) + + def test_netcdf_from_url(self): + url = "https://github.com/PyPSA/PyPSA/raw/master/examples/networks/scigrid-de/scigrid-de.nc" + pypsa.Network(url) + + def test_netcdf_io_no_compression(self, scipy_network, tmpdir): + fn = tmpdir / "netcdf_export.nc" + scipy_network.export_to_netcdf(fn, float32=False, compression=None) + scipy_network_compressed = pypsa.Network(fn) + assert ( + ( + scipy_network.c.loads.dynamic.p_set + == scipy_network_compressed.c.loads.dynamic.p_set + ) + .all() + .all() + ) + + def test_netcdf_io_custom_compression(self, scipy_network, tmpdir): + fn = tmpdir / "netcdf_export.nc" + digits = 5 + compression = {"zlib": True, "complevel": 9, "least_significant_digit": digits} + scipy_network.export_to_netcdf(fn, compression=compression) + scipy_network_compressed = pypsa.Network(fn) + assert ( + ( + ( + scipy_network.c.loads.dynamic.p_set + - scipy_network_compressed.c.loads.dynamic.p_set + ).abs() + < 1 / 10**digits + ) + .all() + .all() + ) + + def test_netcdf_io_typecast(self, scipy_network, tmpdir): + fn = tmpdir / "netcdf_export.nc" + scipy_network.export_to_netcdf(fn, float32=True, compression=None) + pypsa.Network(fn) + + def test_netcdf_io_typecast_and_compression(self, scipy_network, tmpdir): + fn = tmpdir / "netcdf_export.nc" + scipy_network.export_to_netcdf(fn, float32=True) + pypsa.Network(fn) + + @pytest.mark.skipif( + sys.version_info < (3, 13) or sys.platform not in ["linux", "darwin"], + reason="Unstable test in CI. Remove with 1.0", + ) + def test_io_equality(self, networks_including_solved, tmp_path): + """ + Test if the network is equal after export and import using netCDF format. + """ + n = networks_including_solved + n.export_to_netcdf(tmp_path / "network.nc") + n2 = pypsa.Network(tmp_path / "network.nc") + # Allow difference for solved networks + # TODO: Remove _components.links with #1128 + ignore = ( + [ + "_components.sub_networks.static.obj", + "_components.links", + "_components.lines", + ] + if n.model is not None + else [] + ) + assert custom_equals(n, n2, ignore_attrs=ignore) + + +@pytest.mark.skipif(not tables_installed, reason="PyTables not installed") +class TestHDF5: + @pytest.mark.parametrize( + "meta", + [ + {"test": "test"}, + {"test": "test", "test2": "test2"}, + {"test": {"test": "test", "test2": "test2"}}, + ], + ) + def test_hdf5_io(self, scipy_network, tmpdir, meta): + fn = tmpdir / "hdf5_export.h5" + scipy_network.meta = meta + scipy_network.export_to_hdf5(fn) + pypsa.Network(fn) + reloaded = pypsa.Network(fn) + assert reloaded.meta == scipy_network.meta + + def test_hdf5_io_Path(self, scipy_network, tmpdir): + fn = tmpdir / "hdf5_export.h5" + scipy_network.export_to_hdf5(fn) + pypsa.Network(fn) + + def test_hdf5_io_multiindexed(self, ac_dc_periods, tmpdir): + fn = tmpdir / "hdf5_export.h5" + ac_dc_periods.export_to_hdf5(fn) + m = pypsa.Network(fn) + pd.testing.assert_frame_equal( + m.c.generators.dynamic.p, + ac_dc_periods.c.generators.dynamic.p, + ) + + def test_hdf5_io_shapes(self, ac_dc_shapes, tmpdir): + fn = tmpdir / "hdf5_export.h5" + ac_dc_shapes.export_to_hdf5(fn) + m = pypsa.Network(fn) + assert_geodataframe_equal( + m.c.shapes.static, + ac_dc_shapes.c.shapes.static, + check_less_precise=True, + ) + + def test_hdf5_io_shapes_with_missing(self, ac_dc_shapes, tmpdir): + fn = tmpdir / "hdf5_export.h5" + n = ac_dc_shapes.copy() + n.c.shapes.static.loc["Manchester", "geometry"] = None + n.export_to_hdf5(fn) + m = pypsa.Network(fn) + assert_geodataframe_equal( + m.c.shapes.static, + n.c.shapes.static, + check_less_precise=True, + ) + + @pytest.mark.skipif( + sys.version_info < (3, 13) or sys.platform not in ["linux", "darwin"], + reason="Unstable test in CI. Remove with 1.0", + ) + def test_io_equality(self, networks_including_solved, tmp_path): + """ + Test if the network is equal after export and import using HDF5 format. + """ + n = networks_including_solved + if n.has_scenarios: + with pytest.raises( + NotImplementedError, + match="Stochastic networks are not supported*", + ): + n.export_to_hdf5(tmp_path / "network.h5") + return + n.export_to_hdf5(tmp_path / "network.h5") + n5 = pypsa.Network(tmp_path / "network.h5") + # Allow difference for solved networks + # TODO: Remove _components.links with #1128 + ignore = ( + [ + "_components.sub_networks.static.obj", + "_components.links", + "_components.lines", + ] + if n.model is not None + else [] + ) + assert custom_equals(n, n5, ignore_attrs=ignore) + + +@pytest.mark.skipif(not excel_installed, reason="openpyxl not installed") +class TestExcelIO: + @pytest.mark.parametrize( + "meta", + [ + {"test": "test"}, + {"test": "test", "test2": "test2"}, + {"test": {"test": "test", "test2": "test2"}}, + ], + ) + def test_excel_io(self, scipy_network, tmpdir, meta): + fn = tmpdir / "excel_export.xlsx" + scipy_network.meta = meta + scipy_network.export_to_excel(fn) + reloaded = pypsa.Network(fn) + assert reloaded.meta == scipy_network.meta + + def test_excel_io_Path(self, scipy_network, tmpdir): + fn = tmpdir / "excel_export.xlsx" + scipy_network.export_to_excel(fn) + pypsa.Network(fn) + + def test_excel_io_datetime(self, tmpdir): + fn = tmpdir / "temp.xlsx" + exported_sns = pd.date_range(start="2013-03-01", end="2013-03-02", freq="h") + n = pypsa.Network() + n.set_snapshots(exported_sns) + n.export_to_excel(fn) + imported_sns = pypsa.Network(fn).snapshots + assert (imported_sns == exported_sns).all() + + def test_excel_io_multiindexed(self, ac_dc_periods, tmpdir): + fn = tmpdir / "excel_export.xlsx" + ac_dc_periods.export_to_excel(fn) + m = pypsa.Network(fn) + pd.testing.assert_frame_equal( + m.c.generators.dynamic.p, + ac_dc_periods.c.generators.dynamic.p, + ) + pd.testing.assert_frame_equal( + m.snapshot_weightings, + ac_dc_periods.snapshot_weightings[m.snapshot_weightings.columns], + check_dtype=False, # TODO Remove once validation layer leads to safer types + ) + + def test_excel_io_shapes(self, ac_dc_shapes, tmpdir): + fn = tmpdir / "excel_export.xlsx" + ac_dc_shapes.export_to_excel(fn) + m = pypsa.Network(fn) + assert_geodataframe_equal( + m.c.shapes.static, + ac_dc_shapes.c.shapes.static, + check_less_precise=True, + ) + + def test_excel_io_shapes_with_missing(self, ac_dc_shapes, tmpdir): + fn = tmpdir / "excel_export.xlsx" + n = ac_dc_shapes.copy() + n.c.shapes.static.loc["Manchester", "geometry"] = None + n.export_to_excel(fn) + m = pypsa.Network(fn) + assert_geodataframe_equal( + m.c.shapes.static, + n.c.shapes.static, + check_less_precise=True, + ) + + @pytest.mark.skipif( + sys.version_info < (3, 13) or sys.platform not in ["linux", "darwin"], + reason="Unstable test in CI. Remove with 1.0", + ) + def test_io_equality(self, networks_including_solved, tmp_path): + """ + Test if the network is equal after export and import using Excel format. + """ + n = networks_including_solved + if n.has_scenarios: + with pytest.raises( + NotImplementedError, + match="Stochastic networks are not supported*", + ): + n.export_to_excel(tmp_path / "network.xlsx") + return + n.export_to_excel(tmp_path / "network.xlsx") + n4 = pypsa.Network(tmp_path / "network.xlsx") + # Allow difference for solved networks + # TODO: Remove _components.links with #1128 + ignore = ( + [ + "_components.sub_networks.static.obj", + "_components.links", + "_components.lines", + ] + if n.model is not None + else [] + ) + assert custom_equals(n, n4, ignore_attrs=ignore) + + def test_io_time_dependent_efficiencies_excel(self, tmpdir): + n = pypsa.Network() + s = [1, 0.95, 0.99] + n.snapshots = range(len(s)) + n.add("Bus", "bus") + n.add("Generator", "gen", bus="bus", efficiency=s) + n.add("Store", "sto", bus="bus", standing_loss=s) + n.add( + "StorageUnit", + "su", + bus="bus", + efficiency_store=s, + efficiency_dispatch=s, + standing_loss=s, + ) + fn = tmpdir / "network-time-eff.xlsx" + n.export_to_excel(fn) + m = pypsa.Network(fn) + assert not m.c.stores.dynamic.standing_loss.empty + assert not m.c.storage_units.dynamic.standing_loss.empty + assert not m.c.generators.dynamic.efficiency.empty + assert not m.c.storage_units.dynamic.efficiency_store.empty + assert not m.c.storage_units.dynamic.efficiency_dispatch.empty + equal(m.c.stores.dynamic.standing_loss, n.c.stores.dynamic.standing_loss) + equal( + m.c.storage_units.dynamic.standing_loss, + n.c.storage_units.dynamic.standing_loss, + ) + equal(m.c.generators.dynamic.efficiency, n.c.generators.dynamic.efficiency) + equal( + m.c.storage_units.dynamic.efficiency_store, + n.c.storage_units.dynamic.efficiency_store, + ) + equal( + m.c.storage_units.dynamic.efficiency_dispatch, + n.c.storage_units.dynamic.efficiency_dispatch, + ) + + +@pytest.mark.skipif( + sys.version_info < (3, 13) or sys.platform not in ["linux", "darwin"], + reason="Unstable test in CI. Remove with 1.0", +) +def test_io_equality(networks_including_solved, tmp_path): + """ + Test if the network is equal after export and import. + """ + n = networks_including_solved + n.export_to_netcdf(tmp_path / "network.nc") + n2 = pypsa.Network(tmp_path / "network.nc") + # Allow difference for solved networks + # TODO: Remove _components.links with #1128 + ignore = ( + [ + "_components.sub_networks.static.obj", + "_components.links", + "_components.lines", + ] + if n.model is not None + else [] + ) + assert custom_equals(n, n2, ignore_attrs=ignore) + + # Only check with supported io formats + if n.has_scenarios: + return + + n.export_to_csv_folder(tmp_path / "network") + n3 = pypsa.Network(tmp_path / "network") + assert custom_equals(n, n3, ignore_attrs=ignore) + + if excel_installed: + n.export_to_excel(tmp_path / "network.xlsx") + n4 = pypsa.Network(tmp_path / "network.xlsx") + assert custom_equals(n, n4, ignore_attrs=ignore) + + if tables_installed: + n.export_to_hdf5(tmp_path / "network.h5") + n5 = pypsa.Network(tmp_path / "network.h5") + assert custom_equals(n, n5, ignore_attrs=ignore) + + +@pytest.mark.skipif( + sys.version_info < (3, 13) or sys.platform not in ["linux", "darwin"], + reason="Only check once since it is an optional test when examples are updated.", +) +@pytest.mark.parametrize( + "example_network", + [ + "ac-dc-meshed", + "storage-hvdc", + "scigrid-de", + "model-energy", + ], +) +def test_examples_against_master(tmp_path, example_network): + # Test examples are unchanged + n = pypsa.Network(f"examples/networks/{example_network}/{example_network}") + # Test examples vs master + example_network = pypsa.Network( + f"https://github.com/PyPSA/PyPSA/raw/master/examples/networks/{example_network}/{example_network}.nc" + ) + assert n.equals(example_network, log_mode="strict") + + +@pytest.mark.parametrize( + "example_network", + [ + "ac-dc-meshed", + "storage-hvdc", + "scigrid-de", + "model-energy", + ], +) +def test_examples_consistency(tmp_path, example_network): + # Test examples are unchanged + n = pypsa.Network(f"examples/networks/{example_network}/{example_network}") + n.export_to_csv_folder(tmp_path / "network") + n2 = pypsa.Network(tmp_path / "network") + assert n.equals(n2, log_mode="strict") + + +@pytest.mark.skipif( + sys.version_info < (3, 12), reason="Test requires Python 3.12 or higher" +) +@pytest.mark.parametrize("use_pandapower_index", [True, False]) +@pytest.mark.parametrize("extra_line_data", [True, False]) +def test_import_from_pandapower_network( + pandapower_custom_network, + pandapower_cigre_network, + extra_line_data, + use_pandapower_index, +): + nets = [pandapower_custom_network, pandapower_cigre_network] + for net in nets: + n = pypsa.Network() + n.import_from_pandapower_net( + net, + use_pandapower_index=use_pandapower_index, + extra_line_data=extra_line_data, + ) + assert len(n.c.buses.static) == len(net.bus) + assert len(n.c.generators.static) == ( + len(net.gen) + len(net.sgen) + len(net.ext_grid) + ) + assert len(n.loads) == len(net.load) + assert len(n.c.transformers.static) == len(net.trafo) + assert len(n.c.shunt_impedances.static) == len(net.shunt) + + +def test_io_time_dependent_efficiencies(tmpdir): + n = pypsa.Network() + s = [1, 0.95, 0.99] + n.snapshots = range(len(s)) + n.add("Bus", "bus") + n.add("Generator", "gen", bus="bus", efficiency=s) + n.add("Store", "sto", bus="bus", standing_loss=s) + n.add( + "StorageUnit", + "su", + bus="bus", + efficiency_store=s, + efficiency_dispatch=s, + standing_loss=s, + ) + + fn = tmpdir / "network-time-eff.nc" + n.export_to_netcdf(fn) + m = pypsa.Network(fn) + + assert not m.c.stores.dynamic.standing_loss.empty + assert not m.c.storage_units.dynamic.standing_loss.empty + assert not m.c.generators.dynamic.efficiency.empty + assert not m.c.storage_units.dynamic.efficiency_store.empty + assert not m.c.storage_units.dynamic.efficiency_dispatch.empty + + equal(m.c.stores.dynamic.standing_loss, n.c.stores.dynamic.standing_loss) + equal( + m.c.storage_units.dynamic.standing_loss, n.c.storage_units.dynamic.standing_loss + ) + equal(m.c.generators.dynamic.efficiency, n.c.generators.dynamic.efficiency) + equal( + m.c.storage_units.dynamic.efficiency_store, + n.c.storage_units.dynamic.efficiency_store, + ) + equal( + m.c.storage_units.dynamic.efficiency_dispatch, + n.c.storage_units.dynamic.efficiency_dispatch, + ) + + +def test_sort_attrs(): + """Ensure _sort_attrs preserves attribute order semantics.""" + from pypsa.network.io import _sort_attrs + + axis_labels = pd.Index(["c", "a", "b", "d"]) + attrs_list = ["a", "b", "c"] + ordered = _sort_attrs(axis_labels, attrs_list) + assert list(ordered) == ["a", "b", "c", "d"] + + # Ignore attributes that are not present on the axis + attrs_list = ["a", "x", "b", "y"] + ordered = _sort_attrs(axis_labels, attrs_list) + assert list(ordered) == ["a", "b", "c", "d"] + + # Missing attrs_list should leave order untouched + ordered = _sort_attrs(axis_labels, []) + assert ordered.equals(axis_labels) + + # Empty axis behaves like a no-op + empty_axis = pd.Index([]) + ordered = _sort_attrs(empty_axis, ["a", "b"]) + assert ordered.equals(empty_axis) + + # Works with non-unique Index types (e.g. MultiIndex) + axis_labels = pd.MultiIndex.from_product([["a", "b"], ["x", "y"]]) + attrs_list = pd.MultiIndex.from_product([["b", "a"], ["y"]]) + ordered = _sort_attrs(axis_labels, attrs_list) + assert list(ordered) == [ + ("b", "y"), + ("a", "y"), + ("a", "x"), + ("b", "x"), + ] + + +def test_version_warning(caplog): + # Assert no info logged with "version" + n = pypsa.examples.ac_dc_meshed() + assert "Importing network from PyPSA version" not in caplog.text + + n._pypsa_version = "0.10.0" + n.export_to_netcdf("test.nc") + pypsa.Network("test.nc") + assert "Importing network from PyPSA version v0.10.0" in caplog.text diff --git a/PyPSA/source/test/test_io_cloudpathlib.py b/PyPSA/source/test/test_io_cloudpathlib.py new file mode 100644 index 0000000000000000000000000000000000000000..bed84240a17f6e5deebaa81082a6f35a62532406 --- /dev/null +++ b/PyPSA/source/test/test_io_cloudpathlib.py @@ -0,0 +1,100 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import sys +from pathlib import Path +from unittest.mock import patch + +import pytest + +import pypsa + +pytest.importorskip("cloudpathlib", reason="cloudpathlib not installed") + +from cloudpathlib import AnyPath, CloudPath, implementation_registry +from cloudpathlib.local import ( + local_azure_blob_implementation, + local_gs_implementation, + local_s3_implementation, +) + + +@pytest.fixture( + params=[ + local_s3_implementation, + local_gs_implementation, + local_azure_blob_implementation, + ], + ids=["s3", "gs", "azure"], +) +def cloudpathlib_local_implementation(request): + implementation = request.param + with ( + patch.dict(implementation_registry, {implementation.name: implementation}), + # NOTE: Bypassing "AzureBlobClient does not support anonymous instantiation" + patch.dict("os.environ", {"AZURE_STORAGE_CONNECTION_STRING": ""}), + ): + yield implementation._path_class + implementation._client_class.reset_default_storage_dir() + + +@pytest.fixture +def cloudpath_local_bucket(cloudpathlib_local_implementation): + local_cls = cloudpathlib_local_implementation + local_bucket = local_cls(f"{local_cls.cloud_prefix}test-local-bucket") + assert isinstance(AnyPath(local_bucket), CloudPath) + return local_bucket + + +@pytest.fixture +def cloudpath_network_parameterized_ext(request, cloudpath_local_bucket): + ext = request.param + cloudpath = cloudpath_local_bucket / f"network{ext}" + assert cloudpath.suffix == request.param + return cloudpath + + +@pytest.fixture(params=[True, False], ids=["uri", "cloudpath"]) +def cloudpath_network(request, cloudpath_network_parameterized_ext): + if request.param: + return cloudpath_network_parameterized_ext.as_uri() + return cloudpath_network_parameterized_ext + + +@pytest.mark.skipif( + sys.platform == "win32", reason="Windows leads to permission errors" +) +class TestIOCloudpath: + @pytest.mark.parametrize( + "cloudpath_network_parameterized_ext", [".nc"], indirect=True, ids=["netcdf"] + ) + def test_io_cloudpath_netcdf(self, cloudpath_network, scipy_network): + scipy_network.export_to_netcdf(cloudpath_network) + n = pypsa.Network() + n.import_from_netcdf(cloudpath_network) + + @pytest.mark.parametrize( + "cloudpath_network_parameterized_ext", [".h5"], indirect=True, ids=["hdf5"] + ) + def test_io_cloudpath_hdf5(self, cloudpath_network, scipy_network): + scipy_network.export_to_hdf5(cloudpath_network) + # FIXME: why is this needed for hdf5? cloudpathlib is claiming that the local + # cached file from export is newer on disk than the cloud file being imported + with patch.dict("os.environ", {"CLOUDPATHLIB_FORCE_OVERWRITE_FROM_CLOUD": "1"}): + n = pypsa.Network() + n.import_from_hdf5(cloudpath_network) + + @pytest.mark.parametrize( + "cloudpath_network_parameterized_ext", [""], indirect=True, ids=["csv_folder"] + ) + def test_io_cloudpath_csv_folder(self, cloudpath_network, scipy_network): + scipy_network.export_to_csv_folder(cloudpath_network) + n = pypsa.Network() + n.import_from_csv_folder(cloudpath_network) + + +def test_cloudpathlib_anypath_uses_pathlib_path_locally(): + path = AnyPath(".") + assert isinstance(path, Path) + assert not isinstance(path, CloudPath) diff --git a/PyPSA/source/test/test_link_post_discretization.py b/PyPSA/source/test/test_link_post_discretization.py new file mode 100644 index 0000000000000000000000000000000000000000..01db508a01e75228ccf0de583b44fa44ab7eff27 --- /dev/null +++ b/PyPSA/source/test/test_link_post_discretization.py @@ -0,0 +1,150 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pypsa +from pypsa.optimization.abstract import discretized_capacity + + +def build_network(unit_size=10): + """ + ' + Build a network with two buses and links that are have + - p_nom_max = unit_size + - p_nom_max = 2*unit_size + - p_nom_max < unit_size + - p_nom_max > unit_size and not a multiple of unit_size + - p_nom_max = np.inf + """ + n = pypsa.Network() + + # add buses + n.add("Bus", "Bus0") + n.add("Bus", "Bus1") + + # add links + n.add( + "Link", + "Link0", + bus0="Bus0", + bus1="Bus1", + p_nom=0, + p_nom_opt=0.5 * unit_size, + p_nom_max=unit_size, + p_nom_extendable=True, + ) + + n.add( + "Link", + "Link1", + bus0="Bus0", + bus1="Bus1", + p_nom=0, + p_nom_opt=1.5 * unit_size, + p_nom_max=2 * unit_size, + p_nom_extendable=True, + ) + n.add( + "Link", + "Link2", + bus0="Bus0", + bus1="Bus1", + p_nom=0, + p_nom_opt=0.5 * unit_size, + p_nom_max=0.8 * unit_size, + p_nom_extendable=True, + ) + n.add( + "Link", + "Link3", + bus0="Bus0", + bus1="Bus1", + p_nom=0, + p_nom_opt=1.3 * unit_size, + p_nom_max=1.5 * unit_size, + p_nom_extendable=True, + ) + n.add( + "Link", + "Link4", + bus0="Bus0", + bus1="Bus1", + p_nom=5, + p_nom_opt=1.1 * unit_size, + p_nom_max=1.5 * unit_size, + p_nom_extendable=True, + ) + n.add( + "Link", + "Link5", + bus0="Bus0", + bus1="Bus1", + p_nom=0, + p_nom_opt=1.5 * unit_size, + p_nom_extendable=True, + ) + return n + + +def test_post_discretization(): + """ + This test checks the post discretization function. + If a Link has a p_nom_max that is not a multiple of the unit_size, + depending on the variable fractional_last_unit_size the p_nom should + either be the last full unit_size or the p_nom_max. + """ + unit_size = 10 + + n = build_network(unit_size=unit_size) + + n.c.links.static["p_nom"] = n.c.links.static.apply( + lambda row: discretized_capacity( + nom_opt=row["p_nom_opt"], + nom_max=row["p_nom_max"], + unit_size=unit_size, + threshold=0.3, + fractional_last_unit_size=True, + ), + axis=1, + ) + + # p_nom_opt | p_nom_max | p_nom | unit_size + # 5 | 10 | 10 | 10 + assert n.c.links.static.loc["Link0"].p_nom == unit_size + # 15 | 20 | 20 | 10 + assert n.c.links.static.loc["Link1"].p_nom == 2 * unit_size + # 5 | 8 | 8 | 10 + assert n.c.links.static.loc["Link2"].p_nom == 0.8 * unit_size + # 13 | 15 | 15 | 10 + assert n.c.links.static.loc["Link3"].p_nom == 1.5 * unit_size + # 11 | 15 | 5 | 10 + assert n.c.links.static.loc["Link4"].p_nom == unit_size + # 15 | inf | 20 | 10 + assert n.c.links.static.loc["Link5"].p_nom == 2 * unit_size + + n = build_network(unit_size=unit_size) + + n.c.links.static["p_nom"] = n.c.links.static.apply( + lambda row: discretized_capacity( + nom_opt=row["p_nom_opt"], + nom_max=row["p_nom_max"], + unit_size=unit_size, + threshold=0.3, + fractional_last_unit_size=False, + ), + axis=1, + ) + + # p_nom_opt | p_nom_max | p_nom | unit_size + # 5 | 10 | 10 | 10 + assert n.c.links.static.loc["Link0"].p_nom == unit_size + # 15 | 20 | 20 | 10 + assert n.c.links.static.loc["Link1"].p_nom == 2 * unit_size + # 5 | 8 | 0 | 10 + assert n.c.links.static.loc["Link2"].p_nom == 8 + # 13 | 15 | 10 | 10 + assert n.c.links.static.loc["Link3"].p_nom == unit_size + # 11 | 15 | 10 | 10 + assert n.c.links.static.loc["Link4"].p_nom == unit_size + # 15 | inf | 20 | 10 + assert n.c.links.static.loc["Link5"].p_nom == 2 * unit_size diff --git a/PyPSA/source/test/test_lopf_ac_dc.py b/PyPSA/source/test/test_lopf_ac_dc.py new file mode 100644 index 0000000000000000000000000000000000000000..74f53b952e678642f0a11a52d18ae27b190c548d --- /dev/null +++ b/PyPSA/source/test/test_lopf_ac_dc.py @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +from numpy.testing import assert_array_almost_equal as equal + + +def test_optimize(ac_dc_network, ac_dc_network_r): + n = ac_dc_network + n_r = ac_dc_network_r + + status, _ = n.optimize(snapshots=n.snapshots) + + assert status == "ok" + + equal( + n.c.generators.dynamic.p.loc[:, n.c.generators.static.index], + n_r.c.generators.dynamic.p.loc[:, n.c.generators.static.index], + decimal=2, + ) + + equal( + n.c.lines.dynamic.p0.loc[:, n.c.lines.static.index], + n_r.c.lines.dynamic.p0.loc[:, n.c.lines.static.index], + decimal=2, + ) + + equal( + n.c.links.dynamic.p0.loc[:, n.c.links.static.index], + n_r.c.links.dynamic.p0.loc[:, n.c.links.static.index], + decimal=2, + ) diff --git a/PyPSA/source/test/test_lopf_activity_mask.py b/PyPSA/source/test/test_lopf_activity_mask.py new file mode 100644 index 0000000000000000000000000000000000000000..7c352d3736ea205ba1a5873c49339013a7a41b1f --- /dev/null +++ b/PyPSA/source/test/test_lopf_activity_mask.py @@ -0,0 +1,516 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import numpy as np +import pandas as pd +import pytest + +import pypsa + + +def test_optimize(ac_dc_network): + n = ac_dc_network + + inactive_links = ["DC link"] + + n.c.links.static.loc[inactive_links, "active"] = False + + status, _ = n.optimize(snapshots=n.snapshots) + + assert status == "ok" + + assert n.c.links.dynamic.p0.loc[:, inactive_links].eq(0).all().all() + + assert "Bremen Converter" in n.model.variables["Link-p_nom"].coords["name"] + assert "DC link" not in n.model.variables["Link-p_nom"].coords["name"] + + assert "Bremen Converter" in n.model.variables["Link-p"].coords["name"] + assert "DC link" not in n.model.variables["Link-p"].coords["name"] + + assert ( + "Bremen Converter" in n.model.constraints["Link-ext-p_nom-lower"].coords["name"] + ) + assert "DC link" not in n.model.constraints["Link-ext-p_nom-lower"].coords["name"] + + +def test_optimize_with_power_flow(scipy_network): + """ + Test the functionality of the 'active' attribute in PyPSA components. + + This test function verifies that the 'active' attribute of network components + (specifically lines in this case) is correctly respected during optimization, + non-linear power flow, and linear power flow calculations. + + The test performs the following checks: + 1. Optimization respects the 'active' status of lines. + 2. Non-linear power flow calculations adhere to the 'active' attribute. + 3. Linear power flow (LPF) results are consistent with the 'active' status. + + The test is performed for both active (True) and inactive (False) scenarios + to ensure proper behavior in both cases. + """ + + @pytest.mark.parametrize("line_active", [True, False]) + def test_scenario(line_active): + n = scipy_network.copy() + switchable_lines = n.c.lines.static.index[100] + n.c.lines.static.loc[switchable_lines, "active"] = line_active + + # Test optimization and non-linear power flow + res = n.optimize.optimize_and_run_non_linear_powerflow( + snapshots=n.snapshots[:1] + ) + + assert res["status"] == "ok", f"Optimization failed with status {res['status']}" + assert res["converged"].all().all(), "Non-linear power flow did not converge" + + expected_flow = ( + not np.isclose(n.c.lines.dynamic.p0.loc[:, switchable_lines], 0).all().all() + ) + msg = f"'active' attribute not respected in optimization/non-linear power flow: expected {'non-zero' if line_active else 'zero'} flow" + assert expected_flow == line_active, msg + + # Test linear power flow + n.lpf() + expected_flow = ( + not np.isclose(n.c.lines.dynamic.p0.loc[:, switchable_lines], 0).all().all() + ) + msg = f"'active' attribute not respected in linear power flow: expected {'non-zero' if line_active else 'zero'} flow" + assert expected_flow == line_active, msg + + msg = "Power balance not maintained" + assert np.isclose(n.c.buses.dynamic.p.sum().sum(), 0, atol=1e-5), msg + + test_scenario(True) + test_scenario(False) + + +def test_generators_with_active_attribute(): + """Test generators with active=False attribute.""" + n = pypsa.Network() + + n.set_snapshots(pd.date_range("2023-01-01", periods=2, freq="h")) + + n.add("Bus", "bus", carrier="AC") + n.add("Carrier", "AC") + + n.add("Load", "load", bus="bus", p_set=100) + + # Add one active generator to meet demand + n.add( + "Generator", "gen_active", bus="bus", p_nom=200, marginal_cost=20, active=True + ) + + # Add one inactive generator + n.add( + "Generator", + "gen_inactive", + bus="bus", + p_nom=150, + marginal_cost=10, + active=False, + ) + + status, _ = n.optimize() + + assert status == "ok" + + # Inactive generator should have zero output + assert (n.c.generators.dynamic.p["gen_inactive"] == 0).all() + + # Inactive generator should not be in model variables + assert "gen_inactive" not in n.model.variables["Generator-p"].coords["name"] + assert "gen_active" in n.model.variables["Generator-p"].coords["name"] + + +def test_stores_with_active_attribute(): + """Test stores with active=False attribute.""" + n = pypsa.Network() + + n.set_snapshots(pd.date_range("2023-01-01", periods=3, freq="h")) + + n.add("Bus", "bus0", carrier="AC") + n.add("Bus", "bus1", carrier="AC") + n.add("Carrier", "AC") + + n.add("Load", "load", bus="bus0", p_set=100) + n.add( + "Generator", + "gen", + bus="bus0", + p_nom=200, + marginal_cost=10, + carrier="AC", + ) + + # Add stores with different active states + n.add( + "Store", + "store_active", + bus="bus0", + e_nom=500, + active=True, + carrier="AC", + ) + n.add( + "Store", + "store_inactive", + bus="bus1", + e_nom=300, + active=False, + carrier="AC", + ) + + status, _ = n.optimize() + + assert status == "ok" + + # Inactive store should have zero flow + assert (n.c.stores.dynamic.p["store_inactive"] == 0).all() + + # Inactive store should not be in model variables + assert "store_inactive" not in n.model.variables["Store-p"].coords["name"] + assert "store_active" in n.model.variables["Store-p"].coords["name"] + assert "store_inactive" not in n.model.variables["Store-e"].coords["name"] + assert "store_active" in n.model.variables["Store-e"].coords["name"] + + +def test_storage_units_with_active_attribute(): + """Test storage units with active=False attribute.""" + n = pypsa.Network() + + n.set_snapshots(pd.date_range("2023-01-01", periods=3, freq="h")) + + n.add("Bus", "bus0", carrier="AC") + n.add("Bus", "bus1", carrier="AC") + n.add("Carrier", "AC") + + n.add("Load", "load", bus="bus0", p_set=100) + n.add( + "Generator", + "gen", + bus="bus0", + p_nom=200, + marginal_cost=10, + carrier="AC", + ) + + # Add storage units with different active states + n.add( + "StorageUnit", + "battery_active", + bus="bus0", + p_nom=50, + active=True, + carrier="AC", + ) + n.add( + "StorageUnit", + "battery_inactive", + bus="bus1", + p_nom=30, + active=False, + carrier="AC", + ) + + status, _ = n.optimize() + + assert status == "ok" + + # Inactive storage unit should have zero dispatch and store + assert (n.c.storage_units.dynamic.p_dispatch["battery_inactive"] == 0).all() + assert (n.c.storage_units.dynamic.p_store["battery_inactive"] == 0).all() + + # Inactive storage unit should not be in model variables + assert ( + "battery_inactive" + not in n.model.variables["StorageUnit-p_dispatch"].coords["name"] + ) + assert ( + "battery_active" in n.model.variables["StorageUnit-p_dispatch"].coords["name"] + ) + assert ( + "battery_inactive" + not in n.model.variables["StorageUnit-state_of_charge"].coords["name"] + ) + assert ( + "battery_active" + in n.model.variables["StorageUnit-state_of_charge"].coords["name"] + ) + + +def test_cyclic_overrides_initial_warnings(caplog): + """Test that warnings are issued when cyclic constraints override initial values for active components only.""" + n = pypsa.Network() + + n.set_snapshots(pd.date_range("2023-01-01", periods=3, freq="h")) + + n.add("Bus", "bus0", carrier="AC") + n.add("Bus", "bus1", carrier="AC") + n.add("Carrier", "AC") + + n.add("Load", "load", bus="bus0", p_set=100) + n.add("Generator", "gen", bus="bus0", p_nom=200, marginal_cost=10, carrier="AC") + + # Add StorageUnits: active with cyclic+initial, inactive with cyclic+initial + n.add( + "StorageUnit", + "battery_active_cyclic", + bus="bus0", + p_nom=50, + state_of_charge_initial=10, + cyclic_state_of_charge=True, + active=True, + carrier="AC", + ) + n.add( + "StorageUnit", + "battery_inactive_cyclic", + bus="bus1", + p_nom=30, + state_of_charge_initial=5, + cyclic_state_of_charge=True, # Also cyclic with initial + active=False, + carrier="AC", + ) + + # Add Stores: active with cyclic+initial, inactive with cyclic+initial + n.add( + "Store", + "store_active_cyclic", + bus="bus0", + e_nom=500, + e_initial=50, + e_cyclic=True, + active=True, + carrier="AC", + ) + n.add( + "Store", + "store_inactive_cyclic", + bus="bus1", + e_nom=300, + e_initial=30, + e_cyclic=True, # Also cyclic with initial + active=False, + carrier="AC", + ) + + caplog.clear() + status, _ = n.optimize() + + assert status == "ok" + + # Check that warnings are issued ONLY for active components + storage_unit_warning = any( + "Cyclic state of charge constraint overrules initial storage level setting" + in record.message + and "battery_active_cyclic" in record.message + for record in caplog.records + ) + assert storage_unit_warning, ( + "Expected warning for active StorageUnit with cyclic + initial" + ) + + store_warning = any( + "Cyclic energy level constraint overrules initial value setting" + in record.message + and "store_active_cyclic" in record.message + for record in caplog.records + ) + assert store_warning, "Expected warning for active Store with cyclic + initial" + + # Verify inactive components are NOT mentioned in any warnings + assert not any( + "battery_inactive_cyclic" in record.message for record in caplog.records + ), "Inactive StorageUnit should not appear in warnings" + + assert not any( + "store_inactive_cyclic" in record.message for record in caplog.records + ), "Inactive Store should not appear in warnings" + + +def test_mixed_components_with_active_attribute(): + """Test mixed component types with active=False attributes.""" + n = pypsa.Network() + + n.set_snapshots(pd.date_range("2023-01-01", periods=3, freq="h")) + + # Create buses + for i in range(3): + n.add("Bus", f"bus{i}", carrier="AC") + + n.add("Carrier", "electricity") + n.add("Carrier", "AC") + + n.add("Load", "load", bus="bus0", p_set=100) + + # Add generators - some active, some not + n.add( + "Generator", + "gen_active", + bus="bus0", + p_nom=150, + marginal_cost=30, + carrier="electricity", + active=True, + ) + n.add( + "Generator", + "gen_inactive", + bus="bus1", + p_nom=120, + marginal_cost=25, + carrier="electricity", + active=False, + ) + + # Add links - some active, some not + n.add( + "Link", + "link_active", + bus0="bus1", + bus1="bus2", + p_nom=90, + marginal_cost=5, + active=True, + carrier="electricity", + ) + n.add( + "Link", + "link_inactive", + bus0="bus0", + bus1="bus1", + p_nom=70, + marginal_cost=2, + active=False, + carrier="electricity", + ) + + # Add stores - some active, some not + n.add( + "Store", + "store_active", + bus="bus0", + e_nom=300, + e_cyclic=True, + marginal_cost=1, + active=True, + carrier="electricity", + ) + n.add( + "Store", + "store_inactive", + bus="bus1", + e_nom=200, + e_cyclic=False, + marginal_cost=3, + active=False, + carrier="electricity", + ) + + status, _ = n.optimize() + + assert status == "ok" + + # Test inactive components have zero flows + assert (n.c.generators.dynamic.p["gen_inactive"] == 0).all() + assert (n.c.links.dynamic.p0["link_inactive"] == 0).all() + assert (n.c.stores.dynamic.p["store_inactive"] == 0).all() + + # Test inactive components not in model variables + assert "gen_inactive" not in n.model.variables["Generator-p"].coords["name"] + assert "link_inactive" not in n.model.variables["Link-p"].coords["name"] + assert "store_inactive" not in n.model.variables["Store-p"].coords["name"] + + +def test_inactive_stores_with_global_operational_limit(): + """Test that inactive stores are excluded from global operational limits.""" + n = pypsa.Network() + + n.set_snapshots(pd.date_range("2023-01-01", periods=3, freq="h")) + + n.add("Bus", "bus0", carrier="AC") + n.add("Bus", "bus1", carrier="AC") + n.add("Carrier", "electricity") + n.add("Carrier", "AC") + + n.add("Load", "load", bus="bus0", p_set=100) + n.add( + "Generator", + "gen", + bus="bus0", + p_nom=200, + marginal_cost=10, + carrier="electricity", + ) + + # Add active and inactive stores with the same carrier + n.add( + "Store", + "store_active", + bus="bus0", + e_nom=500, + e_cyclic=False, + e_initial=100, + active=True, + carrier="electricity", + ) + n.add( + "Store", + "store_inactive", + bus="bus1", + e_nom=300, + e_cyclic=False, + e_initial=50, + active=False, + carrier="electricity", + ) + + # Add global operational limit for electricity carrier + n.add( + "GlobalConstraint", + "electricity_limit", + type="operational_limit", + carrier_attribute="electricity", + sense="<=", + constant=1000, # Generous limit to allow optimization to succeed + ) + + status, _ = n.optimize() + + assert status == "ok" + + # Test inactive store has zero flow + assert (n.c.stores.dynamic.p["store_inactive"] == 0).all() + + # Test inactive store is not in model variables + assert "store_inactive" not in n.model.variables["Store-p"].coords["name"] + assert "store_active" in n.model.variables["Store-p"].coords["name"] + + # Test inactive store is not in model constraints related to energy + assert "store_inactive" not in n.model.variables["Store-e"].coords["name"] + assert "store_active" in n.model.variables["Store-e"].coords["name"] + + +def test_all_storage_units_inactive(): + """Test that optimization works when all storage units are inactive.""" + n = pypsa.Network() + n.set_snapshots(pd.date_range("2023-01-01", periods=2, freq="h")) + + n.add("Bus", "bus") + n.add("Generator", "gen", bus="bus", p_nom=100, marginal_cost=5) + n.add("StorageUnit", "storage1", bus="bus", p_nom=50, active=False) + n.add("StorageUnit", "storage2", bus="bus", p_nom=50, active=False) + + status, _ = n.optimize() + + assert status == "ok" + + # Test that no spillage variables were created for inactive storage units + assert "StorageUnit-spill" not in n.model.variables + + # Test that inactive storage units have zero dispatch + assert (n.c.storage_units.dynamic.p["storage1"] == 0).all() + assert (n.c.storage_units.dynamic.p["storage2"] == 0).all() diff --git a/PyPSA/source/test/test_lopf_and_subsequent_power_flow.py b/PyPSA/source/test/test_lopf_and_subsequent_power_flow.py new file mode 100644 index 0000000000000000000000000000000000000000..8af81c9b86c9a199d1445005617fe8fe834e4dd0 --- /dev/null +++ b/PyPSA/source/test/test_lopf_and_subsequent_power_flow.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + + +def test_optimize_with_power_flow(scipy_network): + n = scipy_network + + res = n.optimize.optimize_and_run_non_linear_powerflow(snapshots=n.snapshots[:4]) + + assert res["status"] == "ok" + assert res["converged"].all().all() diff --git a/PyPSA/source/test/test_lopf_basic_constraints.py b/PyPSA/source/test/test_lopf_basic_constraints.py new file mode 100644 index 0000000000000000000000000000000000000000..87a33a52ab982766010e0b554903403b88536652 --- /dev/null +++ b/PyPSA/source/test/test_lopf_basic_constraints.py @@ -0,0 +1,406 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pandas as pd +import pytest + +import pypsa +from pypsa.common import expand_series +from pypsa.descriptors import nominal_attrs + +TOLERANCE = 1e-2 + + +def describe_storage_unit_contraints(n): + """ + Checks whether all storage units are balanced over time. + + This function requires the network to contain the separate variables + p_store and p_dispatch, since they cannot be reconstructed from p. + The latter results from times tau where p_store(tau) > 0 **and** + p_dispatch(tau) > 0, which is allowed (even though not economic). + Therefor p_store is necessarily equal to negative entries of p, vice + versa for p_dispatch. + """ + sus = n.c.storage_units.static + sus_i = sus.index + if sus_i.empty: + return None + sns = n.snapshots + c = "StorageUnit" + dynamic = n.c[c].dynamic + + eh = expand_series(n.snapshot_weightings.stores[sns], sus_i) + stand_eff = (1 - n.get_switchable_as_dense(c, "standing_loss", sns)).pow(eh) + dispatch_eff = n.get_switchable_as_dense(c, "efficiency_dispatch", sns) + store_eff = n.get_switchable_as_dense(c, "efficiency_store", sns) + inflow = n.get_switchable_as_dense(c, "inflow") * eh + spill = eh[dynamic.spill.columns] * dynamic.spill + + description = { + "Spillage Limit": pd.Series( + {"min": (inflow[spill.columns] - spill).min().min()} + ) + } + if "p_store" in dynamic: + soc = dynamic.state_of_charge + + store = store_eff * eh * dynamic.p_store # .clip(upper=0) + dispatch = 1 / dispatch_eff * eh * dynamic.p_dispatch # (lower=0) + start = soc.iloc[-1].where( + sus.cyclic_state_of_charge, sus.state_of_charge_initial + ) + previous_soc = stand_eff * soc.shift().fillna(start) + + reconstructed = ( + previous_soc.add(store, fill_value=0) + .add(inflow, fill_value=0) + .add(-dispatch, fill_value=0) + .add(-spill, fill_value=0) + ) + description["SOC Balance StorageUnit"] = ( + (reconstructed - soc).unstack().describe() + ) + return pd.concat(description, axis=1, sort=False) + + +def describe_nodal_balance_constraint(n): + """ + Helper function to double check whether network flow is balanced. + """ + network_injection = ( + pd.concat( + [ + n.c[c].dynamic[f"p{inout}"].rename(columns=n.c[c].static[f"bus{inout}"]) + for inout in (0, 1) + for c in ("Line", "Transformer") + ], + axis=1, + ) + .T.groupby(level=0) + .sum() + .T + ) + return ( + (n.c.buses.dynamic.p - network_injection) + .unstack() + .describe() + .to_frame("Nodal Balance Constr.") + ) + + +def describe_upper_dispatch_constraints(n): + """ + Recalculates the minimum gap between operational status and nominal + capacity. + """ + description = {} + key = " Upper Limit" + for c, attr in nominal_attrs.items(): + dispatch_attr = "p0" if c in ["Line", "Transformer", "Link"] else attr[0] + description[c + key] = pd.Series( + { + "min": ( + n.c[c].static[attr + "_opt"] + * n.get_switchable_as_dense(c, attr[0] + "_max_pu") + - n.c[c].dynamic[dispatch_attr] + ) + .min() + .min() + } + ) + return pd.concat(description, axis=1) + + +def describe_lower_dispatch_constraints(n): + description = {} + key = " Lower Limit" + for c, attr in nominal_attrs.items(): + if c in ["Line", "Transformer", "Link"]: + dispatch_attr = "p0" + description[c] = pd.Series( + { + "min": ( + n.c[c].static[attr + "_opt"] + * n.get_switchable_as_dense(c, attr[0] + "_max_pu") + + n.c[c].dynamic[dispatch_attr] + ) + .min() + .min() + } + ) + else: + dispatch_attr = attr[0] + description[c + key] = pd.Series( + { + "min": ( + -n.c[c].static[attr + "_opt"] + * n.get_switchable_as_dense(c, attr[0] + "_min_pu") + + n.c[c].dynamic[dispatch_attr] + ) + .min() + .min() + } + ) + return pd.concat(description, axis=1) + + +def describe_store_contraints(n): + """ + Checks whether all stores are balanced over time. + """ + stores = n.c.stores.static + stores_i = stores.index + if stores_i.empty: + return None + sns = n.snapshots + c = "Store" + dynamic = n.c[c].dynamic + + eh = expand_series(n.snapshot_weightings.stores[sns], stores_i) + stand_eff = (1 - n.get_switchable_as_dense(c, "standing_loss", sns)).pow(eh) + + start = dynamic.e.iloc[-1].where(stores.e_cyclic, stores.e_initial) + previous_e = stand_eff * dynamic.e.shift().fillna(start) + + return ( + (previous_e - dynamic.p - dynamic.e) + .unstack() + .describe() + .to_frame("SOC Balance Store") + ) + + +def describe_cycle_constraints(n): + weightings = n.c.lines.static.x_pu_eff.where( + n.c.lines.static.carrier == "AC", n.c.lines.static.r_pu_eff + ) + + def cycle_flow(sub): + C = pd.DataFrame(sub.C.todense(), index=sub.components.lines.static.index) + if C.empty: + return None + C_weighted = 1e5 * C.mul(weightings[sub.components.lines.static.index], axis=0) + return C_weighted.apply(lambda ds: ds @ n.c.lines.dynamic.p0[ds.index].T) + + return ( + pd.concat([cycle_flow(sub) for sub in n.c.sub_networks.static.obj], axis=0) + .stack() + .describe() + .to_frame("Cycle Constr.") + ) + + +funcs = ( + [ + describe_cycle_constraints, + # describe_store_contraints, + # describe_storage_unit_contraints, + describe_nodal_balance_constraint, + describe_lower_dispatch_constraints, + describe_upper_dispatch_constraints, + ], +) + + +@pytest.fixture +def solved_n(ac_dc_network): + n = ac_dc_network + n.optimize() + n.c.lines.static["carrier"] = n.c.lines.static.bus0.map(n.c.buses.static.carrier) + return n + + +@pytest.mark.parametrize("func", *funcs) +def test_tolerance(solved_n, func): + n = solved_n + description = func(n).fillna(0) + for col in description: + assert abs(description[col]["min"]) < TOLERANCE + if "max" in description: + assert description[col]["max"] < TOLERANCE + + +def test_optimization_with_strongly_meshed_bus(): + """ + Test that an optimization with a strongly meshed bus works. + + In the linopy framework, the nodal balance constraint is separately + defined for buses with a large number of components. + """ + n = pypsa.Network() + n.set_snapshots(range(2)) + + n.add("Bus", "bus") + n.add("Generator", "gen", bus="bus", p_nom=1, marginal_cost=10) + n.add("Load", "load", bus="bus", p_set=1) + + n.add("Bus", "bus2") + n.add("Generator", pd.RangeIndex(50), bus="bus2", p_nom=1, marginal_cost=10) + n.add("Load", "load2", bus="bus2", p_set=1) + + n.add("Line", "line", bus0="bus", bus1="bus2", s_nom=1) + + n.optimize() + + assert n.c.buses.dynamic.marginal_price.shape == (2, 2) + assert n.c.buses.dynamic.marginal_price.eq(10).all().all() + + +def test_define_generator_constraints_static(): + """ + Test define_generator_constraints functionionality without snapshots in the network. + """ + n = pypsa.Network() + n.add("Bus", "bus0") + n.add("Load", "load0", bus="bus0", p_set=10) + n.add("Generator", "gen0", bus="bus0", p_nom=10, marginal_cost=5) + n.add("Generator", "gen1", bus="bus0", p_nom=10, marginal_cost=0, e_sum_max=0) + n.add("Generator", "gen2", bus="bus0", p_nom=10, marginal_cost=10, e_sum_min=10) + + n.optimize() + + assert n.c.generators.dynamic.p["gen0"].eq(0).all() + assert n.c.generators.dynamic.p["gen1"].eq(0).all() + assert n.c.generators.dynamic.p["gen2"].eq(10).all() + + +def test_define_generator_constraints(): + """ + Test define_generator_constraints functionionality with snapshots in the network. + """ + n = pypsa.Network() + + eh = 10 + snapshots = pd.date_range("2023-01-01", periods=3, freq=f"{eh}h") + n.set_snapshots(snapshots, eh) + + n.add("Carrier", "carrier") + n.add("Bus", "bus0") + n.add("Load", "load0", carrier="carrier", bus="bus0", p_set=10) + n.add("Generator", "gen0", carrier="carrier", bus="bus0", p_nom=10, marginal_cost=5) + n.add( + "Generator", + "gen1", + carrier="carrier", + bus="bus0", + p_nom=10, + marginal_cost=0, + e_sum_max=0, + ) + + e_sum_min = 10 * (len(n.snapshots) - 2) * eh + n.add( + "Generator", + "gen2", + carrier="carrier", + bus="bus0", + p_nom=10, + marginal_cost=10, + e_sum_min=e_sum_min, + ) + + e_sum_max = 10 * eh + n.add( + "Generator", + "gen3", + carrier="carrier", + bus="bus0", + p_nom=10, + marginal_cost=0, + e_sum_max=e_sum_max, + ) + + n.optimize() + + assert ( + n.snapshot_weightings.generators @ n.c.generators.dynamic.p["gen0"] == 10 * eh + ) + assert n.c.generators.dynamic.p["gen1"].eq(0).all() + assert ( + n.snapshot_weightings.generators @ n.c.generators.dynamic.p["gen2"] == e_sum_min + ) + assert ( + n.snapshot_weightings.generators @ n.c.generators.dynamic.p["gen3"] == e_sum_max + ) + + +def test_define_fixed_operational_constraints_positive(): + """ + Test fixed operational constraints: fix to a positive value + """ + n = pypsa.Network() + n.add("Bus", "bus0") + n.add("Load", "load0", bus="bus0", p_set=10) + n.add("Generator", "gen0", bus="bus0", p_nom=4, marginal_cost=0) + n.add("Generator", "gen1", bus="bus0", p_nom=5, marginal_cost=5) + n.add("Generator", "gen2", bus="bus0", p_nom=10, marginal_cost=9) + + n.c.generators.dynamic.p_set["gen2"] = 10 + + n.optimize() + + assert n.c.generators.dynamic.p["gen2"].eq(10).all() + assert n.c.generators.dynamic.p["gen0"].eq(0).all() + + +def test_define_fixed_operational_constraints_zero(): + """ + Test fixed operational constraints: fix to a zero value + """ + n = pypsa.Network() + n.add("Bus", "bus0") + n.add("Load", "load0", bus="bus0", p_set=10) + n.add("Generator", "gen0", bus="bus0", p_nom=4, marginal_cost=0) + n.add("Generator", "gen1", bus="bus0", p_nom=5, marginal_cost=5) + n.add("Generator", "gen2", bus="bus0", p_nom=10, marginal_cost=9) + + n.c.generators.dynamic.p_set["gen0"] = 0 + + n.optimize() + + assert n.c.generators.dynamic.p["gen0"].eq(0).all() + assert n.c.generators.dynamic.p["gen1"].eq(5).all() + assert n.c.generators.dynamic.p["gen2"].eq(5).all() + + +def test_define_fixed_operational_constraints_extendable(): + """ + Test fixed operational constraints: extendable component" + """ + n = pypsa.Network() + n.add("Bus", "bus0") + n.add("Load", "load0", bus="bus0", p_set=10) + n.add( + "Generator", + "gen0", + bus="bus0", + p_nom_extendable=True, + capital_cost=10, + marginal_cost=0, + ) + n.add( + "Generator", + "gen1", + bus="bus0", + p_nom_extendable=True, + capital_cost=10, + marginal_cost=5, + ) + n.add( + "Generator", + "gen2", + bus="bus0", + p_nom_extendable=True, + capital_cost=10, + marginal_cost=9, + ) + + n.c.generators.dynamic.p_set["gen1"] = 5 + + n.optimize() + + assert n.c.generators.dynamic.p["gen0"].eq(5).all() + assert n.c.generators.dynamic.p["gen1"].eq(5).all() + assert n.c.generators.dynamic.p["gen2"].eq(0).all() diff --git a/PyPSA/source/test/test_lopf_global_constraints.py b/PyPSA/source/test/test_lopf_global_constraints.py new file mode 100644 index 0000000000000000000000000000000000000000..2a16f8873404630d7051d0ee7a237504535a8125 --- /dev/null +++ b/PyPSA/source/test/test_lopf_global_constraints.py @@ -0,0 +1,140 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pytest + + +def test_operational_limit_n_ac_dc_meshed(ac_dc_network): + n = ac_dc_network + + limit = 30_000 + + n.c.global_constraints.static.drop( + n.c.global_constraints.static.index, inplace=True + ) + + n.add( + "GlobalConstraint", + "gas_limit", + type="operational_limit", + carrier_attribute="gas", + sense="<=", + constant=limit, + ) + + n.optimize() + assert n.statistics.energy_balance().loc[:, "gas"].sum().round(3) == limit + + +def test_operational_limit_storage_hvdc(storage_hvdc_network): + n = storage_hvdc_network + + limit = 5_000 + + n.c.global_constraints.static.drop( + n.c.global_constraints.static.index, inplace=True + ) + + n.add( + "GlobalConstraint", + "battery_limit", + type="operational_limit", + carrier_attribute="battery", + sense="<=", + constant=limit, + ) + + n.c.storage_units.static["state_of_charge_initial"] = 1_000 + n.c.storage_units.static.p_nom_extendable = True + n.c.storage_units.static.cyclic_state_of_charge = False + + n.optimize() + + soc_diff = ( + n.c.storage_units.static.state_of_charge_initial.sum() + - n.c.storage_units.dynamic.state_of_charge.sum(1).iloc[-1] + ) + assert soc_diff.round(3) == limit + + +@pytest.mark.parametrize("assign", [True, False]) +def test_assign_all_duals(ac_dc_network, assign): + n = ac_dc_network + + limit = 30_000 + + m = n.optimize.create_model() + + transmission = m.variables["Link-p"] + m.add_constraints( + transmission.sum() <= limit, name="GlobalConstraint-generation_limit" + ) + m.add_constraints( + transmission.sum(dim="name") <= limit, + name="GlobalConstraint-generation_limit_dynamic", + ) + + n.optimize.solve_model(assign_all_duals=assign) + + assert ("generation_limit" in n.c.global_constraints.static.index) == assign + assert ("mu_generation_limit_dynamic" in n.c.global_constraints.dynamic) == assign + + +def test_assign_duals_noname(ac_dc_network): + """Test that dual values are correctly assigned back to network, + also for a special case of constraints without component dimension.""" + n = ac_dc_network + + limit = 10000 + m = n.optimize.create_model() + investment = m.variables["Generator-p_nom"] + m.add_constraints( + investment.sum() == limit, name="GlobalConstraint-investment_limit" + ) + n.optimize.solve_model(assign_all_duals=True) + + dual_model_investment = float( + n.model.constraints["GlobalConstraint-investment_limit"].dual + ) + dual_network_investment = float( + n.c.global_constraints.static.mu.loc["investment_limit"] + ) + assert dual_model_investment == pytest.approx( + dual_network_investment, rel=1e-8, abs=1e-10 + ) + + dual_model_co2 = float(n.model.constraints["GlobalConstraint-co2_limit"].dual) + dual_network_co2 = float(n.c.global_constraints.static.mu.loc["co2_limit"]) + assert dual_model_co2 == pytest.approx(dual_network_co2, rel=1e-8, abs=1e-10) + + +def test_assign_custom_variable(ac_dc_network): + """Test that adding custom variables does not raise an error when optimizing.""" + n = ac_dc_network + m = n.optimize.create_model() + + m.add_variables(name="custom_var") + + n.optimize.solve_model() + + +def test_custom_variable_warnings(ac_dc_network, caplog): + """Test that appropriate warnings are logged for custom variables.""" + import logging + + caplog.set_level(logging.INFO) + + m = ac_dc_network.optimize.create_model() + m.add_variables(name="custom_var_no_dash") + m.add_variables(name="NonExistentComponent-attr") + ac_dc_network.optimize.solve_model() + + assert all( + text in caplog.text + for text in [ + "could not be mapped to the network component because it does not include the symbol '-'", + "custom_var_no_dash", + "could not be mapped to the network component because the component 'NonExistentComponent' does not exist", + ] + ) diff --git a/PyPSA/source/test/test_lopf_iteratively.py b/PyPSA/source/test/test_lopf_iteratively.py new file mode 100644 index 0000000000000000000000000000000000000000..3ca8ff57a625b048a17d54ea11ec715854e59bf8 --- /dev/null +++ b/PyPSA/source/test/test_lopf_iteratively.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pypsa + + +def test_optimize_post_discretization(): + n = pypsa.Network() + + n.add("Bus", ["a", "b", "c"], v_nom=380.0) + n.add("Generator", "generator", bus="a", p_nom=900.0, marginal_cost=10.0) + n.add("Load", "load", bus="c", p_set=900.0) + n.add( + "Line", + "ab", + bus0="a", + bus1="b", + x=0.0001, + s_nom_extendable=True, + capital_cost=1000, + ) + n.add( + "Link", + "bc", + bus0="b", + bus1="c", + p_nom_extendable=True, + capital_cost=1000, + carrier="HVDC", + ) + + line_unit_size = 500 + link_unit_size = {"HVDC": 600} + + status, _ = n.optimize.optimize_transmission_expansion_iteratively( + max_iterations=1, + line_unit_size=line_unit_size, + link_unit_size=link_unit_size, + link_threshold={"HVDC": 0.4}, + ) + + assert status == "ok" + assert all( + n.c.lines.static.query("s_nom_extendable").s_nom_opt % line_unit_size == 0.0 + ) + assert all( + n.c.links.static.query("p_nom_extendable and carrier == 'HVDC'").p_nom_opt + % link_unit_size["HVDC"] + == 0.0 + ) diff --git a/PyPSA/source/test/test_lopf_losses.py b/PyPSA/source/test/test_lopf_losses.py new file mode 100644 index 0000000000000000000000000000000000000000..57061e8b9437a6cdaeca0258f4b4a3f966477fb4 --- /dev/null +++ b/PyPSA/source/test/test_lopf_losses.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pytest + + +@pytest.mark.parametrize("transmission_losses", [1, 2]) +def test_optimize_losses(scipy_network, transmission_losses): + n = scipy_network + n.c.lines.static.s_max_pu = 0.7 + n.c.lines.static.loc[["316", "527", "602"], "s_nom"] = 1715 + + n.optimize( + snapshots=n.snapshots[0], + transmission_losses=transmission_losses, + ) + + gen = ( + n.c.generators.dynamic.p.iloc[0].sum() + + n.c.storage_units.dynamic.p.iloc[0].sum() + ) + dem = n.c.loads.dynamic.p_set.iloc[0].sum() + + assert gen > 1.01 * dem, "For this example, losses should be greater than 1%" + assert gen < 1.05 * dem, "For this example, losses should be lower than 5%" diff --git a/PyPSA/source/test/test_lopf_mga.py b/PyPSA/source/test/test_lopf_mga.py new file mode 100644 index 0000000000000000000000000000000000000000..c019e8899cf815edb2ea03cb63e70eb622edde80 --- /dev/null +++ b/PyPSA/source/test/test_lopf_mga.py @@ -0,0 +1,215 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pytest +from numpy.testing import assert_almost_equal as almost_equal + +import pypsa + + +def test_mga(): + n = pypsa.Network() + + n.add("Bus", "bus") + + n.add( + "Generator", + "coal", + bus="bus", + marginal_cost=20, + capital_cost=200, + p_nom_extendable=True, + ) + + n.add( + "Generator", + "gas", + bus="bus", + marginal_cost=40, + capital_cost=230, + p_nom_extendable=True, + ) + + n.add("Load", "load", bus="bus", p_set=100) + + # can only run MGA on solved networks + with pytest.raises(ValueError): + n.optimize.optimize_mga() + + n.optimize() + + opt_capacity = n.c.generators.static.p_nom_opt + opt_cost = (n.statistics.capex() + n.statistics.opex()).sum() + + weights = {"Generator": {"p_nom": {"coal": 1}}} + slack = 0.05 + n.optimize.optimize_mga(slack=0.05, weights=weights) + + mga_capacity = n.c.generators.static.p_nom_opt + mga_cost = (n.statistics.capex() + n.statistics.opex()).sum() + + assert mga_capacity["coal"] <= opt_capacity["coal"] + almost_equal(mga_cost / opt_cost, 1 + slack) + + +def test_mga_in_direction(): + n = pypsa.Network() + n.add("Bus", "bus") + n.add( + "Generator", + "gen1", + bus="bus", + marginal_cost=10, + capital_cost=99, + p_nom_extendable=True, + ) + n.add( + "Generator", + "gen2", + bus="bus", + marginal_cost=10, + capital_cost=100, + p_nom_extendable=True, + ) + n.add("Load", "load", bus="bus", p_set=100) + n.optimize() + + # Define dimensions for the MGA + dimensions = { + "cap1": {"Generator": {"p_nom": {"gen1": 1}}}, + "cap2": {"Generator": {"p_nom": {"gen2": 1}}}, + } + + # Test with a simple direction + direction = {"cap1": -1, "cap2": 1} # Minimize coal, maximize gas + slack = 0.05 + status, condition, coords = n.optimize.optimize_mga_in_direction( + direction=direction, dimensions=dimensions, slack=slack + ) + + assert status == "ok" + assert "cap1" in coords + assert "cap2" in coords + assert n.meta["slack"] == slack + assert n.meta["direction"] == direction + assert n.meta["dimensions"] == { + "cap1": {"Generator": {"p_nom": {"gen1": 1}}}, + "cap2": {"Generator": {"p_nom": {"gen2": 1}}}, + } + + # Assert that the capacity of gen1 is less than gen2 + assert ( + n.c.generators.static.p_nom_opt["gen1"] + < n.c.generators.static.p_nom_opt["gen2"] + ) + + # Test error before solving network + n_unsolved = pypsa.Network() + n_unsolved.add("Bus", "bus") + n_unsolved.add("Load", "load", bus="bus", p_set=100) + n_unsolved.add( + "Generator", + "some_gen", + bus="bus", + marginal_cost=10, + capital_cost=100, + p_nom_extendable=True, + ) + with pytest.raises(ValueError): + n_unsolved.optimize.optimize_mga_in_direction( + direction={"some_gen_cap": 1}, + dimensions={"some_gen_cap": {"Generator": {"p_nom": {"some_gen": 1}}}}, + ) + + # Test inconsistent direction/dimensions keys + with pytest.raises( + ValueError, match="Keys of `direction` and `dimensions` arguments must match." + ): + n.optimize.optimize_mga_in_direction( + direction={"cap1": -1}, + dimensions={"cap2": {"Generator": {"p_nom": {"gen2": 1}}}}, + ) + + +def test_mga_in_multiple_directions(): + n = pypsa.Network() + n.add("Bus", "bus") + n.add( + "Generator", + "coal", + bus="bus", + marginal_cost=20, + capital_cost=200, + p_nom_extendable=True, + ) + n.add( + "Generator", + "gas", + bus="bus", + marginal_cost=40, + capital_cost=230, + p_nom_extendable=True, + ) + n.add("Load", "load", bus="bus", p_set=100) + n.optimize() + + # Define dimensions for the MGA + dimensions = { + "coal_cap": {"Generator": {"p_nom": {"coal": 1}}}, + "gas_cap": {"Generator": {"p_nom": {"gas": 1}}}, + } + + # Generate some example directions + directions_list = [ + {"coal_cap": -1, "gas_cap": 1}, + {"coal_cap": 1, "gas_cap": -1}, + ] + + successful_directions, successful_coordinates = ( + n.optimize.optimize_mga_in_multiple_directions( + directions=directions_list, + dimensions=dimensions, + max_parallel=1, # use 1 for reliable testing + ) + ) + + assert not successful_directions.empty + assert not successful_coordinates.empty + assert len(successful_directions) <= len(directions_list) + assert len(successful_coordinates) <= len(directions_list) + assert "coal_cap" in successful_coordinates.columns + assert "gas_cap" in successful_coordinates.columns + + +def test_generate_directions(): + keys = ["dim1", "dim2", "dim3"] + n_directions = 5 + + # Test generate_directions_random + random_directions = pypsa.optimization.mga.generate_directions_random( + keys, n_directions, seed=0 + ) + assert random_directions.shape == (n_directions, len(keys)) + for _, row in random_directions.iterrows(): + almost_equal(sum(val**2 for val in row), 1.0) # Check unit vector normalization + + # Test generate_directions_evenly_spaced + keys_2d = ["x", "y"] + n_directions_2d = 4 + evenly_spaced_directions = pypsa.optimization.mga.generate_directions_evenly_spaced( + keys_2d, n_directions_2d + ) + assert evenly_spaced_directions.shape == (n_directions_2d, len(keys_2d)) + for _, row in evenly_spaced_directions.iterrows(): + almost_equal(sum(val**2 for val in row), 1.0) # Check unit vector normalization + with pytest.raises(ValueError, match="This function only supports two keys"): + pypsa.optimization.mga.generate_directions_evenly_spaced(keys, n_directions) + + # Test generate_directions_halton + halton_directions = pypsa.optimization.mga.generate_directions_halton( + keys, n_directions, seed=0 + ) + assert halton_directions.shape == (n_directions, len(keys)) + for _, row in halton_directions.iterrows(): + almost_equal(sum(val**2 for val in row), 1.0) # Check unit vector normalization diff --git a/PyPSA/source/test/test_lopf_modularity.py b/PyPSA/source/test/test_lopf_modularity.py new file mode 100644 index 0000000000000000000000000000000000000000..447ebb91c91f476dfff1603cb4efb032d44eee62 --- /dev/null +++ b/PyPSA/source/test/test_lopf_modularity.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import numpy as np +from numpy.testing import assert_array_almost_equal as equal + +import pypsa + + +def test_modular_components(): + """ + This test is based on https://docs.pypsa.org/en/latest/examples/unit- + commitment.html and is not very comprehensive. + """ + n = pypsa.Network() + + snapshots = range(4) + + n.set_snapshots(snapshots) + + n.add("Bus", "bus") + + n.add( + "Generator", + "gas", + bus="bus", + p_nom_extendable=True, + marginal_cost=10, + p_nom_max=5000, + p_nom_mod=1000, + ) + + n.add( + "Store", + "Store_unit", + bus="bus", + e_nom_extendable=True, + e_nom_max=2000, + e_nom_mod=100, + capital_cost=100, + ) + + n.add("Load", "load", bus="bus", p_set=[4000, 5000, 6000, 800]) + + n.optimize() + + expected_p_nom_opt_gen = np.array([5000], dtype=float).T + expected_e_nom_opt_store = np.array([1000], dtype=float).T + + equal(n.c.generators.static.p_nom_opt, expected_p_nom_opt_gen) + equal(n.c.stores.static.e_nom_opt, expected_e_nom_opt_store) diff --git a/PyPSA/source/test/test_lopf_multiinvest.py b/PyPSA/source/test/test_lopf_multiinvest.py new file mode 100644 index 0000000000000000000000000000000000000000..0e34b15077cc70cb9e3b4cc5c74ce661176deb71 --- /dev/null +++ b/PyPSA/source/test/test_lopf_multiinvest.py @@ -0,0 +1,1134 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pandas as pd +import pytest +from numpy.testing import assert_almost_equal as almost_equal +from numpy.testing import assert_array_almost_equal as equal +from pandas import IndexSlice as idx + +import pypsa + +kwargs = {"multi_investment_periods": True} + + +@pytest.fixture +def n(): + n = pypsa.Network(snapshots=range(10)) + n.investment_periods = [2020, 2030, 2040, 2050] + n.add("Carrier", "gencarrier") + n.add("Bus", [1, 2]) + + for i, period in enumerate(n.investment_periods): + factor = (10 + i) / 10 + n.add( + "Generator", + [f"gen1-{period}", f"gen2-{period}"], + bus=[1, 2], + lifetime=30, + build_year=period, + capital_cost=[100 / factor, 100 * factor], + marginal_cost=[i + 2, i + 1], + p_nom_extendable=True, + carrier="gencarrier", + ) + + for i, period in enumerate(n.investment_periods): + n.add( + "Line", + f"line-{period}", + bus0=1, + bus1=2, + length=1, + build_year=period, + lifetime=40, + capital_cost=30 + i, + x=0.0001, + s_nom_extendable=True, + ) + + load = range(100, 100 + len(n.snapshots)) + load = pd.DataFrame({"load1": load, "load2": load}, index=n.snapshots) + n.add( + "Load", + ["load1", "load2"], + bus=[1, 2], + p_set=load, + ) + + return n + + +@pytest.fixture +def n_sus(n): + # only keep generators which are getting more expensiv and push generator + # capital cost, so that sus are activated + n.remove("Generator", n.c.generators.static.query('bus == "1"').index) + n.c.generators.static.capital_cost *= 5 + + for i, period in enumerate(n.investment_periods): + factor = (10 + i) / 10 + n.add( + "StorageUnit", + f"sto1-{period}", + bus=1, + lifetime=30, + build_year=period, + capital_cost=10 / factor, + marginal_cost=i, + p_nom_extendable=True, + ) + return n + + +@pytest.fixture +def n_sts(n): + # only keep generators which are getting more expensiv and push generator + # capital cost, so that sus are activated + n.remove("Generator", n.c.generators.static.query('bus == "1"').index) + n.c.generators.static.capital_cost *= 5 + + n.add("Bus", "1 battery") + + n.add( + "Store", + "sto1-2020", + bus="1 battery", + e_nom_extendable=True, + e_initial=20, + build_year=2020, + lifetime=30, + capital_cost=0.1, + ) + + n.add( + "Link", "bus2 battery charger", bus0=1, bus1="1 battery", p_nom_extendable=True + ) + + n.add( + "Link", + "My bus2 battery discharger", + bus0="1 battery", + bus1=1, + p_nom_extendable=True, + ) + + return n + + +def test_single_to_multi_level_snapshots(): + n = pypsa.Network(snapshots=range(2)) + years = [2030, 2040] + n.investment_periods = years + assert isinstance(n.snapshots, pd.MultiIndex) + equal(n.snapshots.unique(level="period"), years) + + +def test_investment_period_values(): + sns = pd.MultiIndex.from_product([[2020, 2030, 2040], [1, 2, 3]]) + n = pypsa.Network(snapshots=sns) + + with pytest.raises(ValueError): + n.investment_periods = [2040, 2030, 2020] + + with pytest.raises(ValueError): + n.investment_periods = ["2020", "2030", "2040"] + + with pytest.raises(NotImplementedError): + n.investment_periods = [2020] + + n = pypsa.Network(snapshots=range(2)) + with pytest.raises(ValueError): + n.investment_periods = ["2020", "2030", "2040"] + + +def test_active_assets(n): + active_gens = n.c.generators.get_active_assets(2030)[lambda ds: ds].index + assert (active_gens == ["gen1-2020", "gen2-2020", "gen1-2030", "gen2-2030"]).all() + + active_gens = n.c.generators.get_active_assets(2050)[lambda ds: ds].index + assert ( + active_gens + == [ + "gen1-2030", + "gen2-2030", + "gen1-2040", + "gen2-2040", + "gen1-2050", + "gen2-2050", + ] + ).all() + + +def test_tiny_with_default(): + n = pypsa.Network(snapshots=range(2)) + n.investment_periods = [2020, 2030] + n.add("Bus", 1) + n.add("Generator", 1, bus=1, p_nom_extendable=True, capital_cost=10) + n.add("Load", 1, bus=1, p_set=100) + status, _ = n.optimize(**kwargs) + assert status == "ok" + assert n.c.generators.static.p_nom_opt.item() == 100 + + +def test_tiny_with_build_year(): + n = pypsa.Network(snapshots=range(2)) + n.investment_periods = [2020, 2030] + n.add("Bus", 1) + n.add( + "Generator", 1, bus=1, p_nom_extendable=True, capital_cost=10, build_year=2020 + ) + n.add("Load", 1, bus=1, p_set=100) + status, _ = n.optimize(**kwargs) + assert status == "ok" + assert n.c.generators.static.p_nom_opt.item() == 100 + + +def test_tiny_infeasible(): + n = pypsa.Network(snapshots=range(2)) + n.investment_periods = [2020, 2030] + n.add("Bus", 1) + n.add( + "Generator", 1, bus=1, p_nom_extendable=True, capital_cost=10, build_year=2030 + ) + n.add("Load", 1, bus=1, p_set=100) + with pytest.raises(ValueError): + status, cond = n.optimize(**kwargs) + + +def test_simple_network(n): + status, cond = n.optimize(**kwargs) + assert status == "ok" + assert cond == "optimal" + + assert (n.c.generators.dynamic.p.loc[[2020, 2030, 2040], "gen1-2050"] == 0).all() + assert (n.c.generators.dynamic.p.loc[[2050], "gen1-2020"] == 0).all() + + assert (n.c.lines.dynamic.p0.loc[[2020, 2030, 2040], "line-2050"] == 0).all() + + +def test_simple_network_snapshot_subset(n): + status, cond = n.optimize(n.snapshots[:20], **kwargs) + assert status == "ok" + assert cond == "optimal" + + assert (n.c.generators.dynamic.p.loc[[2020, 2030, 2040], "gen1-2050"] == 0).all() + assert (n.c.generators.dynamic.p.loc[[2050], "gen1-2020"] == 0).all() + + assert (n.c.lines.dynamic.p0.loc[[2020, 2030, 2040], "line-2050"] == 0).all() + + +def test_simple_network_storage_noncyclic(n_sus): + n_sus.c.storage_units.static["state_of_charge_initial"] = 200 + n_sus.c.storage_units.static["cyclic_state_of_charge"] = False + n_sus.c.storage_units.static["state_of_charge_initial_per_period"] = False + + status, cond = n_sus.optimize(**kwargs) + assert status == "ok" + assert cond == "optimal" + + soc = n_sus.c.storage_units.dynamic.state_of_charge + p = n_sus.c.storage_units.dynamic.p + assert round((soc + p).loc[idx[2020, 0], "sto1-2020"], 4) == 200 + assert soc.loc[idx[2040, 9], "sto1-2020"] == 0 + + +def test_simple_network_storage_noncyclic_per_period(n_sus): + n_sus.c.storage_units.static["state_of_charge_initial"] = 200 + n_sus.c.storage_units.static["cyclic_state_of_charge"] = False + n_sus.c.storage_units.static["state_of_charge_initial_per_period"] = True + + status, cond = n_sus.optimize(**kwargs) + assert status == "ok" + assert cond == "optimal" + + assert ( + n_sus.c.storage_units.dynamic.p.loc[[2020, 2030, 2040], "sto1-2050"] == 0 + ).all() + assert (n_sus.c.storage_units.dynamic.p.loc[[2050], "sto1-2020"] == 0).all() + + soc_initial = ( + n_sus.c.storage_units.dynamic.state_of_charge + n_sus.c.storage_units.dynamic.p + ).loc[idx[:, 0], :] + soc_initial = soc_initial.droplevel("timestep") + assert soc_initial.loc[2020, "sto1-2020"] == 200 + assert soc_initial.loc[2030, "sto1-2020"] == 200 + assert soc_initial.loc[2040, "sto1-2040"] == 200 + + +def test_simple_network_storage_cyclic(n_sus): + n_sus.c.storage_units.static["cyclic_state_of_charge"] = True + n_sus.c.storage_units.static["cyclic_state_of_charge_per_period"] = False + + status, cond = n_sus.optimize(**kwargs) + assert status == "ok" + assert cond == "optimal" + + soc = n_sus.c.storage_units.dynamic.state_of_charge + p = n_sus.c.storage_units.dynamic.p + assert ( + soc.loc[idx[2040, 9], "sto1-2020"] == (soc + p).loc[idx[2020, 0], "sto1-2020"] + ) + assert ( + soc.loc[idx[2050, 9], "sto1-2030"] == (soc + p).loc[idx[2030, 0], "sto1-2030"] + ) + + +def test_simple_network_storage_cyclic_per_period(n_sus): + # Watch out breaks with xarray version 2022.06.00 ! + n_sus.c.storage_units.static["cyclic_state_of_charge"] = True + n_sus.c.storage_units.static["cyclic_state_of_charge_per_period"] = True + + status, cond = n_sus.optimize(**kwargs) + assert status == "ok" + assert cond == "optimal" + + soc = n_sus.c.storage_units.dynamic.state_of_charge + p = n_sus.c.storage_units.dynamic.p + assert ( + soc.loc[idx[2020, 9], "sto1-2020"] == (soc + p).loc[idx[2020, 0], "sto1-2020"] + ) + + +def test_simple_network_store_noncyclic(n_sts): + n_sts.c.stores.static["e_cyclic"] = False + n_sts.c.stores.static["e_initial_per_period"] = False + + status, cond = n_sts.optimize(**kwargs) + assert status == "ok" + assert cond == "optimal" + + assert (n_sts.c.stores.dynamic.p.loc[[2050], "sto1-2020"] == 0).all() + + e_initial = (n_sts.c.stores.dynamic.e + n_sts.c.stores.dynamic.p).loc[idx[:, 0], :] + e_initial = e_initial.droplevel("timestep") + assert e_initial.loc[2020, "sto1-2020"] == 20 + + +def test_simple_network_store_noncyclic_per_period(n_sts): + n_sts.c.stores.static["e_cyclic"] = False + n_sts.c.stores.static["e_initial_per_period"] = True + + status, cond = n_sts.optimize(**kwargs) + assert status == "ok" + assert cond == "optimal" + + assert (n_sts.c.stores.dynamic.p.loc[[2050], "sto1-2020"] == 0).all() + + e_initial = (n_sts.c.stores.dynamic.e + n_sts.c.stores.dynamic.p).loc[idx[:, 0], :] + e_initial = e_initial.droplevel("timestep") + assert e_initial.loc[2020, "sto1-2020"] == 20 + assert e_initial.loc[2030, "sto1-2020"] == 20 + + # lifetime is over here + assert e_initial.loc[2050, "sto1-2020"] == 0 + + +def test_simple_network_store_cyclic(n_sts): + n_sts.c.stores.static["e_cyclic"] = True + n_sts.c.stores.static["e_cyclic_per_period"] = False + + status, cond = n_sts.optimize(**kwargs) + assert status == "ok" + assert cond == "optimal" + + assert (n_sts.c.stores.dynamic.p.loc[[2050], "sto1-2020"] == 0).all() + + e = n_sts.c.stores.dynamic.e + p = n_sts.c.stores.dynamic.p + assert e.loc[idx[2040, 9], "sto1-2020"] == (e + p).loc[idx[2020, 0], "sto1-2020"] + + +def test_simple_network_store_cyclic_per_period(n_sts): + # Watch out breaks with xarray version 2022.06.00 ! + n_sts.c.stores.static["e_cyclic"] = True + n_sts.c.stores.static["e_cyclic_per_period"] = True + + status, cond = n_sts.optimize(**kwargs) + assert status == "ok" + assert cond == "optimal" + + assert (n_sts.c.stores.dynamic.p.loc[[2050], "sto1-2020"] == 0).all() + + e = n_sts.c.stores.dynamic.e + p = n_sts.c.stores.dynamic.p + assert e.loc[idx[2020, 9], "sto1-2020"] == (e + p).loc[idx[2020, 0], "sto1-2020"] + + +def test_global_constraint_primary_energy_storage(n_sus): + c = n_sus.components["StorageUnit"] + n_sus.add("Carrier", "emitting_carrier", co2_emissions=100) + c.static["state_of_charge_initial"] = 200 + c.static["cyclic_state_of_charge"] = False + c.static["state_of_charge_initial_per_period"] = False + c.static["carrier"] = "emitting_carrier" + + n_sus.add("GlobalConstraint", name="co2limit", type="primary_energy", constant=3000) + + status, cond = n_sus.optimize(**kwargs) + + active = c.get_activity_mask() + soc_end = c.dynamic.state_of_charge.where(active).ffill().iloc[-1] + soc_diff = c.static.state_of_charge_initial - soc_end + emissions = c.static.carrier.map(n_sus.c.carriers.static.co2_emissions) + assert round(soc_diff @ emissions, 0) == 3000 + + +def test_global_constraint_primary_energy_store(n_sts): + c = n_sts.components["Store"] + n_sts.add("Carrier", "emitting_carrier", co2_emissions=100) + c.static["e_initial"] = 200 + c.static["e_cyclic"] = False + c.static["e_initial_per_period"] = False + + n_sts.c.buses.static.loc["1 battery", "carrier"] = "emitting_carrier" + + n_sts.add("GlobalConstraint", name="co2limit", type="primary_energy", constant=3000) + + status, cond = n_sts.optimize(**kwargs) + + active = c.get_activity_mask() + soc_end = c.dynamic.e.where(active).ffill().iloc[-1] + soc_diff = c.static.e_initial - soc_end + emissions = c.static.carrier.map(n_sts.c.carriers.static.co2_emissions) + assert round(soc_diff @ emissions, 0) == 3000 + + +def test_global_constraint_primary_energy_storage_stochastic(n_sus): + """ + Test global constraints with primary energy for storage in stochastic networks. + + This test ensures that multi-period optimization with storage units and + global constraints work correctly when scenarios are present. + """ + + c = "StorageUnit" + + n_sus.add("Carrier", "emitting_carrier", co2_emissions=100) + n_sus.c[c].static["state_of_charge_initial"] = 200 + n_sus.c[c].static["cyclic_state_of_charge"] = False + n_sus.c[c].static["state_of_charge_initial_per_period"] = False + n_sus.c[c].static["carrier"] = "emitting_carrier" + + n_sus.add("GlobalConstraint", name="co2limit", type="primary_energy", constant=3000) + n_sus.set_scenarios({"s1": 0.5, "s2": 0.5}) + + status, cond = n_sus.optimize(multi_investment_periods=True) + assert status == "ok" + assert n_sus.model.constraints["GlobalConstraint-co2limit"].rhs[0] == -77000.0 + + +def test_global_constraint_transmission_expansion_limit(n): + n.add( + "GlobalConstraint", + "expansion_limit", + type="transmission_volume_expansion_limit", + constant=100, + sense="==", + carrier_attribute="AC", + ) + + status, cond = n.optimize(**kwargs) + assert n.c.lines.static.s_nom_opt.sum() == 100 + + # when only optimizing the first 10 snapshots the contraint must hold for + # the 2020 period + status, cond = n.optimize(n.snapshots[:10], **kwargs) + assert n.c.lines.static.loc["line-2020", "s_nom_opt"] == 100 + + n.c.global_constraints.static["investment_period"] = 2030 + status, cond = n.optimize(**kwargs) + assert n.c.lines.static.s_nom_opt[["line-2020", "line-2030"]].sum() == 100 + + +def test_global_constraint_transmission_cost_limit(n): + n.add( + "GlobalConstraint", + "expansion_limit", + type="transmission_expansion_cost_limit", + constant=1000, + sense="==", + carrier_attribute="AC", + ) + + active = pd.concat( + { + period: n.c.lines.get_active_assets(period) + for period in n.investment_periods + }, + axis=1, + ) + weight = active @ n.investment_period_weightings.objective + + status, cond = n.optimize(**kwargs) + assert ( + round((weight * n.c.lines.static.eval("s_nom_opt * capital_cost")).sum(), 2) + == 1000 + ) + + # when only optimizing the first 10 snapshots the contraint must hold for + # the 2020 period + status, cond = n.optimize(n.snapshots[:10], **kwargs) + assert ( + round(n.c.lines.static.eval("s_nom_opt * capital_cost")["line-2020"].sum(), 2) + == 1000 + ) + + n.c.global_constraints.static["investment_period"] = 2030 + status, cond = n.optimize(**kwargs) + lines = n.c.lines.static.loc[["line-2020", "line-2030"]] + assert round(lines.eval("s_nom_opt * capital_cost").sum(), 2) == 1000 + + +def test_global_constraint_bus_tech_limit(n): + n.add( + "GlobalConstraint", + "expansion_limit", + type="tech_capacity_expansion_limit", + constant=300, + sense="==", + carrier_attribute="gencarrier", + investment_period=2020, + ) + + status, cond = n.optimize(**kwargs) + assert ( + round(n.c.generators.static.p_nom_opt[["gen1-2020", "gen2-2020"]], 1).sum() + == 300 + ) + + n.c.global_constraints.static["bus"] = 1 + status, cond = n.optimize(**kwargs) + assert n.c.generators.static.at["gen1-2020", "p_nom_opt"] == 300 + + # make the constraint non-binding and check that the shadow price is zero + n.c.global_constraints.static.sense = "<=" + status, cond = n.optimize(**kwargs) + assert n.c.global_constraints.static.at["expansion_limit", "mu"] == 0 + + +def test_nominal_constraint_bus_carrier_expansion_limit(n): + n.c.buses.static.at["1", "nom_max_gencarrier"] = 100 + with pytest.warns( + # DeprecationWarning, match="Nominal constraints per bus carrier are deprecated" + DeprecationWarning, + match=".+", + ): + status, cond = n.optimize(**kwargs) + gen1s = [f"gen1-{period}" for period in n.investment_periods] + assert round(n.c.generators.static.p_nom_opt[gen1s], 0).sum() == 100 + n.c.buses.static.drop(["nom_max_gencarrier"], inplace=True, axis=1) + + n.c.buses.static.at["1", "nom_max_gencarrier_2020"] = 100 + with pytest.warns( + # DeprecationWarning, match="Nominal constraints per bus carrier are deprecated" + DeprecationWarning, + match=".+", + ): + status, cond = n.optimize(**kwargs) + assert n.c.generators.static.at["gen1-2020", "p_nom_opt"] == 100 + n.c.buses.static.drop(["nom_max_gencarrier_2020"], inplace=True, axis=1) + + # make the constraint non-binding and check that the shadow price is zero + n.c.buses.static.at["1", "nom_min_gencarrier_2020"] = 100 + with pytest.warns( + # DeprecationWarning, match="Nominal constraints per bus carrier are deprecated" + DeprecationWarning, + match=".+", + ): + status, cond = n.optimize(**kwargs) + assert (n.model.constraints["Bus-nom_min_gencarrier_2020"].dual).item() == 0 + + +def test_max_growth_constraint(n): + # test generator grow limit + gen_carrier = n.c.generators.static.carrier.unique()[0] + n.c.carriers.static.at[gen_carrier, "max_growth"] = 218 + status, cond = n.optimize(**kwargs) + assert all( + n.c.generators.static.p_nom_opt.groupby(n.c.generators.static.build_year).sum() + <= 218 + ) + + +def test_max_relative_growth_constraint(n): + # test generator relative grow limit + gen_carrier = n.c.generators.static.carrier.unique()[0] + n.c.carriers.static.at[gen_carrier, "max_growth"] = 218 + n.c.carriers.static.at[gen_carrier, "max_relative_growth"] = 1.5 + status, cond = n.optimize(**kwargs) + built_per_period = n.c.generators.static.p_nom_opt.groupby( + n.c.generators.static.build_year + ).sum() + assert all(built_per_period - built_per_period.shift(fill_value=0) * 1.5 <= 218) + + +def test_store_primary_energy_and_operational_limit_constraint_without_per_period(): + """Test that Store with primary energy constraint raises NotImplementedError without e_initial_per_period.""" + + n = pypsa.Network() + years = [2030, 2040] + timesteps = [1, 2] + + n.snapshots = pd.MultiIndex.from_product([years, timesteps]) + n.investment_periods = years + n.investment_period_weightings.loc[:, :] = 2 + + n.add("Bus", "bus") + n.add("Carrier", "gas", co2_emissions=0.2) + n.add( + "Store", + "store", + bus="bus", + carrier="gas", + marginal_cost=1, + e_nom=10, + e_initial=10, + ) + n.add("Generator", "gen", bus="bus", marginal_cost=10, p_nom=2) + n.add("Load", "load", bus="bus", p_set=pd.Series(1, index=n.snapshots)) + + n.add( + "GlobalConstraint", + "co2", + type="primary_energy", + carrier_attribute="co2_emissions", + sense="<=", + constant=0.8, + ) + + with pytest.raises(NotImplementedError): + n.optimize(multi_investment_periods=True) + + n.remove("GlobalConstraint", n.c.global_constraints.static.index) + n.add( + "GlobalConstraint", + "co2", + type="operational_limit", + carrier_attribute="gas", + sense="<=", + constant=6, + ) + + with pytest.raises(NotImplementedError): + n.optimize(multi_investment_periods=True) + + +def test_store_primary_energy_and_operational_limit_constraint_with_per_period(): + """Test that Store with primary energy constraint works with e_initial_per_period=True.""" + + n = pypsa.Network() + years = [2030, 2040] + timesteps = [1, 2] + + n.snapshots = pd.MultiIndex.from_product([years, timesteps]) + n.investment_periods = years + n.investment_period_weightings.loc[:, :] = 2 + + n.add("Bus", "bus") + n.add("Carrier", "gas", co2_emissions=0.2) + n.add( + "Store", + "store", + bus="bus", + carrier="gas", + marginal_cost=1, + e_nom=10, + e_initial=10, + ) + n.add("Generator", "gen", bus="bus", marginal_cost=10, p_nom=2) + n.add("Load", "load", bus="bus", p_set=pd.Series(1, index=n.snapshots)) + + n.c.stores.static.e_initial_per_period = True + n.add( + "GlobalConstraint", + "co2", + type="primary_energy", + carrier_attribute="co2_emissions", + sense="<=", + constant=0.8, + ) + + n.optimize(multi_investment_periods=True) + almost_equal(n.objective, 10 * 4 + 1 * 4) + + # Test operational limit constraint as well + n.remove("GlobalConstraint", n.c.global_constraints.names) + n.add( + "GlobalConstraint", + "dispatch", + carrier_attribute="gas", + type="operational_limit", + sense="<=", + constant=6, + ) + + n.optimize(multi_investment_periods=True) + almost_equal(n.objective, 10 * 2 + 1 * 6) + + +def test_storage_unit_primary_energy_and_operational_limit_constraint_without_per_period(): + """Test that StorageUnit with primary energy constraint raises NotImplementedError without state_of_charge_initial_per_period.""" + + n = pypsa.Network() + years = [2030, 2040] + timesteps = [1, 2] + + n.snapshots = pd.MultiIndex.from_product([years, timesteps]) + n.investment_periods = years + n.investment_period_weightings.loc[:, :] = 2 + + n.add("Bus", "bus") + n.add("Carrier", "gas", co2_emissions=0.2) + n.add( + "StorageUnit", + "su", + bus="bus", + carrier="gas", + marginal_cost=1, + p_nom=10, + max_hours=1, + state_of_charge_initial=10, + ) + n.add("Generator", "gen", bus="bus", marginal_cost=10, p_nom=2) + n.add("Load", "load", bus="bus", p_set=pd.Series(1, index=n.snapshots)) + + n.add( + "GlobalConstraint", + "co2", + type="primary_energy", + carrier_attribute="co2_emissions", + sense="<=", + constant=0.8, + ) + + with pytest.raises(NotImplementedError): + n.optimize(multi_investment_periods=True) + + n.remove("GlobalConstraint", n.c.global_constraints.names) + n.add( + "GlobalConstraint", + "co2", + type="operational_limit", + carrier_attribute="gas", + sense="<=", + constant=6, + ) + + with pytest.raises(NotImplementedError): + n.optimize(multi_investment_periods=True) + + +def test_storage_unit_primary_energy_and_operational_limit_constraint_with_per_period(): + """Test that StorageUnit with primary energy constraint works with state_of_charge_initial_per_period=True.""" + + n = pypsa.Network() + years = [2030, 2040] + timesteps = [1, 2] + + n.snapshots = pd.MultiIndex.from_product([years, timesteps]) + n.investment_periods = years + n.investment_period_weightings.loc[:, :] = 2 + + n.add("Bus", "bus") + n.add("Carrier", "gas", co2_emissions=0.2) + n.add( + "StorageUnit", + "su", + bus="bus", + carrier="gas", + marginal_cost=1, + p_nom=10, + max_hours=1, + state_of_charge_initial=10, + ) + n.add("Generator", "gen", bus="bus", marginal_cost=10, p_nom=2) + n.add("Load", "load", bus="bus", p_set=pd.Series(1, index=n.snapshots)) + + n.c.storage_units.static.state_of_charge_initial_per_period = True + n.add( + "GlobalConstraint", + "co2", + type="primary_energy", + carrier_attribute="co2_emissions", + sense="<=", + constant=0.8, + ) + + n.optimize(multi_investment_periods=True) + almost_equal(n.objective, 10 * 4 + 1 * 4) + + # Test operational limit constraint as well + n.remove("GlobalConstraint", n.c.global_constraints.names) + n.add( + "GlobalConstraint", + "dispatch", + carrier_attribute="gas", + type="operational_limit", + sense="<=", + constant=6, + ) + + n.optimize(multi_investment_periods=True) + almost_equal(n.objective, 10 * 2 + 1 * 6) + + +def test_bug_1360_stores(): + """ + Storage state of charge should behave correctly with various snapshot configurations. + See https://github.com/PyPSA/PyPSA/issues/1360. + """ + # Case 1: Simple snapshots without multi_investment_periods + # Expected: stores_t.e [9, 8, 7, 6] - continuous discharge + n = pypsa.Network() + n.snapshots = [1, 2, 3, 4] + + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=[1, 1, 1, 1]) + n.add("Store", "store", bus="bus", e_nom=10, e_initial=10, marginal_cost=1) + + n.optimize(multi_investment_periods=False) + equal(n.stores_t.e["store"].values, [9, 8, 7, 6], decimal=5) + + # Case 2: Multi-indexed snapshots without multi_investment_periods + # Expected: stores_t.e [9, 8, 7, 6] - continuous discharge + n = pypsa.Network() + years = [2030, 2040] + timesteps = [1, 2] + + n.snapshots = pd.MultiIndex.from_product([years, timesteps]) + n.investment_periods = years + + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=[1, 1, 1, 1]) + n.add( + "Store", + "store", + bus="bus", + e_nom=10, + e_initial=10, + marginal_cost=1, + e_initial_per_period=False, + ) + + n.optimize(multi_investment_periods=False) + equal(n.stores_t.e["store"].values, [9, 8, 7, 6], decimal=5) + + # Case 3: Multi_investment_periods with e_initial_per_period=True + # Expected: stores_t.e [9, 8, 9, 8] - reset at each period + n = pypsa.Network() + n.snapshots = pd.MultiIndex.from_product([years, timesteps]) + n.investment_periods = years + + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=[1, 1, 1, 1]) + n.add( + "Store", + "store", + bus="bus", + e_nom=10, + e_initial=10, + marginal_cost=1, + e_initial_per_period=True, + ) + + n.optimize(multi_investment_periods=True) + equal(n.stores_t.e["store"].values, [9, 8, 9, 8], decimal=5) + + # Case 4: Multi_investment_periods with e_initial_per_period=False + # Expected: stores_t.e [9, 8, 7, 6] - continuous discharge across periods + n = pypsa.Network() + n.snapshots = pd.MultiIndex.from_product([years, timesteps]) + n.investment_periods = years + + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=[1, 1, 1, 1]) + n.add( + "Store", + "store", + bus="bus", + e_nom=10, + e_initial=10, + marginal_cost=1, + e_initial_per_period=False, + ) + + n.optimize(multi_investment_periods=True) + equal(n.stores_t.e["store"].values, [9, 8, 7, 6], decimal=5) + + +def test_bug_1360_storage_units(): + """ + Storage units state of charge should behave correctly with various snapshot configurations. + See https://github.com/PyPSA/PyPSA/issues/1360. + """ + # Case 1: Simple snapshots without multi_investment_periods + # Expected: storage_units_t.state_of_charge [0.9, 0.8, 0.7, 0.6] - continuous discharge + n = pypsa.Network() + n.snapshots = [1, 2, 3, 4] + + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=[0.1, 0.1, 0.1, 0.1]) + n.add( + "StorageUnit", + "storage_unit", + bus="bus", + p_nom=1, + max_hours=1, + state_of_charge_initial=1, + marginal_cost=1, + ) + + n.optimize(multi_investment_periods=False) + equal( + n.c.storage_units.dynamic.state_of_charge["storage_unit"].values, + [0.9, 0.8, 0.7, 0.6], + decimal=5, + ) + + # Case 2: Multi-indexed snapshots without multi_investment_periods + # Expected: storage_units_t.state_of_charge [0.9, 0.8, 0.7, 0.6] - continuous discharge + n = pypsa.Network() + years = [2030, 2040] + timesteps = [1, 2] + + n.snapshots = pd.MultiIndex.from_product([years, timesteps]) + n.investment_periods = years + + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=[0.1, 0.1, 0.1, 0.1]) + n.add( + "StorageUnit", + "storage_unit", + bus="bus", + p_nom=1, + max_hours=1, + state_of_charge_initial=1, + marginal_cost=1, + state_of_charge_initial_per_period=False, + ) + + n.optimize(multi_investment_periods=False) + equal( + n.c.storage_units.dynamic.state_of_charge["storage_unit"].values, + [0.9, 0.8, 0.7, 0.6], + decimal=5, + ) + + # Case 3: Multi_investment_periods with state_of_charge_initial_per_period=True + # Expected: storage_units_t.state_of_charge [0.9, 0.8, 0.9, 0.8] - reset at each period + n = pypsa.Network() + n.snapshots = pd.MultiIndex.from_product([years, timesteps]) + n.investment_periods = years + + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=[0.1, 0.1, 0.1, 0.1]) + n.add( + "StorageUnit", + "storage_unit", + bus="bus", + p_nom=1, + max_hours=1, + state_of_charge_initial=1, + marginal_cost=1, + state_of_charge_initial_per_period=True, + ) + + n.optimize(multi_investment_periods=True) + equal( + n.c.storage_units.dynamic.state_of_charge["storage_unit"].values, + [0.9, 0.8, 0.9, 0.8], + decimal=5, + ) + + # Case 4: Multi_investment_periods with state_of_charge_initial_per_period=False + # Expected: storage_units_t.state_of_charge [0.9, 0.8, 0.7, 0.6] - continuous discharge across periods + n = pypsa.Network() + n.snapshots = pd.MultiIndex.from_product([years, timesteps]) + n.investment_periods = years + + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=[0.1, 0.1, 0.1, 0.1]) + n.add( + "StorageUnit", + "storage_unit", + bus="bus", + p_nom=1, + max_hours=1, + state_of_charge_initial=1, + marginal_cost=1, + state_of_charge_initial_per_period=False, + ) + + n.optimize(multi_investment_periods=True) + equal( + n.c.storage_units.dynamic.state_of_charge["storage_unit"].values, + [0.9, 0.8, 0.7, 0.6], + decimal=5, + ) + + +def test_storageunit_cp_only_wraps_per_period(): + """cp=True, ip=False, c=False: per-period wrap is enforced. + + Verifies that with cyclic per period enabled (and no per-period initial reset), + the model links the first snapshot of each period to the last snapshot of the + same period (wrap) and thus avoids a purely continuous discharge pattern. + """ + n = pypsa.Network() + years = [2030, 2040] + timesteps = [1, 2] + n.snapshots = pd.MultiIndex.from_product([years, timesteps]) + n.investment_periods = years + + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=[0.1, 0.1, 0.1, 0.1]) + # Add a generator that is more expensive than discharging the storage so the + # optimizer prefers to discharge if cyclic-per-period isn't enforced. This + # keeps the test sensitive: with correct semantics it would need to + # recharge within each period, producing level pattern like [0.9,1.0,0.9,1.0]. + n.add( + "Generator", + "gen", + bus="bus", + p_nom=1.0, + marginal_cost=1.0, + ) + n.add( + "StorageUnit", + "su", + bus="bus", + p_nom=1, + max_hours=1, + state_of_charge_initial=1, + cyclic_state_of_charge=False, + cyclic_state_of_charge_per_period=True, + state_of_charge_initial_per_period=False, + marginal_cost=0.0, # cheaper than generator + ) + + status, _ = n.optimize(multi_investment_periods=True) + assert status == "ok" + soc = n.c.storage_units.dynamic.state_of_charge["su"].values + # Detect monotonic discharge signature. + expected_continuous = [0.9, 0.8, 0.7, 0.6] + if all(abs(soc[i] - expected_continuous[i]) < 1e-6 for i in range(4)): + raise AssertionError( + "Observed continuous discharge pattern [0.9,0.8,0.7,0.6]" + "cyclic_state_of_charge_per_period=True was ignored" + ) + + +def test_storageunit_ip_only_resets_per_period(): + """ip=True, cp=False, c=False: per-period resets to initial are enforced. + + Verifies that with per-period initial enabled (and no cyclic-per-period wrap), + the model resets state of charge to the user-provided initial at each period + start, avoiding a continuous discharge pattern across period boundaries. + """ + n = pypsa.Network() + years = [2030, 2040] + timesteps = [1, 2] + n.snapshots = pd.MultiIndex.from_product([years, timesteps]) + n.investment_periods = years + + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=[0.1, 0.1, 0.1, 0.1]) + n.add( + "StorageUnit", + "su_ip_only", + bus="bus", + p_nom=1, + max_hours=1, + state_of_charge_initial=1, + cyclic_state_of_charge=False, + cyclic_state_of_charge_per_period=False, + state_of_charge_initial_per_period=True, # target behavior + marginal_cost=1.0, # non-zero cost for objective contribution + ) + + status, _ = n.optimize(multi_investment_periods=True) + assert status == "ok" + soc = n.c.storage_units.dynamic.state_of_charge["su_ip_only"].values + continuous = [0.9, 0.8, 0.7, 0.6] + # If we observe the continuous pattern, storage level reset did not occur. + if all(abs(soc[i] - continuous[i]) < 1e-6 for i in range(4)): + raise AssertionError( + "Observed continuous discharge [0.9,0.8,0.7,0.6]" + "state_of_charge_initial_per_period=True was ignored" + ) + + +def test_operational_limit_with_investment_period_storage(): + """ + Test operational limit constraint with specific investment_period for storage. + + Regression test for bug #1437: snapshot selection for operational limit in + multi-period optimization. The bug was that period_last_sns was calculated + from all snapshots instead of filtered snapshots when investment_period was + specified in the global constraint. + """ + n = pypsa.Network(snapshots=range(4)) + n.investment_periods = [2020, 2030, 2040] + n.investment_period_weightings.loc[:, "years"] = 1 + + # Add bus + n.add("Bus", "bus1") + n.add("Carrier", ["battery", "electricity"]) + + # Add generator with costs + n.add( + "Generator", + "gen1", + bus="bus1", + p_nom=100, + carrier="electricity", + marginal_cost=1, + ) + + # Add storage unit with per-period initial state of charge + n.add( + "StorageUnit", + "storage1", + bus="bus1", + carrier="battery", + p_nom=50, + state_of_charge_initial=100, + state_of_charge_initial_per_period=True, + cyclic_state_of_charge=False, + max_hours=10, + ) + + # Add load + n.add("Load", "load1", bus="bus1", p_set=[10, 20, 15, 25] * 3) + + # Add operational limit for ONLY the 2030 period + # This is where the bug manifests + n.add( + "GlobalConstraint", + "battery_limit_2030", + type="operational_limit", + carrier_attribute="battery", + sense="<=", + constant=25, + investment_period=2030, # Only apply to 2030 period + ) + + # This should succeed without KeyError + status, _ = n.optimize(multi_investment_periods=True) + assert status == "ok" + + # Verify the constraint is applied correctly to 2030 period + period_2030_sns = n.snapshots[n.snapshots.get_loc(2030)] + + # Calculate storage SOC delta in 2030 + soc_initial = n.c.storage_units.static.loc["storage1", "state_of_charge_initial"] + soc_final = n.c.storage_units.dynamic.state_of_charge.loc[ + period_2030_sns[-1], "storage1" + ] + soc_delta = soc_initial - soc_final + + # Total operational value for 2030 should respect the limit + total_2030 = soc_delta + assert total_2030 <= 25 + 1e-6 # Allow small numerical tolerance diff --git a/PyPSA/source/test/test_lopf_multilink.py b/PyPSA/source/test/test_lopf_multilink.py new file mode 100644 index 0000000000000000000000000000000000000000..3672422be3241b0ff71bf0351da547cae2d8cebe --- /dev/null +++ b/PyPSA/source/test/test_lopf_multilink.py @@ -0,0 +1,169 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pytest + +import pypsa + + +@pytest.fixture +def n(): + n = pypsa.Network() + n.set_snapshots(range(10)) + + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=1.0) + + n.add("Bus", "transport") + n.add("Load", "transport", bus="transport", p_set=1.0) + + n.add("Bus", "diesel") + + n.add("Store", "diesel", bus="diesel", e_cyclic=True, e_nom=1000.0) + + n.add("Bus", "hydrogen") + + n.add("Store", "hydrogen", bus="hydrogen", e_cyclic=True, e_nom=1000.0) + + n.add( + "Link", "electrolysis", p_nom=2.0, efficiency=0.8, bus0="bus", bus1="hydrogen" + ) + + n.add( + "Link", + "FT", + p_nom=4, + bus0="hydrogen", + bus1="diesel", + bus2="co2 stored", + efficiency=1.0, + efficiency2=-1, + ) + + # minus sign because opposite to how fossil fuels used: + # CH4 burning puts CH4 down, atmosphere up + n.add("Carrier", "co2", co2_emissions=-1.0) + + # this tracks CO2 in the atmosphere + n.add("Bus", "co2 atmosphere", carrier="co2") + + # NB: can also be negative + n.add("Store", "co2 atmosphere", e_nom=1000, e_min_pu=-1, bus="co2 atmosphere") + + # this tracks CO2 stored, e.g. underground + n.add("Bus", "co2 stored") + + # NB: can also be negative + n.add("Store", "co2 stored", e_nom=1000, e_min_pu=-1, bus="co2 stored") + + n.add( + "Link", + "DAC", + bus0="bus", + bus1="co2 stored", + bus2="co2 atmosphere", + efficiency=1, + efficiency2=-1, + p_nom=5.0, + ) + + n.add( + "Link", + "diesel car", + bus0="diesel", + bus1="transport", + bus2="co2 atmosphere", + efficiency=1.0, + efficiency2=1.0, + p_nom=2.0, + ) + + n.add("Bus", "gas") + + n.add("Store", "gas", e_initial=50, e_nom=50, marginal_cost=20, bus="gas") + + n.add( + "Link", + "OCGT", + bus0="gas", + bus1="bus", + bus2="co2 atmosphere", + p_nom_extendable=True, + efficiency=0.5, + efficiency2=1, + ) + + n.add( + "Link", + "OCGT+CCS", + bus0="gas", + bus1="bus", + bus2="co2 stored", + bus3="co2 atmosphere", + p_nom_extendable=True, + efficiency=0.4, + efficiency2=0.9, + efficiency3=0.1, + ) + + # Add a cheap and a expensive biomass generator. + biomass_marginal_cost = [20.0, 50.0] + biomass_stored = [40.0, 15.0] + + for i in range(2): + n.add("Bus", f"biomass{str(i)}") + + n.add( + "Store", + f"biomass{str(i)}", + bus=f"biomass{str(i)}", + e_nom_extendable=True, + marginal_cost=biomass_marginal_cost[i], + e_nom=biomass_stored[i], + e_initial=biomass_stored[i], + ) + + # simultaneously empties and refills co2 atmosphere + n.add( + "Link", + f"biomass{str(i)}", + bus0=f"biomass{str(i)}", + bus1="bus", + p_nom_extendable=True, + efficiency=0.5, + ) + + n.add( + "Link", + f"biomass+CCS{str(i)}", + bus0=f"biomass{str(i)}", + bus1="bus", + bus2="co2 stored", + bus3="co2 atmosphere", + p_nom_extendable=True, + efficiency=0.4, + efficiency2=1.0, + efficiency3=-1, + ) + + # can go to -50, but at some point can't generate enough electricity for DAC and demand + target = -50 + n.add( + "GlobalConstraint", + "co2_limit", + sense="<=", + carrier_attribute="co2_emissions", + constant=target, + ) + return n + + +def test_attribution_assignment(n): + assert "bus2" in n.components["Link"]["defaults"].index + assert n.components["Link"]["defaults"].loc["bus2", "default"] == "" + + +def test_optimize(n): + status, condition = n.optimize() + assert status == "ok" diff --git a/PyPSA/source/test/test_lopf_quadratic_costs.py b/PyPSA/source/test/test_lopf_quadratic_costs.py new file mode 100644 index 0000000000000000000000000000000000000000..85171d86e7b3274cd2001da07fb1f4ae63c09248 --- /dev/null +++ b/PyPSA/source/test/test_lopf_quadratic_costs.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pytest +from linopy import available_solvers + + +@pytest.mark.skipif("gurobi" not in available_solvers, reason="Gurobi not installed") +def test_optimize_quadratic(ac_dc_network): + n = ac_dc_network + + status, _ = n.optimize(solver_name="gurobi") + + assert status == "ok" + + gas_i = n.c.generators.static.index[n.c.generators.static.carrier == "gas"] + + objective_linear = n.objective + + # quadratic costs + n.c.generators.static.loc[gas_i, "marginal_cost_quadratic"] = 2 + status, _ = n.optimize(solver_name="gurobi") + + assert status == "ok" + assert n.objective > objective_linear diff --git a/PyPSA/source/test/test_lopf_rolling_horizon.py b/PyPSA/source/test/test_lopf_rolling_horizon.py new file mode 100644 index 0000000000000000000000000000000000000000..4ecd44cd25ff419e527a22276a8a2db15e7e7287 --- /dev/null +++ b/PyPSA/source/test/test_lopf_rolling_horizon.py @@ -0,0 +1,111 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import numpy as np +import pytest + +import pypsa + + +def get_network(committable): + n = pypsa.Network(snapshots=range(12)) + + n.add("Bus", "bus") + + n.add( + "Generator", + "coal", + bus="bus", + ramp_limit_up=0.1, + ramp_limit_down=0.3, + marginal_cost=20, + capital_cost=200, + p_nom=1000, + committable=committable, + ) + + n.add( + "Generator", + "gas", + bus="bus", + ramp_limit_up=0.5, + ramp_limit_down=0.5, + marginal_cost=40, + capital_cost=200, + p_nom=1000, + committable=committable, + ) + + n.add("Load", "load", bus="bus", p_set=[400, 600, 500, 800] * 3) + + return n + + +@pytest.mark.parametrize("committable", [True, False]) +def test_rolling_horizon(committable): + n = get_network(committable) + # now rolling horizon + for sns in np.array_split(n.snapshots, 4): + status, condition = n.optimize(snapshots=sns) + assert status == "ok" + + ramping = n.c.generators.dynamic.p.diff().fillna(0) + assert ( + (ramping <= n.c.generators.static.eval("ramp_limit_up * p_nom_opt")).all().all() + ) + assert ( + (ramping >= -n.c.generators.static.eval("ramp_limit_down * p_nom_opt")) + .all() + .all() + ) + + +@pytest.mark.parametrize("committable", [True, False]) +def test_rolling_horizon_integrated(committable): + n = get_network(committable) + n.add( + "StorageUnit", + "storage", + bus="bus", + p_nom=100, + p_nom_extendable=False, + marginal_cost=10, + ) + + n.optimize.optimize_with_rolling_horizon(horizon=3) + ramping = n.c.generators.dynamic.p.diff().fillna(0) + assert ( + (ramping <= n.c.generators.static.eval("ramp_limit_up * p_nom_opt")).all().all() + ) + assert ( + (ramping >= -n.c.generators.static.eval("ramp_limit_down * p_nom_opt")) + .all() + .all() + ) + + +def test_rolling_horizon_integrated_overlap(): + n = get_network(committable=True) + n.add( + "StorageUnit", + "storage", + bus="bus", + p_nom=100, + p_nom_extendable=False, + marginal_cost=10, + ) + + with pytest.raises(ValueError): + n.optimize.optimize_with_rolling_horizon(horizon=1, overlap=2) + + n.optimize.optimize_with_rolling_horizon(horizon=3, overlap=1) + ramping = n.c.generators.dynamic.p.diff().fillna(0) + assert ( + (ramping <= n.c.generators.static.eval("ramp_limit_up * p_nom_opt")).all().all() + ) + assert ( + (ramping >= -n.c.generators.static.eval("ramp_limit_down * p_nom_opt")) + .all() + .all() + ) diff --git a/PyPSA/source/test/test_lopf_storage.py b/PyPSA/source/test/test_lopf_storage.py new file mode 100644 index 0000000000000000000000000000000000000000..bc1401f0fdeb0f8823708ba4f6fc174f48bcf450 --- /dev/null +++ b/PyPSA/source/test/test_lopf_storage.py @@ -0,0 +1,111 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +from pathlib import Path + +import pandas as pd +import pytest +from numpy.testing import assert_array_almost_equal as equal + +import pypsa + + +@pytest.fixture +def target_gen_p(): + target_path = ( + Path(__file__).parent + / "data" + / "storage-hvdc" + / "results-lopf" + / "generators-p.csv" + ) + return pd.read_csv(target_path, index_col=0, parse_dates=True) + + +def test_optimize(storage_hvdc_network, target_gen_p): + n = storage_hvdc_network + n.optimize() + equal(n.c.generators.dynamic.p.reindex_like(target_gen_p), target_gen_p, decimal=2) + + +def test_storage_energy_marginal_cost(): + n = pypsa.Network() + n.snapshots = range(3) + n.add("Bus", "bus") + n.add( + "Generator", + "gen", + marginal_cost=1, + bus="bus", + p_nom=3, + p_max_pu=[1, 0, 0], + ) + n.add("Load", "load", bus="bus", p_set=1) + n.add( + "Store", + "store", + bus="bus", + marginal_cost_storage=0.2, + e_initial=1, + e_nom=10, + ) + n.optimize() + assert n.objective == 2.6 + + +def test_spill_cost(): + sets_of_snapshots = 2 + p_set = [100, 100, 100, 100, 100] + + for has_spill_cost in [False, True]: + n = pypsa.Network(snapshots=range(len(p_set) * sets_of_snapshots)) + + n.add("Bus", "bus") + + # Add high capacity generator to help + n.add( + "Generator", "help", bus="bus", p_nom=1000, control="PQ", marginal_cost=100 + ) + + # Add hydro unit + if has_spill_cost: + n.add( + "StorageUnit", + "hydro", + bus="bus", + p_nom=100, + max_hours=10, + inflow=[200, 200, 200, 200, 200, 50, 50, 50, 50, 50], + spill_cost=1, + ) + else: + n.add( + "StorageUnit", + "hydro", + bus="bus", + p_nom=100, + max_hours=10, + inflow=[200, 200, 200, 200, 200, 50, 50, 50, 50, 50], + ) + + # Add Load + n.add("Load", "load", bus="bus", p_set=p_set * sets_of_snapshots) + + overlap = 2 + for i in range(sets_of_snapshots): + if i == 1: + n.c.storage_units.static.state_of_charge_initial = ( + n.c.storage_units.dynamic.state_of_charge.loc[n.snapshots[4]] + ) + n.optimize( + n.snapshots[i * len(p_set) : (i + 1) * len(p_set) + overlap], + ) + + spill = n.c.storage_units.dynamic["spill"].loc[:, "hydro"] + total_spill = spill.sum() + + if has_spill_cost: + assert total_spill == 0 + else: + assert total_spill == 400 diff --git a/PyPSA/source/test/test_lopf_unit_commitment.py b/PyPSA/source/test/test_lopf_unit_commitment.py new file mode 100644 index 0000000000000000000000000000000000000000..b3aeaa1a2fd3e236de1bcc44b742876664df5340 --- /dev/null +++ b/PyPSA/source/test/test_lopf_unit_commitment.py @@ -0,0 +1,679 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import numpy as np +import pandas as pd +from numpy.testing import assert_array_almost_equal as equal + +import pypsa + + +def test_unit_commitment(): + """ + This test is based on https://docs.pypsa.org/en/latest/examples/unit- + commitment.html and is not very comprehensive. + """ + n = pypsa.Network() + + snapshots = range(4) + + n.set_snapshots(snapshots) + + n.add("Bus", "bus") + + n.add( + "Generator", + "coal", + bus="bus", + committable=True, + p_min_pu=0.3, + marginal_cost=20, + p_nom=10000, + ) + + n.add( + "Generator", + "gas", + bus="bus", + committable=True, + marginal_cost=70, + p_min_pu=0.1, + p_nom=1000, + ) + + n.add("Load", "load", bus="bus", p_set=[4000, 6000, 5000, 800]) + + n.optimize() + + expected_status = np.array([[1, 1, 1, 0], [0, 0, 0, 1]], dtype=float).T + + equal(n.c.generators.dynamic.status.values, expected_status) + + expected_dispatch = np.array([[4000, 6000, 5000, 0], [0, 0, 0, 800]], dtype=float).T + + equal(n.c.generators.dynamic.p.values, expected_dispatch) + + +def test_minimum_up_time(): + """ + This test is based on https://docs.pypsa.org/en/latest/examples/unit- + commitment.html and is not very comprehensive. + """ + n = pypsa.Network() + + snapshots = range(4) + + n.set_snapshots(snapshots) + + n.add("Bus", "bus") + + n.add( + "Generator", + "coal", + bus="bus", + committable=True, + p_min_pu=0.3, + marginal_cost=20, + p_nom=10000, + ) + + n.add( + "Generator", + "gas", + bus="bus", + committable=True, + marginal_cost=70, + p_min_pu=0.1, + up_time_before=0, + min_up_time=3, + p_nom=1000, + ) + + n.add("Load", "load", bus="bus", p_set=[4000, 800, 5000, 3000]) + + n.optimize() + + expected_status = np.array([[1, 0, 1, 1], [1, 1, 1, 0]], dtype=float).T + + equal(n.c.generators.dynamic.status.values, expected_status) + + expected_dispatch = np.array( + [[3900, 0, 4900, 3000], [100, 800, 100, 0]], dtype=float + ).T + + equal(n.c.generators.dynamic.p.values, expected_dispatch) + + +def test_minimum_up_time_up_time_before(): + """ + This test is based on https://docs.pypsa.org/en/latest/examples/unit- + commitment.html and is not very comprehensive. + """ + n = pypsa.Network() + + snapshots = range(4) + + n.set_snapshots(snapshots) + + n.add("Bus", "bus") + + n.add( + "Generator", + "coal", + bus="bus", + committable=True, + p_min_pu=0.3, + marginal_cost=20, + p_nom=10000, + ) + + n.add( + "Generator", + "gas", + bus="bus", + committable=True, + marginal_cost=70, + p_min_pu=0.1, + up_time_before=1, + min_up_time=4, + p_nom=1000, + ) + + n.add("Load", "load", bus="bus", p_set=[4000, 800, 5000, 3000]) + + n.optimize() + + expected_status = np.array([[1, 0, 1, 1], [1, 1, 1, 0]], dtype=float).T + + equal(n.c.generators.dynamic.status.values, expected_status) + + expected_dispatch = np.array( + [[3900, 0, 4900, 3000], [100, 800, 100, 0]], dtype=float + ).T + + equal(n.c.generators.dynamic.p.values, expected_dispatch) + + +def test_minimum_down_time(): + """ + This test is based on https://docs.pypsa.org/en/latest/examples/unit- + commitment.html and is not very comprehensive. + """ + n = pypsa.Network() + + n.set_snapshots(range(4)) + + n.add("Bus", "bus") + + n.add( + "Generator", + "coal", + bus="bus", + committable=True, + p_min_pu=0.3, + marginal_cost=20, + min_down_time=2, + down_time_before=1, + p_nom=10000, + ) + + n.add( + "Generator", + "gas", + bus="bus", + committable=True, + marginal_cost=70, + p_min_pu=0.1, + p_nom=4000, + ) + + n.add("Load", "load", bus="bus", p_set=[3000, 800, 3000, 8000]) + + n.optimize() + + expected_status = np.array([[0, 0, 1, 1], [1, 1, 0, 0]], dtype=float).T + + equal(n.c.generators.dynamic.status.values, expected_status) + + expected_dispatch = np.array([[0, 0, 3000, 8000], [3000, 800, 0, 0]], dtype=float).T + + equal(n.c.generators.dynamic.p.values, expected_dispatch) + + +def test_minimum_down_time_up_time_before(): + """ + This test is based on https://docs.pypsa.org/en/latest/examples/unit- + commitment.html and is not very comprehensive. + """ + n = pypsa.Network() + + n.set_snapshots(range(4)) + + n.add("Bus", "bus") + + n.add( + "Generator", + "coal", + bus="bus", + committable=True, + p_min_pu=0.3, + marginal_cost=20, + min_down_time=3, + down_time_before=2, + up_time_before=0, + p_nom=10000, + ) + + n.add( + "Generator", + "gas", + bus="bus", + committable=True, + marginal_cost=70, + p_min_pu=0.1, + p_nom=4000, + ) + + n.add("Load", "load", bus="bus", p_set=[3000, 800, 3000, 8000]) + + n.optimize() + + expected_status = np.array([[0, 0, 1, 1], [1, 1, 0, 0]], dtype=float).T + + equal(n.c.generators.dynamic.status.values, expected_status) + + expected_dispatch = np.array([[0, 0, 3000, 8000], [3000, 800, 0, 0]], dtype=float).T + + equal(n.c.generators.dynamic.p.values, expected_dispatch) + + +def test_start_up_costs(): + n = pypsa.Network() + + n.snapshots = range(4) + + n.add("Bus", "bus") + + n.add( + "Generator", + "coal", + bus="bus", + committable=True, + p_min_pu=0.1, + up_time_before=0, + marginal_cost=20, + start_up_cost=3000, + p_nom=10000, + ) + + n.add( + "Generator", + "gas", + bus="bus", + committable=True, + marginal_cost=70, + p_min_pu=0.0, + up_time_before=1, + start_up_cost=1000, + p_nom=10000, + ) + + n.add("Load", "load", bus="bus", p_set=[4000, 6000, 5000, 800]) + + n.optimize() + + assert n.objective == 359000 + + +def test_shut_down_costs(): + n = pypsa.Network() + + n.snapshots = range(4) + + n.add("Bus", "bus") + + n.add( + "Generator", + "coal", + bus="bus", + committable=True, + p_min_pu=0.3, + marginal_cost=20, + shut_down_cost=1000, + p_nom=10000, + ) + + n.add( + "Generator", + "gas", + bus="bus", + committable=True, + marginal_cost=70, + shut_down_cost=1000, + p_min_pu=0.1, + p_nom=1000, + ) + + n.add("Load", "load", bus="bus", p_set=[4000, 6000, 5000, 800]) + + n.optimize() + + assert n.objective == 358000 + + +def test_unit_commitment_rolling_horizon(): + n = pypsa.Network() + n.snapshots = range(7) + + n.add("Bus", "bus") + + n.add( + "Generator", + "coal", + bus="bus", + committable=True, + p_min_pu=0.1, + up_time_before=1, + marginal_cost=20, + min_up_time=2, + min_down_time=2, + start_up_cost=10000, + p_nom=10000, + ) + + n.add( + "Generator", + "gas", + bus="bus", + committable=True, + marginal_cost=70, + p_min_pu=0.01, + start_up_cost=100, + up_time_before=0, + p_nom=10000, + ) + n.add("Load", "load", bus="bus", p_set=[4000, 6000, 800, 5000, 3000, 950, 800]) + + n.optimize(snapshots=[0, 1, 2]) + n.optimize(snapshots=[2, 3, 4]) + n.optimize(snapshots=[4, 5, 6]) + + expected_status = np.array( + [[1, 1, 0, 0, 0, 0, 0], [0, 0, 1, 1, 1, 1, 1]], dtype=float + ).T + equal(n.c.generators.dynamic.status.values, expected_status) + + expected_dispatch = np.array( + [[4000, 6000, 0, 0, 0, 0, 0], [0, 0, 800, 5000, 3000, 950, 800]] + ).T + + equal(n.c.generators.dynamic.p.values, expected_dispatch) + + +def test_linearized_unit_commitment(): + n = pypsa.Network() + n.snapshots = pd.date_range("2022-01-01", "2022-02-09", freq="d") + + load = np.zeros(len(n.snapshots)) + load[:5] = 5 + load[5:10] = 6 + load[10:15] = 8 + load[15:20] = 10 + load[20:30] = 7 + load[30:40] = 6 + load *= 100 + + n.add("Bus", "bus") + + for seed, i in enumerate(range(40), start=1): + rng = np.random.default_rng(seed) # Create a random number generator + p_min_pu = rng.integers(1, 5) / 10 + marginal_cost = rng.integers(1, 11) * 10 + min_up_time = rng.integers(0, 6) + min_down_time = rng.integers(0, 6) + p_nom = rng.integers(1, 10) * 5 + start_up_cost = rng.integers(1, 5) * 100 + + # the constraint tightening proposed in Baldick et al. depends on start_up_cost + # and shut_down_cost being equal therefore, we force them to be equal for first + # 20 generators + shut_down_cost = rng.integers(1, 5) * 100 if i >= 20 else start_up_cost + + n.add( + "Generator", + f"{i}", + bus="bus", + committable=True, + up_time_before=0, + p_min_pu=p_min_pu, + marginal_cost=marginal_cost, + min_up_time=min_up_time, + min_down_time=min_down_time, + p_nom=p_nom, + start_up_cost=start_up_cost, + shut_down_cost=shut_down_cost, + ) + n.add("Load", "load", bus="bus", p_set=load) + + n.optimize(linearized_unit_commitment=True) + + MILP_objective = 1241100 + assert round(n.objective / MILP_objective, 2) == 1 + + +def test_link_unit_commitment(): + n = pypsa.Network() + + snapshots = range(4) + + n.set_snapshots(snapshots) + + n.add("Bus", ["gas", "electricity"]) + + n.add("Generator", "gas", bus="gas", marginal_cost=10, p_nom=20000) + + n.add( + "Link", + "OCGT", + bus0="gas", + bus1="electricity", + committable=True, + p_min_pu=0.1, + efficiency=0.5, + up_time_before=0, + min_up_time=3, + start_up_cost=3333, + p_nom=12000, + ) + + n.add( + "Generator", + "wind", + bus="electricity", + p_nom=800, + ) + + n.add("Load", "load", bus="electricity", p_set=[4000, 6000, 800, 5000]) + + n.optimize() + + expected_status = [1.0, 1.0, 1.0, 1.0] + + equal(n.c.links.dynamic.status["OCGT"].values, expected_status) + + expected_dispatch = [3200.0, 5200.0, 600.0, 4200.0] + + equal(-n.c.links.dynamic.p1["OCGT"].values, expected_dispatch) + + assert round(n.objective, 1) == 267333.0 + + +def test_link_ramp_limits(): + """ + Test that ramp limits work for Links. + """ + n = pypsa.Network() + + snapshots = range(6) + n.set_snapshots(snapshots) + + n.add("Bus", ["gas", "electricity"]) + + n.add("Generator", "gas", bus="gas", marginal_cost=10, p_nom=20000) + + n.add( + "Link", + "OCGT", + bus0="gas", + bus1="electricity", + p_min_pu=0.1, + efficiency=0.5, + p_nom=10000, + ramp_limit_up=0.3, # 30% of p_nom per timestep = 3000 MW + ramp_limit_down=0.4, # 40% of p_nom per timestep = 4000 MW + marginal_cost=20, + ) + + n.add("Generator", "backup", bus="electricity", marginal_cost=100, p_nom=10000) + + # Varying load to induce ramping + n.add("Load", "load", bus="electricity", p_set=[2000, 7000, 1500, 5500, 5000, 2500]) + + n.optimize() + + # Check that ramp limits are respected + # For Links, use p0 (the power at bus0) which is the optimization variable + p_diff = n.c.links.dynamic.p0["OCGT"].diff() + max_ramp_up = 0.3 * 10000 # 3000 MW + max_ramp_down = 0.4 * 10000 # 4000 MW + + # Check ramp up (positive changes) + ramp_ups = p_diff[p_diff > 0] + if not ramp_ups.empty: + assert ramp_ups.max() <= max_ramp_up + 1e-4, ( + f"Ramp up limit violated: {ramp_ups.max()} > {max_ramp_up}" + ) + + # Check ramp down (negative changes) + ramp_downs = p_diff[p_diff < 0].abs() + if not ramp_downs.empty: + assert ramp_downs.max() <= max_ramp_down + 1e-4, ( + f"Ramp down limit violated: {ramp_downs.max()} > {max_ramp_down}" + ) + + +def test_link_ramp_limits_rolling_horizon(): + """ + Test that ramp limits work for Links in rolling horizon optimization. + This specifically tests the historical data retrieval for p0 in Links + when sns[0] != n.snapshots[0]. + """ + n = pypsa.Network() + + snapshots = range(12) + n.set_snapshots(snapshots) + + n.add("Bus", ["gas", "electricity"]) + + n.add("Generator", "gas", bus="gas", marginal_cost=10, p_nom=20000) + + n.add( + "Link", + "OCGT", + bus0="gas", + bus1="electricity", + p_min_pu=0.1, + efficiency=0.5, + p_nom=10000, + ramp_limit_up=0.3, # 30% of p_nom per timestep = 3000 MW + ramp_limit_down=0.4, # 40% of p_nom per timestep = 4000 MW + marginal_cost=20, + ) + + n.add("Generator", "backup", bus="electricity", marginal_cost=100, p_nom=10000) + + # Varying load to induce ramping with jumps > ramp limits (3000 up, 4000 down) + n.add( + "Load", + "load", + bus="electricity", + p_set=[2000, 6000, 1000, 5500, 9000, 3000, 7500, 2500, 7000, 1500, 6000, 2000], + ) + + n.optimize.optimize_with_rolling_horizon(horizon=4, overlap=1) + + # Check that ramp limits are respected across all snapshots + # For Links, use p0 (the power at bus0) + p_diff = n.c.links.dynamic.p0["OCGT"].diff() + max_ramp_up = 0.3 * 10000 # 3000 MW + max_ramp_down = 0.4 * 10000 # 4000 MW + + # Check ramp up (positive changes) + ramp_ups = p_diff[p_diff > 0] + if not ramp_ups.empty: + assert ramp_ups.max() <= max_ramp_up + 1e-4, ( + f"Ramp up limit violated: {ramp_ups.max()} > {max_ramp_up}" + ) + + # Check ramp down (negative changes) + ramp_downs = p_diff[p_diff < 0].abs() + if not ramp_downs.empty: + assert ramp_downs.max() <= max_ramp_down + 1e-4, ( + f"Ramp down limit violated: {ramp_downs.max()} > {max_ramp_down}" + ) + + +def test_dynamic_ramp_rates(): + """ + This test checks that dynamic ramp rates are correctly applied when + considering a unit outage represented by p_max_pu. + """ + n = pypsa.Network() + + snapshots = range(15) + n.set_snapshots(snapshots) + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=100) + + # vary marginal price of gen1 to induce ramping + gen1_marginal = pd.Series(100, index=n.snapshots) + gen1_marginal[[4, 5, 6, 10, 11, 12]] = 200 + + static_ramp_up = 0.8 + static_ramp_down = 1 + p_max_pu = pd.Series(1, index=n.snapshots).astype(float) + p_max_pu.loc[n.snapshots[0:6]] = 0.5 # 50% capacity outage for 6 periods + + n.add( + "Generator", + "gen1", + bus="bus", + p_nom=100, + p_max_pu=p_max_pu, + ramp_limit_up=static_ramp_up * p_max_pu, + ramp_limit_down=static_ramp_down * p_max_pu, + marginal_cost=gen1_marginal, + ) + + n.add("Generator", "gen2", bus="bus", p_nom=100, marginal_cost=150) + + n.optimize() + + assert (n.c.generators.dynamic.p.diff().loc[0:6, "gen1"]).max() <= 0.5 * 80 + assert (n.c.generators.dynamic.p.diff().loc[0:6, "gen1"]).min() >= -0.5 * 100 + assert (n.c.generators.dynamic.p.diff().loc[6:, "gen1"]).max() <= 80 + assert (n.c.generators.dynamic.p.diff().loc[6:, "gen1"]).min() >= -100 + + +def test_dynamic_start_up_rates_for_commitables(): + """ + This test checks that start up ramp rate constraints within unit commitment functionality runs through and is considered correctly. + """ + n = pypsa.Network() + + snapshots = range(15) + n.set_snapshots(snapshots) + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=100) + + # vary marginal price of gen1 to induce ramping + gen1_marginal = pd.Series(100, index=n.snapshots) + gen1_marginal[[4, 5, 6, 10, 11, 12]] = 200 + + n.add( + "Generator", + "gen1", + bus="bus", + p_nom=100, + committable=True, + p_min_pu=0.3, + p_max_pu=1, + ramp_limit_up=1, + ramp_limit_start_up=0.3, + ramp_limit_shut_down=1, + start_up_cost=10, + shut_down_cost=10, + marginal_cost=gen1_marginal, + ) + + n.add("Generator", "gen2", bus="bus", p_nom=100, marginal_cost=150) + + status, _ = n.optimize(snapshots=n.snapshots) + + assert status == "ok" + + # Check that ramp_limit_start_up constraint is respected + gen1_status = n.c.generators.dynamic.status["gen1"] + gen1_p = n.c.generators.dynamic.p["gen1"] + + # Find startup events (status changes from 0 to 1) + startup_snapshots = gen1_status[ + (gen1_status == 1) & (gen1_status.shift(1) == 0) + ].index + + for snapshot in startup_snapshots: + expected_max_startup = 0.3 * 100 # ramp_limit_start_up * p_nom + assert gen1_p[snapshot] <= expected_max_startup, ( + f"Startup ramp limit violated at snapshot {snapshot}: {gen1_p[snapshot]} > {expected_max_startup}" + ) diff --git a/PyPSA/source/test/test_lopf_varying_inputs.py b/PyPSA/source/test/test_lopf_varying_inputs.py new file mode 100644 index 0000000000000000000000000000000000000000..a2b8580633aa99072549dfa76f81d8cd35c55ce4 --- /dev/null +++ b/PyPSA/source/test/test_lopf_varying_inputs.py @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +from numpy.testing import assert_array_almost_equal as equal + +import pypsa + + +def test_time_dependent_generator_efficiency(): + n = pypsa.Network() + s = [1, 0.25, 0.2] + limit = sum(1 / i for i in s) + n.snapshots = range(len(s)) + n.add("Bus", "bus") + n.add("Carrier", "carrier", co2_emissions=1) + n.add( + "Generator", + "gen", + carrier="carrier", + marginal_cost=1, + bus="bus", + p_nom=1, + efficiency=s, + ) + n.add("Load", "load", bus="bus", p_set=1) + n.add("GlobalConstraint", "limit", constant=limit) + status, _ = n.optimize() + assert status == "ok" + + +def test_time_dependent_standing_losses_storage_units(): + n = pypsa.Network() + s = [0, 0.1, 0.2] + n.snapshots = range(len(s)) + n.add("Bus", "bus") + n.add( + "StorageUnit", + "su", + bus="bus", + marginal_cost=1, + p_nom=1, + max_hours=1, + state_of_charge_initial=1, + standing_loss=s, + ) + status, _ = n.optimize() + assert status == "ok" + equal(n.c.storage_units.dynamic.state_of_charge.su.values, [1.0, 0.9, 0.72]) + + +def test_time_dependent_standing_losses_stores(): + n = pypsa.Network() + s = [0, 0.1, 0.2] + n.snapshots = range(len(s)) + n.add("Bus", "bus") + n.add( + "Store", + "sto", + bus="bus", + marginal_cost=1, + e_nom=1, + e_initial=1, + standing_loss=s, + ) + status, _ = n.optimize() + assert status == "ok" + equal(n.c.stores.dynamic.e.sto.values, [1.0, 0.9, 0.72]) diff --git a/PyPSA/source/test/test_lpf_ac_dc.py b/PyPSA/source/test/test_lpf_ac_dc.py new file mode 100644 index 0000000000000000000000000000000000000000..c0ab107f6defcf48ef30972b4c00a0448ba223e8 --- /dev/null +++ b/PyPSA/source/test/test_lpf_ac_dc.py @@ -0,0 +1,59 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +from pathlib import Path + +import pytest +from numpy.testing import assert_array_almost_equal as equal + +import pypsa + + +@pytest.fixture +def ac_dc_network_r(): + csv_folder = Path(__file__).parent / "data" / "ac-dc-meshed" / "results-lpf" + return pypsa.Network(csv_folder) + + +def test_lpf(ac_dc_network, ac_dc_network_r): + n = ac_dc_network + n_r = ac_dc_network_r + n.c.links.dynamic.p_set = n_r.c.links.dynamic.p_set + + n.lpf(snapshots=n.snapshots) + + equal( + n.c.generators.dynamic.p[n.c.generators.static.index], + n_r.c.generators.dynamic.p[n.c.generators.static.index], + ) + equal( + n.c.lines.dynamic.p0[n.c.lines.static.index], + n_r.c.lines.dynamic.p0[n.c.lines.static.index], + ) + equal( + n.c.links.dynamic.p0[n.c.links.static.index], + n_r.c.links.dynamic.p0[n.c.links.static.index], + ) + + +def test_lpf_chunks(ac_dc_network, ac_dc_network_r): + n = ac_dc_network + n_r = ac_dc_network_r + n.c.links.dynamic.p_set = n_r.c.links.dynamic.p_set + + for snapshot in n.snapshots: + n.lpf(snapshot) + + equal( + n.c.generators.dynamic.p[n.c.generators.static.index], + n_r.c.generators.dynamic.p[n.c.generators.static.index], + ) + equal( + n.c.lines.dynamic.p0[n.c.lines.static.index], + n_r.c.lines.dynamic.p0[n.c.lines.static.index], + ) + equal( + n.c.links.dynamic.p0[n.c.links.static.index], + n_r.c.links.dynamic.p0[n.c.links.static.index], + ) diff --git a/PyPSA/source/test/test_lpf_against_pypower.py b/PyPSA/source/test/test_lpf_against_pypower.py new file mode 100644 index 0000000000000000000000000000000000000000..1a3aa2d9abe6e6fe35c93f0b05f01860f8448205 --- /dev/null +++ b/PyPSA/source/test/test_lpf_against_pypower.py @@ -0,0 +1,116 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pandas as pd +from numpy.testing import assert_array_almost_equal as equal +from pypower.case30 import case30 as case +from pypower.ppoption import ppoption +from pypower.runpf import runpf + +import pypsa +from pypsa.constants import DEFAULT_TIMESTAMP + +# NB: this test doesn't work for other cases because transformer tap +# ratio and phase angle not supported for lpf + + +def test_pypower_case(): + # ppopt is a dictionary with the details of the optimization routine to run + ppopt = ppoption(PF_ALG=2) + + # choose DC or AC + ppopt["PF_DC"] = True + + # ppc is a dictionary with details about the network, including baseMVA, branches and generators + ppc = case() + + results, success = runpf(ppc, ppopt) + + # branches + columns = [ + "bus0", + "bus1", + "r", + "x", + "b", + "rateA", + "rateB", + "rateC", + "ratio", + "angle", + "status", + "angmin", + "angmax", + "p0", + "q0", + "p1", + "q1", + ] + results_df = {"branch": pd.DataFrame(data=results["branch"], columns=columns)} + # buses + columns = [ + "bus", + "type", + "Pd", + "Qd", + "Gs", + "Bs", + "area", + "v_mag_pu_set", + "v_ang_set", + "v_nom", + "zone", + "Vmax", + "Vmin", + ] + results_df["bus"] = pd.DataFrame( + data=results["bus"], columns=columns, index=results["bus"][:, 0] + ) + + # generators + columns = [ + "bus", + "p", + "q", + "q_max", + "q_min", + "Vg", + "mBase", + "status", + "p_max", + "p_min", + "Pc1", + "Pc2", + "Qc1min", + "Qc1max", + "Qc2min", + "Qc2max", + "ramp_agc", + "ramp_10", + "ramp_30", + "ramp_q", + "apf", + ] + results_df["gen"] = pd.DataFrame(data=results["gen"], columns=columns) + + # now compute in PyPSA + n = pypsa.Network() + n.import_from_pypower_ppc(ppc) + n.lpf() + + # compare generator dispatch + p_pypsa = n.c.generators.dynamic.p.loc[DEFAULT_TIMESTAMP].values + p_pypower = results_df["gen"]["p"].values + + equal(p_pypsa, p_pypower) + + # compare branch flows + for item in ["lines", "transformers"]: + df = getattr(n.c, item).static + dynamic = getattr(n.c, item).dynamic + + for si in ["p0", "p1"]: + si_pypsa = getattr(dynamic, si).loc[DEFAULT_TIMESTAMP].values + si_pypower = results_df["branch"][si][df.original_index].values + equal(si_pypsa, si_pypower) diff --git a/PyPSA/source/test/test_meshed_threshold.py b/PyPSA/source/test/test_meshed_threshold.py new file mode 100644 index 0000000000000000000000000000000000000000..a74c6dd27ae783ebcd45e884138ec3556f401a62 --- /dev/null +++ b/PyPSA/source/test/test_meshed_threshold.py @@ -0,0 +1,127 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pandas as pd + +import pypsa +from pypsa.optimization.common import get_strongly_meshed_buses + + +def test_meshed_threshold(): + """ + This test is based on https://docs.pypsa.org/en/latest/examples/unit- + commitment.html and is not very comprehensive. + """ + # marginal costs in EUR/MWh + marginal_costs = {"Wind": 0, "Hydro": 0, "Coal": 30, "Gas": 60, "Oil": 80} + + # power plant capacities (nominal powers in MW) in each country (not necessarily realistic) + power_plant_p_nom = { + "Country1": { + "Coal": 35000, + "Wind": 3000, + "Gas": 8000, + "Oil": 2000, + }, + "Country2": { + "Gas": 600, + }, + "Country3": { + "Gas": 600, + }, + "Country4": { + "Gas": 600, + }, + "Country5": { + "Gas": 600, + }, + "Country6": { + "Gas": 600, + }, + "Country7": { + "Gas": 600, + }, + "Country8": { + "Gas": 600, + }, + "Country9": { + "Gas": 600, + }, + } + + # transmission capacities in MW (not necessarily realistic) + transmission = { + "Country1": {"Country2": 100, "Country4": 250, "Country7": 250}, + "Country2": {"Country3": 100, "Country5": 250, "Country8": 250}, + "Country3": {"Country4": 100, "Country6": 250, "Country9": 250}, + "Country4": {"Country5": 100, "Country7": 250, "Country1": 250}, + "Country5": {"Country6": 100, "Country8": 250, "Country2": 250}, + "Country6": {"Country7": 100, "Country9": 250, "Country3": 250}, + "Country7": {"Country8": 100, "Country1": 250, "Country4": 250}, + "Country8": {"Country9": 100, "Country2": 250, "Country5": 250}, + "Country9": {"Country1": 100, "Country3": 250, "Country6": 250}, + } + + # country electrical loads in MW (not necessarily realistic) + loads = { + "Country1": 42000, + "Country2": 650, + "Country3": 250, + "Country4": 250, + "Country5": 250, + "Country6": 250, + "Country7": 250, + "Country8": 250, + "Country9": 250, + } + + network = pypsa.Network() + + network.add("carriers", "AC") + + countries = [] + for i in range(1, 10): + countries.extend(["Country" + str(i)]) + + for country in countries: + network.add("Bus", country) + + for country in countries: + for tech in power_plant_p_nom[country]: + network.add( + "Generator", + f"{country} {tech}", + bus=country, + p_nom=power_plant_p_nom[country][tech], + marginal_cost=marginal_costs[tech], + ) + network.add("Load", f"{country} load", bus=country, p_set=loads[country]) + # + # add transmission as controllable Link + if country not in transmission: + continue + for other_country in countries: + if other_country not in transmission[country]: + continue + # + # NB: Link is by default unidirectional, so have to set p_min_pu = -1 + # to allow bidirectional (i.e. also negative) flow + network.add( + "Link", + f"{country} - {other_country} link", + bus0=country, + bus1=other_country, + p_nom=transmission[country][other_country], + p_min_pu=-1, + ) + + expected_retCode = pd.Index([], name="Bus-meshed") + retCode = get_strongly_meshed_buses(network) + + assert len(retCode) == len(expected_retCode) + + expected_retCode = pd.Index(["Country1"], name="Bus-meshed") + retCode = get_strongly_meshed_buses(network, threshold=10) + + assert len(retCode) == len(expected_retCode) diff --git a/PyPSA/source/test/test_network.py b/PyPSA/source/test/test_network.py new file mode 100644 index 0000000000000000000000000000000000000000..0394908078b20309146468bcffd9fc90a1f95321 --- /dev/null +++ b/PyPSA/source/test/test_network.py @@ -0,0 +1,682 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import copy +import sys +import warnings + +import linopy +import numpy as np +import pandas as pd +import pytest + +import pypsa + +rng = np.random.default_rng() + + +def swap_df_index(df, axis=0): + df_swapped = df.copy() + + if axis == 0: + # Swap rows: swap row 0 with row 1 + df_swapped.iloc[[0, 1]] = df_swapped.iloc[[1, 0]].values + elif axis == 1: + # Swap columns: swap column 0 with column 1 + df_swapped.iloc[:, [0, 1]] = df_swapped.iloc[:, [1, 0]].values + else: + raise ValueError( + f"Invalid axis {axis}. Allowed values are 0 (rows) and 1 (columns)." + ) + + return df_swapped + + +@pytest.fixture +def n_5bus(): + # Set up empty network with 5 buses. + n = pypsa.Network() + n.add("Bus", [f"bus_{i} " for i in range(5)]) + return n + + +@pytest.fixture +def n_5bus_7sn(): + # Set up empty network with 5 buses and 7 snapshots. + n = pypsa.Network() + n_buses = 5 + n_snapshots = 7 + n.add("Bus", [f"bus_{i} " for i in range(n_buses)]) + n.set_snapshots(range(n_snapshots)) + return n + + +def test_remove(ac_dc_network): + """ + GIVEN the AC DC exemplary pypsa network. + + WHEN two components of Generator are removed with remove + + THEN the generator dataframe and the time-dependent generator + dataframe should not contain the removed elements. + """ + n = ac_dc_network + + generators = {"Manchester Wind", "Frankfurt Wind"} + + n.remove("Generator", generators) + + assert not generators.issubset(n.c.generators.static.index) + assert not generators.issubset(n.c.generators.dynamic.p_max_pu.columns) + + +def test_remove_misspelled_component(ac_dc_network): + """ + GIVEN the AC DC exemplary pypsa network. + + WHEN a misspelled component is removed with remove + + THEN the function should not change anything in the Line + component dataframe and an error should be logged. + """ + n = ac_dc_network + misspelled_component = "Liness" + with pytest.raises(AttributeError, match=f"components '{misspelled_component}'"): + n.remove(misspelled_component, ["0", "1"]) + + +def test_add_misspelled_component(n_5bus): + """ + GIVEN an empty PyPSA network with 5 buses. + + WHEN multiple components of a misspelled component are added + + THEN the function should not change anything and an error should + be logged. + """ + misspelled_component = "Generatro" + with pytest.raises(AttributeError, match=f"components '{misspelled_component}'"): + n_5bus.add( + misspelled_component, + ["g_1", "g_2"], + bus=["bus_1", "bus_2"], + ) + + +def test_add_duplicated_names(n_5bus): + """ + GIVEN an empty PyPSA network with 5 buses. + + WHEN adding generators with the same name + + THEN the function should fail and an error should be logged. + """ + with pytest.raises(ValueError, match="must be unique"): + n_5bus.add( + "Generator", + ["g_1", "g_1"], + bus=["bus_1", "bus_2"], + ) + + +@pytest.mark.parametrize("slicer", [0, slice(0, 1), slice(None, None)]) +def test_add_static(n_5bus, slicer): + buses = n_5bus.c.buses.static.index[slicer] + + load_names = "load_" + buses + + n_5bus.add("Load", load_names, bus=buses, p_set=3) + + if slicer == 0: + load_names = pd.Index([load_names]) + + assert len(n_5bus.c.loads.static) == len(load_names) + assert n_5bus.c.loads.static.index.name == "name" + assert n_5bus.c.loads.static.index.equals(load_names) + assert (n_5bus.c.loads.static.bus == buses).all() + assert (n_5bus.c.loads.static.p_set == 3).all() + + if slicer == slice(None, None): + # Test different names shape + with pytest.raises(ValueError, match="for each component name."): + n_5bus.add("Load", load_names[1:] + "_a", bus=buses) + + +@pytest.mark.parametrize("slicer", [slice(0, 1), slice(None, None)]) +def test_add_static_with_index(n_5bus, slicer): + buses = n_5bus.c.buses.static.index[slicer] + + load_names = "load_" + buses + buses = pd.Series(buses, index=load_names) + + n_5bus.add( + "Load", + load_names, + bus=buses, + p_set=3, + ) + + assert len(n_5bus.c.loads.static) == len(load_names) + assert n_5bus.c.loads.static.index.name == "name" + assert n_5bus.c.loads.static.index.equals(load_names) + assert (n_5bus.c.loads.static.bus == buses).all() + assert (n_5bus.c.loads.static.p_set == 3).all() + + if len(buses) > 1: + # Test unaligned names index + with pytest.raises(ValueError, match="index which does not align"): + n_5bus.add("Load", load_names + "_a", bus=swap_df_index(buses)) + + +def test_add_varying_single(n_5bus_7sn): + buses = n_5bus_7sn.c.buses.static.index + + # Add load component at every bus with time-dependent attribute p_set. + p_set = rng.random(size=(len(n_5bus_7sn.snapshots))) + n_5bus_7sn.add( + "Load", + "load_1", + bus=buses[0], + p_set=p_set, + ) + + assert len(n_5bus_7sn.c.loads.static) == 1 + assert n_5bus_7sn.c.loads.static.index.name == "name" + assert (n_5bus_7sn.c.loads.static.index == "load_1").all() + assert (n_5bus_7sn.c.loads.static.bus == buses[0]).all() + assert (p_set == n_5bus_7sn.c.loads.dynamic.p_set.T).all().all() + assert ( + n_5bus_7sn.c.loads.static.p_set == 0 + ).all() # Assert that default value is set + + # Test different snapshots shape + with pytest.raises(ValueError, match="for each snapshot"): + n_5bus_7sn.add( + "Load", + "load_1_a", + p_set=p_set[1:], + ) + + +@pytest.mark.parametrize("slicer", [slice(0, 1), slice(None, None)]) +def test_add_varying_multiple(n_5bus_7sn, slicer): + buses = n_5bus_7sn.c.buses.static.index[slicer] + + # Add load component at every bus with time-dependent attribute p_set. + load_names = "load_" + buses + p_set = rng.random(size=(len(n_5bus_7sn.snapshots), len(buses))) + n_5bus_7sn.add( + "Load", + load_names, + bus=buses, + p_set=p_set, + ) + + assert len(n_5bus_7sn.c.loads.static) == len(load_names) + assert n_5bus_7sn.c.loads.static.index.name == "name" + assert n_5bus_7sn.c.loads.static.index.equals(load_names) + assert (n_5bus_7sn.c.loads.static.bus == buses).all() + assert (n_5bus_7sn.c.loads.dynamic.p_set == p_set).all().all() + assert ( + n_5bus_7sn.c.loads.static.p_set == 0 + ).all() # Assert that default value is set + + if len(buses) > 1: + # Test different names shape + with pytest.raises(ValueError, match="but expected"): + n_5bus_7sn.add("Load", load_names[1:] + "_a", p_set=p_set) + + # Test different snapshots shape + with pytest.raises(ValueError, match="but expected"): + n_5bus_7sn.add("Load", load_names + "_c", p_set=p_set[1:]) + + +def test_add_varying_multiple_with_index(n_5bus_7sn): + buses = n_5bus_7sn.c.buses.static.index + + # Add load component at every bus with time-dependent attribute p_set. + load_names = "load_" + buses + p_set = pd.DataFrame( + rng.random(size=(len(n_5bus_7sn.snapshots), len(buses))), + index=n_5bus_7sn.snapshots, + columns=load_names, + ) + + n_5bus_7sn.add( + "Load", + load_names, + bus=buses, + p_set=p_set, + ) + + assert len(n_5bus_7sn.c.loads.static) == len(load_names) + assert n_5bus_7sn.c.loads.static.index.name == "name" + assert n_5bus_7sn.c.loads.static.index.equals(load_names) + assert (n_5bus_7sn.c.loads.static.bus == buses).all() + assert (n_5bus_7sn.c.loads.dynamic.p_set == p_set).all().all() + assert ( + n_5bus_7sn.c.loads.static.p_set == 0 + ).all() # Assert that default value is set + + # Test different names shape + with pytest.raises(ValueError, match="index which does not align"): + n_5bus_7sn.add("Load", load_names[1:] + "_a", p_set=p_set) + + # Test unaligned names index + with pytest.raises(ValueError, match="index which does not align"): + n_5bus_7sn.add("Load", load_names + "_b", p_set=swap_df_index(p_set)) + + # Test different snapshots shape + with pytest.raises(ValueError, match="index which does not align"): + n_5bus_7sn.add("Load", load_names + "_c", p_set=p_set[1:]) + + # Test different snapshots index + with pytest.raises(ValueError, match="index which does not align"): + n_5bus_7sn.add("Load", load_names + "_d", p_set=swap_df_index(p_set, axis=1)) + + +def test_add_overwrite_static(n_5bus, caplog): + n_5bus.add("Bus", [f"bus_{i} " for i in range(6)], x=1) + + assert (n_5bus.c.buses.static.iloc[:5].x == 0).all() + assert (n_5bus.c.buses.static.iloc[5].x == 1).all() + assert caplog.records[-1].levelname == "WARNING" + + n_5bus.add("Bus", [f"bus_{i} " for i in range(5)], x=1, overwrite=True) + assert (n_5bus.c.buses.static.x == 1).all() + + +def test_add_overwrite_varying(n_5bus_7sn, caplog): + bus_names = [f"bus_{i} " for i in range(6)] + + n_5bus_7sn.add("Bus", bus_names, p=[1] * 6) + assert (n_5bus_7sn.c.buses.dynamic.p.iloc[:, :5] == 0).all().all() + assert (n_5bus_7sn.c.buses.dynamic.p.iloc[:, 5] == 1).all().all() + assert caplog.records[-1].levelname == "WARNING" + + n_5bus_7sn.add("Bus", bus_names[:5], p=[2] * 5, overwrite=True) + assert (n_5bus_7sn.c.buses.dynamic.p.loc[:, bus_names[:5]] == 2).all().all() + assert (n_5bus_7sn.c.buses.dynamic.p.loc[:, bus_names[5]] == 1).all().all() + + p = rng.random(size=(7, 5)) + n_5bus_7sn.add("Bus", bus_names[:5], p=p, overwrite=False) + assert (n_5bus_7sn.c.buses.dynamic.p.loc[:, bus_names[:5]] == 2).all().all() + n_5bus_7sn.add("Bus", bus_names[:5], p=p, overwrite=True) + assert (n_5bus_7sn.c.buses.dynamic.p.loc[:, bus_names[:5]] == p).all().all() + + +def test_add_stochastic(): + n = pypsa.Network() + n.add("Bus", "bus_1", v_mag_pu_set=0.1) + n.add("Bus", "bus_2", v_mag_pu_set=0.1) + + multi_indexed = pd.MultiIndex.from_product( + [["bus_3", "bus_4"], ["scenario_1", "scenario_2"]] + ) + + with pytest.raises(TypeError, match="Component names must be a one-dimensional."): + n.add("Bus", multi_indexed, v_mag_pu_set=0.1) + + n.set_scenarios(["scenario_1", "scenario_2"]) + + with pytest.raises( + TypeError, + match=( + "Component names must be a one-dimensional. For stochastic networks, they " + "will be casted to all dimensions and data per scenario can be changed after adding them." + ), + ): + n.add("Bus", multi_indexed, v_mag_pu_set=0.1) + + +def test_multiple_add_defaults(n_5bus): + """ + GIVEN an empty PyPSA network with 5 buses. + + WHEN adding multiple components of Generator and Load with add + + THEN the defaults should be set correctly according to + n.default_component_attrs. + """ + gen_names = ["g_1", "g_2"] + n_5bus.add( + "Generator", + gen_names, + bus=["bus_1", "bus_2"], + ) + + line_names = ["l_1", "l_2"] + n_5bus.add( + "Load", + line_names, + bus=["bus_1", "bus_2"], + ) + + # TODO: Improve tests since component is the same now + assert ( + n_5bus.c.generators.static.loc[gen_names[0], "control"] + == n_5bus.components.Generator.defaults.loc["control", "default"] + ) + + assert ( + n_5bus.c.loads.static.loc[line_names[0], "p_set"] + == n_5bus.components.Load.defaults.loc["p_set", "default"] + ) + + +def test_add_return_names(): + """Test that return_names parameter controls return behavior.""" + n = pypsa.Network() + + # Default behavior - should return None + assert n.add("Bus", "bus1") is None + assert n.add("Bus", "bus2", return_names=False) is None + + # With return_names=True - should return Index + result = n.add("Bus", "bus3", return_names=True) + assert isinstance(result, pd.Index) + assert result[0] == "bus3" + + # Multiple components + result = n.add("Bus", ["bus4", "bus5"], return_names=True) + assert len(result) == 2 + assert all(name in result for name in ["bus4", "bus5"]) + + # Component method + assert n.components.buses.add("bus6") is None + result = n.components.buses.add("bus7", return_names=True) + assert isinstance(result, pd.Index) + assert result[0] == "bus7" + + +@pytest.mark.skipif( + sys.platform == "win32", + reason="pd.equals fails on windows (https://stackoverflow.com/questions/62128721).", +) +def test_equality_behavior(networks): + """ + GIVEN the AC DC exemplary pypsa network. + + WHEN comparing the network to itself + + THEN the networks should be equal. + """ + n = networks + deep_copy = copy.deepcopy(n) + assert n is not deep_copy + assert n.equals(deep_copy, log_mode="strict") + + assert n == deep_copy + + # TODO: Could add more property based tests here (hypothesis) + deep_copy.name = "new_name" + assert n != deep_copy + + assert n != "other_type" + + +@pytest.mark.skipif( + sys.platform == "win32", + reason="pd.equals fails on windows (https://stackoverflow.com/questions/62128721).", +) +def test_copy_default_behavior(networks): + """ + GIVEN the AC DC exemplary pypsa network. + + WHEN copying the network with timestamps + + THEN the copied network should have the same generators, loads + and timestamps. + """ + n = networks + network_copy = n.copy() + assert n == network_copy + assert n is not network_copy + + +def test_copy_with_model(ac_dc_network): + n = ac_dc_network + n.optimize.create_model() + n_copy = n.copy() + + assert n.equals(n_copy, log_mode="strict") + assert isinstance(n.model, linopy.Model) + assert isinstance(n_copy.model, linopy.Model) + + n.optimize.solve_model() + with pytest.raises( + ValueError, + match="Copying a solved network with an attached solver model is not supported.", + ): + n_copy = n.copy() + + n.optimize() + with pytest.raises( + ValueError, + match="Copying a solved network with an attached solver model is not supported.", + ): + n_copy = n.copy() + + +@pytest.mark.skipif( + sys.platform == "win32", + reason="pd.equals fails on windows (https://stackoverflow.com/questions/62128721).", +) +def test_copy_snapshots(networks): + """ + GIVEN the AC DC exemplary pypsa network. + + WHEN copying the network without snapshots + + THEN the copied network should only have the current time index. + """ + n = networks + + if n.has_scenarios: + with pytest.raises( + NotImplementedError, + match="Copying a stochastic network with a selection is currently not supported.", + ): + n.copy(snapshots=n.snapshots[:5]) + + return + + copied_n = n.copy(snapshots=[]) + assert copied_n.snapshots.size == 1 + + copied_n = n.copy(snapshots=n.snapshots[:5]) + n.set_snapshots(n.snapshots[:5]) + assert copied_n == n + + +def test_single_add_network_static(ac_dc_network, n_5bus): + """ + GIVEN the AC DC exemplary pypsa network and an empty PyPSA network with 5 + buses. + + WHEN the second network is added to the first + + THEN the first network should now contain its original buses and + also the buses in the second network + """ + n = ac_dc_network.merge(n_5bus, with_time=False) + new_buses = set(n.c.buses.static.index) + assert new_buses.issuperset(n_5bus.c.buses.static.index) + + +def test_single_add_network_with_time(ac_dc_network, n_5bus): + """ + GIVEN the AC DC exemplary pypsa network and an empty PyPSA network with 5 + buses and the same snapshots. + + WHEN the second network is added to the first + + THEN the first network should now contain its original buses and + also the buses in the second network + """ + with pytest.raises(ValueError): + ac_dc_network.merge(n_5bus, with_time=True) + + n_5bus.set_snapshots(ac_dc_network.snapshots) + n = ac_dc_network.merge(n_5bus, with_time=True) + new_buses = set(n.c.buses.static.index) + assert new_buses.issuperset(n_5bus.c.buses.static.index) + + +def test_shape_reprojection(ac_dc_shapes): + n = ac_dc_shapes + + with pytest.warns(UserWarning): # noqa + area_before = n.c.shapes.static.geometry.area.sum() + x, y = n.c.buses.static.x.values, n.c.buses.static.y.values + + n.to_crs("epsg:3035") + + assert n.c.shapes.static.crs == "epsg:3035" + assert n.crs == "epsg:3035" + assert area_before != n.c.shapes.static.geometry.area.sum() + assert not np.allclose(x, n.c.buses.static.x.values) + assert not np.allclose(y, n.c.buses.static.y.values) + + +def test_components_referencing(ac_dc_network): + with pypsa.option_context("api.new_components_api", False): + ac_dc_network = pypsa.examples.ac_dc_meshed() + assert id(ac_dc_network.buses) == id(ac_dc_network.components.buses.static) + assert id(ac_dc_network.c.buses.dynamic) == id( + ac_dc_network.components.buses.dynamic + ) + assert id(ac_dc_network.components.buses) == id(ac_dc_network.components.Bus) + + +@pytest.mark.parametrize("use_component", [True, False]) +def test_rename_component_names(use_component): + n = pypsa.Network() + n.snapshots = [0, 1] + + n.add("Bus", "bus1", v_mag_pu_set=[0.1, 0.2]) + n.add("Bus", "bus2", v_mag_pu_set=[0.1, 0.2]) + n.add("Line", "line1", bus0="bus1", bus1="bus2", s_max_pu=[0.1, 0.2]) + n.add("Generator", "gen1", bus="bus1", p_min_pu=[0.1, 0.2]) + + if use_component: + c = n.c.buses + c.rename_component_names(bus1="bus3") + else: + n.rename_component_names("Bus", bus1="bus3") + + with pytest.raises(ValueError): + n.rename_component_names("Bus", bus1=10) + + assert "bus1" not in n.c.buses.static.index + assert "bus1" not in n.c.buses.dynamic.v_mag_pu_set.columns + assert "bus2" in n.c.buses.static.index + assert "bus2" in n.c.buses.dynamic.v_mag_pu_set.columns + assert "bus3" in n.c.buses.static.index + assert "bus3" in n.c.buses.dynamic.v_mag_pu_set.columns + + assert "bus1" not in n.c.lines.static.bus0.to_list() + assert "bus1" not in n.c.lines.static.bus1.to_list() + assert "bus3" in n.c.lines.static.bus0.to_list() + assert "bus2" in n.c.lines.static.bus1.to_list() + + assert "bus1" not in n.c.generators.static.bus.to_list() + assert "bus3" in n.c.generators.static.bus.to_list() + + +def test_components_repr(ac_dc_network): + n = ac_dc_network + + assert repr(n).startswith("PyPSA Network 'AC-DC-Meshed'") + assert len(repr(n)) > len(str(n)) + + n = pypsa.Network() + assert repr(n).startswith("Empty PyPSA Network 'Unnamed Network'") + assert len(repr(n)) > len(str(n)) + + +@pytest.mark.parametrize("new_components_api", [True, False]) +def test_api_components_legacy(new_components_api): + """ + Test the API of the components module. + """ + with pypsa.option_context("api.new_components_api", new_components_api): + n = pypsa.examples.ac_dc_meshed() + + if not new_components_api: + assert n.buses is n.components.buses.static + assert n.c.buses.dynamic is n.components.buses.dynamic + assert n.lines is n.components.lines.static + assert n.c.lines.dynamic is n.components.lines.dynamic + assert n.generators is n.components.generators.static + assert n.c.generators.dynamic is n.components.generators.dynamic + else: + assert n.buses is n.components.buses + with pytest.warns( + DeprecationWarning, match=r"Use `n\.buses\.dynamic` as a drop-in" + ): + assert n.buses_t is n.components.buses.dynamic + assert n.lines is n.components.lines + with pytest.warns( + DeprecationWarning, match=r"Use `n\.lines\.dynamic` as a drop-in" + ): + assert n.lines_t is n.components.lines.dynamic + assert n.generators is n.components.generators + with pytest.warns( + DeprecationWarning, match=r"Use `n\.generators\.dynamic` as a drop-in" + ): + assert n.generators_t is n.components.generators.dynamic + + +@pytest.mark.parametrize("new_components_api", [True, False]) +def test_api_new_components_api(component_name, new_components_api): + """ + Test the API of the components module. + """ + warnings.filterwarnings( + "ignore", + message=".*is deprecated as of 1.0 and will be .*", + category=DeprecationWarning, + ) + with pypsa.option_context("api.new_components_api", new_components_api): + n = pypsa.examples.ac_dc_meshed() + if not new_components_api: + with pytest.warns( + DeprecationWarning, + match="Use `self.components..static` instead.", + ): + assert n.static(component_name) is n.c[component_name].static + with pytest.warns( + DeprecationWarning, + match="Use `self.components..dynamic` instead.", + ): + assert n.dynamic(component_name) is n.c[component_name].dynamic + + setattr(n, component_name, "test") + with pytest.warns( + DeprecationWarning, + match="Use `self.components..static` instead.", + ): + assert n.static(component_name) == "test" + setattr(n, f"{component_name}_t", "test") + with pytest.warns( + DeprecationWarning, + match="Use `self.components..dynamic` instead.", + ): + assert n.dynamic(component_name) == "test" + else: + with pytest.warns( + DeprecationWarning, + match="Use `self.components..static` instead.", + ): + assert n.static(component_name) is n.c[component_name].static + with pytest.warns( + DeprecationWarning, + match="Use `self.components..dynamic` instead.", + ): + assert n.dynamic(component_name) is n.c[component_name].dynamic + with pytest.raises(AttributeError): + setattr(n, component_name, "test") + with pytest.warns(DeprecationWarning, match="cannot be set"): + setattr(n, f"{component_name}_t", "test") diff --git a/PyPSA/source/test/test_network_cycles.py b/PyPSA/source/test/test_network_cycles.py new file mode 100644 index 0000000000000000000000000000000000000000..9668550931636f0e41f1b0a6e101b88ad6d23c63 --- /dev/null +++ b/PyPSA/source/test/test_network_cycles.py @@ -0,0 +1,360 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import numpy as np + +import pypsa + + +def test_simple_cycle() -> None: + """Test the cycles function in a simple network with a known cycle.""" + # Create a test network with a cycle + n = pypsa.Network() + + # Add buses + for i in range(3): + n.add("Bus", f"bus{i}", v_nom=220) + + # Add a cycle of lines + n.add("Line", "line0-1", bus0="bus0", bus1="bus1", x=0.1, s_nom=100) + n.add("Line", "line1-2", bus0="bus1", bus1="bus2", x=0.1, s_nom=100) + n.add("Line", "line2-0", bus0="bus2", bus1="bus0", x=0.1, s_nom=100) + + # Get the cycles + cycles_df = n.cycle_matrix() + + # A simple network with one cycle should have one column in the cycles matrix + assert cycles_df.shape[1] == 1, f"Expected 1 cycle, got {cycles_df.shape[1]}" + + # The values in the cycles matrix should be either 1, -1, or 0 + assert set(np.unique(cycles_df.values)).issubset({0, 1, -1}), ( + "Unexpected values in cycles matrix" + ) + + # All rows should have a non-zero value, as all lines are part of the cycle + assert (cycles_df.abs().sum(axis=1) > 0).all(), ( + "Some lines are not part of any cycle" + ) + + # For each cycle, we need to account for the carrier when checking the sum + column_sums = cycles_df.sum() + # Check that the values are consistent + assert isinstance(column_sums.iloc[0], int | float), "Column sum should be numeric" + + +def test_multiple_cycles() -> None: + """Test the cycles function in a network with multiple cycles.""" + n = pypsa.Network() + + # Create a network with two cycles sharing an edge + # 0 -- 1 -- 2 + # | | | + # 3 -- 4 -- 5 + + # Add buses + for i in range(6): + n.add("Bus", f"bus{i}", v_nom=220) + + # Add lines to form two cycles + # First cycle: 0-1-4-3-0 + n.add("Line", "line0-1", bus0="bus0", bus1="bus1", x=0.1, s_nom=100) + n.add("Line", "line1-4", bus0="bus1", bus1="bus4", x=0.1, s_nom=100) + n.add("Line", "line4-3", bus0="bus4", bus1="bus3", x=0.1, s_nom=100) + n.add("Line", "line3-0", bus0="bus3", bus1="bus0", x=0.1, s_nom=100) + + # Second cycle: 1-2-5-4-1 + n.add("Line", "line1-2", bus0="bus1", bus1="bus2", x=0.1, s_nom=100) + n.add("Line", "line2-5", bus0="bus2", bus1="bus5", x=0.1, s_nom=100) + n.add("Line", "line5-4", bus0="bus5", bus1="bus4", x=0.1, s_nom=100) + # line1-4 already added + + # Get the cycles + cycles_df = n.cycle_matrix() + + # Network should have 2 independent cycles + assert cycles_df.shape[1] == 2, f"Expected 2 cycles, got {cycles_df.shape[1]}" + + # Each edge should appear in at least one cycle + assert (cycles_df.abs().sum(axis=1) > 0).all(), ( + "Some lines are not part of any cycle" + ) + + # The shared edge (1-4) should appear in both cycles + line1_4_idx = ("Line", "line1-4") + if line1_4_idx in cycles_df.index: + assert (cycles_df.loc[line1_4_idx].abs() > 0).all(), ( + "Shared line should be part of both cycles" + ) + + # For each cycle, check the numeric value without requiring it to be zero + for col in cycles_df.columns: + cycle_sum = cycles_df[col].sum() + assert isinstance(cycle_sum, int | float), f"Column {col} sum should be numeric" + + +def test_no_cycles() -> None: + """Test the cycles function in a network with no cycles (tree structure).""" + n = pypsa.Network() + + # Add buses in a tree structure + for i in range(4): + n.add("Bus", f"bus{i}", v_nom=220) + + # Add lines in a tree (no cycles) + n.add("Line", "line0-1", bus0="bus0", bus1="bus1", x=0.1, s_nom=100) + n.add("Line", "line0-2", bus0="bus0", bus1="bus2", x=0.1, s_nom=100) + n.add("Line", "line0-3", bus0="bus0", bus1="bus3", x=0.1, s_nom=100) + + # Get the cycles + cycles_df = n.cycle_matrix() + + # Should be an empty DataFrame with no cycles + assert cycles_df.empty or cycles_df.shape[1] == 0, ( + "Network with tree structure should have no cycles" + ) + + +def test_multiple_subnetworks() -> None: + """Test the cycles function in a network with multiple subnetworks.""" + n = pypsa.Network() + + # First subnetwork with a cycle + for i in range(3): + n.add("Bus", f"sub1_bus{i}", v_nom=220) + + n.add("Line", "sub1_line0-1", bus0="sub1_bus0", bus1="sub1_bus1", x=0.1, s_nom=100) + n.add("Line", "sub1_line1-2", bus0="sub1_bus1", bus1="sub1_bus2", x=0.1, s_nom=100) + n.add("Line", "sub1_line2-0", bus0="sub1_bus2", bus1="sub1_bus0", x=0.1, s_nom=100) + + # Second subnetwork with a cycle + for i in range(3): + n.add("Bus", f"sub2_bus{i}", v_nom=220) + + n.add("Line", "sub2_line0-1", bus0="sub2_bus0", bus1="sub2_bus1", x=0.1, s_nom=100) + n.add("Line", "sub2_line1-2", bus0="sub2_bus1", bus1="sub2_bus2", x=0.1, s_nom=100) + n.add("Line", "sub2_line2-0", bus0="sub2_bus2", bus1="sub2_bus0", x=0.1, s_nom=100) + + # Get the cycles + cycles_df = n.cycle_matrix() + + # Should have 2 cycles (one from each subnetwork) + assert cycles_df.shape[1] == 2, f"Expected 2 cycles, got {cycles_df.shape[1]}" + + # Each subnetwork's lines should participate in exactly one cycle + sub1_lines = [("Line", f"sub1_line{i}-{(i + 1) % 3}") for i in range(3)] + sub2_lines = [("Line", f"sub2_line{i}-{(i + 1) % 3}") for i in range(3)] + + # Check that each line in subnetwork 1 participates in one cycle + for line in sub1_lines: + if line in cycles_df.index: + non_zero_count = (cycles_df.loc[line].abs() > 0).sum() + assert non_zero_count == 1, ( + f"Line {line} should participate in exactly 1 cycle" + ) + + # Check that each line in subnetwork 2 participates in one cycle + for line in sub2_lines: + if line in cycles_df.index: + non_zero_count = (cycles_df.loc[line].abs() > 0).sum() + assert non_zero_count == 1, ( + f"Line {line} should participate in exactly 1 cycle" + ) + + +def test_mixed_branch_types() -> None: + """Test the cycles function with different branch component types.""" + n = pypsa.Network() + + # Add buses + for i in range(3): + n.add("Bus", f"bus{i}", v_nom=220) + + # Add a cycle with a mix of lines and transformers + n.add("Line", "line0-1", bus0="bus0", bus1="bus1", x=0.1, s_nom=100) + n.add("Transformer", "transformer1-2", bus0="bus1", bus1="bus2", x=0.1, s_nom=100) + n.add("Line", "line2-0", bus0="bus2", bus1="bus0", x=0.1, s_nom=100) + + # Get the cycles + cycles_df = n.cycle_matrix() + + # Should have 1 cycle + assert cycles_df.shape[1] == 1, f"Expected 1 cycle, got {cycles_df.shape[1]}" + + # Both lines and transformer should participate in the cycle + components_in_cycles = { + idx[0] for idx in cycles_df.index if (cycles_df.loc[idx].abs() > 0).any() + } + assert "Line" in components_in_cycles, "Lines should be part of cycle" + assert "Transformer" in components_in_cycles, "Transformer should be part of cycle" + + +def test_investment_periods() -> None: + """Test the cycles function with investment periods.""" + n = pypsa.Network() + + # Set investment periods + n.set_investment_periods([2020, 2030, 2040]) + + # Add buses + for i in range(4): + n.add("Bus", f"bus{i}", v_nom=220) + + # Add a cycle with lines built in different periods + n.add( + "Line", + "line0-1", + bus0="bus0", + bus1="bus1", + x=0.1, + s_nom=100, + build_year=2020, + lifetime=40, + ) # Available in all periods + + n.add( + "Line", + "line1-2", + bus0="bus1", + bus1="bus2", + x=0.1, + s_nom=100, + build_year=2020, + lifetime=40, + ) # Available in all periods + + n.add( + "Line", + "line2-3", + bus0="bus2", + bus1="bus3", + x=0.1, + s_nom=100, + build_year=2030, + lifetime=20, + ) # Available in 2030 and 2040 + + n.add( + "Line", + "line3-0", + bus0="bus3", + bus1="bus0", + x=0.1, + s_nom=100, + build_year=2040, + lifetime=10, + ) # Available only in 2040 + + # Test for 2020: should have no complete cycles + cycles_2020 = n.cycle_matrix(investment_period=2020) + assert cycles_2020.empty or cycles_2020.shape[1] == 0, ( + "Should have no cycles in 2020" + ) + + # Test for 2030: should have no complete cycles + cycles_2030 = n.cycle_matrix(investment_period=2030) + assert cycles_2030.empty or cycles_2030.shape[1] == 0, ( + "Should have no cycles in 2030" + ) + + # Test for 2040: should have one complete cycle + cycles_2040 = n.cycle_matrix(investment_period=2040) + assert cycles_2040.shape[1] == 1, ( + f"Expected 1 cycle in 2040, got {cycles_2040.shape[1]}" + ) + + # Test without investment period: should include all branches + cycles_all = n.cycle_matrix() + assert cycles_all.shape[1] == 1, ( + f"Expected 1 cycle with all branches, got {cycles_all.shape[1]}" + ) + + +def test_weighted_cycles() -> None: + """Test the apply_weights parameter in the cycles function.""" + # Create a test network with a cycle + n = pypsa.Network() + + # Add buses + for i in range(3): + n.add("Bus", f"bus{i}", v_nom=220, carrier="AC") + + # Add a cycle of lines with different reactance values + n.add("Line", "line0-1", bus0="bus0", bus1="bus1", x=0.1, r=0.01, s_nom=100) + n.add("Line", "line1-2", bus0="bus1", bus1="bus2", x=0.2, r=0.02, s_nom=100) + n.add("Line", "line2-0", bus0="bus2", bus1="bus0", x=0.3, r=0.03, s_nom=100) + + # Calculate the network topology and dependent values + n.determine_network_topology() + n.calculate_dependent_values() + + # Get the unweighted cycles + cycles_unweighted = n.cycle_matrix(apply_weights=False) + + # Get the weighted cycles + cycles_weighted = n.cycle_matrix(apply_weights=True) + + # Both should have the same structure + assert cycles_unweighted.shape == cycles_weighted.shape + assert (np.sign(cycles_unweighted) == np.sign(cycles_weighted)).all().all() + + # But the weighted version should have weights applied + for line in ["line0-1", "line1-2", "line2-0"]: + line_idx = ("Line", line) + if not (cycles_weighted.loc[line_idx] == 0).all(): + # For AC networks, weight should be the per-unit effective reactance + x_value = n.c.lines.static.at[line, "x_pu_eff"] + # Find the column where this line has a non-zero value + col = cycles_weighted.columns[(cycles_weighted.loc[line_idx] != 0).values][ + 0 + ] + + # Unweighted value should be either 1 or -1 + unweighted_val = cycles_unweighted.loc[line_idx, col] + + # Weighted value should be x_value * unweighted_val + weighted_val = cycles_weighted.loc[line_idx, col] + assert np.isclose(weighted_val, x_value * unweighted_val) + + +def test_weighted_cycles_dc_network() -> None: + """Test the apply_weights parameter with DC network.""" + n = pypsa.Network() + + # Add buses with DC carrier + for i in range(3): + n.add("Bus", f"bus{i}", v_nom=400, carrier="DC") + + # Add DC links with resistance values + n.add("Line", "line0-1", bus0="bus0", bus1="bus1", x=0.0, r=0.01, s_nom=100) + n.add("Line", "line1-2", bus0="bus1", bus1="bus2", x=0.0, r=0.02, s_nom=100) + n.add("Line", "line2-0", bus0="bus2", bus1="bus0", x=0.0, r=0.03, s_nom=100) + + # Calculate the network topology + n.determine_network_topology() + n.calculate_dependent_values() + + # Get the unweighted and weighted cycles + cycles_unweighted = n.cycle_matrix(apply_weights=False) + cycles_weighted = n.cycle_matrix(apply_weights=True) + + # Both should have the same structure + assert cycles_unweighted.shape == cycles_weighted.shape + assert (np.sign(cycles_unweighted) == np.sign(cycles_weighted)).all().all() + + # For DC networks, weights should be resistance + for line in ["line0-1", "line1-2", "line2-0"]: + line_idx = ("Line", line) + if not (cycles_weighted.loc[line_idx] == 0).all(): + # Find the column where this line has a non-zero value + col = cycles_weighted.columns[(cycles_weighted.loc[line_idx] != 0).values][ + 0 + ] + + # Unweighted value should be either 1 or -1 + unweighted_val = cycles_unweighted.loc[line_idx, col] + + # For DC, weighted value should be r_value * unweighted_val + r_value = n.c.lines.static.at[line, "r_pu_eff"] + weighted_val = cycles_weighted.loc[line_idx, col] + assert np.isclose(weighted_val, r_value * unweighted_val) diff --git a/PyPSA/source/test/test_network_index.py b/PyPSA/source/test/test_network_index.py new file mode 100644 index 0000000000000000000000000000000000000000..de78126881b946363ed64eab2ce713d27d63e67a --- /dev/null +++ b/PyPSA/source/test/test_network_index.py @@ -0,0 +1,363 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pandas as pd +import pytest + +import pypsa + + +class TestNetworkScenarioIndex: + def test_empty_input(self, ac_dc_network): + """Test that an error is raised when no scenarios are provided.""" + n = ac_dc_network + with pytest.raises(ValueError, match="You must pass either"): + n.set_scenarios() + + def test_both_kwargs_and_scenarios(self, ac_dc_network): + """Test that an error is raised when both kwargs and scenarios are provided.""" + n = ac_dc_network + with pytest.raises(ValueError, match="You can pass scenarios either via"): + n.set_scenarios(scenarios=["a", "b"], weights=[1, 2], scenario1=1) + + def test_dict_with_weights(self, ac_dc_network): + """Test that an error is raised when a dict is provided with weights.""" + n = ac_dc_network + with pytest.raises(ValueError, match="You can pass scenarios either via"): + n.set_scenarios(scenarios={"a": 1, "b": 2}, weights=[1, 2]) + + def test_series_with_weights(self, ac_dc_network): + """Test that an error is raised when a Series is provided with weights.""" + n = ac_dc_network + with pytest.raises(ValueError, match="You can pass scenarios either via"): + n.set_scenarios(scenarios=pd.Series({"a": 1, "b": 2}), weights=[1, 2]) + + def test_mismatched_weights_length(self, ac_dc_network): + """Test that an error is raised when weights length doesn't match scenarios length.""" + n = ac_dc_network + with pytest.raises(ValueError, match="You can pass scenarios either via"): + n.set_scenarios(scenarios=["a", "b", "c"], weights=[1, 2]) + + def test_dict_scenarios(self, ac_dc_network): + """Test setting scenarios from a dict.""" + n = ac_dc_network + n.set_scenarios(scenarios={"scenario1": 0.3, "scenario2": 0.7}) + expected_index = pd.Index(["scenario1", "scenario2"], name="scenario") + expected_weights = pd.DataFrame({"weight": [0.3, 0.7]}, index=expected_index) + pd.testing.assert_index_equal(n.scenarios, expected_index) + pd.testing.assert_frame_equal(n.scenario_weightings, expected_weights) + + def test_series_scenarios(self, ac_dc_network): + """Test setting scenarios from a Series.""" + n = ac_dc_network + series = pd.Series({"scenario1": 0.3, "scenario2": 0.7}) + n.set_scenarios(scenarios=series) + expected_index = pd.Index(["scenario1", "scenario2"], name="scenario") + expected_weights = pd.DataFrame({"weight": [0.3, 0.7]}, index=expected_index) + pd.testing.assert_index_equal(n.scenarios, expected_index) + pd.testing.assert_frame_equal(n.scenario_weightings, expected_weights) + + def test_sequence_scenarios(self, ac_dc_network): + """Test setting scenarios from a sequence with weights.""" + n = ac_dc_network + n.set_scenarios(scenarios=["scenario1", "scenario2"]) + expected_index = pd.Index(["scenario1", "scenario2"], name="scenario") + expected_weights = pd.DataFrame({"weight": [0.5, 0.5]}, index=expected_index) + pd.testing.assert_index_equal(n.scenarios, expected_index) + pd.testing.assert_frame_equal(n.scenario_weightings, expected_weights) + + def test_kwargs_scenarios(self, ac_dc_network): + """Test setting scenarios from keyword arguments.""" + n = ac_dc_network + n.set_scenarios(scenario1=0.3, scenario2=0.7) + expected_index = pd.Index(["scenario1", "scenario2"], name="scenario") + expected_weights = pd.DataFrame({"weight": [0.3, 0.7]}, index=expected_index) + pd.testing.assert_index_equal(n.scenarios, expected_index) + pd.testing.assert_frame_equal(n.scenario_weightings, expected_weights) + + def test_series_name_preserved(self, ac_dc_network): + """Test that the scenario_weightings column name is set to 'weight'.""" + n = ac_dc_network + series = pd.Series({"scenario1": 0.3, "scenario2": 0.7}, name="original_name") + n.set_scenarios(scenarios=series) + assert n.scenario_weightings.columns[0] == "weight" + assert n.scenarios.name == "scenario" + + def test_sequence_without_weights(self, ac_dc_network): + """Test setting scenarios from a sequence without weights.""" + n = ac_dc_network + n.set_scenarios(scenarios=["scenario1", "scenario2", "scenario3"]) + + # When no weights are provided, equal weights (1/n) should be assigned + expected_index = pd.Index( + ["scenario1", "scenario2", "scenario3"], name="scenario" + ) + expected_weights = pd.DataFrame( + {"weight": [1 / 3, 1 / 3, 1 / 3]}, index=expected_index + ) + + pd.testing.assert_index_equal(n.scenarios, expected_index) + pd.testing.assert_frame_equal(n.scenario_weightings, expected_weights) + + def test_weights_must_sum_to_one(self, ac_dc_network): + """Test that an error is raised when scenario weights don't sum to 1.""" + n = ac_dc_network + # Create a series with weights that don't sum to 1 + scenarios = pd.Series({"scenario1": 0.3, "scenario2": 0.4}) # Sum = 0.7 + + with pytest.raises( + ValueError, match="The sum of the weights in `scenarios` must be equal to 1" + ): + n.set_scenarios(scenarios=scenarios) + + +def test_get_scenario(): + n = pypsa.examples.ac_dc_meshed() + n.set_scenarios(high=0.1, low=0.9) + n.c.generators.static.loc[("high", "Manchester Wind"), "p_nom"] = 200 + + n_high = n.get_scenario("high") + n_low = n.get_scenario("low") + + assert n_high.name == "AC-DC-Meshed - Scenario 'high'" + + n_high.name = n.name + n_low.name = n.name + + ac_dc_meshed = pypsa.examples.ac_dc_meshed() + assert n_low.equals(ac_dc_meshed, log_mode="strict") + assert n_low is not ac_dc_meshed + + ac_dc_meshed.c.generators.static.loc[("Manchester Wind"), "p_nom"] = 200 + assert n_high.equals(ac_dc_meshed, log_mode="strict") + assert n_high is not ac_dc_meshed + + # Test ValueError when network has no scenarios + n_no_scenarios = pypsa.examples.ac_dc_meshed() + with pytest.raises( + ValueError, + match="This method can only be used on a stochastic network with scenarios", + ): + n_no_scenarios.get_scenario("high") + + # Test KeyError when scenario doesn't exist + with pytest.raises( + KeyError, match="Scenario 'nonexistent' not found in network scenarios" + ): + n.get_scenario("nonexistent") + + +def test_get_scenario_empty_components_bug_1402(): + """ + Test that get_scenario() and __getitem__ properly reset MultiIndex for empty components. + + See https://github.com/PyPSA/PyPSA/issues/1402: + When extracting a scenario from a stochastic network, empty components (like Links) + should have their MultiIndex reset to a simple Index, not retain the scenario level. + This ensures the extracted network can be optimized without MultiIndex conflicts. + """ + n = pypsa.Network() + n.set_snapshots(range(3)) + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=100) + n.add( + "Generator", + "gen", + bus="bus", + p_nom_extendable=True, + capital_cost=1000, + marginal_cost=10, + ) + + n.set_scenarios({"low": 0.5, "high": 0.5}) + + # Verify Links component is empty but has MultiIndex after set_scenarios + assert n.c.links.static.empty + assert isinstance(n.c.links.static.index, pd.MultiIndex) + assert n.c.links.static.index.names == ["scenario", "name"] + + # Test get_scenario() method + n_low = n.get_scenario("low") + + # After extraction, Links should have simple Index, not MultiIndex + assert n_low.c.links.static.empty + assert not isinstance(n_low.c.links.static.index, pd.MultiIndex) + assert n_low.c.links.static.index.name == "name" + + # Verify network can be optimized without errors + status, _ = n_low.optimize(solver_name="highs", log_to_console=False) + assert status == "ok" + + # Test __getitem__ method (n['scenario']) + n_high = n["high"] + + # After extraction via __getitem__, Links should also have simple Index + assert n_high.c.links.static.empty + assert not isinstance(n_high.c.links.static.index, pd.MultiIndex) + assert n_high.c.links.static.index.name == "name" + + # Verify this network can also be optimized + status, _ = n_high.optimize(solver_name="highs", log_to_console=False) + assert status == "ok" + + +def test_get_network_from_collection(): + n = pypsa.examples.ac_dc_meshed() + n2 = n.copy() + n2.name = "AC-DC-Meshed Copy" + n2.c.generators.static.loc[("Manchester Wind"), "p_nom"] = 200 + + nc = pypsa.NetworkCollection([n, n2]) + + nc1 = nc.get_network("AC-DC-Meshed") + nc2 = nc.get_network("AC-DC-Meshed Copy") + + # Make sure a collection only holds references to the original networks + assert nc1 is n + assert nc2 is n2 + + # Test KeyError when collection doesn't exist + with pytest.raises( + KeyError, match="Collection 'nonexistent' not found in network collection" + ): + nc.get_network("nonexistent") + + # Stochastic network in collection + n2.set_scenarios(high=0.1, low=0.9) + n2.c.generators.static.loc[("high", "Manchester Wind"), "p_nom"] = 300 + nc = pypsa.NetworkCollection([n, n2]) + + nc2 = nc.get_network("AC-DC-Meshed Copy") + + assert nc2 is n2 + + with pytest.raises( + KeyError, match="Collection 'nonexistent' not found in network collection" + ): + nc.get_network("nonexistent") + + # Test ValueError when network is not a collection + n_no_collection = pypsa.examples.ac_dc_meshed() + with pytest.raises( + ValueError, + match="This method can only be used on a NetworkCollection", + ): + n_no_collection.get_network("high") + + +def test_slice_network(): + n = pypsa.examples.ac_dc_meshed() + + # Test slicing by buses - single bus + n_single = n.slice_network(buses="Manchester") + assert len(n_single.buses) == 1 + assert "Manchester" in n_single.c.buses.static.index + # Check connected components are included + assert len(n_single.c.generators) > 0 + assert all(n_single.c.generators.static.bus == "Manchester") + + # Test slicing by buses - list of buses + bus_list = ["Manchester", "Frankfurt"] + n_subset = n.slice_network(buses=bus_list) + assert len(n_subset.buses) == 2 + assert set(n_subset.c.buses.static.index) == set(bus_list) + # Check lines between selected buses + lines_between = n.c.lines.static[ + n.c.lines.static.bus0.isin(bus_list) & n.c.lines.static.bus1.isin(bus_list) + ] + assert len(n_subset.c.lines.static) == len(lines_between) + + # Test slicing by snapshots using slice object + n_snap = n.slice_network(snapshots=slice(None, 2)) + assert len(n_snap.snapshots) == 2 + assert all(n_snap.snapshots == n.snapshots[:2]) + assert len(n_snap.buses) == len(n.buses) # All buses preserved + + # Test slicing by both buses and snapshots + n_both = n.slice_network(buses="Manchester", snapshots=0) + assert len(n_both.buses) == 1 + assert len(n_both.snapshots) == 1 + assert n_both.snapshots[0] == n.snapshots[0] + + # Test with boolean mask for buses + bus_mask = n.c.buses.static.v_nom > 300 + n_hv = n.slice_network(buses=bus_mask) + assert all(n_hv.c.buses.static.v_nom > 300) + + # Test error when neither buses nor snapshots provided + with pytest.raises( + ValueError, match="Either `buses` or `snapshots` must be provided" + ): + n.slice_network() + + # Test that dynamic data is properly sliced + n.generators_t.p_set.loc[:, "Manchester Wind"] = range(len(n.snapshots)) + n_snap2 = n.slice_network(snapshots=slice(1, 3)) + assert all(n_snap2.generators_t.p_set.loc[:, "Manchester Wind"] == [1, 2]) + + # Test that network attributes are preserved + n.name = "Test Network" + n_sliced = n.slice_network(buses="Manchester") + assert n_sliced.name == "Test Network" + + # Test snapshot weightings are properly sliced with list of indices + n_snap3 = n.slice_network(snapshots=[0, 2]) + expected_weights = n.snapshot_weightings.iloc[[0, 2]] + pd.testing.assert_frame_equal(n_snap3.snapshot_weightings, expected_weights) + + +def test_getitem_index_methods(): + """Test that __getitem__ mirrors all three index methods.""" + # Test network slicing via __getitem__ + n = pypsa.examples.ac_dc_meshed() + + # Test slicing by single bus (mirrors slice_network) + n_manchester = n["Manchester"] + n_manchester_slice = n.slice_network(buses="Manchester") + assert n_manchester.equals(n_manchester_slice, log_mode="strict") + + # Test slicing by multiple buses + bus_list = ["Manchester", "Frankfurt"] + n_multi = n[bus_list] + n_multi_slice = n.slice_network(buses=bus_list) + assert n_multi.equals(n_multi_slice, log_mode="strict") + + # Slicing by snapshots is currently not supported in __getitem__ + # as it would clash with the other two methods. + + # Test NetworkCollection __getitem__ (mirrors get_network) + n2 = n.copy() + n2.name = "Test Network Copy" + nc = pypsa.NetworkCollection([n, n2]) + + nc_getitem = nc["AC-DC-Meshed"] + nc_get = nc.get_network("AC-DC-Meshed") + assert nc_getitem is nc_get # Should return same reference + + # Test stochastic network __getitem__ (mirrors get_scenario) + n_stoch = pypsa.examples.ac_dc_meshed() + n_stoch.set_scenarios(high=0.3, low=0.7) + n_stoch.c.generators.static.loc[("high", "Manchester Wind"), "p_nom"] = 200 + + n_high_getitem = n_stoch["high"] + n_high_get = n_stoch.get_scenario("high") + assert n_high_getitem.equals(n_high_get, log_mode="strict") + + # Test error cases + with pytest.raises(KeyError): + nc["nonexistent"] + + with pytest.raises(KeyError): + n_stoch["nonexistent"] + + # Test that regular network raises KeyError for non-existent bus + with pytest.raises(KeyError): + n["nonexistent_bus"] + + # Test deprecated tuple slicing raises warning and error + with pytest.warns( + DeprecationWarning, match="Slicing by \\(buses, snapshots\\) tuples" + ): + with pytest.raises(NotImplementedError, match="Tuple slicing is deprecated"): + n[("Manchester", slice(0, 2))] diff --git a/PyPSA/source/test/test_network_transform.py b/PyPSA/source/test/test_network_transform.py new file mode 100644 index 0000000000000000000000000000000000000000..df0ffad1e54c762f0ed21cb0e7da66b1cbf49a83 --- /dev/null +++ b/PyPSA/source/test/test_network_transform.py @@ -0,0 +1,196 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pytest + +import pypsa +from pypsa._options import option_context +from pypsa.components.types import all_standard_attrs_set +from pypsa.network.transform import _get_potential_typos # Replace with actual import + + +class TestGetPotentialTypos: + def test_basic_functionality_and_edge_cases(self): + """Test basic functionality, empty inputs, and exact matches.""" + # Empty inputs + assert _get_potential_typos([], all_standard_attrs_set) == set() + assert _get_potential_typos(["test"], set()) == set() + assert _get_potential_typos([], set()) == set() + + # Exact matches are not typos + custom_attrs = ["p_nom", "s_nom", "bus"] + assert _get_potential_typos(custom_attrs, all_standard_attrs_set) == set() + + # Return type validation + result = _get_potential_typos(["p_nok"], all_standard_attrs_set) + assert isinstance(result, set) + for item in result: + assert isinstance(item, tuple) + assert len(item) == 2 + + @pytest.mark.parametrize( + ("string_a", "string_b", "should_match"), + [ + # Valid typos (distance = 1) + ("p_nok", "p_nom", True), # substitution + ("p_nomm", "p_nom", True), # insertion + ("p_no", "p_nom", True), # deletion + ("p_Nom", "p_nom", True), # case difference + ("activ", "active", True), # deletion + ("buss", "bus", True), # insertion + ("p-nom", "p_nom", True), # special character + ("p_nom", "p_nom_", True), # trailing underscore + ("_p_nom", "p_nom", True), # leading underscore + ( + "pp_nom", + "p_nom", + True, + ), # typo with different amount of characters before _ + # Should not raise typos + ("p_nom", "s_nom", False), # different base attributes + ("p_nom", "p_nom", False), # exact match + ("bus0", "bus1", False), # numeric suffix + ("bus", "bus1", False), # numeric suffix + ("bus1", "bus10", False), # numeric suffix + ("bus", "bus10", False), # numeric suffix + ("hello_dear", "hello", False), # distance > 1 + ("a", "ab", False), # single char + ("ab", "a", False), # single char + ("a", "b", False), # single char + ("typ", "type", False), # Special case + ], + ) + def test_single_matches(self, string_a, string_b, should_match): + """Test various typo detection patterns and exclusions.""" + result = _get_potential_typos([string_a], [string_b]) + + if should_match: + assert result == {(string_a, string_b)} + else: + assert result == set() + + def test_edge_cases_and_special_scenarios(self): + """Test edge cases, long attributes, and special characters.""" + # Very long attributes + long_custom = "very_long_attribute_name_that_might_cause_issues" + long_standard = "very_long_attribute_name_that_might_cause_issue" # missing 's' + result = _get_potential_typos([long_custom], {long_standard}) + assert (long_custom, long_standard) in result + + # Multiple typos of same standard attr + custom_attrs = ["p_nok", "p_nom_", "pnom"] # All potential typos of p_nom + result = _get_potential_typos(custom_attrs, {"p_nom"}) + assert len(result) >= 1 # Should find at least some matches + + # Underscore variations + underscore_tests = [ + ("p_nom_", "p_nom"), # trailing underscore + ("_p_nom", "p_nom"), # leading underscore + ] + for custom, standard in underscore_tests: + result = _get_potential_typos([custom], {standard}) + assert (custom, standard) in result + + # No false positives among standard attrs + standard_sample = list(all_standard_attrs_set)[:5] + result = _get_potential_typos(standard_sample, all_standard_attrs_set) + # This mainly ensures no crashes with real data + assert isinstance(result, set) + + def test_unintended_attribute_warning(self, caplog): + """Test warning for attributes that are standard for other components.""" + n = pypsa.Network() + n.add("Bus", "test_bus") + + # Add a generator attribute 'p_nom' to a bus - this should trigger the warning + # p_nom is a standard attribute for generators but not for buses + n.add("Bus", "bus_with_generator_attr", p_nom=100.0) + assert "is a standard attribute for other components" in caplog.text + assert "p_nom" in caplog.text + caplog.clear() + + # Add a bus attribute 'v_nom' to a generator - this should trigger the warning + # v_nom is a standard attribute for buses but not for generators + n.add("Generator", "gen_with_bus_attr", bus="test_bus", v_nom=380.0) + assert "is a standard attribute for other components" in caplog.text + assert "v_nom" in caplog.text + + def test_potential_typo_warning(self, caplog): + """Test warning for potential typos in attribute names.""" + n = pypsa.Network() + n.add("Bus", "test_bus") + + # Add a generator with a typo in p_nom (p_nok) + n.add("Generator", "gen_with_typo", bus="test_bus", p_nok=100.0) + assert "is not a standard attribute for" in caplog.text + assert "p_nok" in caplog.text + assert "p_nom" in caplog.text + caplog.clear() + + # Add a bus with a typo in v_nom (v_nok) + n.add("Bus", "bus_with_typo", v_nok=380.0) + assert "is not a standard attribute for" in caplog.text + assert "v_nok" in caplog.text + assert "v_nom" in caplog.text + caplog.clear() + + # Add a generator with trailing underscore in p_nom (p_nom_) + n.add("Generator", "gen_with_underscore", bus="test_bus", p_nom_=100.0) + assert "is not a standard attribute for" in caplog.text + assert "p_nom_" in caplog.text + assert "p_nom" in caplog.text + + def test_both_warnings_simultaneously(self, caplog): + """Test that both warnings can be triggered in the same add operation.""" + n = pypsa.Network() + n.add("Bus", "test_bus") + + # Add a generator with both unintended attribute and typo + n.add( + "Generator", + "gen_with_both_issues", + bus="test_bus", + v_nom=380.0, # Unintended attribute (bus attribute on generator) + p_nok=100.0, + ) # Typo (should be p_nom) + + # Check that both warnings were logged + assert "is a standard attribute for other components" in caplog.text + assert "is not a standard attribute for" in caplog.text + assert "v_nom" in caplog.text + assert "p_nok" in caplog.text + + def test_no_warning_for_valid_custom_attributes(self, caplog): + """Test that custom attributes that are not typos or unintended don't trigger warnings.""" + n = pypsa.Network() + n.add("Bus", "test_bus") + + # Add a generator with a clearly custom attribute that shouldn't trigger warnings + n.add( + "Generator", + "gen_with_custom_attr", + bus="test_bus", + my_custom_attribute="custom_value", + another_custom_123=42, + ) + + # Check that no attribute-related warnings were logged + assert "are default attributes for other components" not in caplog.text + assert "is likely a typo of standard attribute" not in caplog.text + + def test_typo_warning_option_disabled(self, caplog): + """Test that typo warnings can be disabled via option while unintended warnings remain.""" + n = pypsa.Network() + n.add("Bus", "test_bus") + + # Disable typo warnings via option + with option_context("warnings.attribute_typos", False): + # Add a generator with a typo - this should NOT trigger typo warning + n.add("Generator", "gen_with_typo", bus="test_bus", p_nok=100.0) + assert "is likely a typo of standard attribute" not in caplog.text + + # Add a generator with unintended attribute - this should still trigger warning + n.add("Generator", "gen_with_unintended", bus="test_bus", v_nom=380.0) + assert "is a standard attribute for other components" in caplog.text + assert "v_nom" in caplog.text diff --git a/PyPSA/source/test/test_optimization_expressions.py b/PyPSA/source/test/test_optimization_expressions.py new file mode 100644 index 0000000000000000000000000000000000000000..9f3d2eb906a00d0af6256205305899d5a93471d2 --- /dev/null +++ b/PyPSA/source/test/test_optimization_expressions.py @@ -0,0 +1,182 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pytest +from linopy import LinearExpression + +from pypsa.statistics import groupers + +TOLERANCE = 1e-2 + + +GROUPER_PARAMETERS = [ + groupers.carrier, + groupers["carrier"], + [groupers.bus, groupers.carrier], + ["name", "bus", "carrier"], + ["carrier", "bus_carrier"], + ["bus", "carrier", "bus_carrier"], + False, +] +KWARGS_PARAMETERS = [ + {"at_port": True}, + {"bus_carrier": "AC"}, + {"carrier": "AC"}, + {"nice_names": True}, + {"aggregate_across_components": True}, +] +AGGREGRATE_TIME_PARAMETERS = ["sum", "mean", None] + + +@pytest.fixture +def prepared_network(ac_dc_network): + n = ac_dc_network.copy() + n.optimize.create_model() + n.c.lines.static["carrier"] = n.c.lines.static.bus0.map(n.c.buses.static.carrier) + n.c.generators.static.loc[n.c.generators.static.index[0], "p_nom_extendable"] = ( + False + ) + return n + + +@pytest.fixture +def prepared_network_with_snapshot_subset(ac_dc_network): + n = ac_dc_network.copy() + n.optimize.create_model(snapshots=n.snapshots[:2]) + n.c.lines.static["carrier"] = n.c.lines.static.bus0.map(n.c.buses.static.carrier) + n.c.generators.static.loc[n.c.generators.static.index[0], "p_nom_extendable"] = ( + False + ) + return n + + +# Test one static function for each groupby option and other options +@pytest.mark.parametrize("groupby", GROUPER_PARAMETERS) +def test_expressions_capacity(prepared_network, groupby): + n = prepared_network + expr = n.optimize.expressions.capacity(groupby=groupby) + assert isinstance(expr, LinearExpression) + assert expr.size > 0 + + +@pytest.mark.parametrize("aggregate_across_components", [True, False]) +def test_expression_capacity_all_filtered( + prepared_network, aggregate_across_components +): + n = prepared_network + expr = n.optimize.expressions.capacity( + bus_carrier="non-existent", + aggregate_across_components=aggregate_across_components, + ) + assert isinstance(expr, LinearExpression) + assert expr.size == 0 + + +@pytest.mark.parametrize( + "kwargs", KWARGS_PARAMETERS + [{"include_non_extendable": True}] +) +def test_expressions_capacity_other_options(prepared_network, kwargs): + n = prepared_network + expr = n.optimize.expressions.capacity(**kwargs) + assert isinstance(expr, LinearExpression) + assert expr.size > 0 + + +def test_expressions_capex(prepared_network): + n = prepared_network + expr = n.optimize.expressions.capex() + assert isinstance(expr, LinearExpression) + assert expr.size > 0 + + +# Test one dynamic function for each groupby option and other options +@pytest.mark.parametrize("groupby_time", AGGREGRATE_TIME_PARAMETERS) +@pytest.mark.parametrize("groupby", GROUPER_PARAMETERS) +def test_expressions_energy_balance(prepared_network, groupby, groupby_time): + n = prepared_network + expr = n.optimize.expressions.energy_balance( + groupby=groupby, groupby_time=groupby_time + ) + assert isinstance(expr, LinearExpression) + assert expr.size > 0 + + +@pytest.mark.parametrize("groupby_time", AGGREGRATE_TIME_PARAMETERS) +@pytest.mark.parametrize("groupby", GROUPER_PARAMETERS) +def test_expressions_energy_balance_with_snapshot_subset( + prepared_network_with_snapshot_subset, groupby, groupby_time +): + n = prepared_network_with_snapshot_subset + expr = n.optimize.expressions.energy_balance( + groupby=groupby, groupby_time=groupby_time + ) + assert isinstance(expr, LinearExpression) + assert expr.size > 0 + + +def test_other_dynamic_expressions_with_snapshot_subset( + prepared_network_with_snapshot_subset, +): + n = prepared_network_with_snapshot_subset + for expr_str in [ + "opex", + "curtailment", + "operation", + "supply", + "withdrawal", + "transmission", + ]: + expr = getattr(n.optimize.expressions, expr_str)() + assert isinstance(expr, LinearExpression) + assert expr.size > 0 + + +@pytest.mark.parametrize("kwargs", KWARGS_PARAMETERS) +def test_expressions_energy_balance_other_options(prepared_network, kwargs): + n = prepared_network + expr = n.optimize.expressions.energy_balance(**kwargs) + assert isinstance(expr, LinearExpression) + assert expr.size > 0 + + +def test_expressions_supply(prepared_network): + n = prepared_network + expr = n.optimize.expressions.supply() + assert isinstance(expr, LinearExpression) + assert expr.size > 0 + + +def test_expressions_withdrawal(prepared_network): + n = prepared_network + expr = n.optimize.expressions.withdrawal() + assert isinstance(expr, LinearExpression) + assert expr.size > 0 + + +def test_expressions_transmission(prepared_network): + n = prepared_network + expr = n.optimize.expressions.transmission() + assert isinstance(expr, LinearExpression) + assert expr.size > 0 + + +def test_expressions_opex(prepared_network): + n = prepared_network + expr = n.optimize.expressions.opex() + assert isinstance(expr, LinearExpression) + assert expr.size > 0 + + +def test_expressions_curtailment(prepared_network): + n = prepared_network + expr = n.optimize.expressions.curtailment() + assert isinstance(expr, LinearExpression) + assert expr.size > 0 + + +def test_expressions_operation(prepared_network): + n = prepared_network + expr = n.optimize.expressions.operation() + assert isinstance(expr, LinearExpression) + assert expr.size > 0 diff --git a/PyPSA/source/test/test_optional_dependencies.py b/PyPSA/source/test/test_optional_dependencies.py new file mode 100644 index 0000000000000000000000000000000000000000..9860e7d01ef83aa53aeeb03fb39afd8af219ae94 --- /dev/null +++ b/PyPSA/source/test/test_optional_dependencies.py @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import builtins +import importlib.util +from unittest.mock import patch + +import pytest + +from pypsa.plot.maps.common import _is_cartopy_available + + +@pytest.fixture +def hide_pytables(monkeypatch): + import_orig = builtins.__import__ + + def mocked_import(name, *args, **kwargs): + if name == "tables": + raise ImportError() + return import_orig(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", mocked_import) + + +@pytest.mark.usefixtures("hide_pytables") +def test_message(ac_dc_network, hide_pytables): + n = ac_dc_network + + with pytest.raises( + ImportError, + match=r"Missing optional dependencies to use HDF5 files\.", + ): + n.export_to_hdf5("test.h5") + + +class TestDynamicDependencyChecking: + """Test dynamic dependency checking (fixes for GitHub issue #1341).""" + + def test_cartopy_availability_is_dynamic(self): + """Test that cartopy availability is checked dynamically.""" + actual_availability = importlib.util.find_spec("cartopy") is not None + assert _is_cartopy_available() == actual_availability + + def test_cartopy_checking_not_cached(self): + """Test that cartopy checking responds to changes.""" + with patch( + "pypsa.plot.maps.common.importlib.util.find_spec", return_value=None + ): + assert _is_cartopy_available() is False + + with patch( + "pypsa.plot.maps.common.importlib.util.find_spec", return_value="mock" + ): + assert _is_cartopy_available() is True + + def test_cartopy_graceful_fallback(self): + """Test that cartopy falls back gracefully when not available.""" + import pypsa + + n = pypsa.Network() + n.add("Bus", "bus1", x=0, y=0) + n.add( + "Bus", "bus2", x=1, y=1 + ) # TODO: Actual issue lies in x_min, x_max, y_min, y_max calculation when only one bus is present. Fix this properly. Reproduce by removing one bus. + + with patch("pypsa.plot.maps.common._is_cartopy_available", return_value=False): + n.plot(geomap=True) # Should work despite geomap=True diff --git a/PyPSA/source/test/test_options.py b/PyPSA/source/test/test_options.py new file mode 100644 index 0000000000000000000000000000000000000000..1491d8751f9130a1eca52d393f1e41955b6dde65 --- /dev/null +++ b/PyPSA/source/test/test_options.py @@ -0,0 +1,265 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pytest + +import pypsa + + +@pytest.fixture +def mocked_pypsa(): + import pypsa + + # Add options for testing + pypsa.options._add_option("test.test_option", True, "Test option") + pypsa.options._add_option("test.nested.test_option", False, "Another test option") + + return pypsa + + +def test_getter(mocked_pypsa): + # Default init and get + mocked_pypsa.options.test.test_option = True + assert mocked_pypsa.options.test.test_option is True + with pytest.raises(AttributeError): + mocked_pypsa.options.test.invalid_option + with pytest.raises(AttributeError): + mocked_pypsa.options.invalid_category.invalid_option + + # Nested + mocked_pypsa.options.test.nested.test_option = False + assert mocked_pypsa.options.test.nested.test_option is False + with pytest.raises(AttributeError): + mocked_pypsa.options.test.nested.invalid_option + with pytest.raises(AttributeError): + mocked_pypsa.options.invalid_category.test.nested.invalid_option + + +def test_setter(mocked_pypsa): + mocked_pypsa.options.test.test_option = False + assert mocked_pypsa.options.test.test_option is False + with pytest.raises(AttributeError): + mocked_pypsa.options.test.invalid_option = False + with pytest.raises(AttributeError): + mocked_pypsa.options.invalid_category.invalid_option = False + + # Nested + mocked_pypsa.options.test.nested.test_option = False + assert mocked_pypsa.options.test.nested.test_option is False + with pytest.raises(AttributeError): + mocked_pypsa.options.test.test_options.invalid_option = False + with pytest.raises(AttributeError): + mocked_pypsa.options.invalid_category.some_stuff.invalid_option = False + + +def test_getter_method(mocked_pypsa): + mocked_pypsa.options.test.test_option = True + assert mocked_pypsa.get_option("test.test_option") is True + mocked_pypsa.options.test.test_option = False + assert mocked_pypsa.get_option("test.test_option") is False + + with pytest.raises(AttributeError, match="Invalid option"): + mocked_pypsa.get_option("test.invalid_option") + with pytest.raises(AttributeError, match="Invalid option"): + mocked_pypsa.get_option("test.invalid_option") + + # Nested + mocked_pypsa.options.test.nested.test_option = True + assert mocked_pypsa.get_option("test.nested.test_option") is True + mocked_pypsa.options.test.nested.test_option = False + assert mocked_pypsa.get_option("test.nested.test_option") is False + with pytest.raises(AttributeError, match="Invalid option"): + mocked_pypsa.get_option("test.test_options.invalid_option") + with pytest.raises(AttributeError, match="Invalid option"): + mocked_pypsa.get_option("invalid_warning.some_stuff.invalid_option") + + +def test_setter_method(mocked_pypsa): + mocked_pypsa.set_option("test.test_option", False) + assert mocked_pypsa.options.test.test_option is False + assert mocked_pypsa.get_option("test.test_option") is False + + with pytest.raises(AttributeError, match="Invalid option"): + mocked_pypsa.set_option("test.invalid_option", False) + + with pytest.raises(AttributeError, match="Invalid option"): + mocked_pypsa.set_option("test.invalid_option", False) + + # Nested + mocked_pypsa.set_option("test.nested.test_option", False) + assert mocked_pypsa.options.test.nested.test_option is False + assert mocked_pypsa.get_option("test.nested.test_option") is False + with pytest.raises(AttributeError, match="Invalid option"): + mocked_pypsa.set_option("test.test_options.invalid_option", False) + + +def test_describe_method(capsys, mocked_pypsa): + mocked_pypsa.options._describe_options() + all_options = capsys.readouterr().out + + assert all_options.startswith("PyPSA Options") + assert "test.test_option" in all_options + assert "test.nested.test_option" in all_options + + mocked_pypsa.options._describe_options() + all_options_module = capsys.readouterr().out + assert all_options == all_options_module + + # Test options with no description + mocked_pypsa.options.test.nested.describe() + nested_options = capsys.readouterr().out + assert "test.nested.test_option" not in nested_options + assert "test.test_option" not in nested_options + assert "test_option" in nested_options + + +def test_option_context(mocked_pypsa): + """Test option_context functionality.""" + # Basic usage + mocked_pypsa.options.test.test_option = True + assert mocked_pypsa.options.test.test_option is True + with mocked_pypsa.option_context("test.test_option", False): + assert mocked_pypsa.options.test.test_option is False + assert mocked_pypsa.options.test.test_option is True + + # Nested contexts + with mocked_pypsa.option_context("test.test_option", False): + assert mocked_pypsa.options.test.test_option is False + with mocked_pypsa.option_context("test.test_option", True): + assert mocked_pypsa.options.test.test_option is True + assert mocked_pypsa.options.test.test_option is False + + # Exception handling + with pytest.raises(ValueError): + with mocked_pypsa.option_context("test.test_option", False): + raise ValueError() + assert mocked_pypsa.options.test.test_option is True + + # Invalid arguments + with pytest.raises(ValueError, match="Arguments must be paired"): + with mocked_pypsa.option_context("test.test_option"): + pass + + with pytest.raises(AttributeError): + with mocked_pypsa.option_context("invalid.option", True): + pass + + # Different value types + test_values = [1, "test", None, 3.14, [1, 2, 3]] + for val in test_values: + with mocked_pypsa.option_context("test.test_option", val): + assert mocked_pypsa.options.test.test_option == val + assert mocked_pypsa.options.test.test_option is True + + +def test_nested_option_context(mocked_pypsa): + """Test nested option_context functionality.""" + # Basic usage + mocked_pypsa.options.test.nested.test_option = True + assert mocked_pypsa.options.test.nested.test_option is True + with mocked_pypsa.option_context("test.nested.test_option", False): + assert mocked_pypsa.options.test.nested.test_option is False + assert mocked_pypsa.options.test.nested.test_option is True + + # Nested contexts + with mocked_pypsa.option_context("test.nested.test_option", False): + assert mocked_pypsa.options.test.nested.test_option is False + with mocked_pypsa.option_context("test.nested.test_option", True): + assert mocked_pypsa.options.test.nested.test_option is True + assert mocked_pypsa.options.test.nested.test_option is False + + # Exception handling + with pytest.raises(ValueError): + with mocked_pypsa.option_context("test.nested.test_option", False): + raise ValueError() + assert mocked_pypsa.options.test.nested.test_option is True + + # Invalid arguments + with pytest.raises(ValueError, match="Arguments must be paired"): + with mocked_pypsa.option_context("test.nested.test_option"): + pass + + with pytest.raises(AttributeError): + with mocked_pypsa.option_context("invalid.option", True): + pass + + # Different value types + test_values = [1, "test", None, 3.14, [1, 2, 3]] + for val in test_values: + with mocked_pypsa.option_context("test.nested.test_option", val): + assert mocked_pypsa.options.test.nested.test_option == val + assert mocked_pypsa.options.test.nested.test_option is True + + +def test_general_allow_network_requests(): + """Test the general.allow_network_requests option.""" + import pypsa + + # Test default value + assert pypsa.get_option("general.allow_network_requests") is True + + # Test setting to False + pypsa.set_option("general.allow_network_requests", False) + assert pypsa.get_option("general.allow_network_requests") is False + + # Test setting back to True + pypsa.set_option("general.allow_network_requests", True) + assert pypsa.get_option("general.allow_network_requests") is True + + # Test using option_context + with pypsa.option_context("general.allow_network_requests", False): + assert pypsa.get_option("general.allow_network_requests") is False + assert pypsa.get_option("general.allow_network_requests") is True + + +def test_add_return_names_option(): + """Test the params.add.return_names option.""" + import pandas as pd + + import pypsa + + n = pypsa.Network() + + # Default: option is False, returns None + assert pypsa.get_option("params.add.return_names") is False + assert n.add("Bus", "bus1") is None + + # Set option to True, now returns Index + pypsa.set_option("params.add.return_names", True) + result = n.add("Bus", "bus2") + assert isinstance(result, pd.Index) + assert result[0] == "bus2" + + # Explicit parameter overrides option + assert n.add("Bus", "bus3", return_names=False) is None + pypsa.set_option("params.add.return_names", False) + result = n.add("Bus", "bus4", return_names=True) + assert isinstance(result, pd.Index) + assert result[0] == "bus4" + + # Test with option_context + with pypsa.option_context("params.add.return_names", True): + result = n.add("Bus", "bus5") + assert isinstance(result, pd.Index) + assert result[0] == "bus5" + assert n.add("Bus", "bus6") is None # Back to False + + +def test_params_optimize(): + n = pypsa.examples.ac_dc_meshed() + + n.optimize() + assert n.model.solver_name == "highs" + + n.optimize.create_model() + n.optimize.solve_model() + assert n.model.solver_name == "highs" + + with pypsa.option_context("params.optimize.solver_name", "gurobi"): + n.optimize() + assert n.model.solver_name == "gurobi" + + n.optimize.create_model() + n.optimize.solve_model() + assert n.model.solver_name == "gurobi" diff --git a/PyPSA/source/test/test_pf_activity_mask.py b/PyPSA/source/test/test_pf_activity_mask.py new file mode 100644 index 0000000000000000000000000000000000000000..272fba8ecb9fbfac70f329de1de8d461cf0a2b3b --- /dev/null +++ b/PyPSA/source/test/test_pf_activity_mask.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pytest + + +@pytest.fixture +def sub_network_full(scipy_network): + n = scipy_network.copy() + return n.c.sub_networks.static.obj.iloc[0] + + +@pytest.fixture +def sub_network_filtered(scipy_network): + n = scipy_network.copy() + n.c.lines.static.loc["2", "active"] = False + return n.c.sub_networks.static.obj.iloc[0] + + +def test_different_shape_incidence_matrix(sub_network_full, sub_network_filtered): + k_full = sub_network_full.incidence_matrix() + k_filtered = sub_network_filtered.incidence_matrix() + + assert k_full.shape[0] == k_filtered.shape[0] + assert k_full.shape[1] == k_filtered.shape[1] + + +def test_subnetwork_full_pf(sub_network_full): + sub_network_full.pf(sub_network_full.snapshots[:3]) + + +def test_subnetwork_filtered_pf(sub_network_filtered): + sub_network_filtered.pf(sub_network_filtered.snapshots[:3]) + n = sub_network_filtered.n + assert n.c.lines.dynamic.p0.loc[:, ~n.c.lines.static.active].eq(0).all().all() diff --git a/PyPSA/source/test/test_pf_against_pandapower.py b/PyPSA/source/test/test_pf_against_pandapower.py new file mode 100644 index 0000000000000000000000000000000000000000..7cbe849997de071f482ae822326e7dae53ed759f --- /dev/null +++ b/PyPSA/source/test/test_pf_against_pandapower.py @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import sys + +import numpy as np +import pytest +from numpy.testing import assert_array_almost_equal as equal + +import pypsa +from pypsa.constants import DEFAULT_TIMESTAMP + + +@pytest.mark.skipif( + sys.version_info < (3, 12), reason="Test requires Python 3.12 or higher" +) +@pytest.mark.parametrize("use_pandapower_index", [True, False]) +@pytest.mark.parametrize("extra_line_data", [True, False]) +def test_pandapower_custom_case( + pandapower_custom_network, use_pandapower_index, extra_line_data +): + import pandapower as pp + + net = pandapower_custom_network + # because of phase angles, need to init with DC + pp.runpp(net, calculate_voltage_angles=True, init="dc") + n = pypsa.Network() + n.import_from_pandapower_net( + net, use_pandapower_index=use_pandapower_index, extra_line_data=extra_line_data + ) + + # seed PF with LPF solution because of phase angle jumps + n.lpf() + n.pf(use_seed=True) + + # use same index for everything + net.res_bus.index = net.bus.name.values + net.res_line.index = net.line.name.values + + # compare bus angles + equal( + n.c.buses.dynamic.v_ang.loc[DEFAULT_TIMESTAMP] * 180 / np.pi, + net.res_bus.va_degree, + ) + + # compare bus voltage magnitudes + equal(n.c.buses.dynamic.v_mag_pu.loc[DEFAULT_TIMESTAMP], net.res_bus.vm_pu) + + # compare bus active power (NB: pandapower uses load signs) + equal(n.c.buses.dynamic.p.loc[DEFAULT_TIMESTAMP], -net.res_bus.p_mw) + + # compare bus active power (NB: pandapower uses load signs) + equal(n.c.buses.dynamic.q.loc[DEFAULT_TIMESTAMP], -net.res_bus.q_mvar) + + # compare branch flows + equal(n.c.lines.dynamic.p0.loc[DEFAULT_TIMESTAMP], net.res_line.p_from_mw) + equal(n.c.lines.dynamic.p1.loc[DEFAULT_TIMESTAMP], net.res_line.p_to_mw) + equal(n.c.lines.dynamic.q0.loc[DEFAULT_TIMESTAMP], net.res_line.q_from_mvar) + equal(n.c.lines.dynamic.q1.loc[DEFAULT_TIMESTAMP], net.res_line.q_to_mvar) + + equal(n.c.transformers.dynamic.p0.loc[DEFAULT_TIMESTAMP], net.res_trafo.p_hv_mw) + equal(n.c.transformers.dynamic.p1.loc[DEFAULT_TIMESTAMP], net.res_trafo.p_lv_mw) + equal(n.c.transformers.dynamic.q0.loc[DEFAULT_TIMESTAMP], net.res_trafo.q_hv_mvar) + equal(n.c.transformers.dynamic.q1.loc[DEFAULT_TIMESTAMP], net.res_trafo.q_lv_mvar) diff --git a/PyPSA/source/test/test_pf_against_pypower.py b/PyPSA/source/test/test_pf_against_pypower.py new file mode 100644 index 0000000000000000000000000000000000000000..eec9b9ccb0b5159ddabba08ac0d38f36701981e2 --- /dev/null +++ b/PyPSA/source/test/test_pf_against_pypower.py @@ -0,0 +1,133 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import numpy as np +import pandas as pd +from numpy.testing import assert_array_almost_equal as equal +from pypower.case30 import case30 as case +from pypower.ppoption import ppoption +from pypower.runpf import runpf + +import pypsa +from pypsa.constants import DEFAULT_TIMESTAMP + + +def test_pypower_case(): + # ppopt is a dictionary with the details of the optimization routine to run + ppopt = ppoption(PF_ALG=2) + + # choose DC or AC + ppopt["PF_DC"] = False + + # ppc is a dictionary with details about the network, including baseMVA, branches and generators + ppc = case() + + results, success = runpf(ppc, ppopt) + + # branches + columns = [ + "bus0", + "bus1", + "r", + "x", + "b", + "rateA", + "rateB", + "rateC", + "ratio", + "angle", + "status", + "angmin", + "angmax", + "p0", + "q0", + "p1", + "q1", + ] + results_df = {"branch": pd.DataFrame(data=results["branch"], columns=columns)} + # buses + columns = [ + "bus", + "type", + "Pd", + "Qd", + "Gs", + "Bs", + "area", + "v_mag_pu", + "v_ang", + "v_nom", + "zone", + "Vmax", + "Vmin", + ] + results_df["bus"] = pd.DataFrame( + data=results["bus"], columns=columns, index=results["bus"][:, 0] + ) + + # generators + columns = [ + "bus", + "p", + "q", + "q_max", + "q_min", + "Vg", + "mBase", + "status", + "p_max", + "p_min", + "Pc1", + "Pc2", + "Qc1min", + "Qc1max", + "Qc2min", + "Qc2max", + "ramp_agc", + "ramp_10", + "ramp_30", + "ramp_q", + "apf", + ] + results_df["gen"] = pd.DataFrame(data=results["gen"], columns=columns) + + # now compute in PyPSA + + n = pypsa.Network() + n.import_from_pypower_ppc(ppc) + + # PYPOWER uses PI model for transformers, whereas PyPSA defaults to + # T since version 0.8.0 + n.c.transformers.static.model = "pi" + + n.pf() + + # compare branch flows + for c in n.components: + if c.name not in n.passive_branch_components: + continue + for si in ["p0", "p1", "q0", "q1"]: + si_pypsa = getattr(c.dynamic, si).loc[DEFAULT_TIMESTAMP].values + si_pypower = results_df["branch"][si][c.static.original_index].values + equal(si_pypsa, si_pypower) + + # compare generator dispatch + for s in ["p", "q"]: + s_pypsa = getattr(n.c.generators.dynamic, s).loc[DEFAULT_TIMESTAMP].values + s_pypower = results_df["gen"][s].values + equal(s_pypsa, s_pypower) + + # compare voltages + v_mag_pypsa = n.c.buses.dynamic.v_mag_pu.loc[DEFAULT_TIMESTAMP] + v_mag_pypower = results_df["bus"]["v_mag_pu"] + + equal(v_mag_pypsa, v_mag_pypower) + + v_ang_pypsa = n.c.buses.dynamic.v_ang.loc[DEFAULT_TIMESTAMP] + pypower_slack_angle = results_df["bus"]["v_ang"][ + results_df["bus"]["type"] == 3 + ].values[0] + v_ang_pypower = (results_df["bus"]["v_ang"] - pypower_slack_angle) * np.pi / 180.0 + + equal(v_ang_pypsa, v_ang_pypower) diff --git a/PyPSA/source/test/test_pf_distributed_slack.py b/PyPSA/source/test/test_pf_distributed_slack.py new file mode 100644 index 0000000000000000000000000000000000000000..d55383b92d38650d4f567196a5bf1d23fa0f98af --- /dev/null +++ b/PyPSA/source/test/test_pf_distributed_slack.py @@ -0,0 +1,80 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +from numpy.testing import assert_array_almost_equal as equal + + +def normed(s): + return s / s.sum() + + +def test_pf_distributed_slack(scipy_network): + n = scipy_network + n.set_snapshots(n.snapshots[:2]) + + # There are some infeasibilities without line extensions + n.c.lines.static.s_max_pu = 0.7 + n.c.lines.static.loc[["316", "527", "602"], "s_nom"] = 1715 + n.c.storage_units.static.state_of_charge_initial = 0.0 + + n.optimize(n.snapshots) + + # For the PF, set the P to the optimised P + n.c.generators.dynamic.p_set = n.c.generators.dynamic.p + n.c.storage_units.dynamic.p_set = n.c.storage_units.dynamic.p + + # set all buses to PV, since we don't know what Q set points are + n.c.generators.static.control = "PV" + + # Need some PQ buses so that Jacobian doesn't break + f = n.c.generators.static[n.c.generators.static.bus == "492"] + n.c.generators.static.loc[f.index, "control"] = "PQ" + # by dispatch + n.pf(distribute_slack=True, slack_weights="p_set") + + equal( + n.c.generators.dynamic.p_set.apply(normed, axis=1), + (n.c.generators.dynamic.p - n.c.generators.dynamic.p_set).apply(normed, axis=1), + ) + + # by capacity + n.pf(distribute_slack=True, slack_weights="p_nom") + + slack_shares_by_capacity = ( + n.c.generators.dynamic.p - n.c.generators.dynamic.p_set + ).apply(normed, axis=1) + + for _, row in slack_shares_by_capacity.iterrows(): + equal(n.c.generators.static.p_nom.pipe(normed).fillna(0.0), row) + + # by custom weights (mirror 'capacity' via custom slack weights by bus) + custom_weights = {} + for sub_network in n.c.sub_networks.static.obj: + buses_o = sub_network.buses_o + generators = sub_network.c.generators.static + custom_weights[sub_network.name] = ( + generators.p_nom.groupby(generators.bus) + .sum() + .reindex(buses_o) + .pipe(normed) + .fillna(0.0) + ) + + n.pf(distribute_slack=True, slack_weights=custom_weights) + + equal( + slack_shares_by_capacity, + (n.c.generators.dynamic.p - n.c.generators.dynamic.p_set).apply(normed, axis=1), + ) + + custom_weights = { + sub_network.name: sub_network.c.generators.static.p_nom + for sub_network in n.c.sub_networks.static.obj + } + n.pf(distribute_slack=True, slack_weights=custom_weights) + + equal( + slack_shares_by_capacity, + (n.c.generators.dynamic.p - n.c.generators.dynamic.p_set).apply(normed, axis=1), + ) diff --git a/PyPSA/source/test/test_plot_maps_common.py b/PyPSA/source/test/test_plot_maps_common.py new file mode 100644 index 0000000000000000000000000000000000000000..546bd00ec87eb2686f2102b0872008d7f7552c30 --- /dev/null +++ b/PyPSA/source/test/test_plot_maps_common.py @@ -0,0 +1,719 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +from types import SimpleNamespace + +import matplotlib.colors as mcolors +import matplotlib.pyplot as plt +import networkx as nx +import numpy as np +import pandas as pd +import pytest +from shapely.geometry import LineString, MultiPolygon, Point, Polygon + +import pypsa +from pypsa.plot.maps.common import ( + _is_cartopy_available, + add_jitter, + apply_cmap, + apply_layouter, + calculate_angle, + calculate_midpoint, + clip_lat, + df_to_html_table, + feature_to_geojson, + flip_polygon, + get_global_stat, + linestring_to_pdk_path, + meters_to_lonlat, + poly_to_geojson, + rotate_polygon, + round_value, + scale_polygon_by_width, + scale_to_max_abs, + series_to_pdk_path, + set_tooltip_style, + shapefile_to_geojson, + shorten_string, + to_rgba255, + to_rgba255_css, + translate_polygon, + wkt_to_linestring, +) + +cartopy_present = _is_cartopy_available() + + +@pytest.fixture +def simple_network(): + n = pypsa.Network() + + # Add two buses + n.add("Bus", "A") + n.add("Bus", "B") + + # Add a line between them + n.add("Line", "L1", bus0="A", bus1="B", x=0.1) + n.add("Line", "L2", bus0="B", bus1="C", x=0.1) + n.add("Line", "L3", bus0="A", bus1="C", x=0.1) + + # Add a generator on A + n.add("Generator", "G1", bus="A", p_nom=100) + + # Add a load on B + n.add("Load", "L1", bus="B", p_set=50) + + return n + + +# apply_cmap +def test_apply_cmap_numeric_default(): + s = pd.Series([0, 0.5, 1.0]) + result = apply_cmap(s, cmap="viridis") + + assert isinstance(result, pd.Series) + # Each entry should be an RGBA tuple + assert all(isinstance(c, tuple) for c in result) + assert all(len(c) == 4 for c in result) + + +def test_apply_cmap_numeric_with_colormap_object(): + s = pd.Series([0, 0.5, 1.0]) + cmap_obj = plt.get_cmap("plasma") + result = apply_cmap(s, cmap=cmap_obj) + + assert isinstance(result, pd.Series) + assert all(isinstance(c, tuple) for c in result) + assert all(len(c) == 4 for c in result) + + +# apply_layouter +def test_apply_layouter_returns_coordinates(simple_network): + x, y = apply_layouter(simple_network, inplace=False) + + assert isinstance(x, pd.Series) + assert isinstance(y, pd.Series) + # Same bus index + assert set(x.index) == {"A", "B", "C"} + assert set(y.index) == {"A", "B", "C"} + + +def test_apply_layouter_inplace(simple_network): + result = apply_layouter(simple_network, inplace=True) + + # inplace should return None + assert result is None + + # coordinates must now exist in buses table + assert "x" in simple_network.c.buses.static.columns + assert "y" in simple_network.c.buses.static.columns + assert not simple_network.c.buses.static[["x", "y"]].isna().any().any() + + +def test_apply_layouter_custom_layouter(simple_network): + x, y = apply_layouter(simple_network, layouter=nx.circular_layout) + + # circular layout should give points approximately on the unit circle + r = x**2 + y**2 + np.testing.assert_allclose(r, 1.0, rtol=1e-6, atol=1e-6) + + +# add_jitter +def test_add_jitter_zero(): + x = pd.Series([1, 2, 3], name="x") + y = pd.Series([4, 5, 6], name="y") + + x_j, y_j = add_jitter(x, y, jitter=0) + + # No changes + pd.testing.assert_series_equal(x, x_j, check_dtype=False) + pd.testing.assert_series_equal(y, y_j, check_dtype=False) + + +def test_add_jitter_within_bounds(): + x = pd.Series([0.0, 1.0, 2.0], name="x") + y = pd.Series([10.0, 11.0, 12.0], name="y") + jitter = 0.5 + + x_j, y_j = add_jitter(x, y, jitter=jitter) + + # Preserve index and name + assert x_j.index.equals(x.index) + assert y_j.index.equals(y.index) + assert x_j.name == "x" + assert y_j.name == "y" + + # Differences are bounded by jitter + dx = (x_j - x).abs() + dy = (y_j - y).abs() + assert (dx <= jitter).all() + assert (dy <= jitter).all() + + +# to_rgba255 +def test_to_rgba_named_default_alpha(): + result = to_rgba255("red") + assert result == [255, 0, 0, 255] + + +def test_to_rgba_named_custom_alpha(): + result = to_rgba255("blue", alpha=0.5) + assert result == [0, 0, 255, 128] + + +def test_to_rgba_hex_default_alpha(): + result = to_rgba255("#00FF00") + assert result == [0, 255, 0, 255] + + +def test_to_rgba_hex_custom_alpha(): + result = to_rgba255("#123456", alpha=0.2) + expected_rgb = [round(c * 255) for c in mcolors.to_rgb("#123456")] + [ + round(0.2 * 255) + ] + assert result == expected_rgb + + +# to_rgba255_css +def test_to_rgba_css_named_default_alpha(): + result = to_rgba255_css("red") + assert result == "rgba(255, 0, 0, 1.00)" + + +def test_to_rgba_css_named_custom_alpha(): + result = to_rgba255_css("blue", alpha=0.5) + assert result == "rgba(0, 0, 255, 0.50)" + + +def test_to_rgba_css_hex_default_alpha(): + result = to_rgba255_css("#00FF00") + assert result == "rgba(0, 255, 0, 1.00)" + + +def test_to_rgba_css_hex_custom_alpha(): + result = to_rgba255_css("#123456", alpha=0.2) + expected_rgb = [round(c * 255) for c in mcolors.to_rgb("#123456")] + expected = f"rgba({expected_rgb[0]}, {expected_rgb[1]}, {expected_rgb[2]}, 0.20)" + assert result == expected + + +# set_tooltip_style +def test_set_tooltip_style_defaults(): + style = set_tooltip_style() + assert style["backgroundColor"] == "rgba(0, 0, 0, 0.70)" + assert style["color"] == "white" + assert style["fontFamily"] == "Arial" + assert style["fontSize"] == "12px" + assert style["padding"] == "10px" + assert style["maxWidth"] == "300px" + assert style["overflowWrap"] == "break-word" + + +def test_set_tooltip_style_custom_values(): + style = set_tooltip_style( + background_alpha=0.5, + background_color="red", + font_color="yellow", + font_family="Courier", + font_size=16, + max_width=500, + padding=20, + ) + assert style["backgroundColor"] == "rgba(255, 0, 0, 0.50)" + assert style["color"] == "yellow" + assert style["fontFamily"] == "Courier" + assert style["fontSize"] == "16px" + assert style["padding"] == "20px" + assert style["maxWidth"] == "500px" + + +# scale_to_max_abs +def test_scale_to_max_abs_positive(): + s = pd.Series([1, 2, 3]) + scaled = scale_to_max_abs(s, max_value=6) + expected = pd.Series([2, 4, 6]) + pd.testing.assert_series_equal(scaled, expected, check_dtype=False) + + +def test_scale_to_max_abs_negative(): + s = pd.Series([-1, -5, -3]) + scaled = scale_to_max_abs(s, max_value=10) + expected = pd.Series([-2, -10, -6]) + pd.testing.assert_series_equal(scaled, expected, check_dtype=False) + + +# get_global_stat +def test_get_global_stat_numbers(): + elems = [1, -3, 2] + assert get_global_stat(elems) == 3 # max absolute + assert get_global_stat(elems, absolute=False) == 2 # max raw + + +def test_get_global_stat_dicts(): + elems = [{"a": 1, "b": -5}, {"c": 2}] + assert get_global_stat(elems) == 5 + assert get_global_stat(elems, absolute=False) == 2 + + +def test_get_global_stat_series(): + s1 = pd.Series([1, -4, np.nan]) + s2 = pd.Series([2, -1]) + elems = [s1, s2] + assert get_global_stat(elems) == 4 + assert get_global_stat(elems, absolute=False) == 2 + + +def test_get_global_stat_mixed(): + s = pd.Series([1, 2]) + elems = [1, -2, {"a": -3}, s] + assert get_global_stat(elems) == 3 + + +def test_get_global_stat_custom_stat(): + elems = [1, 2, -5] + # median of absolute values + assert get_global_stat(elems, stat="median") == 2 + # custom function + assert ( + get_global_stat(elems, stat=lambda arr: np.min(arr) + 1) == 2 + ) # check behavior + + +def test_get_global_stat_none_or_empty(): + assert get_global_stat([None, pd.Series([], dtype=float)]) is None + assert get_global_stat([]) is None + + +def test_get_global_stat_invalid_type(): + with pytest.raises(TypeError): + get_global_stat([object()]) + + +def test_get_global_stat_invalid_stat(): + with pytest.raises(ValueError): + get_global_stat([1, 2], stat="unsupported") + + +# wkt_to_linestring +def test_wkt_to_linestring_valid(): + wkt_str = "LINESTRING (0 0, 1 1, 2 2)" + ls = wkt_to_linestring(wkt_str) + assert isinstance(ls, LineString) + assert list(ls.coords) == [(0, 0), (1, 1), (2, 2)] + + +def test_wkt_to_linestring_wrong_geometry(): + wkt_point = "POINT (0 0)" + with pytest.raises(TypeError, match="Expected LineString"): + wkt_to_linestring(wkt_point) + + +def test_wkt_to_linestring_invalid_string(): + invalid_wkt = "LINE (0 0, 1 1)" # malformed + with pytest.raises(Exception): + wkt_to_linestring(invalid_wkt) + + +# linestring_to_pdk_path +def test_linestring_to_pdk_path_valid(): + line = LineString([(0, 0), (1, 1), (2, 2)]) + path = linestring_to_pdk_path(line) + assert path == [[0, 0], [1, 1], [2, 2]] + # Ensure all elements are lists of two floats + for coord in path: + assert isinstance(coord, list) + assert len(coord) == 2 + assert all(isinstance(v, (int | float)) for v in coord) + + +def test_linestring_to_pdk_path_wrong_type(): + pt = Point(0, 0) + with pytest.raises(TypeError, match="Expected LineString"): + linestring_to_pdk_path(pt) + + +def test_linestring_to_pdk_path_empty_linestring(): + empty_line = LineString([]) + path = linestring_to_pdk_path(empty_line) + assert path == [] + + +# series_to_pdk_path +def test_series_to_pdk_path_linestrings(): + geoms = pd.Series([LineString([(0, 0), (1, 1)]), LineString([(2, 2), (3, 3)])]) + paths = series_to_pdk_path(geoms) + expected = [[[0, 0], [1, 1]], [[2, 2], [3, 3]]] + assert paths == expected + + +def test_series_to_pdk_path_wkt_strings(): + geoms = pd.Series(["LINESTRING (0 0, 1 1)", "LINESTRING (2 2, 3 3)"]) + paths = series_to_pdk_path(geoms) + expected = [[[0, 0], [1, 1]], [[2, 2], [3, 3]]] + assert paths == expected + + +def test_series_to_pdk_path_mixed(): + geoms = pd.Series([LineString([(0, 0), (1, 1)]), "LINESTRING (2 2, 3 3)"]) + paths = series_to_pdk_path(geoms) + expected = [[[0, 0], [1, 1]], [[2, 2], [3, 3]]] + assert paths == expected + + +def test_series_to_pdk_path_invalid_string(): + geoms = pd.Series(["POINT (0 0)"]) + with pytest.raises(TypeError): + series_to_pdk_path(geoms) + + +# rotate_polygon +def test_rotate_polygon_identity(): + # rotating by 0 radians should return the same polygon + poly = np.array([[1, 0], [0, 1], [-1, 0]]) + rotated = rotate_polygon(poly, 0) + np.testing.assert_allclose(rotated, poly, rtol=1e-12, atol=1e-12) + + +def test_rotate_polygon_90deg(): + # 90 degrees rotation + poly = np.array([[1, 0], [0, 1]]) + rotated = rotate_polygon(poly, np.pi / 2) + expected = np.array([[0, 1], [-1, 0]]) + np.testing.assert_allclose(rotated, expected, rtol=1e-12, atol=1e-12) + + +def test_rotate_polygon_negative_angle(): + # negative rotation + poly = np.array([[1, 0]]) + rotated = rotate_polygon(poly, -np.pi / 2) + expected = np.array([[0, -1]]) + np.testing.assert_allclose(rotated, expected, rtol=1e-12, atol=1e-12) + + +def test_rotate_polygon_square(): + # rotation preserves distances + poly = np.array([[1, 0], [0, 1], [-1, 0], [0, -1]]) + rotated = rotate_polygon(poly, np.pi / 4) + # all distances from origin should remain 1 + dists = np.linalg.norm(rotated, axis=1) + np.testing.assert_allclose(dists, 1.0, rtol=1e-12, atol=1e-12) + + +# flip_polygon +def test_flip_polygon_x_axis(): + poly = np.array([[1, 2], [-3, 4]]) + flipped = flip_polygon(poly, axis="x") + expected = np.array([[1, -2], [-3, -4]]) + np.testing.assert_allclose(flipped, expected) + + +def test_flip_polygon_y_axis(): + poly = np.array([[1, 2], [-3, 4]]) + flipped = flip_polygon(poly, axis="y") + expected = np.array([[-1, 2], [3, 4]]) + np.testing.assert_allclose(flipped, expected) + + +def test_flip_polygon_invalid_axis(): + poly = np.array([[0, 0]]) + with pytest.raises(ValueError): + flip_polygon(poly, axis="z") + + +def test_flip_polygon_preserves_shape(): + rng = np.random.default_rng(42) + poly = rng.random((5, 2)) + flipped = flip_polygon(poly, axis="x") + assert flipped.shape == poly.shape + + +# scale_polygon_by_width +def test_scale_polygon_basic(): + poly = np.array([[0, 0], [0, 2], [1, 2]]) + target_width = 4 + scaled = scale_polygon_by_width(poly, target_width) + width_scaled = scaled[:, 1].max() - scaled[:, 1].min() + np.testing.assert_allclose(width_scaled, target_width, rtol=1e-12, atol=1e-12) + + +def test_scale_polygon_preserves_ratios(): + poly = np.array([[1, 0], [2, 2]]) + target_width = 6 + scaled = scale_polygon_by_width(poly, target_width) + original_ratio = (poly[:, 0].max() - poly[:, 0].min()) / ( + poly[:, 1].max() - poly[:, 1].min() + ) + scaled_ratio = (scaled[:, 0].max() - scaled[:, 0].min()) / ( + scaled[:, 1].max() - scaled[:, 1].min() + ) + np.testing.assert_allclose(scaled_ratio, original_ratio, rtol=1e-12) + + +def test_scale_polygon_zero_width_raises(): + poly = np.array([[0, 1]]) + with pytest.raises(ValueError, match="Cannot scale polygon with zero width"): + scale_polygon_by_width(poly, 10) + + +# translate_polygon +def test_translate_polygon(): + poly = np.array([[0, 0], [1, 1], [2, 2]]) + offset = (3, -1) + translated = translate_polygon(poly, offset) + + expected = np.array([[3, -1], [4, 0], [5, 1]]) + np.testing.assert_allclose(translated, expected, rtol=1e-12, atol=1e-12) + + +def test_translate_polygon_zero_offset(): + poly = np.array([[0, 0], [1, 1]]) + translated = translate_polygon(poly, (0, 0)) + np.testing.assert_allclose(translated, poly, rtol=1e-12, atol=1e-12) + + +# calculate_midpoint +def test_calculate_midpoint(): + p0 = (0, 0) + p1 = (2, 4) + midpoint = calculate_midpoint(p0, p1) + expected = (1.0, 2.0) + assert midpoint == expected + + +def test_calculate_angle(): + p0 = (0, 0) + p1 = (1, 1) + angle = calculate_angle(p0, p1) + expected = np.pi / 4 + np.testing.assert_allclose(angle, expected, rtol=1e-12, atol=1e-12) + + # test horizontal + p1 = (1, 0) + angle = calculate_angle(p0, p1) + np.testing.assert_allclose(angle, 0, rtol=1e-12, atol=1e-12) + + # test vertical + p1 = (0, 1) + angle = calculate_angle(p0, p1) + np.testing.assert_allclose(angle, np.pi / 2, rtol=1e-12, atol=1e-12) + + +def test_meters_to_lonlat(): + # Simple square of 100 m offsets from reference + poly = np.array([[0, 0], [100, 0], [100, 100], [0, 100]]) + p0 = (0, 0) # reference lon/lat in degrees + result = meters_to_lonlat(poly, p0) + + # small approximation checks + assert result.shape == poly.shape + # first point should equal reference + np.testing.assert_allclose(result[0], [0, 0], rtol=1e-12, atol=1e-12) + # distances in degrees should be positive + assert all(result[:, 0] >= 0) + assert all(result[:, 1] >= 0) + + +# clip_lat +def test_clip_lat(): + coords = [(0, 91), (0, -91), (10, 45), (20, -45)] + clipped = clip_lat(coords) + + # Ensure no latitude exceeds poles minus/plus buffer + for lon, lat in clipped: + assert -90 < lat < 90, f"Latitude {lat} out of bounds" + + # Check that normal latitudes are unchanged + assert clipped[2] == (10, 45) + assert clipped[3] == (20, -45) + + # Check that extreme latitudes are clipped correctly + pole_buffer = 1e-6 + assert clipped[0][1] == 90 - pole_buffer + assert clipped[1][1] == -90 + pole_buffer + + +# poly_to_geojson +def test_poly_to_geojson(): + # Polygon with some coordinates exceeding the poles + poly = Polygon([(0, 91), (1, 91), (1, 92), (0, 92), (0, 91)]) + + geojson = poly_to_geojson(poly) + + # The GeoJSON should have type Feature + assert geojson["type"] == "Feature" + + # The geometry should be a Polygon + geom = geojson["geometry"] + assert geom["type"] == "Polygon" + + # All latitudes should be within [-90 + pole_buffer, 90 - pole_buffer] + pole_buffer = 1e-6 + for ring in geom["coordinates"]: + for lon, lat in ring: + assert -90 + pole_buffer <= lat <= 90 - pole_buffer + + # The polygon should be closed + exterior = geom["coordinates"][0] + assert exterior[0] == exterior[-1] + + +# feature_to_geojson +@pytest.mark.skipif(not cartopy_present, reason="Cartopy not installed") +def test_feature_to_geojson_mock(): + # Mock a Cartopy-like feature with geometries() method + poly1 = Polygon([(0, 91), (1, 91), (1, 92), (0, 92), (0, 91)]) + poly2 = Polygon([(10, -91), (11, -91), (11, -92), (10, -92), (10, -91)]) + multipoly = MultiPolygon([poly1, poly2]) + + # Mock NaturalEarthFeature with geometries method + mock_feature = SimpleNamespace(geometries=lambda: [poly1, multipoly]) + + features = feature_to_geojson(mock_feature) + + # Should return 3 features: 1 from poly1, 2 from multipoly + assert len(features) == 3 + + # Each feature should be a GeoJSON Feature + for f in features: + assert f["type"] == "Feature" + geom = f["geometry"] + assert geom["type"] == "Polygon" + # All latitudes should be clipped + for ring in geom["coordinates"]: + for lon, lat in ring: + assert -90 + 1e-6 <= lat <= 90 - 1e-6 + # Polygon should be closed + exterior = geom["coordinates"][0] + assert exterior[0] == exterior[-1] + + +# shapefile_to_geojson +@pytest.mark.skipif(not cartopy_present, reason="Cartopy not installed") +def test_shapefile_to_geojson_basic(): + # Use the smallest dataset to keep test fast + features = shapefile_to_geojson( + resolution="110m", + category="cultural", + name="admin_0_countries", + pole_buffer=1e-6, + ) + + # Should return at least one feature + assert len(features) > 0 + + # Each feature should have geometry and properties + for f in features: + assert f["type"] == "Feature" + assert "geometry" in f + assert "properties" in f + + geom = f["geometry"] + # Geometry type should be Polygon + assert geom["type"] == "Polygon" + # Latitudes should be clipped + for ring in geom["coordinates"]: + for lon, lat in ring: + assert -90 + 1e-6 <= lat <= 90 - 1e-6 + # Polygon should be closed + exterior = geom["coordinates"][0] + assert exterior[0] == exterior[-1] + + +# shorten_string +def test_shorten_string(): + # Converts non-string inputs + assert shorten_string(123) == "123" + assert shorten_string(None) == "None" + + # No shortening if max_length not set + assert shorten_string("hello") == "hello" + + # Shorten string longer than max_length + assert shorten_string("hello world", max_length=5) == "hello..." + + # Exactly max_length should not add ellipsis + assert shorten_string("hello", max_length=5) == "hello" + + # Empty string + assert shorten_string("", max_length=5) == "" + + +# round_value +def test_round_value(): + # Rounding with integer precision + assert round_value(3.14159, rounding=2) == 3.14 + assert round_value(2.0, rounding=2) == 2 # integer float rounds to int + assert round_value(5, rounding=2) == 5 # int stays int + + # Rounding with dict precision + rounding_dict = {"a": 1, "b": 3} + assert round_value(3.14159, rounding=rounding_dict, key="a") == 3.1 + assert round_value(3.14159, rounding=rounding_dict, key="b") == 3.142 + # Key not in dict, returns original + assert round_value(3.14159, rounding=rounding_dict, key="c") == 3.14159 + + # No rounding + assert round_value(3.14159) == 3.14159 + assert round_value("hello") == "hello" + assert round_value(None) is None + + +# df_to_html_table +def test_df_to_html_table_basic(): + df = pd.DataFrame( + { + "A": [1, 2], + "B": [3, 4], + }, + index=["row1", "row2"], + ) + + html_series = df_to_html_table(df, columns=["A", "B"], bold_header=True) + + # Check we have one HTML string per row + assert len(html_series) == 2 + + for s in html_series: + # Check table tags are present + assert "" in s + assert "
" in s + + # Check column headers are present + assert "A:" in s + assert "B:" in s + + # Check value alignment (default left) + assert "text-align:left" in s + + # Check bold header + assert "font-weight:bold" in s + + +def test_df_to_html_table_rounding_and_max_length(): + df = pd.DataFrame( + { + "A": [1.23456, 2.34567], + "B": [3.98765, 4.87654], + }, + index=["row1", "row2"], + ) + + html_series = df_to_html_table( + df, + columns=["A", "B"], + rounding=2, + max_value_length=4, + max_header_length=1, + ) + + for s in html_series: + # Rounded numbers + assert "1.23" in s or "2.35" in s or "3.99" in s or "4.88" in s + + # Header shortening + assert "A" in s + assert "B" in s + + # Ellipsis should appear if header or value truncated + assert "..." not in s or True # Optional, just ensure no errors diff --git a/PyPSA/source/test/test_plot_maps_interactive.py b/PyPSA/source/test/test_plot_maps_interactive.py new file mode 100644 index 0000000000000000000000000000000000000000..295cebbb830abffd9d6be1c5399d11b0b9fc952f --- /dev/null +++ b/PyPSA/source/test/test_plot_maps_interactive.py @@ -0,0 +1,198 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import sys + +import pydeck as pdk +import pytest + +import pypsa.plot.maps.interactive as interactive +from pypsa.plot.maps.common import _is_cartopy_available + +cartopy_present = _is_cartopy_available() + + +# Test explore() function +def test_plot_explore_alias_for_explore(ac_dc_network): + """Test that n.plot.explore is an alias for n.explore().""" + n = ac_dc_network + + result1 = n.plot.explore() + result2 = n.explore() + + assert type(result1) is type(result2) + + +def test_explore_parameters(ac_dc_network): + n = ac_dc_network + + # Custom map style + deck = n.plot.explore(map_style="dark") + assert deck.map_style == "dark" or pdk.Deck(map_style="dark").map_style + + # Custom view state + view_state = pdk.ViewState(latitude=0, longitude=0, zoom=5) + deck = n.plot.explore(view_state=view_state) + assert deck.initial_view_state.latitude == 0 + assert deck.initial_view_state.zoom == 5 + + # With jitter + deck = n.plot.explore(jitter=0.1) + assert isinstance(deck, pdk.Deck) + + +# Test subplotter properties +def test_pdkplotter_piecharts(ac_dc_network): + n = ac_dc_network + + pies = n.statistics.installed_capacity( + groupby=["bus", "carrier"], + ) + pies.index = pies.index.droplevel(0) + + plotter = interactive.PydeckPlotter(n, map_style="light") + plotter.build_layers(bus_size=pies) + plotter.deck() + + +def test_pdk_plotter_piecharts_bus_split_circle(ac_dc_network): + n = ac_dc_network + pies = n.statistics.installed_capacity( + groupby=["bus", "carrier"], + ) + pies.index = pies.index.droplevel(0) + + load = -n.loads_t.p_set.sum(axis=0) + load.index.name = "bus" + + pies = pies.unstack().fillna(0) + pies["load"] = load + pies.fillna(0, inplace=True) + pies = pies.stack() + + plotter = interactive.PydeckPlotter(n, map_style="light") + plotter.build_layers(bus_size=pies, bus_split_circle=True) + plotter.deck() + + +def test_pdk_plotter_auto_scale(ac_dc_network): + n = ac_dc_network + plotter = interactive.PydeckPlotter(n, map_style="light") + + # Test autoscale with default parameters + plotter.build_layers(auto_scale=True) + plotter.deck() + + +def test_pdk_plotter_flows(ac_dc_network): + n = ac_dc_network + plotter = interactive.PydeckPlotter(n, map_style="light") + + line_flow = n.c.lines.static.s_nom / 1e3 + link_flow = n.c.links.static.p_nom / 1e3 + + # Test flows with default parameters + plotter.build_layers( + line_flow=line_flow, + link_flow=link_flow, + ) + plotter.deck() + + +def test_pdk_color_cmaps(ac_dc_network): + n = ac_dc_network + plotter = interactive.PydeckPlotter(n, map_style="light") + + # Test with different colormaps + bus_color = n.c.buses.static.v_nom + line_color = n.c.lines.static.s_nom + link_color = n.c.links.static.p_nom + + plotter.build_layers( + bus_color=bus_color, + line_color=line_color, + link_color=link_color, + ) + plotter.deck() + + +def test_pdk_tooltips(ac_dc_network): + n = ac_dc_network + plotter = interactive.PydeckPlotter(n, map_style="light") + + # Test with custom tooltip columns + plotter.build_layers( + bus_columns=["name", "v_nom"], + line_columns=["name", "s_nom"], + link_columns=["name", "p_nom"], + ) + plotter.deck(tooltip=True) + plotter.deck(tooltip=False) + + +@pytest.mark.skipif(sys.platform != "linux", reason="Cartopy issues on macos.") +def test_geomap_warning(ac_dc_network, caplog): + n = ac_dc_network + with caplog.at_level("WARNING"): + n.plot.explore(geomap=True) + + +def test_pdkplotter_properties(ac_dc_network): + n = ac_dc_network + plotter = interactive.PydeckPlotter(n, map_style="dark") + + assert plotter.map_style == "dark" + assert isinstance(plotter._tooltip_style, dict) + assert isinstance(plotter._layers, dict) + + +def test_init_map_style_valid(): + class Dummy: + VALID_MAP_STYLES = {"light": "something", "dark": "something"} + _init_map_style = interactive.PydeckPlotter._init_map_style + + d = Dummy() + assert d._init_map_style("light") == "light" + assert d._init_map_style("dark") == "dark" + + +def test_init_map_style_invalid(): + class Dummy: + VALID_MAP_STYLES = {"light": "something"} + _init_map_style = interactive.PydeckPlotter._init_map_style + + d = Dummy() + with pytest.raises(ValueError, match="Invalid"): + d._init_map_style("foo") + + +def test_extra_columns(ac_dc_network, caplog): + n = ac_dc_network + plotter = interactive.PydeckPlotter(n, map_style="light") + + with caplog.at_level("WARNING"): + plotter.build_layers(line_columns=["bus0", "doesnotexist"]) + + # Check that a warning was logged + assert "not found" in caplog.text + + # Validate columns + df = plotter._component_data["Line"] + assert "bus0" in df.columns + assert "doesnotexist" not in df.columns + + +@pytest.mark.skipif(sys.platform != "linux", reason="Cartopy issues on macos.") +@pytest.mark.skipif(not cartopy_present, reason="Cartopy not installed") +def test_geomap_params(ac_dc_network): + n = ac_dc_network + plotter = interactive.PydeckPlotter(n, map_style="light") + + # Test geomap with default parameters + plotter.build_layers( + geomap=True, + geomap_alpha=1, + geomap_resolution="110m", + ) + plotter.deck() diff --git a/PyPSA/source/test/test_plot_maps_static.py b/PyPSA/source/test/test_plot_maps_static.py new file mode 100644 index 0000000000000000000000000000000000000000..6d115ff43c3e5cbb451daed68cf7a60b236b7566 --- /dev/null +++ b/PyPSA/source/test/test_plot_maps_static.py @@ -0,0 +1,340 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import os + +import matplotlib.pyplot as plt +import networkx as nx +import numpy as np +import pandas as pd +import pytest + +from pypsa.plot.maps.common import _is_cartopy_available +from pypsa.plot.maps.static import ( + add_legend_circles, + add_legend_lines, + add_legend_patches, + add_legend_semicircles, +) +from pypsa.statistics import get_transmission_branches, groupers + +# Use dynamic checking for test skipping +cartopy_present = _is_cartopy_available() + +# Import actual packages for test usage (these imports are conditional on availability) +if cartopy_present: + import cartopy.crs as ccrs + + +@pytest.mark.parametrize("margin", [None, 0.1]) +@pytest.mark.parametrize("jitter", [None, 1]) +@pytest.mark.skipif(os.name == "nt", reason="tcl_findLibrary on Windows") +def test_plot_standard_params_wo_geomap(ac_dc_network, margin, jitter): + n = ac_dc_network + n.plot.map(geomap=False, margin=margin, jitter=jitter) + plt.close() + + +@pytest.mark.skipif(not cartopy_present, reason="Cartopy not installed") +@pytest.mark.parametrize("margin", [None, 0.1]) +@pytest.mark.parametrize("jitter", [None, 1]) +def test_plot_standard_params_w_geomap(ac_dc_network, margin, jitter): + n = ac_dc_network + n.plot.map(geomap=True, margin=margin, jitter=jitter) + plt.close() + + +def test_plot_on_axis_wo_geomap(ac_dc_network): + n = ac_dc_network + fig, ax = plt.subplots() + n.plot.map(ax=ax, geomap=False) + plt.close() + + +@pytest.mark.skipif(not cartopy_present, reason="Cartopy not installed") +def test_plot_on_axis_w_geomap(ac_dc_network): + n = ac_dc_network + fig, ax = plt.subplots() + + with pytest.raises(ValueError): + n.plot.map(ax=ax, geomap=True) + plt.close() + + +def test_plot_bus_circles(ac_dc_network): + n = ac_dc_network + + bus_size = n.c.generators.static.groupby(["bus", "carrier"]).p_nom.mean() + bus_size[:] = 1 + bus_color = n.c.carriers.static.color + n.plot.map(bus_size=bus_size, bus_color=bus_color, geomap=False) + plt.close() + + # Retrieving the colors from carriers also should work + n.c.carriers.static["color"] = bus_color + n.plot.map(bus_size=bus_size) + plt.close() + + # Retrieving the colors from carriers also should work + n.c.carriers.static["color"] = bus_color + n.plot.map(bus_size=bus_size) + plt.close() + + +def test_plot_split_circles(ac_dc_network): + n = ac_dc_network + + gen_sizes = n.c.generators.static.groupby(["bus", "carrier"]).p_nom.sum() + gen_sizes[:] = 500 + n.loads.carrier = "load" + load_sizes = ( + -n.c.loads.dynamic.p_set.mean() + .groupby([n.c.loads.static.bus, n.c.loads.static.carrier]) + .max() + ) + bus_size = pd.concat((gen_sizes, load_sizes)) / 1e3 + bus_color = n.c.carriers.static.color + n.plot.map( + bus_size=bus_size, bus_color=bus_color, bus_split_circle=True, geomap=False + ) + plt.close() + + +def test_plot_with_bus_cmap(ac_dc_network): + n = ac_dc_network + + buses = n.c.buses.static.index + rng = np.random.default_rng() # Create a random number generator + colors = pd.Series(rng.random(size=len(buses)), buses) + n.plot.map(bus_color=colors, bus_cmap="coolwarm", geomap=False) + plt.close() + + +def test_plot_with_line_cmap(ac_dc_network): + n = ac_dc_network + + lines = n.c.lines.static.index + rng = np.random.default_rng() # Create a random number generator + colors = pd.Series(rng.random(size=len(lines)), lines) + n.plot.map(line_color=colors, line_cmap="coolwarm", geomap=False) + plt.close() + + +def test_plot_alpha(ac_dc_network): + n = ac_dc_network + + bus_size = n.c.generators.static.groupby(["bus", "carrier"]).p_nom.mean() + bus_size[:] = 1 + bus_color = n.c.carriers.static.color + n.plot.map( + bus_size=bus_size, + bus_color=bus_color, + geomap=False, + bus_alpha=0.5, + line_alpha=0.5, + link_alpha=0.5, + ) + plt.close() + + # Retrieving the colors from carriers also should work + n.c.carriers.static["color"] = bus_color + n.plot.map(bus_size=bus_size) + plt.close() + + +def test_plot_line_subset(ac_dc_network): + n = ac_dc_network + + lines = n.c.lines.static.index[:2] + rng = np.random.default_rng() # Create a random number generator + colors = pd.Series(rng.random(size=len(lines)), lines) + n.plot.map(line_color=colors, line_cmap="coolwarm", geomap=False) + plt.close() + + +def test_plot_bus_subset(ac_dc_network): + n = ac_dc_network + + buses = n.c.buses.static.index[:2] + rng = np.random.default_rng() # Create a random number generator + colors = pd.Series(rng.random(size=len(buses)), buses) + n.plot.map(bus_color=colors, bus_cmap="coolwarm", geomap=False) + plt.close() + + bus_size = n.c.generators.static.groupby(["bus", "carrier"]).p_nom.mean()[:3] + bus_size[:] = 1 + bus_color = n.c.carriers.static.color + n.plot.map( + bus_size=bus_size, + bus_color=bus_color, + geomap=False, + bus_alpha=0.5, + line_alpha=0.5, + link_alpha=0.5, + ) + plt.close() + + +def test_plot_from_statistics(ac_dc_network): + n = ac_dc_network + bus_carrier = "AC" + + grouper = groupers["bus", "carrier"] + bus_size = n.statistics.installed_capacity( + bus_carrier=bus_carrier, groupby=grouper, nice_names=False + ) + bus_size = bus_size.Generator + + transmission_branches = get_transmission_branches(n, bus_carrier=bus_carrier) + branch_width = n.statistics.installed_capacity(groupby=False).loc[ + transmission_branches + ] + + bus_scale = 5e-6 + branch_scale = 1e-4 + bus_color = n.c.carriers.static.color + + n.plot.map( + bus_size=bus_size * bus_scale, + bus_alpha=0.8, + bus_color=bus_color, + link_width=branch_width.get("Link", 0) * branch_scale, + line_width=branch_width.get("Line", 0) * branch_scale, + ) + plt.close() + + +def test_plot_layouter(ac_dc_network): + n = ac_dc_network + + n.plot.map(layouter=nx.layout.planar_layout, geomap=False) + plt.close() + + +def test_plot_map_flow(ac_dc_network): + n = ac_dc_network + + branches = n.branches() + lines = branches.loc["Line"] + line_flow = pd.Series(range(len(lines)), index=lines.index) + links = branches.loc["Link"] + link_flow = pd.Series(range(len(links)), index=links.index) + n.plot.map(line_flow=line_flow, link_flow=link_flow, geomap=False) + plt.close() + + n.c.lines.dynamic.p0.loc[:, line_flow.index] = 0 + n.c.lines.dynamic.p0 += line_flow + n.plot.map(line_flow="mean", geomap=False) + plt.close() + + n.plot.map(line_flow=n.snapshots[0], geomap=False) + plt.close() + + +def test_plot_map_line_colorbar(ac_dc_network): + n = ac_dc_network + + norm = plt.Normalize(vmin=0, vmax=10) + + n.plot.map( + line_color=n.c.lines.static.index.astype(int), + line_cmap="viridis", + line_cmap_norm=norm, + ) + + plt.colorbar(plt.cm.ScalarMappable(cmap="viridis", norm=norm), ax=plt.gca()) + + +def test_plot_map_bus_colorbar(ac_dc_network): + n = ac_dc_network + + norm = plt.Normalize(vmin=0, vmax=10) + + n.plot.map(bus_color=n.c.buses.static.x, bus_cmap="viridis", bus_cmap_norm=norm) + + plt.colorbar(plt.cm.ScalarMappable(cmap="viridis", norm=norm), ax=plt.gca()) + + +def test_plot_legend_lines(ac_dc_network): + n = ac_dc_network + + fig, ax = plt.subplots() + n.plot.map(ax=ax, geomap=False) + + add_legend_lines( + ax, + [2, 5], + ["label a", "label b"], + patch_kw={"alpha": 0.5}, + legend_kw={"frameon": False}, + ) + + plt.close() + + +def test_plot_legend_patches(ac_dc_network): + n = ac_dc_network + + fig, ax = plt.subplots() + n.plot.map(ax=ax, geomap=False) + + add_legend_patches( + ax, + ["r", "g", "b"], + ["red", "green", "blue"], + legend_kw={"frameon": False}, + ) + + plt.close() + + +def test_plot_legend_circles_no_geomap(ac_dc_network): + n = ac_dc_network + + fig, ax = plt.subplots() + n.plot.map(ax=ax, geomap=False) + + add_legend_circles(ax, 1, "reference size") + + plt.close() + + +@pytest.mark.skipif(not cartopy_present, reason="Cartopy not installed") +def test_plot_legend_circles_geomap(ac_dc_network): + n = ac_dc_network + + fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) + n.plot.map(ax=ax, geomap=True) + + add_legend_circles(ax, [1, 0.5], ["reference A", "reference B"]) + + plt.close() + + +@pytest.mark.skipif(not cartopy_present, reason="Cartopy not installed") +def test_plot_legend_semicircles_geomap(ac_dc_network): + n = ac_dc_network + + fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) + n.plot.map(ax=ax, geomap=True) + + add_legend_semicircles(ax, [1, 0.5], ["reference A", "reference B"]) + + plt.close() + + +def test_plot_alias_for_plot_map(ac_dc_network): + """Test that n.plot() is an alias for n.plot.map().""" + n = ac_dc_network + + # Both should return the same type of object and produce equivalent plots + # Test without geomap to avoid external dependencies + result1 = n.plot(geomap=False) + plt.close() + + result2 = n.plot.map(geomap=False) + plt.close() + + # Both should return the same type of object + assert type(result1) is type(result2) diff --git a/PyPSA/source/test/test_riskaversion.py b/PyPSA/source/test/test_riskaversion.py new file mode 100644 index 0000000000000000000000000000000000000000..ba4248d37c849456ca682f0810e2ba652f847288 --- /dev/null +++ b/PyPSA/source/test/test_riskaversion.py @@ -0,0 +1,353 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pytest +from numpy.testing import assert_array_almost_equal as almost_equal + +import pypsa + + +def test_set_risk_preference_requires_scenarios(): + n = pypsa.Network() + # No scenarios defined: should raise RuntimeError + with pytest.raises(ValueError, match=r"set_scenarios\(\)"): + n.set_risk_preference(alpha=0.1, omega=0.5) + + +def test_set_and_get_risk_preference_dict_and_flags(): + n = pypsa.Network() + # Define scenarios + n.set_scenarios(["s1", "s2"]) # equal weights 0.5/0.5 + + # Initially, should not have risk preference + assert n.has_risk_preference is False + assert n.risk_preference is None + + # Set valid preferences + n.set_risk_preference(alpha=0.2, omega=0.7) + + # Getter returns dict + rp = n.risk_preference + assert isinstance(rp, dict) + assert pytest.approx(rp["alpha"]) == 0.2 + assert pytest.approx(rp["omega"]) == 0.7 + + # Boolean flag flips to True + assert n.has_risk_preference is True + + +@pytest.mark.parametrize( + ("alpha", "omega", "err", "pattern"), + [ + (-0.1, 0.5, ValueError, "Alpha must be between 0 and 1"), + (1.0, 0.5, ValueError, "Alpha must be between 0 and 1"), + (0.2, -0.01, ValueError, "Omega must be between 0 and 1"), + (0.2, 1.01, ValueError, "Omega must be between 0 and 1"), + ], +) +def test_set_risk_preference_validation(alpha, omega, err, pattern): + n = pypsa.Network() + n.set_scenarios({"a": 0.4, "b": 0.6}) + with pytest.raises(err, match=pattern): + n.set_risk_preference(alpha=alpha, omega=omega) + + +@pytest.fixture +def toy_network(): + n = pypsa.examples.model_energy() + n.add( + "Generator", + "gas", + carrier="gas", + bus="electricity", + p_nom=5000, + efficiency=0.5, + marginal_cost=100, + ) + # downsample to first week of July (7 days at 3-hourly resolution -> 56 snapshots) + week_idx = n.snapshots[(n.snapshots.month == 7) & (n.snapshots.day <= 7)] + n.snapshots = week_idx + n.snapshot_weightings = n.snapshot_weightings * 8760 / len(week_idx) + return n + + +def _capacities_mw(n: pypsa.Network): + # A quick helper to return installed capacities as MW Series indexed by (component, carrier) for almost_equal check + cap = n.statistics.optimal_capacity() + if "scenario" in cap.index.names: + without_scen = [lvl for lvl in cap.index.names if lvl != "scenario"] + cap = cap.groupby(level=without_scen).first() + cap = cap.groupby(level=["component", "carrier"]).sum() + return cap.sort_index() + + +def _objective_bil(n: pypsa.Network) -> float: + assert n.objective is not None + return n.objective / 1e9 + + +def test_risk_neutral_equivalence_omega_zero(toy_network): + n = toy_network.copy() + + # Stochastic risk-neutral (omega=0) + n.set_scenarios({"volcano": 0.1, "no_volcano": 0.9}) + # degrade solar in volcano + n.generators_t.p_max_pu.loc[:, ("volcano", "solar")] *= 0.3 + + # Risk neutral run + n.optimize(log_to_console=False) + cap_stoch_neutral = _capacities_mw(n) + obj_stoch_neutral = _objective_bil(n) + + # CVaR with omega=0 should equal risk-neutral (same model/objective) + n.set_risk_preference(alpha=0.2, omega=0.0) + n.optimize(log_to_console=False) + cap_cvar_w0 = _capacities_mw(n) + obj_cvar_w0 = _objective_bil(n) + + almost_equal(cap_stoch_neutral.values, cap_cvar_w0.values) + assert obj_stoch_neutral == pytest.approx(obj_cvar_w0, rel=1e-8, abs=1e-8) + + +def test_worst_case_equivalence_omega_one_single_tail(toy_network): + n = toy_network.copy() + + # Deterministic: volcano-only as worst case baseline + nd = toy_network.copy() + # degrade solar in baseline w/o scenarios + nd.generators_t.p_max_pu.loc[:, ("solar")] *= 0.3 + nd.optimize(log_to_console=False) + cap_det_worst = _capacities_mw(nd) + obj_det_worst = _objective_bil(nd) + + # Stochastic with two scenarios + n.set_scenarios({"volcano": 0.1, "no_volcano": 0.9}) + n.generators_t.p_max_pu.loc[:, ("volcano", "solar")] *= 0.3 + + # CVaR with omega=1 and alpha capturing the worst scenario only + # For p_worst = 0.1, set alpha = 1 - p_worst = 0.9 so 1/(1-alpha)*p_worst = 1 + p_worst = n.scenario_weightings.loc["volcano", "weight"] + n.set_risk_preference(alpha=float(1.0 - p_worst), omega=1.0) + n.optimize(log_to_console=False) + cap_cvar_w1 = _capacities_mw(n) + obj_cvar_w1 = _objective_bil(n) + + almost_equal(cap_det_worst.values, cap_cvar_w1.values) + assert obj_det_worst == pytest.approx(obj_cvar_w1, rel=1e-8, abs=1e-8) + + +def test_monotone_objective_vs_omega(toy_network): + n = toy_network.copy() + + # Setup stochastic + n.set_scenarios({"volcano": 0.1, "no_volcano": 0.9}) + n.generators_t.p_max_pu.loc[:, ("volcano", "solar")] *= 0.3 + + # Risk-neutral baseline + n.optimize(log_to_console=False) + obj_neutral = _objective_bil(n) + + # Evaluate CVaR objective for increasing omega values + alpha = 0.9 + objectives = [] + for omega in [0.25, 0.5, 0.75]: + n.set_risk_preference(alpha=alpha, omega=omega) + n.optimize(log_to_console=False) + objectives.append(_objective_bil(n)) + + obj_025, obj_050, obj_075 = objectives + eps = 1e-8 + + # Ensure strict monotonicity: obj_neutral < obj(0.25) < obj(0.5) < obj(0.75) + assert obj_neutral < obj_025 - eps + assert obj_025 < obj_050 - eps + assert obj_050 < obj_075 - eps + + +def test_cvar_constraints_cover_many_settings(): + """Test CVaR optimization for multiple components and settings, incl. storage_units and stores, spillage, and unit commitment. + + This test builds a tiny stochastic network with: + - Store with non-zero marginal_cost_storage (uses Store-e) + - StorageUnit with non-zero spill_cost (uses StorageUnit-spill) + - Committable Generator with stand_by_cost and start/shutdown costs + + Then enables CVaR and checks that CVaR constraints exist in the model. + """ + import pandas as pd + + n = pypsa.Network(snapshots=pd.RangeIndex(0, 4)) + n.add("Carrier", ["elec", "gas"]) # just to be explicit + n.add("Bus", "b", carrier="elec") + + # Load + n.add("Load", "d", bus="b", p_set=[80, 120, 90, 110]) + + # Generator with marginal and stand_by costs + n.add( + "Generator", + "gen_quad", + bus="b", + p_nom=200, + marginal_cost=5, + carrier="elec", + stand_by_cost=1.0, + ) + + # Store with marginal_cost_storage + n.add( + "Store", + "store1", + bus="b", + e_nom=100, + p_nom=100, + marginal_cost_storage=1.5, # currency/MWh/h + ) + + # StorageUnit with spill cost + n.add( + "StorageUnit", + "su1", + bus="b", + p_nom=120, + max_hours=2, + spill_cost=3.0, + ) + + # Stochastic setup + CVaR + n.set_scenarios({"s1": 0.5, "s2": 0.5}) + n.set_risk_preference(alpha=0.5, omega=0.2) + + # Small scenario-specific variation to avoid degenerate equivalence + n.c.loads.dynamic.p_set.loc[:, ("s2", "d")] = [90, 130, 95, 115] + + status, cond = n.optimize(log_to_console=False, solver_name="highs") + assert status == "ok" + assert cond == "optimal" + + # CVaR constraints created correctly + assert "CVaR-def" in n.model.constraints + for s in n.scenarios: + key = f"CVaR-excess-{s}" + assert key in n.model.constraints + + +def test_cvar_constraints_multiperiod_opt(): + """Cover CVaR weighting path for multi-period investment optimization.""" + n = pypsa.Network(snapshots=range(3)) + n.investment_periods = [2020, 2030] + + n.add("Carrier", "elec") + n.add("Bus", "b", carrier="elec") + + # Two extendable generators in different periods + n.add( + "Generator", + "g20", + bus="b", + build_year=2020, + lifetime=40, + p_nom_extendable=True, + capital_cost=50, + marginal_cost=1, + ) + n.add( + "Generator", + "g30", + bus="b", + build_year=2030, + lifetime=40, + p_nom_extendable=True, + capital_cost=20, + marginal_cost=5, + ) + + n.add("Load", "d", bus="b", p_set=[100, 120, 110, 90, 105, 95]) + + # Scenarios + CVaR + n.set_scenarios({"high": 0.1, "low": 0.9}) + n.set_risk_preference(alpha=0.9, omega=0.5) + + n.c.generators.static.loc[("low", "g20"), "marginal_cost"] = 1.0 + n.c.generators.static.loc[("high", "g20"), "marginal_cost"] = 100.0 + + status, cond = n.optimize(multi_investment_periods=True, log_to_console=False) + assert status == "ok" + assert cond == "optimal" + + # CVaR constraints present + assert "CVaR-def" in n.model.constraints + for s in n.scenarios: + assert f"CVaR-excess-{s}" in n.model.constraints + + +def test_cvar_with_quadratic_opex_raises(): + """Ensure we fail fast when CVaR is enabled together with quadratic marginal costs. + + The guard should raise a ValueError with a clear message before model solve + when `marginal_cost_quadratic` terms are present (unsupported with CVaR). + """ + import pandas as pd + + n = pypsa.Network(snapshots=pd.RangeIndex(0, 3)) + n.add("Carrier", "elec") + n.add("Bus", "b", carrier="elec") + n.add("Load", "d", bus="b", p_set=[50, 60, 55]) + + # Generator with quadratic marginal cost + n.add( + "Generator", + "gq", + bus="b", + p_nom=200, + marginal_cost=0.0, + marginal_cost_quadratic=0.01, # triggers quadratic OPEX calc + ) + + # Stochastic setup + CVaR + n.set_scenarios({"s1": 0.5, "s2": 0.5}) + n.set_risk_preference(alpha=0.5, omega=0.3) + + with pytest.raises(ValueError, match=r"CVaR with quadratic operational costs"): + n.optimize(log_to_console=False) + + +def test_objective_without_any_costs_raises(): + """Cover optimize.py guard when neither CAPEX nor OPEX terms exist.""" + n = pypsa.Network(snapshots=range(2)) + n.add("Carrier", "elec") + n.add("Bus", "b", carrier="elec") + n.add("Load", "d", bus="b", p_set=[1.0, 1.0]) + n.add("Generator", "g", bus="b", p_nom=5.0) + + # No capex/opex terms — objective cannot be formed + with pytest.raises(ValueError, match=r"Objective function could not be created"): + n.optimize(log_to_console=False) + + +def test_objective_includes_standby_cost_for_committable(): + """Trigger more OPEX terms in aux constraints""" + + n = pypsa.Network(snapshots=range(2)) + n.add("Bus", "b", carrier="elec") + # Remember that at t=0, we do not charge a start_up by default + n.add("Load", "d", bus="b", p_set=[0, 50]) + + # Committable generator with non-zero stand-by cost and start-up cost + n.add( + "Generator", + "gc", + bus="b", + p_nom=200, + marginal_cost=10.0, + committable=True, + p_min_pu=0.1, + stand_by_cost=1.0, + start_up_cost=5.0, + ) + + status, cond = n.optimize(log_to_console=False, solver_name="highs") + assert status == "ok" + assert cond == "optimal" + assert n.objective == 506.0 # 10*50 + 5 + 1 diff --git a/PyPSA/source/test/test_sclopf_scigrid.py b/PyPSA/source/test/test_sclopf_scigrid.py new file mode 100644 index 0000000000000000000000000000000000000000..994e7ef95e213aed5e1cc952c1039b7235fc0d73 --- /dev/null +++ b/PyPSA/source/test/test_sclopf_scigrid.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +from numpy.testing import assert_almost_equal as equal + + +def test_optimize_security_constrained(scipy_network): + """Test security-constrained optimization functionality and dual variable assignment.""" + n = scipy_network + + # There are some infeasibilities without line extensions + for line_name in ["316", "527", "602"]: + n.c.lines.static.loc[line_name, "s_nom"] = 1200 + + # Choose the contingencies + branch_outages = n.c.lines.static.index[:2] + + # # Run security-constrained optimization with dual assignment + # # Fight numerical instability using https://ergo-code.github.io/HiGHS/ + solver_options = { + "primal_feasibility_tolerance": 1e-9, + "dual_feasibility_tolerance": 1e-9, + "time_limit": 300, + "presolve": "on", + "parallel": "off", + "random_seed": 123, + } + + n.optimize.optimize_security_constrained( + n.snapshots[0], + branch_outages=branch_outages, + assign_all_duals=True, + solver_options=solver_options, + ) + + # For the PF, set the P to the optimised P + n.c.generators.dynamic.p_set = n.c.generators.dynamic.p.copy() + n.c.storage_units.dynamic.p_set = n.c.storage_units.dynamic.p.copy() + + # TODO see https://github.com/PyPSA/PyPSA/issues/1356 + + # # Check no lines are overloaded with the linear contingency analysis + # p0_test = n.lpf_contingency(n.snapshots[0], branch_outages=branch_outages) + + # # Check loading as per unit of s_nom in each contingency + # max_loading = ( + # abs(p0_test.divide(n.passive_branches().s_nom, axis=0)).describe().loc["max"] + # ) + + # arr_equal(max_loading, np.ones(len(max_loading)), decimal=4) + equal(n.objective, 339758.4578, decimal=1) + + # === Dual variable assignment checks === + + # Verify that marginal prices are assigned (nodal balance duals) + assert hasattr(n.c.buses.dynamic, "marginal_price"), ( + "Marginal prices should be assigned" + ) + assert not n.c.buses.dynamic.marginal_price.empty, ( + "Marginal prices should not be empty" + ) + + # Check that line constraint duals are assigned to n.c.lines.dynamic.mu_* + line_dual_attrs = [ + attr for attr in n.c.lines.dynamic.keys() if attr.startswith("mu_") + ] + + # Verify that standard line duals are assigned + assert "mu_lower" in line_dual_attrs, "mu_lower should be assigned" + assert "mu_upper" in line_dual_attrs, "mu_upper should be assigned" + + # Check for security constraint duals + # TODO add again when dual is written to custom constraint + # security_duals = [attr for attr in line_dual_attrs if "SubNetwork" in str(attr)] + # assert len(security_duals) == 2, ( + # "Should have two security constraint duals assigned" + # ) + + # Verify security constraint duals exist and can be converted + security_constraints = [ + name for name in n.model.dual.data_vars if "security" in name + ] + assert len(security_constraints) == 4, "Should have four security constraint duals" diff --git a/PyPSA/source/test/test_snapshot_weightings.py b/PyPSA/source/test/test_snapshot_weightings.py new file mode 100644 index 0000000000000000000000000000000000000000..9dc41233eb6618cf7bec31f56f97c62ac9a6e087 --- /dev/null +++ b/PyPSA/source/test/test_snapshot_weightings.py @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pandas as pd +import pytest + +import pypsa + + +@pytest.fixture +def time_index(): + start_time = pd.Timestamp("2023-01-01T00:00:00") + end_time = pd.Timestamp("2023-01-31T00:00:00") + freq = "7D" + return pd.date_range(start=start_time, end=end_time, freq=freq) + + +def test_snapshot_weightings_with_timedelta(time_index): + n = pypsa.Network() + + hours_per_step = ( + time_index.to_series() + .diff(periods=1) + .shift(-1) # move index forward + .ffill() # fill the last value (assume same as the one before) + .apply(lambda x: x.total_seconds() / 3600) + ) + + df_true = pd.DataFrame(dict.fromkeys(n.snapshot_weightings.columns, hours_per_step)) + df_true.index.name = "snapshot" + + n.set_snapshots(time_index, weightings_from_timedelta=True) + df_actual = n.snapshot_weightings + + pd.testing.assert_frame_equal(df_true, df_actual) + + +def test_default_snapshot_weightings(time_index): + n = pypsa.Network() + + weightings = pd.Series(2.0, index=time_index) + df_true = pd.DataFrame(dict.fromkeys(n.snapshot_weightings.columns, weightings)) + df_true.index.name = "snapshot" + + n.set_snapshots(time_index, default_snapshot_weightings=2.0) + df_actual = n.snapshot_weightings + + pd.testing.assert_frame_equal(df_true, df_actual) diff --git a/PyPSA/source/test/test_spatial_clustering.py b/PyPSA/source/test/test_spatial_clustering.py new file mode 100644 index 0000000000000000000000000000000000000000..f2e5634aa6e2924df8f5edbf90a6a16eb2b1ae88 --- /dev/null +++ b/PyPSA/source/test/test_spatial_clustering.py @@ -0,0 +1,250 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import numpy as np +import pandas as pd +import pytest + +import pypsa +from pypsa.clustering.spatial import ( + aggregateoneport, + busmap_by_hac, + busmap_by_kmeans, + get_clustering_from_busmap, + normed_or_uniform, +) + + +def test_aggregate_generators(ac_dc_network): + n = ac_dc_network + busmap = pd.Series("all", n.c.buses.static.index) + df, dynamic = aggregateoneport(n, busmap, "Generator") + + assert ( + df.loc["all gas", "p_nom"] + == n.c.generators.static.query("carrier == 'gas'").p_nom.sum() + ) + assert ( + df.loc["all wind", "p_nom"] + == n.c.generators.static.query("carrier == 'wind'").p_nom.sum() + ) + + capacity_norm = normed_or_uniform( + n.c.generators.static.query("carrier == 'wind'").p_nom + ) + assert np.allclose( + dynamic["p_max_pu"]["all wind"], + (n.c.generators.dynamic.p_max_pu * capacity_norm).sum(axis=1), + ) + assert np.allclose( + df.loc["all wind", "marginal_cost"], + (n.c.generators.static.marginal_cost * capacity_norm).sum(), + ) + + +def test_aggregate_generators_custom_strategies(ac_dc_network): + n = ac_dc_network + n.c.generators.static.loc["Frankfurt Wind", "p_nom_max"] = 100 + + busmap = pd.Series("all", n.c.buses.static.index) + + strategies = {"p_max_pu": "max", "p_nom_max": "weighted_min"} + df, dynamic = aggregateoneport(n, busmap, "Generator", custom_strategies=strategies) + + assert ( + df.loc["all gas", "p_nom"] + == n.c.generators.static.query("carrier == 'gas'").p_nom.sum() + ) + assert ( + df.loc["all wind", "p_nom"] + == n.c.generators.static.query("carrier == 'wind'").p_nom.sum() + ) + assert ( + df["p_nom_max"]["all wind"] + == n.c.generators.static.loc["Frankfurt Wind", "p_nom_max"] * 3 + ) + assert np.allclose( + dynamic["p_max_pu"]["all wind"], n.c.generators.dynamic.p_max_pu.max(axis=1) + ) + + +def test_aggregate_generators_consent_error(ac_dc_network): + n = ac_dc_network + n.add( + "Generator", + "Manchester Wind 2", + bus="Manchester", + carrier="wind", + p_nom_extendable=False, + ) + + busmap = pd.Series("all", n.c.buses.static.index) + + with pytest.raises(ValueError): + df, dynamic = aggregateoneport(n, busmap, "Generator") + + +def test_aggregate_storage_units(ac_dc_network): + n = ac_dc_network + + n.add( + "StorageUnit", + "Frankfurt Storage", + bus="Frankfurt", + p_nom_extendable=True, + p_nom_max=100, + p_nom=100, + marginal_cost=10, + capital_cost=100, + ) + n.add( + "StorageUnit", + "Manchester Storage", + bus="Manchester", + p_nom_extendable=True, + p_nom_max=200, + p_nom=200, + marginal_cost=30, + capital_cost=50, + ) + + busmap = pd.Series("all", n.c.buses.static.index) + df, dynamic = aggregateoneport(n, busmap, "StorageUnit") + capacity_norm = normed_or_uniform(n.c.storage_units.static.p_nom) + + assert df.loc["all", "p_nom"] == n.c.storage_units.static.p_nom.sum() + assert ( + df.loc["all", "p_nom_extendable"] + == n.c.storage_units.static.p_nom_extendable.all() + ) + assert df.loc["all", "p_nom_min"] == n.c.storage_units.static.p_nom_min.sum() + assert df.loc["all", "p_nom_max"] == n.c.storage_units.static.p_nom_max.sum() + assert ( + df.loc["all", "marginal_cost"] + == (n.c.storage_units.static.marginal_cost * capacity_norm).sum() + ) + assert ( + df.loc["all", "capital_cost"] + == (n.c.storage_units.static.capital_cost * capacity_norm).sum() + ) + + +def test_aggregate_storage_units_consent_error(ac_dc_network): + n = ac_dc_network + n.add("StorageUnit", "Bremen Storage", bus="Bremen", p_nom_extendable=False) + + +def prepare_network_for_aggregation(n): + n.c.lines.static = n.c.lines.static.reindex( + columns=n.components["Line"]["defaults"].index[1:] + ) + n.c.lines.static["type"] = np.nan + n.c.buses.static = n.c.buses.static.reindex( + columns=n.components["Bus"]["defaults"].index[1:] + ) + n.c.buses.static["frequency"] = 50 + + +def test_default_clustering_k_means(scipy_network): + n = scipy_network + prepare_network_for_aggregation(n) + weighting = pd.Series(1, n.c.buses.static.index) + busmap = busmap_by_kmeans(n, bus_weightings=weighting, n_clusters=50) + C = get_clustering_from_busmap(n, busmap) + nc = C.n + assert len(nc.buses) == 50 + + +def test_default_clustering_hac(scipy_network): + n = scipy_network + prepare_network_for_aggregation(n) + busmap = busmap_by_hac(n, n_clusters=50) + C = get_clustering_from_busmap(n, busmap) + nc = C.n + assert len(nc.buses) == 50 + + +def test_cluster_accessor(scipy_network): + n = scipy_network + prepare_network_for_aggregation(n) + + weighting = pd.Series(1, n.c.buses.static.index) + busmap = n.cluster.busmap_by_kmeans( + bus_weightings=weighting, n_clusters=50, random_state=42 + ) + buses = n.cluster.cluster_by_busmap(busmap).buses + + buses_direct = n.cluster.cluster_spatially_by_kmeans( + bus_weightings=weighting, n_clusters=50, random_state=42 + ).buses + assert buses.equals(buses_direct) + + +def test_custom_line_groupers(scipy_network): + n = scipy_network + random_build_years = [1900, 2000] + rng = np.random.default_rng() + n.c.lines.static.loc[:, "build_year"] = rng.choice( + random_build_years, size=len(n.c.lines.static) + ) + prepare_network_for_aggregation(n) + weighting = pd.Series(1, n.c.buses.static.index) + busmap = busmap_by_kmeans(n, bus_weightings=weighting, n_clusters=20) + C = get_clustering_from_busmap(n, busmap, custom_line_groupers=["build_year"]) + linemap = C.linemap + nc = C.n + assert len(nc.buses) == 20 + assert (n.c.lines.static.groupby(linemap).build_year.nunique() == 1).all() + + +def test_clustering_multiport_links(): + """Test that clustering correctly remaps all bus ports in multi-port links. + + See https://github.com/PyPSA/PyPSA/issues/1439 + """ + + # Create network with multilink + n = pypsa.Network() + n.add("Bus", "gas") + n.add("Bus", "heat") + n.add("Bus", "power A") + n.add("Bus", "power B") + n.add("Bus", "power C") + n.add( + "Link", + "CHP", + bus0="gas", + bus1="heat", + bus2="power A", + efficiency=0.6, + efficiency2=0.3, + ) + + # Create busmap that clusters power buses together + busmap = pd.Series( + ["gas", "heat", "power", "power", "power"], + index=["gas", "heat", "power A", "power B", "power C"], + ) + + # Apply clustering + C = get_clustering_from_busmap(n, busmap) + n = C.n + + # Assert that bus2 is correctly remapped to "power" + assert n.c.links.static.loc["CHP", "bus2"] == "power", ( + f"bus2 should be remapped to 'power', got {n.c.links.static.loc['CHP', 'bus2']}" + ) + + # Assert that the old bus "power A" no longer exists + assert "power A" not in n.c.buses.static.index, "Old bus 'power A' should not exist" + + # Assert all buses referenced by the link exist in the clustered network + for col in ["bus0", "bus1", "bus2"]: + bus_name = n.c.links.static.loc["CHP", col] + assert bus_name in n.c.buses.static.index, ( + f"{col}={bus_name} not found in buses" + ) + + # Assert total number of buses is correct + assert len(n.c.buses.static) == 3, f"Expected 3 buses, got {len(n.c.buses.static)}" diff --git a/PyPSA/source/test/test_stand_by_cost.py b/PyPSA/source/test/test_stand_by_cost.py new file mode 100644 index 0000000000000000000000000000000000000000..1dedc9142a5b9c72f3debb0886ee1d7cec6e9352 --- /dev/null +++ b/PyPSA/source/test/test_stand_by_cost.py @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import numpy as np +from numpy.testing import assert_array_almost_equal as equal + +import pypsa + + +def test_stand_by_cost(): + """ + This test is based on https://docs.pypsa.org/en/latest/examples/unit- + commitment.html and is not very comprehensive. + """ + n = pypsa.Network() + + snapshots = range(4) + + n.set_snapshots(snapshots) + + n.add("Bus", "bus") + + n.add( + "Generator", + "coal", + bus="bus", + committable=True, + p_min_pu=0.3, + marginal_cost=20, + p_nom=10000, + ) + + n.add( + "Generator", + "gas", + bus="bus", + committable=True, + marginal_cost=70, + stand_by_cost=10, + p_min_pu=0.1, + p_nom=1000, + ) + + n.add("Load", "load", bus="bus", p_set=[4000, 6000, 5000, 800]) + + n.optimize() + + cost = ( + n.c.generators.dynamic.p * n.c.generators.static.marginal_cost + + ( + n.c.generators.dynamic.status.reindex( + columns=n.c.generators.static.index, fill_value=0 + ) + * n.c.generators.static.stand_by_cost + ) + ).mul(n.snapshot_weightings.objective, axis=0) + + expected_cost = np.array([80000, 120000, 100000, 56010], dtype=float).T + + equal(cost.sum(1), expected_cost) + + equal(sum(expected_cost), n.objective) diff --git a/PyPSA/source/test/test_statistics.py b/PyPSA/source/test/test_statistics.py new file mode 100644 index 0000000000000000000000000000000000000000..330436047ec5bc44b1352ca9ef5d4149770a5e60 --- /dev/null +++ b/PyPSA/source/test/test_statistics.py @@ -0,0 +1,347 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import numpy as np +import pandas as pd +import pytest + +import pypsa +from pypsa.statistics import groupers +from pypsa.statistics.expressions import StatisticsAccessor + + +@pytest.mark.parametrize("stat_func", StatisticsAccessor._methods) +def test_all_methods(ac_dc_network_r, stat_func): + df = getattr(ac_dc_network_r.statistics, stat_func) + assert not df().empty + + +def test_default_solved(ac_dc_network_r): + df = ac_dc_network_r.statistics() + assert not df.empty + + df = ac_dc_network_r.statistics.energy_balance() + assert not df.empty + assert ( + round( + df.groupby(level="bus_carrier").sum().sum() + / df.where(lambda x: x > 0).groupby(level="bus_carrier").sum().sum(), + 3, + ) + == 0 + ) + + +@pytest.mark.parametrize( + "groupby", + [ + "carrier", + groupers.carrier, + ["bus_carrier", "carrier"], + [groupers.bus_carrier, groupers.carrier], + ], +) +def test_grouping_by_keys_unsolved(ac_dc_network, groupby): + df = ac_dc_network.statistics(groupby=groupby) + assert not df.empty + + +@pytest.mark.parametrize( + "groupby", + [ + "carrier", + groupers.carrier, + ["bus_carrier", "carrier"], + [groupers.bus_carrier, groupers.carrier], + ["bus", "carrier"], + ["country", "carrier"], + ], +) +def test_grouping_by_keys_solved(ac_dc_network_r, groupby): + df = ac_dc_network_r.statistics(groupby=groupby) + assert not df.empty + + +def test_grouping_by_keys_with_specific_column_solved(ac_dc_network_r): + df = ac_dc_network_r.statistics(groupby=["bus0", "carrier"], components={"Link"}) + assert not df.empty + + +def test_grouping_by_new_registered_key(ac_dc_network_r): + def new_grouper(n, c): + return n.c[c].static.index.to_series() + + n = ac_dc_network_r + pypsa.statistics.groupers.add_grouper("new_grouper", new_grouper) + df = n.statistics.supply(groupby="new_grouper") + assert not df.empty + assert df.index.nlevels == 2 + + df = n.statistics.supply(groupby=["new_grouper", "carrier"], components="Link") + assert not df.empty + assert df.index.nlevels == 2 + + +def test_drop_zero(ac_dc_network): + n = ac_dc_network + df = n.statistics.optimal_capacity(drop_zero=True) + assert df.empty + + df = n.statistics.optimal_capacity() + assert df.empty + + df = n.statistics.optimal_capacity(drop_zero=False) + assert not df.empty + assert np.any(df == 0) + + +def test_zero_profit_rule_branches(ac_dc_network_r): + n = ac_dc_network_r + revenue = n.statistics.revenue(groupby_time="sum") + capex = n.statistics.capex() + comps = ["Line", "Link"] + assert np.allclose(revenue[comps], capex[comps]) + + +def test_net_and_gross_revenue(ac_dc_network_r): + n = ac_dc_network_r + target = n.statistics.revenue(groupby_time="sum") + revenue_out = n.statistics.revenue(groupby_time="sum", direction="output") + revenue_in = n.statistics.revenue(groupby_time="sum", direction="input") + revenue = revenue_in.add(revenue_out, fill_value=0) + comps = ["Generator", "Line", "Link"] + assert np.allclose(revenue[comps], target[comps]) + + +def test_supply_withdrawal(ac_dc_network_r): + n = ac_dc_network_r + target = n.statistics.energy_balance() + supply = n.statistics.energy_balance(direction="supply") + withdrawal = n.statistics.energy_balance(direction="withdrawal") + energy_balance = supply.sub(withdrawal, fill_value=0) + assert np.allclose(energy_balance.reindex(target.index), target) + + +def test_opex(): + n = pypsa.Network() + n.set_snapshots([0, 1, 2]) + n.snapshot_weightings.loc[:, :] = 2 + n.add("Bus", "bus") + n.add("Load", "load", bus="bus", p_set=[0, 0, 5]) + n.add( + "Generator", + "gen", + bus="bus", + carrier="gen", + p_nom=10, + p_max_pu=[0, 1, 0], + marginal_cost=2, + marginal_cost_quadratic=0.2, + ) + n.add( + "Store", + "sto", + bus="bus", + carrier="sto", + e_nom=10, + e_initial=0, + marginal_cost_storage=0.5, + ) + n.add("Bus", "bus2") + n.add( + "StorageUnit", + "su", + bus="bus2", + carrier="su", + marginal_cost=5, + p_nom=1, + max_hours=2, + inflow=1, + spill_cost=20, + ) + + n.optimize() + + opex = n.statistics.opex() + + assert opex.loc["Store", "sto"] == 2 * 0.5 * 10 + assert opex.loc["Generator", "gen"] == 2 * 2 * 5 + 2 * 0.2 * 5**2 + assert opex.loc["StorageUnit", "su"] == 2 * 20 * 2 + + n.c.generators.static.marginal_cost_quadratic = 0 + n.c.generators.static.committable = True + n.c.generators.static.start_up_cost = 4 + n.c.generators.static.shut_down_cost = 5 + n.c.generators.static.stand_by_cost = 9 + + n.optimize() + + opex = n.statistics.opex() + + assert opex.loc["Store", "sto"] == 2 * 0.5 * 10 + assert opex.loc["Generator", "gen"] == 2 * 2 * 5 + 1 * 4 + 2 * 5 + 2 * 1 * 9 + + opex = n.statistics.opex(cost_types="marginal_cost") + + assert opex.loc["Generator", "gen"] == 2 * 2 * 5 + with pytest.raises(KeyError): + opex.loc["StorageUnit", "su"] + with pytest.raises(KeyError): + opex.loc["Store", "sto"] + + opex = n.statistics.opex(cost_types="marginal_cost_storage") + + assert opex.loc["Store", "sto"] == 2 * 0.5 * 10 + with pytest.raises(KeyError): + opex.loc["Generator", "gen"] + + +def test_no_grouping(ac_dc_network_r): + df = ac_dc_network_r.statistics(groupby=False) + assert not df.empty + + +def test_no_time_aggregation(ac_dc_network_r): + df = ac_dc_network_r.statistics.supply(groupby_time=False) + assert not df.empty + assert isinstance(df, pd.DataFrame) + + +def test_carrier_selection(ac_dc_network_r): + n = ac_dc_network_r + df = n.statistics(carrier="AC") + assert not df.empty + assert "Line" in df.index.unique(0) + assert list(df.index.unique(1)) == ["AC"] + + df = n.statistics(carrier=["AC"]) + assert "Line" in df.index.unique(0) + assert list(df.index.unique(1)) == ["AC"] + + +def test_bus_carrier_selection(ac_dc_network_r): + df = ac_dc_network_r.statistics(groupby=False, bus_carrier="AC") + assert not df.empty + + +def test_bus_carrier_selection_with_list(ac_dc_network_r): + df = ac_dc_network_r.statistics( + groupby=groupers["bus", "carrier"], bus_carrier=["AC", "DC"] + ) + assert not df.empty + + +def test_storage_capacity(ac_dc_network_r): + n = ac_dc_network_r + df = n.statistics.installed_capacity(storage=True) + assert df.empty + + df = n.statistics.optimal_capacity(storage=True) + assert df.empty + + n.add("Store", "example", carrier="any", bus="Manchester", e_nom=10, e_nom_opt=5) + df = n.statistics.installed_capacity(storage=True) + assert not df.empty + assert df.sum() == 10 + + df = n.statistics.optimal_capacity(storage=True) + assert not df.empty + assert df.sum() == 5 + + +def test_single_component(ac_dc_network_r): + n = ac_dc_network_r + df = n.statistics.installed_capacity(components="Generator") + assert not df.empty + assert df.index.nlevels == 1 + + +def test_aggregate_across_components(ac_dc_network_r): + import warnings + + n = ac_dc_network_r + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + df = n.statistics.installed_capacity( + components=["Generator", "Line"], aggregate_across_components=True + ) + assert not df.empty + assert "component" not in df.index.names + + df = n.statistics.supply( + components=["Generator", "Line"], + aggregate_across_components=True, + groupby_time=False, + ) + assert not df.empty + assert "component" not in df.index.names + + +def test_multiindexed(ac_dc_periods): + n = ac_dc_periods + df = n.statistics() + assert not df.empty + assert df.columns.nlevels == 2 + assert df.columns.unique(1)[0] == 2013 + + +def test_multiindexed_aggregate_across_components(ac_dc_periods): + import warnings + + n = ac_dc_periods + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + df = n.statistics.installed_capacity( + components=["Generator", "Line"], aggregate_across_components=True + ) + assert not df.empty + assert "component" not in df.index.names + + +def test_inactive_exclusion_in_static(ac_dc_network_r): + n = ac_dc_network_r + df = n.statistics() + assert "Line" in df.index.unique(0) + + n.c.lines.static["active"] = False + df = n.statistics() + assert "Line" not in df.index.unique(0) + + n.c.lines.static["active"] = True + + +def test_transmission_carriers(ac_dc_network_r): + n = ac_dc_network_r + n.c.lines.static["carrier"] = "AC" + df = pypsa.statistics.get_transmission_carriers(ac_dc_network_r) + assert "AC" in df.unique(1) + + +def test_system_cost(ac_dc_network_r): + n = ac_dc_network_r + capex = n.statistics.capex().sum() + opex = n.statistics.opex().sum() + system_cost = n.statistics.system_cost().sum() + assert system_cost == capex + opex + + +def test_prices(ac_dc_network_r): + n = ac_dc_network_r + + # Test basic prices (load-weighted by default) + prices = n.statistics.prices() + assert isinstance(prices, pd.Series) + assert len(prices) == len(n.buses) + + time_weighted = n.statistics.prices(weighting="time") + load_weighted = n.statistics.prices(weighting="load") + assert not time_weighted.equals(load_weighted) + + # Test bus carrier filtering + ac_prices = n.statistics.prices(bus_carrier="AC") + assert len(ac_prices) == sum(n.c.buses.static.carrier == "AC") + + # Test groupby bus_carrier + grouped = n.statistics.prices(groupby="bus_carrier") + assert set(grouped.index) == set(n.c.buses.static.carrier.unique()) diff --git a/PyPSA/source/test/test_statistics_plot.py b/PyPSA/source/test/test_statistics_plot.py new file mode 100644 index 0000000000000000000000000000000000000000..306e38cb6523841ad47d725ca9dae695b8769dc1 --- /dev/null +++ b/PyPSA/source/test/test_statistics_plot.py @@ -0,0 +1,237 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import sys + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import pytest +import seaborn as sns + +from pypsa.consistency import ConsistencyError +from pypsa.plot.statistics.charts import CHART_TYPES, ChartGenerator +from pypsa.statistics.expressions import StatisticsAccessor + +# Set random seed for reproducibility +np.random.seed(42) # noqa: NPY002 + +plt.rcdefaults() +plt.rcParams["figure.figsize"] = [8, 6] +plt.rcParams["figure.dpi"] = 100 + + +@pytest.mark.skipif( + sys.version_info < (3, 13) or sys.platform not in ["darwin"], + reason="Run only once for stability.", +) +@pytest.mark.parametrize("stat_func", StatisticsAccessor._methods) +@pytest.mark.mpl_image_compare(tolerance=40) +def test_simple_plot(ac_dc_network_r, stat_func): + plotter = getattr(ac_dc_network_r.statistics, stat_func) + fig, _, _ = plotter.plot() + + return fig + + +@pytest.mark.skipif( + sys.version_info < (3, 13) or sys.platform not in ["darwin"], + reason="Run only once for stability.", +) +@pytest.mark.parametrize("stat_func", StatisticsAccessor._methods) +@pytest.mark.parametrize("kind", CHART_TYPES + ["map"]) +@pytest.mark.mpl_image_compare(tolerance=40) +def test_plot_types(ac_dc_network_r, stat_func, kind): + if kind == "map" and stat_func == "prices": + pytest.skip("Map plotting for 'prices' is not implemented.") + plotter = getattr(ac_dc_network_r.statistics, stat_func) + fig, *_ = plotter.plot(kind=kind) + + return fig + + +def test_to_long_format_static(ac_dc_network_r): + """Test the _to_long_format method with optimal_capacity data.""" + # Create the accessor instance + accessor = ChartGenerator(ac_dc_network_r) + + # Get optimal capacity data from statistics + data = ac_dc_network_r.statistics.optimal_capacity() + + # Convert to long format + long_data = accessor._to_long_format(data) + + # Check the output structure + assert isinstance(long_data, pd.DataFrame) + assert set(long_data.columns) == {"component", "carrier", "value"} + + +def test_to_long_format_dynamic(ac_dc_network_r): + """Test the _to_long_format method with installed_capacity data.""" + # Create the accessor instance + accessor = ChartGenerator(ac_dc_network_r) + + # Get installed capacity data from statistics + data = ac_dc_network_r.statistics.energy_balance() + + # Convert to long format + long_data = accessor._to_long_format(data) + + # Check the output structure + assert isinstance(long_data, pd.DataFrame) + assert set(long_data.columns) == {"component", "carrier", "bus_carrier", "value"} + + +def test_to_long_format_dynamic_multi(network_collection): + """Test the _to_long_format method with installed_capacity data.""" + # Create the accessor instance + accessor = ChartGenerator(network_collection) + + # Get installed capacity data from statistics + data = network_collection.statistics.energy_balance() + + # Convert to long format + long_data = accessor._to_long_format(data) + + # Check the output structure + assert isinstance(long_data, pd.DataFrame) + assert set(long_data.columns) == { + "component", + "carrier", + "bus_carrier", + "value", + }.union(network_collection.index.names) + + +def test_derive_statistic_parameters(ac_dc_network_r): + """Test derivation of statistic parameters""" + # TODO rewrite once function is updated + plotter = ChartGenerator(ac_dc_network_r) + + # Test with default parameters + stats_kwargs = plotter.derive_statistic_parameters("carrier", "value", "carrier") + assert stats_kwargs["groupby"] == ["carrier"] + assert stats_kwargs["aggregate_across_components"] + + # Test with default parameters + stats_kwargs = plotter.derive_statistic_parameters( + "carrier", "value", "bus_carrier" + ) + assert set(stats_kwargs["groupby"]) == {"bus_carrier", "carrier"} + assert stats_kwargs["aggregate_across_components"] + + +def test_get_carrier_colors_and_labels(ac_dc_network_r): + """Test carrier colors and labels retrieval""" + plotter = ChartGenerator(ac_dc_network_r) + + colors = plotter.get_carrier_colors() + assert isinstance(colors, dict) + assert "-" in colors + assert None in colors + + labels = plotter.get_carrier_labels() + assert isinstance(labels, pd.Series) + + # Test with nice_names=False + labels_raw = plotter.get_carrier_labels(nice_names=False) + assert isinstance(labels_raw, pd.Series) + assert (labels_raw.index.values == labels.values).all() + + +def test_query_filtering(ac_dc_network_r): + """Test query filtering in plots""" + plotter = ChartGenerator(ac_dc_network_r) + data = pd.Series([1, 2, 3], index=pd.Index(["a", "b", "c"], name="carrier")) + + fig, ax, g = plotter.plot(data, "bar", x="carrier", y="value", query="value > 1") + assert isinstance(fig, plt.Figure) + assert isinstance(ax, plt.Axes) + assert isinstance(g, sns.FacetGrid) + plt.close(fig) + + +def test_consistency_checks(ac_dc_network_r): + """Test plotting consistency checks""" + plotter = ChartGenerator(ac_dc_network_r) + n = ac_dc_network_r.copy() + plotter = ChartGenerator(n) + n.c.carriers.static.color = pd.Series() + # Test with missing carrier colors + with pytest.raises(ConsistencyError): + plotter.plot(data=pd.DataFrame(), kind="area", x="carrier", y="value") + + +def test_stacking(ac_dc_network_r): + """Test stacking options in bar plots""" + n = ac_dc_network_r + fig, ax, g = n.statistics.supply.plot.bar(x="carrier", y="value", stacked=True) + assert isinstance(fig, plt.Figure) + assert isinstance(ax, plt.Axes) + assert isinstance(g, sns.FacetGrid) + plt.close(fig) + + +@pytest.mark.parametrize("stat_func", StatisticsAccessor._methods) +def test_networks_simple_plot(network_collection, stat_func): + plotter = getattr(network_collection.statistics, stat_func) + fig, ax, g = plotter.plot() + assert isinstance(fig, plt.Figure) + assert isinstance(ax, plt.Axes) + assert isinstance(g, sns.FacetGrid) + + +@pytest.mark.parametrize("stat_func", StatisticsAccessor._methods) +def test_networks_bar_plot(network_collection, stat_func): + plotter = getattr(network_collection.statistics, stat_func) + fig, ax, g = plotter.plot.bar(facet_col="scenario") + assert isinstance(fig, plt.Figure) + assert isinstance(ax, plt.Axes) + assert isinstance(g, sns.FacetGrid) + plt.close(fig) + + +@pytest.mark.parametrize("stat_func", StatisticsAccessor._methods) +def test_networks_line_plot(network_collection, stat_func): + plotter = getattr(network_collection.statistics, stat_func) + fig, ax, g = plotter.plot.line(facet_col="scenario") + assert isinstance(fig, plt.Figure) + assert isinstance(ax, plt.Axes) + assert isinstance(g, sns.FacetGrid) + plt.close(fig) + + +@pytest.mark.parametrize("stat_func", StatisticsAccessor._methods) +def test_networks_area_plot(network_collection, stat_func): + plotter = getattr(network_collection.statistics, stat_func) + fig, ax, g = plotter.plot.area(facet_col="scenario") + assert isinstance(fig, plt.Figure) + assert isinstance(ax, plt.Axes) + assert isinstance(g, sns.FacetGrid) + plt.close(fig) + + +def test_networks_query_filtering(network_collection): + plotter = ChartGenerator(network_collection) + data = network_collection.statistics.energy_balance() + fig, ax, g = plotter.plot( + data, "bar", x="carrier", y="value", facet_col="scenario", query="value > 1" + ) + assert isinstance(fig, plt.Figure) + assert isinstance(ax, plt.Axes) + assert isinstance(g, sns.FacetGrid) + + +def test_networks_stacking(network_collection): + fig, ax, g = network_collection.statistics.supply.plot.bar( + x="carrier", y="value", stacked=True, facet_col="scenario" + ) + assert isinstance(fig, plt.Figure) + assert isinstance(ax, plt.Axes) + assert isinstance(g, sns.FacetGrid) + + +def test_networks_plot_map(network_collection): + with pytest.raises(NotImplementedError): + network_collection.statistics.energy_balance.plot.map() diff --git a/PyPSA/source/test/test_statistics_plot_interactive.py b/PyPSA/source/test/test_statistics_plot_interactive.py new file mode 100644 index 0000000000000000000000000000000000000000..87eb415bf85c6fd907b1eba6543ef68fd6a857e4 --- /dev/null +++ b/PyPSA/source/test/test_statistics_plot_interactive.py @@ -0,0 +1,289 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Tests for interactive statistics plotting.""" + +import pandas as pd +import plotly.graph_objects as go +import pytest + +import pypsa +from pypsa.plot.statistics.charts import ChartGenerator +from pypsa.statistics.expressions import StatisticsAccessor + + +@pytest.fixture +def collection_single_index(ac_dc_network_r): + """Create NetworkCollection with single index for autofaceting tests.""" + n1 = ac_dc_network_r.copy() + n2 = ac_dc_network_r.copy() + n3 = ac_dc_network_r.copy() + + networks = [n1, n2, n3] + index = pd.Index(["scenario_a", "scenario_b", "scenario_c"], name="scenario") + return pypsa.NetworkCollection(networks, index=index) + + +@pytest.fixture +def collection_multiindex(ac_dc_network_r): + """Create NetworkCollection with MultiIndex for autofaceting tests.""" + networks = [ac_dc_network_r.copy() for _ in range(6)] + + index = pd.MultiIndex.from_product( + [["2030", "2040", "2050"], ["low", "high"]], names=["year", "cost"] + ) + return pypsa.NetworkCollection(networks, index=index) + + +def test_iplot_exists(ac_dc_network_r): + """Test that the iplot accessor exists.""" + assert hasattr(ac_dc_network_r.statistics.installed_capacity, "iplot") + + +@pytest.mark.parametrize( + ("plot_type", "expected_trace_type"), + [ + ("bar", go.Bar), + ("line", go.Scatter), + ("area", go.Scatter), + ], +) +def test_iplot_plot_types(ac_dc_network_r, plot_type, expected_trace_type): + """Test creating different plot types.""" + plot_method = getattr( + ac_dc_network_r.statistics.installed_capacity.iplot, plot_type + ) + fig = plot_method() + assert isinstance(fig, go.Figure) + assert any(isinstance(trace, expected_trace_type) for trace in fig.data) + + +@pytest.mark.parametrize( + ("param_name", "param_value"), + [ + ("color", "carrier"), + ("facet_col", "carrier"), + ("facet_row", "carrier"), + ], +) +def test_iplot_layout_parameters(ac_dc_network_r, param_name, param_value): + """Test creating plots with different layout parameters.""" + kwargs = {param_name: param_value} + fig = ac_dc_network_r.statistics.installed_capacity.iplot.bar(**kwargs) + assert isinstance(fig, go.Figure) + + if param_name == "color": + # Plotly Express sets colorway in layout for discrete colors + assert "colorway" in fig.layout or any( + hasattr(trace, "marker") and hasattr(trace.marker, "color") + for trace in fig.data + ) + + +def test_iplot_facet_parameters(ac_dc_network_r): + """Test creating a plot with facets.""" + fig = ac_dc_network_r.statistics.installed_capacity.iplot.bar(facet_col="bus") + assert isinstance(fig, go.Figure) + # Faceted plots in Plotly Express create multiple subplots (if there are enough data points) + if len(fig.data) > 1: + assert "xaxis2" in fig.layout or "yaxis2" in fig.layout + + +def test_iplot_query_parameter(ac_dc_network_r): + """Test creating a plot with a query.""" + # Get the data to create a query + data = ac_dc_network_r.statistics.installed_capacity() + if isinstance(data, pd.Series): + data = data.reset_index() + + if "carrier" in data.columns and len(data["carrier"].unique()) > 1: + test_carrier = data["carrier"].iloc[0] + fig = ac_dc_network_r.statistics.installed_capacity.iplot.bar( + query=f"carrier == '{test_carrier}'" + ) + assert isinstance(fig, go.Figure) + + +@pytest.mark.parametrize("stacked", [True, False]) +def test_iplot_stacked_parameter(ac_dc_network_r, stacked): + """Test creating stacked and unstacked plots.""" + fig = ac_dc_network_r.statistics.installed_capacity.iplot.bar(stacked=stacked) + assert isinstance(fig, go.Figure) + + +def test_iplot_category_orders(ac_dc_network_r): + """Test creating a plot with specified category orders.""" + # Create plot with specified orders if applicable columns exist + carriers = ac_dc_network_r.c.carriers.static.index.unique().tolist() + buses = ac_dc_network_r.c.buses.static.index.unique().tolist() + countries = ac_dc_network_r.c.buses.static.country.unique().tolist() + + # Create plot with the available orders + fig = ac_dc_network_r.statistics.installed_capacity.iplot.bar( + facet_row="bus", + facet_col="country", + color="carrier", + color_order=carriers, + row_order=buses, + col_order=countries, + ) + assert isinstance(fig, go.Figure) + + +def test_iplot_unstacked_area_plot(ac_dc_network_r): + """Test creating an unstacked area plot.""" + fig = ac_dc_network_r.statistics.supply.iplot.area(stacked=False) + assert isinstance(fig, go.Figure) + + +@pytest.mark.parametrize( + ("sharex", "sharey"), + [ + (False, True), + (True, False), + ], +) +def test_iplot_sharex_sharey(ac_dc_network_r, sharex, sharey): + """Test sharex and sharey parameters for faceted plots.""" + fig = ac_dc_network_r.statistics.installed_capacity.iplot.bar( + facet_col="country", + facet_row="bus_carrier", + sharex=sharex, + sharey=sharey, + ) + assert isinstance(fig, go.Figure) + + # Check axis matching behavior if we have multiple axes + if "xaxis2" in fig.layout and not sharex: + assert fig.layout.xaxis.matches is None or not fig.layout.xaxis.matches + if "yaxis2" in fig.layout and not sharey: + assert fig.layout.yaxis.matches is None or not fig.layout.yaxis.matches + + +@pytest.mark.parametrize( + "plot_type", ["", "bar", "line", "area"], ids=["default", "bar", "line", "area"] +) +@pytest.mark.parametrize("stat_func", StatisticsAccessor._methods) +def test_all_stat_functions_all_plot_types(ac_dc_network_r, plot_type, stat_func): + """Consolidated test for all statistics functions with all plot types for both network fixtures.""" + plotter = getattr(ac_dc_network_r.statistics, stat_func) + + if plot_type: + plot_method = getattr(plotter.iplot, plot_type) + fig = plot_method() + else: + fig = plotter.iplot() + + assert isinstance(fig, go.Figure) + + +@pytest.mark.parametrize( + "network_fixture", + ["ac_dc_network_r", "network_collection"], + ids=["single_network", "multiple_networks"], +) +@pytest.mark.parametrize( + "plot_type", ["", "bar", "line", "area"], ids=["default", "bar", "line", "area"] +) +def test_stat_functions_all_plot_types_with_multi(request, network_fixture, plot_type): + """Consolidated test for all statistics functions with all plot types for both network fixtures.""" + network = request.getfixturevalue(network_fixture) + plotter = getattr(network.statistics, "installed_capacity") + + if plot_type: + plot_method = getattr(plotter.iplot, plot_type) + fig = plot_method() + else: + fig = plotter.iplot() + + assert isinstance(fig, go.Figure) + + +def test_networks_interactive_query_filtering(network_collection): + """Test query filtering on networks collection.""" + plotter = ChartGenerator(network_collection) + data = network_collection.statistics.energy_balance() + fig = plotter.iplot( + data, + "bar", + x="carrier", + y="value", + facet_col="scenario", + query="value > 1", + ) + assert isinstance(fig, go.Figure) + + +def test_networks_interactive_stacking(network_collection): + """Test stacking with networks collection.""" + fig = network_collection.statistics.supply.iplot.bar( + x="carrier", y="value", stacked=True, facet_col="scenario" + ) + assert isinstance(fig, go.Figure) + + +class TestAutoFaceting: + """Test automatic faceting functionality for NetworkCollections.""" + + def test_single_index_auto_facet_col(self, collection_single_index): + """Test that single index automatically sets facet_col.""" + # Call plot method and check that the plot is created successfully + # The autofaceting should work transparently + fig = collection_single_index.statistics.installed_capacity.iplot.bar() + assert isinstance(fig, go.Figure) + + # Check that we have data for multiple scenarios + assert len(fig.data) >= 1 + + # The plot should have faceted structure for multiple scenarios + # We can verify this by checking subplot annotations or layout + if hasattr(fig, "layout") and hasattr(fig.layout, "annotations"): + # Faceted plots often have annotations for subplot titles + annotations = [ + ann.text for ann in fig.layout.annotations if hasattr(ann, "text") + ] + # This is a softer check since the exact annotation format may vary + assert len(annotations) >= 0 # Just ensure no errors occurred + + def test_multiindex_auto_facet_both(self, collection_multiindex): + """Test that MultiIndex automatically sets both facet_row and facet_col.""" + # Call plot method and check that the plot is created successfully + fig = collection_multiindex.statistics.installed_capacity.iplot.bar() + assert isinstance(fig, go.Figure) + + # Check that we have data + assert len(fig.data) >= 1 + + # For multiindex, we should have a more complex layout structure + # indicating both row and column faceting + layout = fig.layout + assert layout is not None + + # The presence of multiple axis definitions can indicate faceting + axis_keys = [key for key in dir(layout) if "axis" in key.lower()] + # Should have some axis definitions for the faceted structure + assert len(axis_keys) >= 0 # Ensure no errors in plot creation + + def test_explicit_facet_overrides_auto(self, collection_single_index): + """Test that explicit facet arguments override automatic faceting.""" + # Call plot with explicit facet_col that differs from auto-faceting + fig = collection_single_index.statistics.installed_capacity.iplot.bar( + facet_col="carrier" + ) + assert isinstance(fig, go.Figure) + + # The plot should be created successfully with explicit faceting + assert len(fig.data) >= 1 + + def test_no_autofaceting_for_single_network(self, ac_dc_network_r): + """Test that single network doesn't get automatic faceting.""" + # Single network should not have autofaceting applied + fig = ac_dc_network_r.statistics.installed_capacity.iplot.bar() + assert isinstance(fig, go.Figure) + + # Should create a simple plot without complex faceting structure + assert len(fig.data) >= 1 + + # Check that _index_names is empty for single networks + assert getattr(ac_dc_network_r, "_index_names", []) == [] diff --git a/PyPSA/source/test/test_statistics_plot_schema.py b/PyPSA/source/test/test_statistics_plot_schema.py new file mode 100644 index 0000000000000000000000000000000000000000..b6a7f05dcf5961cafe2002450c2511bafb746c75 --- /dev/null +++ b/PyPSA/source/test/test_statistics_plot_schema.py @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +from pypsa.plot.statistics.schema import apply_parameter_schema + + +class TestSchemaContext: + """Test that schema functions work with context parameter.""" + + def test_apply_parameter_schema_with_context(self): + """Test apply_parameter_schema accepts context parameter.""" + # Test that function accepts context parameter + kwargs = {"x": None, "y": None, "color": None} + context = {"index_names": ["scenario"]} + + # This should not raise an error + result = apply_parameter_schema("installed_capacity", "bar", kwargs, context) + assert isinstance(result, dict) + + def test_apply_parameter_schema_backward_compatible(self): + """Test apply_parameter_schema works without context parameter.""" + from pypsa.plot.statistics.schema import apply_parameter_schema + + # Test backward compatibility + kwargs = {"x": None, "y": None, "color": None} + + # This should not raise an error + result = apply_parameter_schema("installed_capacity", "bar", kwargs) + assert isinstance(result, dict) diff --git a/PyPSA/source/test/test_stochastic.py b/PyPSA/source/test/test_stochastic.py new file mode 100644 index 0000000000000000000000000000000000000000..7ce6b19f9424ec9dbcbc66a23f39a15a43666e97 --- /dev/null +++ b/PyPSA/source/test/test_stochastic.py @@ -0,0 +1,1588 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +""" +Test stochastic functionality of PyPSA networks. +""" + +import warnings +from pathlib import Path + +import pandas as pd +import pytest +from numpy.testing import assert_array_almost_equal as equal +from xarray import DataArray + +import pypsa +from pypsa.common import expand_series +from pypsa.components.common import as_components + + +def test_stoch_example(): + n = pypsa.examples.stochastic_network() + + n.consistency_check(strict="all") + + +def test_network_properties(): + """Test basic properties of a stochastic network.""" + snapshots = pd.date_range("2024-01-01", periods=5, freq="h") + n = pypsa.Network(snapshots=snapshots) + n.add("Bus", "bus1") + n.add("Load", "load1", bus="bus1", p_set=100) + + # Set up scenarios + n.set_scenarios({"low": 0.33, "medium": 0.34, "high": 0.33}) + + # Check scenarios were properly set up + assert len(n.scenarios) == 3 + assert "low" in n.scenarios + assert "medium" in n.scenarios + assert "high" in n.scenarios + assert n.has_scenarios + + # Check probabilities sum to 1 + assert abs(n.scenario_weightings["weight"].sum() - 1.0) < 1e-10 + + p_set = n.get_switchable_as_dense("Load", "p_set") + + assert p_set.columns.names == ["scenario", "name"] + + # Check data shape for each scenario + for scenario in n.scenarios: + assert p_set.loc[:, scenario].shape[0] == 5 + + # Check string representation contains scenario information + assert "Scenarios:" in repr(n) + + +def test_example_consistency(ac_dc_stochastic): + n = ac_dc_stochastic + n.c.lines.static.x = n.c.lines.static.x.where( + n.c.lines.static.x > 0, 0.0001 + ) # Avoid zero reactance + n.c.lines.static.r = n.c.lines.static.r.where( + n.c.lines.static.r > 0, 0.0001 + ) # Avoid zero reactance + n.consistency_check(strict="all") + + +def test_component_functions(ac_dc_stochastic): + assert isinstance(ac_dc_stochastic.branches(), pd.DataFrame) + assert isinstance(ac_dc_stochastic.passive_branches(), pd.DataFrame) + assert isinstance(ac_dc_stochastic.controllable_branches(), pd.DataFrame) + + +def test_calculate_dependent_values(ac_dc_stochastic: pypsa.Network): + """ + Test the calculation of dependent values in a stochastic network. + This includes checking that the function runs without errors and that + the expected attributes are present in the network object. + """ + n = ac_dc_stochastic + n.calculate_dependent_values() + assert n.c.lines.static.x_pu_eff.notnull().all() + + +def test_determine_network_topology(ac_dc_stochastic: pypsa.Network): + """ + Test the determination of network topology in a stochastic network. + This includes checking that the function runs without errors and that + the expected attributes are present in the network object. + """ + n = ac_dc_stochastic + n.determine_network_topology() + + assert not n.c.sub_networks.static.empty + assert "AC" in n.c.sub_networks.static.carrier.values + + sub = n.c.sub_networks.static.obj.loc["0"] + assert not sub.components.generators.static.empty + assert not sub.components.loads.static.empty + + # check slack bus and slack generator assignment via subnetworks and network components + assert set( + n.c.generators.static.query("control == 'Slack'").index.unique("name") + ) == set(n.c.sub_networks.static.obj.map(lambda sub: sub.slack_generator).dropna()) + assert set( + n.c.buses.static.query("control == 'Slack'").index.unique("name") + ) == set(n.c.sub_networks.static.obj.map(lambda sub: sub.slack_bus).dropna()) + + +def test_cycles(ac_dc_stochastic: pypsa.Network): + n = ac_dc_stochastic + C = n.cycle_matrix() + + assert isinstance(C, pd.DataFrame) + assert C.notnull().all().all() # Check for NaN values + + # repeat with apply weights + n.calculate_dependent_values() + C = n.cycle_matrix(apply_weights=True) + assert isinstance(C, pd.DataFrame) + assert C.notnull().all().all() # Check for NaN values + + +def test_model_creation(stochastic_benchmark_network): + """ + Test stochastic optimization model variable and constraint dimensions. + + Verifies that when creating an optimization model for a stochastic network: + + Variables: + - Operational variables (e.g., Generator-p) include scenario dimension + - Investment variables (e.g., Generator-p_nom) exclude scenario dimension + + Constraints: + - Operational constraints include (scenario, component, snapshot) dimensions + - Investment constraints include (component, scenario) dimensions + """ + n = stochastic_benchmark_network + n.optimize.create_model() + + assert hasattr(n, "model") + assert n.model is not None + + # Test operational variable Generator-p has scenario dimension + assert n.model.variables["Generator-p"].dims == ( + "scenario", + "name", + "snapshot", + ) + + # Test that Generator-p_nom does not have scenario dimension (investment variable) + assert n.model.variables["Generator-p_nom"].dims == ("name",) + + # Test operational constraints have scenario dimension + + # Generator-ext-p_nom-lower constraint should have (name, scenario) dimensions + assert { + d + for d in n.model.constraints["Generator-ext-p_nom-lower"].sizes.keys() + if d != "_term" + } == {"name", "scenario"} + + # Generator-ext-p-lower constraint should have (scenario, name, snapshot) dimensions + assert { + d + for d in n.model.constraints["Generator-ext-p-lower"].sizes.keys() + if d != "_term" + } == {"scenario", "name", "snapshot"} + + # Generator-ext-p-upper constraint should have (scenario, name, snapshot) dimensions + assert { + d + for d in n.model.constraints["Generator-ext-p-upper"].sizes.keys() + if d != "_term" + } == {"scenario", "name", "snapshot"} + + # Bus-nodal_balance constraint should have (name, scenario, snapshot) dimensions + assert { + d for d in n.model.constraints["Bus-nodal_balance"].sizes.keys() if d != "_term" + } == {"name", "scenario", "snapshot"} + + +def test_statistics(ac_dc_stochastic_r): + """ + Test the statistics of a stochastic network. + """ + n = ac_dc_stochastic_r + ds = n.statistics.installed_capacity() + assert isinstance(ds, pd.Series) + assert isinstance(ds.index, pd.MultiIndex) + assert "scenario" in ds.index.names + assert not ds.empty + + stats = n.statistics() + assert isinstance(stats, pd.DataFrame) + assert isinstance(stats.index, pd.MultiIndex) + assert "scenario" in ds.index.names + assert not stats.empty + + df = n.statistics.supply(groupby_time=False) + assert isinstance(df, pd.DataFrame) + assert isinstance(df.index, pd.MultiIndex) + assert "scenario" in df.index.names + assert not df.empty + + +def test_statistics_plot(ac_dc_stochastic_r): + """ + Test the statistics plot of a stochastic network. + """ + n = ac_dc_stochastic_r + s = n.statistics + s.installed_capacity.plot.bar() + + +def test_optimization_with_scenarios(ac_dc_stochastic): + """ + Test optimization of a stochastic network and compare results with deterministic equivalent. + + This test verifies that: + - Stochastic optimization completes successfully + - The objective value matches a deterministic network with identical data + """ + n = ac_dc_stochastic + status, _ = n.optimize(solver_name="highs") + assert status == "ok" + + m = pypsa.examples.ac_dc_meshed() + m.optimize(solver_name="highs") + assert abs(m.objective - n.objective) < 1e-2, ( + f"Expected objective {m.objective}, got {n.objective}" + ) + assert abs(m.objective_constant - n.objective_constant) < 1e-2, ( + f"Expected objective constant {m.objective_constant}, got {n.objective_constant}" + ) + + +def test_solved_network_simple(stochastic_benchmark_network): + """ + Solve the stochastic problem and compare results with benchmark data. + Simple test case with a single bus and multiple generators. + """ + # Load the benchmark results + benchmark_path = Path(__file__).parent / "data" / "benchmark-sp" + + if not benchmark_path.exists(): + pytest.skip("Benchmark data not available") + + n_r = pypsa.Network(benchmark_path) + + # Create a new network for the stochastic model + n = stochastic_benchmark_network + + # GAS_PRICES = {"low": 40, "med": 70, "high": 100} + n.c.generators.static.loc[("medium", "gas"), "marginal_cost"] = ( + 70 / n.c.generators.static.loc[("medium", "gas"), "efficiency"] + ) + n.c.generators.static.loc[("high", "gas"), "marginal_cost"] = ( + 100 / n.c.generators.static.loc[("high", "gas"), "efficiency"] + ) + + status, _ = n.optimize(solver_name="highs") + assert status == "ok" + + # Compare generator capacities (these are the main result of stochastic planning) + equal( + n.c.generators.static.p_nom_opt.loc["low", :], + n_r.c.generators.static.p_nom_opt, + decimal=2, + ) + + # Compare objective value + equal(n.objective, n_r.objective, decimal=2) + + +def test_solved_network_multiperiod(): + """ + Test combined stochastic + multiperiod optimization. + + Creates a multiperiod network with investment periods and scenarios, + then verifies that the optimization completes successfully and produces + expected results for both scenarios and investment periods. + """ + + # Suppress pandas FutureWarning about fillna downcasting for entire test + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + category=FutureWarning, + message=".*Downcasting object dtype arrays.*", + ) + + # Combined multiperiod + stochastic optimization + n = pypsa.Network(snapshots=range(3)) + n.investment_periods = [2020, 2030] + + n.add("Carrier", "elec") + n.add("Bus", "bus1", carrier="elec") + n.add( + "Generator", + "gen-2020", + bus="bus1", + p_nom_extendable=True, + capital_cost=100, + marginal_cost=10, + build_year=2020, + lifetime=30, + carrier="elec", + ) + n.add( + "Generator", + "gen-2030", + bus="bus1", + p_nom_extendable=True, + capital_cost=110, + marginal_cost=11, + build_year=2030, + lifetime=30, + carrier="elec", + ) + n.add("Load", "load1", bus="bus1", p_set=100) + + # Now set scenarios + n.set_scenarios({"high": 0.5, "low": 0.5}) + + # Set scenario-specific loads for multiperiod (6 snapshots total: 2 periods × 3 timesteps) + n.c.loads.dynamic.p_set = pd.DataFrame( + index=n.snapshots, + columns=pd.MultiIndex.from_product( + [n.scenarios, ["load1"]], names=["scenario", "name"] + ), + ) + + load_high = [120, 144, 132] * len(n.investment_periods) + load_low = [80, 96, 88] * len(n.investment_periods) + + n.c.loads.dynamic.p_set.loc[:, ("high", "load1")] = load_high + n.c.loads.dynamic.p_set.loc[:, ("low", "load1")] = load_low + + # This should now work with both multiperiod and stochastic features! + status, condition = n.optimize(multi_investment_periods=True) + assert status == "ok" + + # Verify we have results for both scenarios and investment periods + assert "high" in n.c.generators.dynamic.p.columns.get_level_values("scenario") + assert "low" in n.c.generators.dynamic.p.columns.get_level_values("scenario") + + # Check basic energy balance for each scenario + for scenario in ["high", "low"]: + gen_output = ( + n.c.generators.dynamic.p.loc[:, (scenario, slice(None))].sum().sum() + ) + load_demand = n.c.loads.dynamic.p_set.loc[:, (scenario, "load1")].sum() + # Generation should equal load + assert abs(gen_output - load_demand) < 1e-1 + + # Verify that high scenario has higher generation than low scenario + gen_high = n.c.generators.dynamic.p.loc[:, ("high", slice(None))].sum().sum() + gen_low = n.c.generators.dynamic.p.loc[:, ("low", slice(None))].sum().sum() + assert gen_high > gen_low + + # Test multiperiod-specific functionality + p_nom_opt = n.c.generators.static.p_nom_opt + assert ( + len(p_nom_opt) == 4 + ) # Should have optimal capacities for both generators × both scenarios + + # Verify we have generators for both scenarios + scenarios_in_gens = p_nom_opt.index.get_level_values("scenario").unique() + assert "high" in scenarios_in_gens + assert "low" in scenarios_in_gens + + +def test_single_scenario(): + """ + Test that a network with a single scenario works correctly. + + Verifies that: + - Single-scenario stochastic networks optimize successfully + - Scenario indexing works correctly with one scenario + - Solution is identical to a non-stochastic network with same data + """ + + # Suppress pandas FutureWarning about fillna downcasting + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + category=FutureWarning, + message=".*Downcasting object dtype arrays.*", + ) + warnings.filterwarnings( + "ignore", + category=FutureWarning, + message=".*Setting an item of incompatible dtype.*", + ) + + # Create a simple network + n = pypsa.Network(snapshots=range(3)) + n.add("Carrier", "elec") + n.add("Bus", "bus1", carrier="elec") + n.add( + "Generator", + "gen1", + bus="bus1", + p_nom_extendable=True, + capital_cost=100, + marginal_cost=10, + carrier="elec", + ) + n.add("Load", "load1", bus="bus1", p_set=[100, 120, 110]) + + # Solve deterministic problem first + status_det, _ = n.optimize() + assert status_det == "ok" + obj_det = n.objective + capacity_det = n.c.generators.static.p_nom_opt.loc["gen1"] + dispatch_det = n.c.generators.dynamic.p.loc[:, "gen1"].sum() + + # Convert to single-scenario stochastic + n.set_scenarios(["scenario"]) + + # Set scenario-specific load data (same as deterministic) + n.c.loads.dynamic.p_set = pd.DataFrame( + index=n.snapshots, + columns=pd.MultiIndex.from_product( + [n.scenarios, ["load1"]], names=["scenario", "name"] + ), + ) + n.c.loads.dynamic.p_set.loc[:, ("scenario", "load1")] = pd.Series( + [100.0, 120.0, 110.0], dtype=float + ) + + # Solve stochastic problem + status_stoch, _ = n.optimize() + assert status_stoch == "ok" + + # Verify structure + assert len(n.scenarios) == 1 + assert "scenario" in n.scenarios + assert "scenario" in n.c.generators.dynamic.p.columns.get_level_values( + "scenario" + ) + + # Compare solutions (should be identical) + assert abs(n.objective - obj_det) < 1e-6 + + stoch_capacity = n.c.generators.static.p_nom_opt.loc[("scenario", "gen1")] + assert abs(stoch_capacity - capacity_det) < 1e-6 + + stoch_dispatch = n.c.generators.dynamic.p.loc[:, ("scenario", "gen1")].sum() + assert abs(stoch_dispatch - dispatch_det) < 1e-6 + + # Energy balance check + gen_output = ( + n.c.generators.dynamic.p.loc[:, ("scenario", slice(None))].sum().sum() + ) + load_demand = n.c.loads.dynamic.p_set.loc[:, ("scenario", "load1")].sum() + assert abs(gen_output - load_demand) < 1e-1 + + +def test_slack_bus_consistency_check(): + """ + Test that the consistency check correctly identifies when different slack buses + are chosen across scenarios. + """ + + # Suppress pandas FutureWarning about fillna downcasting + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + category=FutureWarning, + message=".*Downcasting object dtype arrays.*", + ) + + # Create a simple stochastic network + n = pypsa.Network(snapshots=range(3)) + n.add("Carrier", "elec") + n.add("Bus", "bus1", carrier="elec") + n.add("Bus", "bus2", carrier="elec") + n.add( + "Generator", + "gen1", + bus="bus1", + p_nom_extendable=True, + capital_cost=100, + marginal_cost=10, + carrier="elec", + ) + n.add( + "Generator", + "gen2", + bus="bus2", + p_nom_extendable=True, + capital_cost=100, + marginal_cost=10, + carrier="elec", + ) + n.add("Load", "load1", bus="bus1", p_set=[100, 120, 110]) + + n.set_scenarios(["scenario1", "scenario2"]) + + # Manually set different slack buses across scenarios to trigger the check + # This simulates what would happen if different slack buses were chosen + # during topology determination + if n.c.buses.static.index.nlevels > 1: + n.c.buses.static.loc[("scenario1", "bus1"), "control"] = "Slack" + n.c.buses.static.loc[("scenario1", "bus2"), "control"] = "PQ" + n.c.buses.static.loc[("scenario2", "bus1"), "control"] = "PQ" + n.c.buses.static.loc[("scenario2", "bus2"), "control"] = ( + "Slack" # Different slack bus! + ) + + # Now run the slack bus consistency check and expect it to raise a warning + from pypsa.consistency import check_stochastic_slack_bus_consistency + + # Test with strict=False (should log warning) + check_stochastic_slack_bus_consistency(n, strict=False) + + # Test with strict=True (should raise error) + import pytest + + with pytest.raises(pypsa.consistency.ConsistencyError): + check_stochastic_slack_bus_consistency(n, strict=True) + + +def test_slack_bus_consistency_check_passes(): + """ + Test that the consistency check passes when the same slack bus is chosen + across scenarios. + """ + + # Suppress pandas FutureWarning about fillna downcasting + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + category=FutureWarning, + message=".*Downcasting object dtype arrays.*", + ) + + # Create a simple stochastic network + n = pypsa.Network(snapshots=range(3)) + n.add("Carrier", "elec") + n.add("Bus", "bus1", carrier="elec") + n.add("Bus", "bus2", carrier="elec") + n.add( + "Generator", + "gen1", + bus="bus1", + p_nom_extendable=True, + capital_cost=100, + marginal_cost=10, + carrier="elec", + ) + n.add( + "Generator", + "gen2", + bus="bus2", + p_nom_extendable=True, + capital_cost=100, + marginal_cost=10, + carrier="elec", + ) + n.add("Load", "load1", bus="bus1", p_set=[100, 120, 110]) + + n.set_scenarios(["scenario1", "scenario2"]) + + # Set the same slack bus across scenarios (should pass) + if n.c.buses.static.index.nlevels > 1: + n.c.buses.static.loc[("scenario1", "bus1"), "control"] = "Slack" + n.c.buses.static.loc[("scenario1", "bus2"), "control"] = "PQ" + n.c.buses.static.loc[("scenario2", "bus1"), "control"] = ( + "Slack" # Same slack bus + ) + n.c.buses.static.loc[("scenario2", "bus2"), "control"] = "PQ" + + # Now run the slack bus consistency check - should pass without error + from pypsa.consistency import check_stochastic_slack_bus_consistency + + # Should not raise any error or warning + check_stochastic_slack_bus_consistency(n, strict=True) + + +def test_store_stochastic_optimization_bug(): + """Test that Store component works correctly with stochastic optimization. + + This test reproduces the bug where Store components failed during stochastic + optimization due to dimension mismatch in the standing efficiency calculation. + + The bug was: + - `expand_series` returns a DataFrame with shape (snapshots, scenarios) + - `standing_loss` is a DataArray with shape (snapshots, scenarios, stores) + - The power operation (1 - standing_loss) ** eh failed with 3D vs 2D mismatch + + The fix: + - Convert expand_series result to DataArray and try to unstack it + - This aligns the dimensions properly for the power operation + """ + n = pypsa.examples.model_energy() + + # Reduce to first day only (8 snapshots) to make test faster + n.set_snapshots(n.snapshots[:8]) + + # Ensure the network has stores (it should) + assert not n.c.stores.static.empty, "Test network should have stores" + + # The bug occured in operation (1 - standing_loss)**eh due to dimension mismatch + n.c.stores.static.at["hydrogen storage", "e_nom"] = 1000 + n.c.stores.static.at["hydrogen storage", "e_cyclic"] = False + n.c.stores.static.at["hydrogen storage", "e_initial"] = 800 + n.c.stores.static.at["hydrogen storage", "standing_loss"] = 0.01 + + # Test without scenarios first (should work) + n_regular = n.copy() + status_regular, condition_regular = n_regular.optimize() + assert status_regular == "ok" + assert condition_regular == "optimal" + + # Test with scenarios (this used to fail) + n_stochastic = n.copy() + n_stochastic.set_scenarios(["scenario_a", "scenario_b"]) + + # This should not raise an error + status_stochastic, condition_stochastic = n_stochastic.optimize() + assert status_stochastic == "ok" + assert condition_stochastic == "optimal" + + # Verify that the stochastic network has the expected structure + assert n_stochastic.has_scenarios + assert len(n_stochastic.scenarios) == 2 + + # Verify stores have MultiIndex + assert isinstance(n_stochastic.c.stores.static.index, pd.MultiIndex) + assert n_stochastic.c.stores.static.index.names == ["scenario", "name"] + + # Verify optimization results exist + assert not n_stochastic.c.stores.dynamic.e.empty + assert not n_stochastic.c.stores.dynamic.p.empty + + # Verify specific energy level at second snapshot + # it is 800 × (1 - 0.01)³ due to 3h temporal clustering + second_hour_energy = n_stochastic.c.stores.dynamic.e.loc[ + n_stochastic.snapshots[1], ("scenario_a", "hydrogen storage") + ] + assert abs(second_hour_energy - 776.2392) < 0.01, ( + f"Expected hydrogen storage energy ~776.24 at second snapshot, got {second_hour_energy}" + ) + + +def test_store_stochastic_dimensions(): + """Test that Store component expansion works correctly with stochastic dimensions. + + This test specifically checks the expand_series -> DataArray conversion + that fixes the dimension mismatch issue. + """ + + n = pypsa.Network() + n.add("Bus", "bus") + n.add("Store", "store", bus="bus", e_nom=100, standing_loss=0.01) + n.add("Load", "load", bus="bus", p_set=50) + n.add("Generator", "gen", bus="bus", p_nom=100, marginal_cost=20) + + n.set_scenarios(["s1", "s2"]) + + c = as_components(n, "Store") + sns = n.snapshots + + # This should work without errors + elapsed_h = expand_series(n.snapshot_weightings.stores[sns], c.static.index) + eh = DataArray(elapsed_h) + + # Test the unstack operation + if n.has_scenarios: + eh_final = eh.unstack("dim_1") + else: + eh_final = eh + + # This should work without dimension errors + standing_loss = c.da.standing_loss.sel(snapshot=sns) + eff_stand = (1 - standing_loss) ** eh_final + + # Verify the result has the expected dimensions + assert isinstance(eff_stand, DataArray) + assert "snapshot" in eff_stand.dims + + # The optimization should also work + status, condition = n.optimize() + assert status == "ok" + assert condition == "optimal" + + +def test_scenario_ordering_bug(): + """Test that scenario ordering is preserved correctly in optimization results. + + This test ensures that when different scenarios have different parameter values, + the optimization results correspond to the correct scenario labels. + """ + n = pypsa.examples.ac_dc_meshed() + + # Say we have good and bad wind scenarios + n.set_scenarios({"wind_lulls": 0.5, "windy": 0.5}) + wind_gens = ["Manchester Wind", "Frankfurt Wind", "Norway Wind"] + for gen_name in wind_gens: + n.c.generators.dynamic.p_max_pu.loc[:, ("wind_lulls", gen_name)] *= 0.3 + + # Check 1: Wind lulls should have lower wind potential in model input + expected_lulls_input = ( + n.c.generators.dynamic.p_max_pu.loc[:, ("wind_lulls", wind_gens)].sum().sum() + ) + expected_windy_input = ( + n.c.generators.dynamic.p_max_pu.loc[:, ("windy", wind_gens)].sum().sum() + ) + + assert expected_lulls_input < expected_windy_input + + # Check 2: Wind lulls scenario should have lower wind generation + n.optimize() + + actual_lulls_result = ( + n.c.generators.dynamic.p.loc[:, ("wind_lulls", wind_gens)].sum().sum() + ) + actual_windy_result = ( + n.c.generators.dynamic.p.loc[:, ("windy", wind_gens)].sum().sum() + ) + + assert actual_lulls_result < actual_windy_result, ( + f"Scenario misalignment detected: wind lulls generation ({actual_lulls_result:.2f}) " + f"should be less than windy generation ({actual_windy_result:.2f})" + ) + + # Check 3: verify that the results match the expected scenario ordering + # by checking that the DataArray scenario coordinate order is preserved + da_p_max_pu = n.c.generators.da.p_max_pu.sel(name="Manchester Wind") + da_scenarios = list(da_p_max_pu.coords["scenario"].values) + network_scenarios = list(n.scenarios) + + assert da_scenarios == network_scenarios, ( + f"DataArray scenario order {da_scenarios} does not match " + f"network scenario order {network_scenarios}" + ) + + +# Multiperiod stochastic fixtures and tests +@pytest.fixture +def n_multiperiod(): + """Basic multiperiod network fixture similar to test_lopf_multiinvest.py""" + n = pypsa.Network(snapshots=range(10)) + n.investment_periods = [2020, 2030, 2040, 2050] + n.add("Carrier", "gencarrier") + n.add("Carrier", "AC") + n.add("Bus", [1, 2], carrier="AC") + + for i, period in enumerate(n.investment_periods): + factor = (10 + i) / 10 + n.add( + "Generator", + [f"gen1-{period}", f"gen2-{period}"], + bus=[1, 2], + lifetime=30, + build_year=period, + capital_cost=[100 / factor, 100 * factor], + marginal_cost=[i + 2, i + 1], + p_nom_extendable=True, + carrier="gencarrier", + ) + + for i, period in enumerate(n.investment_periods): + n.add( + "Line", + f"line-{period}", + bus0=1, + bus1=2, + length=1, + build_year=period, + lifetime=40, + capital_cost=30 + i, + x=0.0001, + r=0.001, + s_nom_extendable=True, + carrier="AC", + ) + + load = range(100, 100 + len(n.snapshots)) + load = pd.DataFrame({"load1": load, "load2": load}, index=n.snapshots) + n.add( + "Load", + ["load1", "load2"], + bus=[1, 2], + p_set=load, + ) + + return n + + +@pytest.fixture +def n_multiperiod_stochastic(n_multiperiod): + """Convert multiperiod network to stochastic""" + n = n_multiperiod + n.set_scenarios({"high": 0.5, "low": 0.5}) + + # Set scenario-specific loads + n.c.loads.dynamic.p_set = pd.DataFrame( + index=n.snapshots, + columns=pd.MultiIndex.from_product( + [n.scenarios, ["load1", "load2"]], names=["scenario", "name"] + ), + ) + + # High scenario: 20% higher load + base_load = range(100, 100 + len(n.snapshots)) + for load_name in ["load1", "load2"]: + n.c.loads.dynamic.p_set.loc[:, ("high", load_name)] = [ + l * 1.2 for l in base_load + ] + n.c.loads.dynamic.p_set.loc[:, ("low", load_name)] = [ + l * 0.8 for l in base_load + ] + + return n + + +@pytest.fixture +def n_multiperiod_sus(n_multiperiod): + """Multiperiod network with storage units""" + n = n_multiperiod + # Remove some generators to activate storage + n.remove("Generator", n.c.generators.static.query('bus == "1"').index) + n.c.generators.static.capital_cost *= 5 + + for i, period in enumerate(n.investment_periods): + factor = (10 + i) / 10 + n.add( + "StorageUnit", + f"sto1-{period}", + bus=1, + lifetime=30, + build_year=period, + capital_cost=10 / factor, + marginal_cost=i, + p_nom_extendable=True, + ) + return n + + +@pytest.fixture +def n_multiperiod_sus_stochastic(n_multiperiod_sus): + """Convert multiperiod storage network to stochastic""" + n = n_multiperiod_sus + n.set_scenarios({"high": 0.6, "low": 0.4}) + + # Set scenario-specific loads + n.c.loads.dynamic.p_set = pd.DataFrame( + index=n.snapshots, + columns=pd.MultiIndex.from_product( + [n.scenarios, ["load1", "load2"]], names=["scenario", "name"] + ), + ) + + # Different load patterns for scenarios + base_load = range(100, 100 + len(n.snapshots)) + for load_name in ["load1", "load2"]: + n.c.loads.dynamic.p_set.loc[:, ("high", load_name)] = [ + l * 1.3 for l in base_load + ] + n.c.loads.dynamic.p_set.loc[:, ("low", load_name)] = [ + l * 0.7 for l in base_load + ] + + return n + + +# Small focused tests +def test_multiperiod_stochastic_tiny_default(): + """Test tiny multiperiod stochastic network with default parameters""" + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=FutureWarning) + + n = pypsa.Network(snapshots=range(2)) + n.investment_periods = [2020, 2030] + n.add("Carrier", "elec") + n.add("Bus", 1, carrier="elec") + n.add( + "Generator", + 1, + bus=1, + p_nom_extendable=True, + capital_cost=10, + carrier="elec", + ) + n.add("Load", 1, bus=1, p_set=100) + + n.set_scenarios({"high": 0.5, "low": 0.5}) + n.c.loads.dynamic.p_set = pd.DataFrame( + index=n.snapshots, + columns=pd.MultiIndex.from_product( + [n.scenarios, ["1"]], names=["scenario", "name"] + ), + ) + n.c.loads.dynamic.p_set.loc[:, ("high", "1")] = [ + 120, + 120, + 120, + 120, + ] + n.c.loads.dynamic.p_set.loc[:, ("low", "1")] = [80, 80, 80, 80] + + status, _ = n.optimize(multi_investment_periods=True) + assert status == "ok" + + # Check that we have results for both scenarios + assert "high" in n.c.generators.static.p_nom_opt.index.get_level_values( + "scenario" + ) + assert "low" in n.c.generators.static.p_nom_opt.index.get_level_values( + "scenario" + ) + + # Capacities should be identical across scenarios in stochastic optimization + high_cap = n.c.generators.static.p_nom_opt.loc[("high", "1")] + low_cap = n.c.generators.static.p_nom_opt.loc[("low", "1")] + assert high_cap == low_cap + + +def test_multiperiod_stochastic_tiny_build_year(): + """Test tiny multiperiod stochastic network with specific build year""" + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=FutureWarning) + + n = pypsa.Network(snapshots=range(2)) + n.investment_periods = [2020, 2030] + n.add("Carrier", "elec") + n.add("Bus", 1, carrier="elec") + n.add( + "Generator", + 1, + bus=1, + p_nom_extendable=True, + capital_cost=10, + build_year=2020, + carrier="elec", + ) + n.add("Load", 1, bus=1, p_set=100) + + n.set_scenarios({"scenario": 1.0}) # Single scenario + n.c.loads.dynamic.p_set = pd.DataFrame( + index=n.snapshots, + columns=pd.MultiIndex.from_product( + [n.scenarios, ["1"]], names=["scenario", "name"] + ), + ) + n.c.loads.dynamic.p_set.loc[:, ("scenario", "1")] = [100, 100, 100, 100] + + status, _ = n.optimize(multi_investment_periods=True) + assert status == "ok" + assert n.c.generators.static.p_nom_opt.loc[("scenario", "1")] == 100 + + +def test_multiperiod_stochastic_tiny_infeasible(): + """Test infeasible multiperiod stochastic network""" + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=FutureWarning) + + n = pypsa.Network(snapshots=range(2)) + n.investment_periods = [2020, 2030] + n.add("Carrier", "elec") + n.add("Bus", 1, carrier="elec") + n.add( + "Generator", + 1, + bus=1, + p_nom_extendable=True, + capital_cost=10, + build_year=2030, + carrier="elec", + ) + n.add("Load", 1, bus=1, p_set=100) + + n.set_scenarios({"scenario": 1.0}) + n.c.loads.dynamic.p_set = pd.DataFrame( + index=n.snapshots, + columns=pd.MultiIndex.from_product( + [n.scenarios, ["1"]], names=["scenario", "name"] + ), + ) + n.c.loads.dynamic.p_set.loc[:, ("scenario", "1")] = [100, 100, 100, 100] + + # This should fail because generator only available in 2030 but load exists in 2020 + with pytest.raises(ValueError): + n.optimize(multi_investment_periods=True) + + +def test_multiperiod_stochastic_simple_network(n_multiperiod_stochastic): + """Test simple multiperiod stochastic network optimization""" + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=FutureWarning) + + n = n_multiperiod_stochastic + status, condition = n.optimize(multi_investment_periods=True) + assert status == "ok" + assert condition == "optimal" + + # Check that generators are only active in their respective periods + for scenario in n.scenarios: + # gen1-2050 should not be active in early periods + assert ( + n.c.generators.dynamic.p.loc[ + (2020, slice(None)), (scenario, "gen1-2050") + ] + == 0 + ).all() + assert ( + n.c.generators.dynamic.p.loc[ + (2030, slice(None)), (scenario, "gen1-2050") + ] + == 0 + ).all() + assert ( + n.c.generators.dynamic.p.loc[ + (2040, slice(None)), (scenario, "gen1-2050") + ] + == 0 + ).all() + + # gen1-2020 should not be active in 2050 (lifetime = 30) + assert ( + n.c.generators.dynamic.p.loc[ + (2050, slice(None)), (scenario, "gen1-2020") + ] + == 0 + ).all() + + # line-2050 should not be active in early periods + assert ( + n.c.lines.dynamic.p0.loc[(2020, slice(None)), (scenario, "line-2050")] + == 0 + ).all() + assert ( + n.c.lines.dynamic.p0.loc[(2030, slice(None)), (scenario, "line-2050")] + == 0 + ).all() + assert ( + n.c.lines.dynamic.p0.loc[(2040, slice(None)), (scenario, "line-2050")] + == 0 + ).all() + + +def test_multiperiod_stochastic_snapshot_subset(n_multiperiod_stochastic): + """Test multiperiod stochastic network with snapshot subset""" + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=FutureWarning) + + n = n_multiperiod_stochastic + status, condition = n.optimize(n.snapshots[:20], multi_investment_periods=True) + assert status == "ok" + assert condition == "optimal" + + # Same checks as above but with subset of snapshots + for scenario in n.scenarios: + assert ( + n.c.generators.dynamic.p.loc[ + (2020, slice(None)), (scenario, "gen1-2050") + ] + == 0 + ).all() + assert ( + n.c.generators.dynamic.p.loc[ + (2030, slice(None)), (scenario, "gen1-2050") + ] + == 0 + ).all() + assert ( + n.c.generators.dynamic.p.loc[ + (2040, slice(None)), (scenario, "gen1-2050") + ] + == 0 + ).all() + + +def test_multiperiod_stochastic_storage_units_bug(n_multiperiod_sus_stochastic): + """Test multiperiod stochastic network with storage units + + This test verifies that storage units work correctly in multiperiod + stochastic + optimization. Previously, this combination caused a broadcasting error in the + storage constraint creation due to dimension mismatches between: + - (snapshots, scenarios, storage_units) from the mask + - (scenarios, storage_units, snapshots) from the previous_soc_pp variable + + The fix ensures dimension order consistency by transposing the previous_soc_pp + variable to match the expected dimension order when scenarios are present. + """ + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=FutureWarning) + + n = n_multiperiod_sus_stochastic + status, condition = n.optimize(multi_investment_periods=True) + assert status == "ok" + assert condition == "optimal" + + # Check that storage units have been built + for scenario in n.scenarios: + total_storage_cap = n.c.storage_units.static.p_nom_opt.loc[ + scenario, : + ].sum() + assert total_storage_cap > 0 + + # Check that storage is only active in appropriate periods + for period in n.investment_periods: + period_snapshots = n.snapshots[ + n.snapshots.get_level_values("period") == period + ] + + # Storage units should respect their build years + for storage_name in n.c.storage_units.static.loc[scenario, :].index: + storage = n.c.storage_units.static.loc[(scenario, storage_name)] + build_year = storage.build_year + lifetime = storage.lifetime + + if build_year <= period <= build_year + lifetime: + # Storage should be available in this period + storage_dispatch = n.c.storage_units.dynamic.p.loc[ + period_snapshots, (scenario, storage_name) + ] + # At least some periods should have non-zero dispatch (charging or discharging) + assert storage_dispatch.abs().sum() >= 0 + else: + storage_dispatch = n.c.storage_units.dynamic.p.loc[ + period_snapshots, (scenario, storage_name) + ] + assert (storage_dispatch == 0).all() + + +def test_multiperiod_stochastic_scenario_differences(n_multiperiod_stochastic): + """Test that scenarios produce different results""" + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=FutureWarning) + + n = n_multiperiod_stochastic + status, condition = n.optimize(multi_investment_periods=True) + assert status == "ok" + assert condition == "optimal" + + # Check that high and low scenarios have different generation patterns + high_total_gen = ( + n.c.generators.dynamic.p.loc[:, ("high", slice(None))].sum().sum() + ) + low_total_gen = ( + n.c.generators.dynamic.p.loc[:, ("low", slice(None))].sum().sum() + ) + + # High load scenario should have higher generation + assert high_total_gen > low_total_gen + + # But capacities should be the same (stochastic optimization) + for gen_name in n.c.generators.static.loc[ + ("high", slice(None)), : + ].index.get_level_values("name"): + high_cap = n.c.generators.static.p_nom_opt.loc[("high", gen_name)] + low_cap = n.c.generators.static.p_nom_opt.loc[("low", gen_name)] + assert high_cap == low_cap + + +def test_multiperiod_stochastic_coordinate_alignment(): + """Test coordinate alignment problem for multiperiod + stochastic + storage units. + + This test reproduces the exact scenario that caused the coordinate alignment bug: + - Multiperiod optimization with investment periods + - Scenarios (creating MultiIndex snapshots) + - Storage units (triggering the problematic constraint) + """ + import warnings + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=FutureWarning) + + # Create minimal multiperiod network + n = pypsa.Network(snapshots=range(4)) + n.investment_periods = [2020, 2030] + + n.add("Carrier", "elec") + n.add("Bus", "bus1", carrier="elec") + + # Fill network with components + n.add( + "Generator", + "gen1", + bus="bus1", + p_nom_extendable=True, + capital_cost=100, + marginal_cost=10, + carrier="elec", + ) + n.add("Load", "load1", bus="bus1", p_set=100) + + n.add("StorageUnit", "storage1", bus="bus1", p_nom=50, max_hours=4) + + # Set scenarios + n.set_scenarios(["scenario_a", "scenario_b"]) + + # The key test: model creation should succeed without coordinate alignment errors + try: + n.optimize.create_model() + model_created = True + except Exception as e: + if "cannot align objects with join='exact'" in str(e): + pytest.fail( + f"Coordinate alignment error in multiperiod stochastic storage: {e}" + ) + else: + raise e + + assert model_created, ( + "Multiperiod stochastic model with storage should be created successfully" + ) + + # Verify the problematic constraint was created correctly + assert "StorageUnit-energy_balance" in n.model.constraints + storage_constraint = n.model.constraints["StorageUnit-energy_balance"] + + # Should have all expected dimensions + expected_dims = {"scenario", "name", "snapshot"} + actual_dims = set(storage_constraint.dims) + assert expected_dims.issubset(actual_dims), ( + f"Missing dimensions in storage constraint: {expected_dims - actual_dims}" + ) + + # Test that full optimization also works + status, condition = n.optimize(multi_investment_periods=True) + assert status == "ok", ( + f"Multiperiod stochastic optimization should succeed, got status: {status}" + ) + + +def test_storage_unit_energy_balance_scenario_sorting_bug(): + """Test that storage unit energy balance constraints have correct RHS values with scenarios. + + This test specifically catches the bug where scenario-based sorting caused storage units + to get incorrect RHS values in their energy balance constraints. The bug occurred when: + 1. Network has scenarios (triggering sorting code) + 2. Storage units are present (creating energy balance constraints) + 3. Different storage units have different inflow values + + The sorting caused RHS values to be misaligned, so storage units would get RHS values + from other storage units alphabetically earlier in the sorted list. + """ + n = pypsa.Network(snapshots=pd.date_range("2020-01-01", periods=1, freq="h")) + + n.add("Carrier", "electricity") + n.add("Bus", "bus1", carrier="electricity") + + # Add storage units with different names (to trigger alphabetical sorting) + # and different inflow values (to detect when values get misaligned) + n.add( + "StorageUnit", + "ZZ_storage", + bus="bus1", + p_nom=100, + inflow=0, + state_of_charge_initial=0, + cyclic_state_of_charge=True, + ) + n.add( + "StorageUnit", + "AA_storage", + bus="bus1", + p_nom=100, + inflow=50, + state_of_charge_initial=0, + cyclic_state_of_charge=True, + ) + + n.add("Generator", "gen", bus="bus1", p_nom=200, marginal_cost=10) + n.add("Load", "load", bus="bus1", p_set=100) + + # Test without scenarios first (should work correctly) + n_no_scenarios = n.copy() + n_no_scenarios.optimize.create_model() + + constraint_no_scenarios = n_no_scenarios.model.constraints[ + "StorageUnit-energy_balance" + ] + + # Get RHS values for both storage units + zz_rhs_no_scenarios = constraint_no_scenarios.loc[ + {"name": "ZZ_storage", "snapshot": n.snapshots[0]} + ].rhs.item() + aa_rhs_no_scenarios = constraint_no_scenarios.loc[ + {"name": "AA_storage", "snapshot": n.snapshots[0]} + ].rhs.item() + + # AA_storage has inflow=50, so its RHS should be -50 (negative inflow) + # ZZ_storage has inflow=0, so its RHS should be 0 + assert abs(aa_rhs_no_scenarios - (-50.0)) < 1e-6, ( + f"AA_storage should have RHS=-50, got {aa_rhs_no_scenarios}" + ) + assert abs(zz_rhs_no_scenarios - 0.0) < 1e-6, ( + f"ZZ_storage should have RHS=0, got {zz_rhs_no_scenarios}" + ) + + # Test with scenarios (this is where the bug would occur) + n_scenarios = n.copy() + n_scenarios.set_scenarios({"scenario1": 1.0}) + n_scenarios.optimize.create_model() + + constraint_scenarios = n_scenarios.model.constraints["StorageUnit-energy_balance"] + + # Get RHS values for both storage units with scenarios + zz_rhs_scenarios = constraint_scenarios.loc[ + {"name": "ZZ_storage", "snapshot": n.snapshots[0]} + ].rhs.item() + aa_rhs_scenarios = constraint_scenarios.loc[ + {"name": "AA_storage", "snapshot": n.snapshots[0]} + ].rhs.item() + + # The key test: RHS values should be the same with and without scenarios + # This would fail before the bug fix because sorting caused value misalignment + assert abs(aa_rhs_scenarios - aa_rhs_no_scenarios) < 1e-6, ( + f"AA_storage RHS mismatch: without scenarios={aa_rhs_no_scenarios}, " + f"with scenarios={aa_rhs_scenarios}. This indicates the scenario sorting bug!" + ) + assert abs(zz_rhs_scenarios - zz_rhs_no_scenarios) < 1e-6, ( + f"ZZ_storage RHS mismatch: without scenarios={zz_rhs_no_scenarios}, " + f"with scenarios={zz_rhs_scenarios}. This indicates the scenario sorting bug!" + ) + + # Additional verification: each storage unit should still have its expected RHS value + assert abs(aa_rhs_scenarios - (-50.0)) < 1e-6, ( + f"AA_storage should have RHS=-50 with scenarios, got {aa_rhs_scenarios}" + ) + assert abs(zz_rhs_scenarios - 0.0) < 1e-6, ( + f"ZZ_storage should have RHS=0 with scenarios, got {zz_rhs_scenarios}" + ) + + +@pytest.fixture +def n(): + n = pypsa.Network(snapshots=range(10)) + n.investment_periods = [2020, 2030, 2040, 2050] + n.add("Carrier", "gencarrier") + n.add("Bus", [1, 2]) + + for i, period in enumerate(n.investment_periods): + factor = (10 + i) / 10 + n.add( + "Generator", + [f"gen1-{period}", f"gen2-{period}"], + bus=[1, 2], + lifetime=30, + build_year=period, + capital_cost=[100 / factor, 100 * factor], + marginal_cost=[i + 2, i + 1], + p_nom_extendable=True, + carrier="gencarrier", + ) + + for i, period in enumerate(n.investment_periods): + n.add( + "Line", + f"line-{period}", + bus0=1, + bus1=2, + length=1, + build_year=period, + lifetime=40, + capital_cost=30 + i, + x=0.0001, + s_nom_extendable=True, + ) + + load = range(100, 100 + len(n.snapshots)) + load = pd.DataFrame({"load1": load, "load2": load}, index=n.snapshots) + n.add( + "Load", + ["load1", "load2"], + bus=[1, 2], + p_set=load, + ) + + return n + + +def test_primary_energy_constraint_stochastic(ac_dc_stochastic): + """Test primary energy constraint works correctly with stochastic networks.""" + n = ac_dc_stochastic + assert ("low", "co2_limit") in n.c.global_constraints.static.index + assert ("high", "co2_limit") in n.c.global_constraints.static.index + n.optimize.create_model() + assert "GlobalConstraint-co2_limit" in n.model.constraints + assert "scenario" in n.model.constraints["GlobalConstraint-co2_limit"].dims + + +def test_operational_limit_constraint_stochastic(): + """Test operational limit constraints work correctly with stochastic networks.""" + n = pypsa.Network(snapshots=range(3)) + + n.add("Carrier", "solar") + n.add("Carrier", "gas") + n.add("Carrier", "AC") + + n.add("Bus", "bus1", carrier="AC") + + n.add( + "Generator", + "solar", + bus="bus1", + p_nom=100, + marginal_cost=0, + carrier="solar", + ) + n.add("Generator", "gas", bus="bus1", p_nom=200, marginal_cost=50, carrier="gas") + n.add("Load", "load1", bus="bus1", p_set=[50, 100, 100]) + + n.add( + "GlobalConstraint", + "solar_limit", + type="operational_limit", + sense="<=", + constant=150, # Total solar generation limit across all snapshots + carrier_attribute="solar", + ) + + n.set_scenarios(["scenario1", "scenario2"]) + + # Verify constraints exist in both scenarios + assert ("scenario1", "solar_limit") in n.c.global_constraints.static.index + assert ("scenario2", "solar_limit") in n.c.global_constraints.static.index + + # Create model to verify constraints are properly added + n.optimize.create_model() + assert "GlobalConstraint-solar_limit" in n.model.constraints + assert "scenario" in n.model.constraints["GlobalConstraint-solar_limit"].dims + + +def test_max_growth_constraint_stochastic(n): + """Test growth limit constraints work correctly with stochastic networks.""" + gen_carrier = n.c.generators.static.carrier.unique()[0] + n.c.carriers.static.at[gen_carrier, "max_growth"] = 300 + n.set_scenarios({"scenario_1": 0.5, "scenario_2": 0.5}) + n.c.carriers.static.loc[("scenario_1", gen_carrier), "max_growth"] = 218 + kwargs = {"multi_investment_periods": True} + status, cond = n.optimize(**kwargs) + + # In stochastic optimization, capacity decisions are shared across scenarios + # The growth limit is constrained with the strictest bound + capacity_per_period = ( + n.c.generators.static.xs("scenario_1") + .p_nom_opt.groupby(n.c.generators.static.xs("scenario_1").build_year) + .sum() + ) + assert all(capacity_per_period <= 218), ( + f"Capacity per period exceeds limit: {capacity_per_period}" + ) + assert "Carrier-growth_limit" in n.model.constraints + + +def test_max_relative_growth_constraint(n): + """Test growth relative limit constraints work correctly with stochastic networks.""" + gen_carrier = n.c.generators.static.carrier.unique()[0] + n.c.carriers.static.at[gen_carrier, "max_growth"] = 218 + n.c.carriers.static.at[gen_carrier, "max_relative_growth"] = 3 + n.set_scenarios({"scenario_1": 0.5, "scenario_2": 0.5}) + n.c.carriers.static.loc[("scenario_1", gen_carrier), "max_relative_growth"] = 1.5 + kwargs = {"multi_investment_periods": True} + status, cond = n.optimize(**kwargs) + built_per_period = ( + n.c.generators.static.xs("scenario_1") + .p_nom_opt.groupby(n.c.generators.static.xs("scenario_1").build_year) + .sum() + ) + assert all(built_per_period - built_per_period.shift(fill_value=0) * 1.5 <= 218) + + +@pytest.mark.parametrize("assign", [True, False]) +def test_assign_all_duals_stochastic(ac_dc_network, assign): + """Test that all duals are written back to the network with stochastic scenarios.""" + n = ac_dc_network + + # Set up two scenarios + n.set_scenarios({"scenario_1": 0.5, "scenario_2": 0.5}) + + limit = 30_000 + + m = n.optimize.create_model() + + transmission = m.variables["Link-p"] + m.add_constraints( + transmission.sum() <= limit, name="GlobalConstraint-generation_limit" + ) + m.add_constraints( + transmission.sum(dim="name") <= limit, + name="GlobalConstraint-generation_limit_dynamic", + ) + + if assign: + # TODO Add when custom constraints duals are written to extra custom constraint + with pytest.raises(NotImplementedError): + # This should raise because we are not assigning duals yet + n.optimize.solve_model(assign_all_duals=assign) + + # assert ("generation_limit" in n.c.global_constraints.static.index) == assign + # assert ("mu_generation_limit_dynamic" in n.c.global_constraints.dynamic) == assign + # if "mu_upper" in n.c.generators.dynamic: + # assert not n.c.generators.dynamic.mu_upper.empty, ( + # "Generator mu_upper should be assigned when assign_all_duals=True" + # ) + # if "mu_lower" in n.c.generators.dynamic: + # assert not n.c.generators.dynamic.mu_lower.empty, ( + # "Generator mu_lower should be assigned when assign_all_duals=True" + # ) + + # if "mu_upper" in n.c.links.dynamic: + # assert not n.c.links.dynamic.mu_upper.empty, ( + # "Link mu_upper should be assigned when assign_all_duals=True" + # ) + # if "mu_lower" in n.c.links.dynamic: + # assert not n.c.links.dynamic.mu_lower.empty, ( + # "Link mu_lower should be assigned when assign_all_duals=True" + # ) + + # # Verify that stochastic dimensions are preserved in dual variables + # if not n.c.buses.dynamic.marginal_price.empty: + # assert isinstance(n.c.buses.dynamic.marginal_price.columns, pd.MultiIndex), ( + # "Marginal prices should have MultiIndex columns with scenarios" + # ) + # scenarios_in_marginal_price = ( + # n.c.buses.dynamic.marginal_price.columns.get_level_values("scenario").unique() + # ) + # assert all(s in scenarios_in_marginal_price for s in n.scenarios), ( + # "All scenarios should be present in marginal prices" + # ) + + else: + n.optimize.solve_model(assign_all_duals=assign) + assert not n.c.buses.dynamic.marginal_price.empty, ( + "Marginal prices should always be assigned" + ) + + +def test_transmission_volume_expansion_limit_constraint_stochastic(): + """Test transmission volume expansion limit works correctly with scenarios.""" + n = pypsa.Network(snapshots=range(3)) + + # Ensure carrier and buses/line exist and line is extendable + n.add("Carrier", "AC") + n.add("Bus", ["b1", "b2"], carrier="AC") + n.add( + "Line", + "l1", + bus0="b1", + bus1="b2", + length=1.0, + x=0.0001, + r=0.001, + s_nom_extendable=True, + carrier="AC", + ) + + n.add("Generator", "g", bus="b1", p_nom=100, marginal_cost=10, carrier="AC") + n.add("Load", "load1", bus="b2", p_set=[50, 50, 50]) + + n.add( + "GlobalConstraint", + "tx_vol", + type="transmission_volume_expansion_limit", + sense="<=", + constant=1e6, + carrier_attribute="AC", + ) + + # Scenarios + n.set_scenarios(["scenario1", "scenario2"]) + + # Verify constraint exists in both scenarios in the input table + assert ("scenario1", "tx_vol") in n.c.global_constraints.static.index + assert ("scenario2", "tx_vol") in n.c.global_constraints.static.index + + # Build model and verify a single constraint with scenario dimension + n.optimize.create_model() + assert "GlobalConstraint-tx_vol" in n.model.constraints + assert "scenario" in n.model.constraints["GlobalConstraint-tx_vol"].dims diff --git a/PyPSA/source/test/test_stochastic_not_implemented.py b/PyPSA/source/test/test_stochastic_not_implemented.py new file mode 100644 index 0000000000000000000000000000000000000000..835da28c5bfac0f8fe1375e20f364b1c4518a379 --- /dev/null +++ b/PyPSA/source/test/test_stochastic_not_implemented.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +"""Test scenarios not implemented decorators.""" + +import pandas as pd +import pytest + +import pypsa + + +def test_clustering(): + """Test that clustering methods raise ValueError when used with stochastic networks.""" + # Create a regular network + n = pypsa.Network() + n.add("Bus", "bus1", x=0, y=0) + n.add("Bus", "bus2", x=1, y=1) + + # Add scenarios to make it a stochastic network + n.scenarios = ["scenario1", "scenario2"] + assert n.has_scenarios + + # Test that all clustering methods raise ValueError + busmap = {"bus1": "cluster1", "bus2": "cluster1"} + bus_weights = pd.Series([1, 1], index=["bus1", "bus2"]) + + with pytest.raises(ValueError, match="not yet implemented for stochastic networks"): + n.cluster.busmap_by_hac(2) + + with pytest.raises(ValueError, match="not yet implemented for stochastic networks"): + n.cluster.busmap_by_kmeans(bus_weights, 2) + + with pytest.raises(ValueError, match="not yet implemented for stochastic networks"): + n.cluster.busmap_by_greedy_modularity(2) + + with pytest.raises(ValueError, match="not yet implemented for stochastic networks"): + n.cluster.cluster_spatially_by_hac(2) + + with pytest.raises(ValueError, match="not yet implemented for stochastic networks"): + n.cluster.cluster_spatially_by_kmeans(bus_weights, 2) + + with pytest.raises(ValueError, match="not yet implemented for stochastic networks"): + n.cluster.cluster_spatially_by_greedy_modularity(2) + + with pytest.raises(ValueError, match="not yet implemented for stochastic networks"): + n.cluster.cluster_by_busmap(busmap) + + with pytest.raises(ValueError, match="not yet implemented for stochastic networks"): + n.cluster.get_clustering_from_busmap(busmap) diff --git a/PyPSA/source/test/test_subnetwork.py b/PyPSA/source/test/test_subnetwork.py new file mode 100644 index 0000000000000000000000000000000000000000..35d05132293df2e289f876dcc70335b86891eacb --- /dev/null +++ b/PyPSA/source/test/test_subnetwork.py @@ -0,0 +1,92 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pytest + +import pypsa +from pypsa import Network, SubNetwork + + +@pytest.fixture +def scipy_subnetwork(scipy_network: Network) -> SubNetwork: + n = scipy_network + n.determine_network_topology() + return n.c.sub_networks.static.obj.iloc[0] + + +@pytest.fixture +def ac_dc_subnetwork(ac_dc_network: Network) -> SubNetwork: + n = ac_dc_network + n.determine_network_topology() + return n.c.sub_networks.static.obj.iloc[1] + + +@pytest.fixture +def ac_dc_subnetwork_inactive(ac_dc_network: Network) -> SubNetwork: + n = ac_dc_network + n.c.lines.static.loc["2", "active"] = False + n.determine_network_topology() + return n.c.sub_networks.static.obj.iloc[1] + + +def test_network(scipy_subnetwork: SubNetwork) -> None: + assert isinstance(scipy_subnetwork.n, pypsa.Network) + + +def test_name(scipy_subnetwork: SubNetwork) -> None: + assert scipy_subnetwork.name == "0" + + +def test_snapshots(scipy_subnetwork: SubNetwork) -> None: + assert scipy_subnetwork.snapshots.equals(scipy_subnetwork.n.snapshots) + + +def test_snapshot_weightings(scipy_subnetwork: SubNetwork) -> None: + assert scipy_subnetwork.snapshot_weightings.equals( + scipy_subnetwork.n.snapshot_weightings + ) + + +def test_investment_periods(scipy_subnetwork: SubNetwork) -> None: + assert scipy_subnetwork.investment_periods.equals( + scipy_subnetwork.n.investment_periods + ) + + +def test_investment_period_weightings(scipy_subnetwork: SubNetwork) -> None: + assert scipy_subnetwork.investment_period_weightings.equals( + scipy_subnetwork.n.investment_period_weightings + ) + + +def test_df(scipy_subnetwork: SubNetwork) -> None: + buses = scipy_subnetwork.components.buses.static + assert not buses.empty + assert buses.index.isin(scipy_subnetwork.n.c.buses.static.index).all() + + component_names = ["Line", "Transformer", "Generator", "Load"] + for c_name in component_names: + df = scipy_subnetwork.components[c_name].static + assert not df.empty + assert df.index.isin(scipy_subnetwork.n.c[c_name].static.index).all() + + with pytest.raises(ValueError): + scipy_subnetwork.components["Link"].static + + with pytest.raises(ValueError): + scipy_subnetwork.components["GlobalConstraint"].static + + +def test_incidence_matrix(ac_dc_subnetwork: SubNetwork) -> None: + lines = ac_dc_subnetwork.components["Line"].static + buses = ac_dc_subnetwork.components["Bus"].static + A = ac_dc_subnetwork.incidence_matrix() + assert A.shape == (len(buses), len(lines)) + + +def test_incidence_matrix_inactive(ac_dc_subnetwork_inactive: SubNetwork) -> None: + lines = ac_dc_subnetwork_inactive.components.lines.static + buses = ac_dc_subnetwork_inactive.components.buses.static + A = ac_dc_subnetwork_inactive.incidence_matrix() + assert A.shape == (len(buses), len(lines[lines["active"]])) diff --git a/PyPSA/source/test/test_version.py b/PyPSA/source/test/test_version.py new file mode 100644 index 0000000000000000000000000000000000000000..5f98d3700cb9a4b291b313c2239c0bf2d0e15d46 --- /dev/null +++ b/PyPSA/source/test/test_version.py @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +import pytest + +import pypsa +from pypsa.version import check_pypsa_version + + +def test_version_check(caplog): + caplog.clear() + check_pypsa_version("0.20.0") + assert caplog.text == "" + + caplog.clear() + check_pypsa_version("0.0") + assert "The correct version of PyPSA could not be resolved" in caplog.text + + +def test_version_deprecations(): + """Test that deprecated version attributes raise DeprecationWarning.""" + # Test deprecated renamed attributes + with pytest.raises( + DeprecationWarning, + match="pypsa.__version_semver__ is deprecated. Use pypsa.__version_base__ instead.", + ): + _ = pypsa.__version_semver__ + + with pytest.raises( + DeprecationWarning, + match="pypsa.__version_short__ is deprecated. Use pypsa.__version_major_minor__ instead.", + ): + _ = pypsa.__version_short__ + + # Test removed tuple attributes + with pytest.raises( + DeprecationWarning, match="pypsa.__version_semver_tuple__ has been removed" + ): + _ = pypsa.__version_semver_tuple__ + + with pytest.raises( + DeprecationWarning, match="pypsa.__version_short_tuple__ has been removed" + ): + _ = pypsa.__version_short_tuple__ + + # Test that new attributes work + assert isinstance(pypsa.__version_base__, str) + assert isinstance(pypsa.__version_major_minor__, str) diff --git a/PyPSA/source/test/test_warnings_cyclic_storage.py b/PyPSA/source/test/test_warnings_cyclic_storage.py new file mode 100644 index 0000000000000000000000000000000000000000..74ddd5739a62e73f3768ca1a331891f181114ed8 --- /dev/null +++ b/PyPSA/source/test/test_warnings_cyclic_storage.py @@ -0,0 +1,234 @@ +# SPDX-FileCopyrightText: PyPSA Contributors +# +# SPDX-License-Identifier: MIT + +""" +Test warnings for cyclic storage overriding initial values. +""" + +import logging + +import pandas as pd + +import pypsa + + +def test_warning_storage_unit_global_cyclic(caplog): + """Test warning when global cyclic_state_of_charge overrides initial value.""" + n = pypsa.Network(snapshots=range(4)) + n.add("Bus", "bus") + n.add("Carrier", "carrier") + n.add("Load", "load", bus="bus", p_set=0.1) + n.add("Generator", "gen", bus="bus", carrier="carrier", p_nom=1, marginal_cost=10) + + # Add storage with both cyclic=True and non-zero initial value + n.add( + "StorageUnit", + "storage", + bus="bus", + carrier="carrier", + p_nom=1, + max_hours=1, + state_of_charge_initial=0.5, # Non-zero initial value + cyclic_state_of_charge=True, # Cyclic enabled + marginal_cost=1, + ) + + with caplog.at_level(logging.WARNING): + n.optimize() + + # Check that warning was issued + assert any( + "Cyclic state of charge constraint overrules initial storage level setting" + in record.message + and "storage" in record.message + for record in caplog.records + ), "Expected warning about cyclic overriding initial values not found" + + +def test_warning_storage_unit_per_period_cyclic(caplog): + """Test warning when per-period cyclic overrides initial value in multi-investment.""" + n = pypsa.Network() + n.set_snapshots( + pd.MultiIndex.from_tuples( + [(2030, 0), (2030, 1), (2040, 0), (2040, 1)], names=["period", "timestep"] + ) + ) + n.set_investment_periods([2030, 2040]) + n.add("Bus", "bus") + n.add("Carrier", "carrier") + n.add("Load", "load", bus="bus", p_set=0.1) + n.add("Generator", "gen", bus="bus", carrier="carrier", p_nom=1, marginal_cost=10) + + # Add storage with both per-period cyclic=True and per-period initial=True + n.add( + "StorageUnit", + "storage", + bus="bus", + carrier="carrier", + p_nom=1, + max_hours=1, + state_of_charge_initial=0.5, # Non-zero initial value + cyclic_state_of_charge_per_period=True, # Per-period cyclic + state_of_charge_initial_per_period=True, # Per-period initial + marginal_cost=1, + ) + + with caplog.at_level(logging.WARNING): + n.optimize(multi_investment_periods=True) + + # Check that warning was issued + assert any( + "Cyclic state of charge constraint overrules initial storage level setting" + in record.message + and "storage" in record.message + for record in caplog.records + ), "Expected warning about cyclic overriding initial values not found" + + +def test_warning_store_global_cyclic(caplog): + """Test warning when global e_cyclic overrides initial value.""" + n = pypsa.Network(snapshots=range(4)) + n.add("Bus", "bus") + n.add("Carrier", "carrier") + n.add("Load", "load", bus="bus", p_set=0.1) + n.add("Generator", "gen", bus="bus", carrier="carrier", p_nom=1, marginal_cost=10) + + # Add store with both cyclic=True and non-zero initial value + n.add( + "Store", + "store", + bus="bus", + carrier="carrier", + e_nom=1, + e_initial=0.5, # Non-zero initial value + e_cyclic=True, # Cyclic enabled + marginal_cost=1, + ) + + with caplog.at_level(logging.WARNING): + n.optimize() + + # Check that warning was issued + assert any( + "Cyclic energy level constraint overrules initial value setting" + in record.message + and "store" in record.message + for record in caplog.records + ), "Expected warning about cyclic overriding initial values not found" + + +def test_warning_store_per_period_cyclic(caplog): + """Test warning when per-period cyclic overrides initial value in multi-investment.""" + n = pypsa.Network() + n.set_snapshots( + pd.MultiIndex.from_tuples( + [(2030, 0), (2030, 1), (2040, 0), (2040, 1)], names=["period", "timestep"] + ) + ) + n.set_investment_periods([2030, 2040]) + n.add("Bus", "bus") + n.add("Carrier", "carrier") + n.add("Load", "load", bus="bus", p_set=0.1) + n.add("Generator", "gen", bus="bus", carrier="carrier", p_nom=1, marginal_cost=10) + + # Add store with both per-period cyclic=True and per-period initial=True + n.add( + "Store", + "store", + bus="bus", + carrier="carrier", + e_nom=1, + e_initial=0.5, # Non-zero initial value + e_cyclic_per_period=True, # Per-period cyclic + e_initial_per_period=True, # Per-period initial + marginal_cost=1, + ) + + with caplog.at_level(logging.WARNING): + n.optimize(multi_investment_periods=True) + + # Check that warning was issued + assert any( + "Cyclic energy level constraint overrules initial value setting" + in record.message + and "store" in record.message + for record in caplog.records + ), "Expected warning about cyclic overriding initial values not found" + + +def test_warning_storage_unit_cp_overrides_c(caplog): + """Test warning when per-period cyclic overrides global cyclic for StorageUnit.""" + n = pypsa.Network() + n.set_snapshots( + pd.MultiIndex.from_tuples( + [(2030, 0), (2030, 1), (2040, 0), (2040, 1)], names=["period", "timestep"] + ) + ) + n.set_investment_periods([2030, 2040]) + n.add("Bus", "bus") + n.add("Carrier", "carrier") + n.add("Load", "load", bus="bus", p_set=0.1) + n.add("Generator", "gen", bus="bus", carrier="carrier", p_nom=1, marginal_cost=10) + + # Add storage with both global cyclic AND per-period cyclic (CP overrides C) + n.add( + "StorageUnit", + "storage", + bus="bus", + carrier="carrier", + p_nom=1, + max_hours=1, + cyclic_state_of_charge=True, # Global cyclic + cyclic_state_of_charge_per_period=True, # Per-period cyclic (overrides global) + marginal_cost=1, + ) + + with caplog.at_level(logging.WARNING): + n.optimize(multi_investment_periods=True) + + # Check that warning was issued + assert any( + "Per-period cyclic (cyclic_state_of_charge_per_period=True) overrides global cyclic" + in record.message + and "storage" in record.message + for record in caplog.records + ), "Expected warning about CP overriding C not found" + + +def test_warning_store_cp_overrides_c(caplog): + """Test warning when per-period cyclic overrides global cyclic for Store.""" + n = pypsa.Network() + n.set_snapshots( + pd.MultiIndex.from_tuples( + [(2030, 0), (2030, 1), (2040, 0), (2040, 1)], names=["period", "timestep"] + ) + ) + n.set_investment_periods([2030, 2040]) + n.add("Bus", "bus") + n.add("Carrier", "carrier") + n.add("Load", "load", bus="bus", p_set=0.1) + n.add("Generator", "gen", bus="bus", carrier="carrier", p_nom=1, marginal_cost=10) + + # Add store with both global cyclic AND per-period cyclic (CP overrides C) + n.add( + "Store", + "store", + bus="bus", + carrier="carrier", + e_nom=1, + e_cyclic=True, # Global cyclic + e_cyclic_per_period=True, # Per-period cyclic (overrides global) + marginal_cost=1, + ) + + with caplog.at_level(logging.WARNING): + n.optimize(multi_investment_periods=True) + + # Check that warning was issued + assert any( + "Per-period cyclic (e_cyclic_per_period=True) overrides global cyclic" + in record.message + and "store" in record.message + for record in caplog.records + ), "Expected warning about CP overriding C not found"