├── .github └── workflows │ ├── reuse-compliance.yml │ └── sync-examples.yml ├── .gitignore ├── LICENSE ├── LICENSES └── MPL-2.0.txt ├── README.md ├── data ├── line.csv ├── line.csv.license ├── node.csv ├── node.csv.license ├── source.csv ├── source.csv.license ├── sym_load.csv └── sym_load.csv.license ├── examples ├── Asymmetric Calculation Example.ipynb ├── Asymmetric Calculation Example.ipynb.license ├── Asymmetric Line.ipynb ├── Asymmetric Line.ipynb.license ├── Generic Branch Example.ipynb ├── Generic Branch Example.ipynb.license ├── Make Test Dataset.ipynb ├── Make Test Dataset.ipynb.license ├── Power Flow Example.ipynb ├── Power Flow Example.ipynb.license ├── Serialization Example.ipynb ├── Serialization Example.ipynb.license ├── Short Circuit Example.ipynb ├── Short Circuit Example.ipynb.license ├── State Estimation Example.ipynb ├── State Estimation Example.ipynb.license ├── Transformer Examples.ipynb ├── Transformer Examples.ipynb.license ├── Validation Examples.ipynb ├── Validation Examples.ipynb.license └── data │ ├── serialized_input.json │ └── serialized_input.json.license ├── power-flow-assignment ├── Power Flow Assignment with Solutions.ipynb ├── Power Flow Assignment with Solutions.ipynb.license ├── Power Flow Assignment.ipynb └── Power Flow Assignment.ipynb.license ├── power-grid-model-ds ├── README.md ├── advanced.ipynb ├── advanced.ipynb.license ├── data │ ├── lines.csv │ ├── lines.csv.license │ ├── loads.csv │ ├── loads.csv.license │ ├── nodes.csv │ └── nodes.csv.license ├── helper.py ├── input_network.png ├── input_network.png.license ├── input_network_with_overload.png ├── input_network_with_overload.png.license ├── introduction.ipynb ├── introduction.ipynb.license └── solutions │ ├── advanced_3_check_for_capacity_issues.py │ ├── advanced_3_check_for_capacity_issues.py.license │ ├── advanced_4_build_new_substation.py │ ├── advanced_4_build_new_substation.py.license │ ├── advanced_5_1_get_all_congested_routes.py │ ├── advanced_5_1_get_all_congested_routes.py.license │ ├── advanced_5_2_find_connection_point.py │ ├── advanced_5_2_find_connection_point.py.license │ ├── advanced_5_3_connect_to_route.py │ ├── advanced_5_3_connect_to_route.py.license │ ├── advanced_6_optimize_route_transfer.py │ ├── advanced_6_optimize_route_transfer.py.license │ ├── introduction_1_1_define_array_extensions.py │ ├── introduction_1_1_define_array_extensions.py.license │ ├── introduction_1_2_define_my_grid.py │ ├── introduction_1_2_define_my_grid.py.license │ ├── introduction_1_3_grid_empty.py │ ├── introduction_1_3_grid_empty.py.license │ ├── introduction_1_3_grid_verification.py │ ├── introduction_1_3_grid_verification.py.license │ ├── introduction_2_1_add_substation.py │ ├── introduction_2_1_add_substation.py.license │ ├── introduction_2_2_add_node.py │ ├── introduction_2_2_add_node.py.license │ ├── introduction_2_3_add_line.py │ ├── introduction_2_3_add_line.py.license │ ├── introduction_2_4_add_load.py │ ├── introduction_2_4_add_load.py.license │ ├── introduction_2_5_add_source.py │ ├── introduction_2_5_add_source.py.license │ ├── introduction_2_6_check_ids.py │ └── introduction_2_6_check_ids.py.license ├── requirements.txt └── state-estimation-assignment ├── State Estimation Assignment with Solutions.ipynb ├── State Estimation Assignment with Solutions.ipynb.license ├── State Estimation Assignment.ipynb └── State Estimation Assignment.ipynb.license /.github/workflows/reuse-compliance.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | name: REUSE Compliance Check 7 | 8 | on: 9 | # run pipeline on push event of main or release branch 10 | push: 11 | branches: 12 | - main 13 | - 'release/**' 14 | # run pipeline on pull request 15 | pull_request: 16 | 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.ref }} 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | reuse-compliance-check: 23 | if: (github.event_name == 'push') || (!startsWith(github.head_ref, 'release')) 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: checkout 27 | uses: actions/checkout@v4 28 | - name: REUSE Compliance Check 29 | uses: fsfe/reuse-action@v2 30 | -------------------------------------------------------------------------------- /.github/workflows/sync-examples.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | name: Sync example notebooks from main PGM repo to PGM-workshop 6 | 7 | on: 8 | workflow_dispatch: 9 | schedule: 10 | - cron: "0 2 * * *" # Based on UTC time 11 | 12 | jobs: 13 | sync-files: 14 | runs-on: ubuntu-latest 15 | 16 | permissions: 17 | contents: write 18 | 19 | steps: 20 | - name: Checkout PGM workshop repository 21 | uses: actions/checkout@v4 22 | with: 23 | path: power-grid-model-workshop 24 | 25 | - name: Checkout PGM Repository examples 26 | uses: actions/checkout@v4 27 | with: 28 | repository: PowerGridModel/power-grid-model 29 | ref: main 30 | path: power-grid-model 31 | sparse-checkout: docs/examples 32 | sparse-checkout-cone-mode: false 33 | 34 | - name: Move pgm examples to pgm workshop repository 35 | run: | 36 | rm -rf power-grid-model-workshop/examples 37 | mv power-grid-model/docs/examples power-grid-model-workshop 38 | 39 | - name: Use REs to replace links 40 | run: sed -i 's|\.\./\(.*\)\.md|https://power-grid-model.readthedocs.io/en/stable/\1.html|g' *.ipynb 41 | working-directory: power-grid-model-workshop/examples 42 | 43 | - name: Commit and Push Changes to PGM workshop repository 44 | uses: stefanzweifel/git-auto-commit-action@v5 45 | with: 46 | commit_message: Update the examples 47 | repository: power-grid-model-workshop 48 | commit_options: '--signoff' 49 | commit_user_name: GitHub Actions Bot 50 | commit_user_email: actions@github.com 51 | commit_author: GitHub Actions Bot 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | pip-wheel-metadata/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | # IDE 136 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. "Contributor" means each individual or legal entity that creates, contributes 6 | to the creation of, or owns Covered Software. 7 | 8 | 1.2. "Contributor Version" means the combination of the Contributions of others 9 | (if any) used by a Contributor and that particular Contributor's Contribution. 10 | 11 | 1.3. "Contribution" means Covered Software of a particular Contributor. 12 | 13 | 1.4. "Covered Software" means Source Code Form to which the initial Contributor 14 | has attached the notice in Exhibit A, the Executable Form of such Source Code 15 | Form, and Modifications of such Source Code Form, in each case including portions 16 | thereof. 17 | 18 | 1.5. "Incompatible With Secondary Licenses" means 19 | 20 | (a) that the initial Contributor has attached the notice described in Exhibit 21 | B to the Covered Software; or 22 | 23 | (b) that the Covered Software was made available under the terms of version 24 | 1.1 or earlier of the License, but not also under the terms of a Secondary 25 | License. 26 | 27 | 1.6. "Executable Form" means any form of the work other than Source Code Form. 28 | 29 | 1.7. "Larger Work" means a work that combines Covered Software with other 30 | material, in a separate file or files, that is not Covered Software. 31 | 32 | 1.8. "License" means this document. 33 | 34 | 1.9. "Licensable" means having the right to grant, to the maximum extent possible, 35 | whether at the time of the initial grant or subsequently, any and all of the 36 | rights conveyed by this License. 37 | 38 | 1.10. "Modifications" means any of the following: 39 | 40 | (a) any file in Source Code Form that results from an addition to, deletion 41 | from, or modification of the contents of Covered Software; or 42 | 43 | (b) any new file in Source Code Form that contains any Covered Software. 44 | 45 | 1.11. "Patent Claims" of a Contributor means any patent claim(s), including 46 | without limitation, method, process, and apparatus claims, in any patent Licensable 47 | by such Contributor that would be infringed, but for the grant of the License, 48 | by the making, using, selling, offering for sale, having made, import, or 49 | transfer of either its Contributions or its Contributor Version. 50 | 51 | 1.12. "Secondary License" means either the GNU General Public License, Version 52 | 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General 53 | Public License, Version 3.0, or any later versions of those licenses. 54 | 55 | 1.13. "Source Code Form" means the form of the work preferred for making modifications. 56 | 57 | 1.14. "You" (or "Your") means an individual or a legal entity exercising rights 58 | under this License. For legal entities, "You" includes any entity that controls, 59 | is controlled by, or is under common control with You. For purposes of this 60 | definition, "control" means (a) the power, direct or indirect, to cause the 61 | direction or management of such entity, whether by contract or otherwise, 62 | or (b) ownership of more than fifty percent (50%) of the outstanding shares 63 | or beneficial ownership of such entity. 64 | 65 | 2. License Grants and Conditions 66 | 67 | 2.1. Grants 68 | Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive 69 | license: 70 | 71 | (a) under intellectual property rights (other than patent or trademark) Licensable 72 | by such Contributor to use, reproduce, make available, modify, display, perform, 73 | distribute, and otherwise exploit its Contributions, either on an unmodified 74 | basis, with Modifications, or as part of a Larger Work; and 75 | 76 | (b) under Patent Claims of such Contributor to make, use, sell, offer for 77 | sale, have made, import, and otherwise transfer either its Contributions or 78 | its Contributor Version. 79 | 80 | 2.2. Effective Date 81 | The licenses granted in Section 2.1 with respect to any Contribution become 82 | effective for each Contribution on the date the Contributor first distributes 83 | such Contribution. 84 | 85 | 2.3. Limitations on Grant Scope 86 | The licenses granted in this Section 2 are the only rights granted under this 87 | License. No additional rights or licenses will be implied from the distribution 88 | or licensing of Covered Software under this License. Notwithstanding Section 89 | 2.1(b) above, no patent license is granted by a Contributor: 90 | 91 | (a) for any code that a Contributor has removed from Covered Software; or 92 | 93 | (b) for infringements caused by: (i) Your and any other third party's modifications 94 | of Covered Software, or (ii) the combination of its Contributions with other 95 | software (except as part of its Contributor Version); or 96 | 97 | (c) under Patent Claims infringed by Covered Software in the absence of its 98 | Contributions. 99 | 100 | This License does not grant any rights in the trademarks, service marks, or 101 | logos of any Contributor (except as may be necessary to comply with the notice 102 | requirements in Section 3.4). 103 | 104 | 2.4. Subsequent Licenses 105 | No Contributor makes additional grants as a result of Your choice to distribute 106 | the Covered Software under a subsequent version of this License (see Section 107 | 10.2) or under the terms of a Secondary License (if permitted under the terms 108 | of Section 3.3). 109 | 110 | 2.5. Representation 111 | Each Contributor represents that the Contributor believes its Contributions 112 | are its original creation(s) or it has sufficient rights to grant the rights 113 | to its Contributions conveyed by this License. 114 | 115 | 2.6. Fair Use 116 | This License is not intended to limit any rights You have under applicable 117 | copyright doctrines of fair use, fair dealing, or other equivalents. 118 | 119 | 2.7. Conditions 120 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 121 | Section 2.1. 122 | 123 | 3. Responsibilities 124 | 125 | 3.1. Distribution of Source Form 126 | All distribution of Covered Software in Source Code Form, including any Modifications 127 | that You create or to which You contribute, must be under the terms of this 128 | License. You must inform recipients that the Source Code Form of the Covered 129 | Software is governed by the terms of this License, and how they can obtain 130 | a copy of this License. You may not attempt to alter or restrict the recipients' 131 | rights in the Source Code Form. 132 | 133 | 3.2. Distribution of Executable Form 134 | If You distribute Covered Software in Executable Form then: 135 | 136 | (a) such Covered Software must also be made available in Source Code Form, 137 | as described in Section 3.1, and You must inform recipients of the Executable 138 | Form how they can obtain a copy of such Source Code Form by reasonable means 139 | in a timely manner, at a charge no more than the cost of distribution to the 140 | recipient; and 141 | 142 | (b) You may distribute such Executable Form under the terms of this License, 143 | or sublicense it under different terms, provided that the license for the 144 | Executable Form does not attempt to limit or alter the recipients' rights 145 | in the Source Code Form under this License. 146 | 147 | 3.3. Distribution of a Larger Work 148 | You may create and distribute a Larger Work under terms of Your choice, provided 149 | that You also comply with the requirements of this License for the Covered 150 | Software. If the Larger Work is a combination of Covered Software with a work 151 | governed by one or more Secondary Licenses, and the Covered Software is not 152 | Incompatible With Secondary Licenses, this License permits You to additionally 153 | distribute such Covered Software under the terms of such Secondary License(s), 154 | so that the recipient of the Larger Work may, at their option, further distribute 155 | the Covered Software under the terms of either this License or such Secondary 156 | License(s). 157 | 158 | 3.4. Notices 159 | You may not remove or alter the substance of any license notices (including 160 | copyright notices, patent notices, disclaimers of warranty, or limitations 161 | of liability) contained within the Source Code Form of the Covered Software, 162 | except that You may alter any license notices to the extent required to remedy 163 | known factual inaccuracies. 164 | 165 | 3.5. Application of Additional Terms 166 | You may choose to offer, and to charge a fee for, warranty, support, indemnity 167 | or liability obligations to one or more recipients of Covered Software. However, 168 | You may do so only on Your own behalf, and not on behalf of any Contributor. 169 | You must make it absolutely clear that any such warranty, support, indemnity, 170 | or liability obligation is offered by You alone, and You hereby agree to indemnify 171 | every Contributor for any liability incurred by such Contributor as a result 172 | of warranty, support, indemnity or liability terms You offer. You may include 173 | additional disclaimers of warranty and limitations of liability specific to 174 | any jurisdiction. 175 | 176 | 4. Inability to Comply Due to Statute or Regulation 177 | If it is impossible for You to comply with any of the terms of this License 178 | with respect to some or all of the Covered Software due to statute, judicial 179 | order, or regulation then You must: (a) comply with the terms of this License 180 | to the maximum extent possible; and (b) describe the limitations and the code 181 | they affect. Such description must be placed in a text file included with 182 | all distributions of the Covered Software under this License. Except to the 183 | extent prohibited by statute or regulation, such description must be sufficiently 184 | detailed for a recipient of ordinary skill to be able to understand it. 185 | 186 | 5. Termination 187 | 188 | 5.1. The rights granted under this License will terminate automatically if 189 | You fail to comply with any of its terms. However, if You become compliant, 190 | then the rights granted under this License from a particular Contributor are 191 | reinstated (a) provisionally, unless and until such Contributor explicitly 192 | and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor 193 | fails to notify You of the non-compliance by some reasonable means prior to 194 | 60 days after You have come back into compliance. Moreover, Your grants from 195 | a particular Contributor are reinstated on an ongoing basis if such Contributor 196 | notifies You of the non-compliance by some reasonable means, this is the first 197 | time You have received notice of non-compliance with this License from such 198 | Contributor, and You become compliant prior to 30 days after Your receipt 199 | of the notice. 200 | 201 | 5.2. If You initiate litigation against any entity by asserting a patent infringement 202 | claim (excluding declaratory judgment actions, counter-claims, and cross-claims) 203 | alleging that a Contributor Version directly or indirectly infringes any patent, 204 | then the rights granted to You by any and all Contributors for the Covered 205 | Software under Section 2.1 of this License shall terminate. 206 | 207 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end 208 | user license agreements (excluding distributors and resellers) which have 209 | been validly granted by You or Your distributors under this License prior 210 | to termination shall survive termination. 211 | 212 | 6. Disclaimer of Warranty 213 | Covered Software is provided under this License on an "as is" basis, without 214 | warranty of any kind, either expressed, implied, or statutory, including, 215 | without limitation, warranties that the Covered Software is free of defects, 216 | merchantable, fit for a particular purpose or non-infringing. The entire risk 217 | as to the quality and performance of the Covered Software is with You. Should 218 | any Covered Software prove defective in any respect, You (not any Contributor) 219 | assume the cost of any necessary servicing, repair, or correction. This disclaimer 220 | of warranty constitutes an essential part of this License. No use of any Covered 221 | Software is authorized under this License except under this disclaimer. 222 | 223 | 7. Limitation of Liability 224 | Under no circumstances and under no legal theory, whether tort (including 225 | negligence), contract, or otherwise, shall any Contributor, or anyone who 226 | distributes Covered Software as permitted above, be liable to You for any 227 | direct, indirect, special, incidental, or consequential damages of any character 228 | including, without limitation, damages for lost profits, loss of goodwill, 229 | work stoppage, computer failure or malfunction, or any and all other commercial 230 | damages or losses, even if such party shall have been informed of the possibility 231 | of such damages. This limitation of liability shall not apply to liability 232 | for death or personal injury resulting from such party's negligence to the 233 | extent applicable law prohibits such limitation. Some jurisdictions do not 234 | allow the exclusion or limitation of incidental or consequential damages, 235 | so this exclusion and limitation may not apply to You. 236 | 237 | 8. Litigation 238 | Any litigation relating to this License may be brought only in the courts 239 | of a jurisdiction where the defendant maintains its principal place of business 240 | and such litigation shall be governed by laws of that jurisdiction, without 241 | reference to its conflict-of-law provisions. Nothing in this Section shall 242 | prevent a party's ability to bring cross-claims or counter-claims. 243 | 244 | 9. Miscellaneous 245 | This License represents the complete agreement concerning the subject matter 246 | hereof. If any provision of this License is held to be unenforceable, such 247 | provision shall be reformed only to the extent necessary to make it enforceable. 248 | Any law or regulation which provides that the language of a contract shall 249 | be construed against the drafter shall not be used to construe this License 250 | against a Contributor. 251 | 252 | 10. Versions of the License 253 | 254 | 10.1. New Versions 255 | Mozilla Foundation is the license steward. Except as provided in Section 10.3, 256 | no one other than the license steward has the right to modify or publish new 257 | versions of this License. Each version will be given a distinguishing version 258 | number. 259 | 260 | 10.2. Effect of New Versions 261 | You may distribute the Covered Software under the terms of the version of 262 | the License under which You originally received the Covered Software, or under 263 | the terms of any subsequent version published by the license steward. 264 | 265 | 10.3. Modified Versions 266 | If you create software not governed by this License, and you want to create 267 | a new license for such software, you may create and use a modified version 268 | of this License if you rename the license and remove any references to the 269 | name of the license steward (except to note that such modified license differs 270 | from this License). 271 | 272 | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses 273 | If You choose to distribute Source Code Form that is Incompatible With Secondary 274 | Licenses under the terms of this version of the License, the notice described 275 | in Exhibit B of this License must be attached. 276 | 277 | Exhibit A - Source Code Form License Notice 278 | 279 | This Source Code Form is subject to the terms of the Mozilla Public License, 280 | v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain 281 | one at http://mozilla.org/MPL/2.0/. 282 | 283 | If it is not possible or desirable to put the notice in a particular file, 284 | then You may include the notice in a location (such as a LICENSE file in a 285 | relevant directory) where a recipient would be likely to look for such a notice. 286 | 287 | You may add additional accurate notices of copyright ownership. 288 | 289 | Exhibit B - "Incompatible With Secondary Licenses" Notice 290 | 291 | This Source Code Form is "Incompatible With Secondary Licenses", as defined 292 | by the Mozilla Public License, v. 2.0. 293 | -------------------------------------------------------------------------------- /LICENSES/MPL-2.0.txt: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. "Contributor" means each individual or legal entity that creates, contributes 6 | to the creation of, or owns Covered Software. 7 | 8 | 1.2. "Contributor Version" means the combination of the Contributions of others 9 | (if any) used by a Contributor and that particular Contributor's Contribution. 10 | 11 | 1.3. "Contribution" means Covered Software of a particular Contributor. 12 | 13 | 1.4. "Covered Software" means Source Code Form to which the initial Contributor 14 | has attached the notice in Exhibit A, the Executable Form of such Source Code 15 | Form, and Modifications of such Source Code Form, in each case including portions 16 | thereof. 17 | 18 | 1.5. "Incompatible With Secondary Licenses" means 19 | 20 | (a) that the initial Contributor has attached the notice described in Exhibit 21 | B to the Covered Software; or 22 | 23 | (b) that the Covered Software was made available under the terms of version 24 | 1.1 or earlier of the License, but not also under the terms of a Secondary 25 | License. 26 | 27 | 1.6. "Executable Form" means any form of the work other than Source Code Form. 28 | 29 | 1.7. "Larger Work" means a work that combines Covered Software with other 30 | material, in a separate file or files, that is not Covered Software. 31 | 32 | 1.8. "License" means this document. 33 | 34 | 1.9. "Licensable" means having the right to grant, to the maximum extent possible, 35 | whether at the time of the initial grant or subsequently, any and all of the 36 | rights conveyed by this License. 37 | 38 | 1.10. "Modifications" means any of the following: 39 | 40 | (a) any file in Source Code Form that results from an addition to, deletion 41 | from, or modification of the contents of Covered Software; or 42 | 43 | (b) any new file in Source Code Form that contains any Covered Software. 44 | 45 | 1.11. "Patent Claims" of a Contributor means any patent claim(s), including 46 | without limitation, method, process, and apparatus claims, in any patent Licensable 47 | by such Contributor that would be infringed, but for the grant of the License, 48 | by the making, using, selling, offering for sale, having made, import, or 49 | transfer of either its Contributions or its Contributor Version. 50 | 51 | 1.12. "Secondary License" means either the GNU General Public License, Version 52 | 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General 53 | Public License, Version 3.0, or any later versions of those licenses. 54 | 55 | 1.13. "Source Code Form" means the form of the work preferred for making modifications. 56 | 57 | 1.14. "You" (or "Your") means an individual or a legal entity exercising rights 58 | under this License. For legal entities, "You" includes any entity that controls, 59 | is controlled by, or is under common control with You. For purposes of this 60 | definition, "control" means (a) the power, direct or indirect, to cause the 61 | direction or management of such entity, whether by contract or otherwise, 62 | or (b) ownership of more than fifty percent (50%) of the outstanding shares 63 | or beneficial ownership of such entity. 64 | 65 | 2. License Grants and Conditions 66 | 67 | 2.1. Grants 68 | Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive 69 | license: 70 | 71 | (a) under intellectual property rights (other than patent or trademark) Licensable 72 | by such Contributor to use, reproduce, make available, modify, display, perform, 73 | distribute, and otherwise exploit its Contributions, either on an unmodified 74 | basis, with Modifications, or as part of a Larger Work; and 75 | 76 | (b) under Patent Claims of such Contributor to make, use, sell, offer for 77 | sale, have made, import, and otherwise transfer either its Contributions or 78 | its Contributor Version. 79 | 80 | 2.2. Effective Date 81 | The licenses granted in Section 2.1 with respect to any Contribution become 82 | effective for each Contribution on the date the Contributor first distributes 83 | such Contribution. 84 | 85 | 2.3. Limitations on Grant Scope 86 | The licenses granted in this Section 2 are the only rights granted under this 87 | License. No additional rights or licenses will be implied from the distribution 88 | or licensing of Covered Software under this License. Notwithstanding Section 89 | 2.1(b) above, no patent license is granted by a Contributor: 90 | 91 | (a) for any code that a Contributor has removed from Covered Software; or 92 | 93 | (b) for infringements caused by: (i) Your and any other third party's modifications 94 | of Covered Software, or (ii) the combination of its Contributions with other 95 | software (except as part of its Contributor Version); or 96 | 97 | (c) under Patent Claims infringed by Covered Software in the absence of its 98 | Contributions. 99 | 100 | This License does not grant any rights in the trademarks, service marks, or 101 | logos of any Contributor (except as may be necessary to comply with the notice 102 | requirements in Section 3.4). 103 | 104 | 2.4. Subsequent Licenses 105 | No Contributor makes additional grants as a result of Your choice to distribute 106 | the Covered Software under a subsequent version of this License (see Section 107 | 10.2) or under the terms of a Secondary License (if permitted under the terms 108 | of Section 3.3). 109 | 110 | 2.5. Representation 111 | Each Contributor represents that the Contributor believes its Contributions 112 | are its original creation(s) or it has sufficient rights to grant the rights 113 | to its Contributions conveyed by this License. 114 | 115 | 2.6. Fair Use 116 | This License is not intended to limit any rights You have under applicable 117 | copyright doctrines of fair use, fair dealing, or other equivalents. 118 | 119 | 2.7. Conditions 120 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 121 | Section 2.1. 122 | 123 | 3. Responsibilities 124 | 125 | 3.1. Distribution of Source Form 126 | All distribution of Covered Software in Source Code Form, including any Modifications 127 | that You create or to which You contribute, must be under the terms of this 128 | License. You must inform recipients that the Source Code Form of the Covered 129 | Software is governed by the terms of this License, and how they can obtain 130 | a copy of this License. You may not attempt to alter or restrict the recipients' 131 | rights in the Source Code Form. 132 | 133 | 3.2. Distribution of Executable Form 134 | If You distribute Covered Software in Executable Form then: 135 | 136 | (a) such Covered Software must also be made available in Source Code Form, 137 | as described in Section 3.1, and You must inform recipients of the Executable 138 | Form how they can obtain a copy of such Source Code Form by reasonable means 139 | in a timely manner, at a charge no more than the cost of distribution to the 140 | recipient; and 141 | 142 | (b) You may distribute such Executable Form under the terms of this License, 143 | or sublicense it under different terms, provided that the license for the 144 | Executable Form does not attempt to limit or alter the recipients' rights 145 | in the Source Code Form under this License. 146 | 147 | 3.3. Distribution of a Larger Work 148 | You may create and distribute a Larger Work under terms of Your choice, provided 149 | that You also comply with the requirements of this License for the Covered 150 | Software. If the Larger Work is a combination of Covered Software with a work 151 | governed by one or more Secondary Licenses, and the Covered Software is not 152 | Incompatible With Secondary Licenses, this License permits You to additionally 153 | distribute such Covered Software under the terms of such Secondary License(s), 154 | so that the recipient of the Larger Work may, at their option, further distribute 155 | the Covered Software under the terms of either this License or such Secondary 156 | License(s). 157 | 158 | 3.4. Notices 159 | You may not remove or alter the substance of any license notices (including 160 | copyright notices, patent notices, disclaimers of warranty, or limitations 161 | of liability) contained within the Source Code Form of the Covered Software, 162 | except that You may alter any license notices to the extent required to remedy 163 | known factual inaccuracies. 164 | 165 | 3.5. Application of Additional Terms 166 | You may choose to offer, and to charge a fee for, warranty, support, indemnity 167 | or liability obligations to one or more recipients of Covered Software. However, 168 | You may do so only on Your own behalf, and not on behalf of any Contributor. 169 | You must make it absolutely clear that any such warranty, support, indemnity, 170 | or liability obligation is offered by You alone, and You hereby agree to indemnify 171 | every Contributor for any liability incurred by such Contributor as a result 172 | of warranty, support, indemnity or liability terms You offer. You may include 173 | additional disclaimers of warranty and limitations of liability specific to 174 | any jurisdiction. 175 | 176 | 4. Inability to Comply Due to Statute or Regulation 177 | If it is impossible for You to comply with any of the terms of this License 178 | with respect to some or all of the Covered Software due to statute, judicial 179 | order, or regulation then You must: (a) comply with the terms of this License 180 | to the maximum extent possible; and (b) describe the limitations and the code 181 | they affect. Such description must be placed in a text file included with 182 | all distributions of the Covered Software under this License. Except to the 183 | extent prohibited by statute or regulation, such description must be sufficiently 184 | detailed for a recipient of ordinary skill to be able to understand it. 185 | 186 | 5. Termination 187 | 188 | 5.1. The rights granted under this License will terminate automatically if 189 | You fail to comply with any of its terms. However, if You become compliant, 190 | then the rights granted under this License from a particular Contributor are 191 | reinstated (a) provisionally, unless and until such Contributor explicitly 192 | and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor 193 | fails to notify You of the non-compliance by some reasonable means prior to 194 | 60 days after You have come back into compliance. Moreover, Your grants from 195 | a particular Contributor are reinstated on an ongoing basis if such Contributor 196 | notifies You of the non-compliance by some reasonable means, this is the first 197 | time You have received notice of non-compliance with this License from such 198 | Contributor, and You become compliant prior to 30 days after Your receipt 199 | of the notice. 200 | 201 | 5.2. If You initiate litigation against any entity by asserting a patent infringement 202 | claim (excluding declaratory judgment actions, counter-claims, and cross-claims) 203 | alleging that a Contributor Version directly or indirectly infringes any patent, 204 | then the rights granted to You by any and all Contributors for the Covered 205 | Software under Section 2.1 of this License shall terminate. 206 | 207 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end 208 | user license agreements (excluding distributors and resellers) which have 209 | been validly granted by You or Your distributors under this License prior 210 | to termination shall survive termination. 211 | 212 | 6. Disclaimer of Warranty 213 | Covered Software is provided under this License on an "as is" basis, without 214 | warranty of any kind, either expressed, implied, or statutory, including, 215 | without limitation, warranties that the Covered Software is free of defects, 216 | merchantable, fit for a particular purpose or non-infringing. The entire risk 217 | as to the quality and performance of the Covered Software is with You. Should 218 | any Covered Software prove defective in any respect, You (not any Contributor) 219 | assume the cost of any necessary servicing, repair, or correction. This disclaimer 220 | of warranty constitutes an essential part of this License. No use of any Covered 221 | Software is authorized under this License except under this disclaimer. 222 | 223 | 7. Limitation of Liability 224 | Under no circumstances and under no legal theory, whether tort (including 225 | negligence), contract, or otherwise, shall any Contributor, or anyone who 226 | distributes Covered Software as permitted above, be liable to You for any 227 | direct, indirect, special, incidental, or consequential damages of any character 228 | including, without limitation, damages for lost profits, loss of goodwill, 229 | work stoppage, computer failure or malfunction, or any and all other commercial 230 | damages or losses, even if such party shall have been informed of the possibility 231 | of such damages. This limitation of liability shall not apply to liability 232 | for death or personal injury resulting from such party's negligence to the 233 | extent applicable law prohibits such limitation. Some jurisdictions do not 234 | allow the exclusion or limitation of incidental or consequential damages, 235 | so this exclusion and limitation may not apply to You. 236 | 237 | 8. Litigation 238 | Any litigation relating to this License may be brought only in the courts 239 | of a jurisdiction where the defendant maintains its principal place of business 240 | and such litigation shall be governed by laws of that jurisdiction, without 241 | reference to its conflict-of-law provisions. Nothing in this Section shall 242 | prevent a party's ability to bring cross-claims or counter-claims. 243 | 244 | 9. Miscellaneous 245 | This License represents the complete agreement concerning the subject matter 246 | hereof. If any provision of this License is held to be unenforceable, such 247 | provision shall be reformed only to the extent necessary to make it enforceable. 248 | Any law or regulation which provides that the language of a contract shall 249 | be construed against the drafter shall not be used to construe this License 250 | against a Contributor. 251 | 252 | 10. Versions of the License 253 | 254 | 10.1. New Versions 255 | Mozilla Foundation is the license steward. Except as provided in Section 10.3, 256 | no one other than the license steward has the right to modify or publish new 257 | versions of this License. Each version will be given a distinguishing version 258 | number. 259 | 260 | 10.2. Effect of New Versions 261 | You may distribute the Covered Software under the terms of the version of 262 | the License under which You originally received the Covered Software, or under 263 | the terms of any subsequent version published by the license steward. 264 | 265 | 10.3. Modified Versions 266 | If you create software not governed by this License, and you want to create 267 | a new license for such software, you may create and use a modified version 268 | of this License if you rename the license and remove any references to the 269 | name of the license steward (except to note that such modified license differs 270 | from this License). 271 | 272 | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses 273 | If You choose to distribute Source Code Form that is Incompatible With Secondary 274 | Licenses under the terms of this version of the License, the notice described 275 | in Exhibit B of this License must be attached. 276 | 277 | Exhibit A - Source Code Form License Notice 278 | 279 | This Source Code Form is subject to the terms of the Mozilla Public License, 280 | v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain 281 | one at http://mozilla.org/MPL/2.0/. 282 | 283 | If it is not possible or desirable to put the notice in a particular file, 284 | then You may include the notice in a location (such as a LICENSE file in a 285 | relevant directory) where a recipient would be likely to look for such a notice. 286 | 287 | You may add additional accurate notices of copyright ownership. 288 | 289 | Exhibit B - "Incompatible With Secondary Licenses" Notice 290 | 291 | This Source Code Form is "Incompatible With Secondary Licenses", as defined 292 | by the Mozilla Public License, v. 2.0. 293 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Power Grid Model Workshop 8 | 9 | The steps for the workshop are to follow `Power Flow Example.ipynb` and `Power Flow Assignment.ipynb` simultaneously. 10 | To complete the workshop on your own, go through `Power Flow Example` notebook while completing the respective assignment from at each checkpoint. 11 | If you like to be guided through the workshop, please visit our [Power Grid Model Workshop](https://www.youtube.com/watch?v=Y8ejfZnnfPM) recording. 12 | 13 | ## Pre-knowledge 14 | 15 | Participants are expected to have knowledge of 16 | [Juypter Notebook](https://jupyter.org/) and 17 | [numpy](https://numpy.org/). 18 | 19 | It is recommended to read the numpy structured array 20 | [documentation](https://numpy.org/doc/stable/user/basics.rec.html). 21 | 22 | ## Preparation 23 | 24 | Please create the relevant environment before the workshop. If you encounter problems, please raise a question in the [discussion board](https://github.com/orgs/PowerGridModel/discussions) 25 | 26 | ### Preparation for Windows (beginners guide) 27 | 28 | 1. [Download](https://github.com/PowerGridModel/power-grid-model-workshop/archive/refs/heads/main.zip) (or 29 | checkout) this workshop/repository and remember the destination location. For example: 30 | `C:\Users\YourUserName\Downloads\power-grid-model-workshop`. 31 | 32 | 1. Install the latest [Python](https://www.python.org/ftp/python/3.13.3/python-3.13.3-amd64.exe) version. 33 | You probably want the **Windows installer (64-bit)** under **Stable Release**. 34 | 35 | 1. Now open a terminal (Windows-Key + R, type `cmd`, press **OK**) and use the `cd` (change dir) command to navigate 36 | to the folder where you downloaded the workshop. For example: 37 | ```shell 38 | C:\Users\YourUserName> cd Downloads\power-grid-model-workshop 39 | C:\Users\YourUserName\Downloads\power-grid-model-workshop>_ 40 | ``` 41 | 1. Optional: Create and activate a virtual environment. You can skip this step, but it helps you to keep your system 42 | clean, as we will be installing about 70 Python packages in the next step. 43 | ```shell 44 | > python -m venv venv 45 | > venv\Scripts\activate 46 | ``` 47 | 1. install Power Grid Model and some other packages we'll use for this workshop: 48 | ```shell 49 | > pip install power-grid-model jupyter pandas matplotlib 50 | ``` 51 | 1. Now run jupyter notebook. It will probably (depending on your system) automatically open a browser at 52 | http://localhost:8888. If not, the console output will tell you where to find the jupyter notebook server. 53 | ```shell 54 | > jupyter notebook 55 | ``` 56 | 1. Try any of the `.ipynb` files, for example 57 | [`Power Flow Example.ipynb`](http://localhost:8888/notebooks/examples/Power%20Flow%20Example.ipynb) and press the `>>` 58 | button to run the entire file. Note that the last section in the Power Flow Example is about error handling, so 59 | don't get scared if you see some error messages there. 60 | 61 | 62 | ### Next time, pick up where you left off 63 | 64 | 1. Open a terminal (Windows-Key + R, type `cmd`, press **OK**) and use the `cd` (change dir) command to navigate 65 | to the folder where you downloaded the workshop. For example: 66 | ```shell 67 | C:\Users\YourUserName> cd Downloads\power-grid-model-workshop 68 | C:\Users\YourUserName\Downloads\power-grid-model-workshop>_ 69 | ``` 70 | 2. Optional: Activate the virtual environment (if you created one initially). 71 | ```shell 72 | > venv\Scripts\activate 73 | ``` 74 | 3. Run jupyter notebook. It will probably (depending on your system) automatically open a browser at 75 | http://localhost:8888. If not, the console output will tell you where to find the jupyter notebook server. 76 | ```shell 77 | > jupyter notebook 78 | ``` 79 | 80 | ### Preparation for WSL2 or Linux (for advanced users) 81 | 82 | If you know WSL2/Linux you should be able to configure environment yourself. 83 | 84 | ```shell 85 | > pip install power-grid-model jupyter pandas 86 | > jupyter notebook 87 | ``` 88 | 89 | Open the jupyter notebook [`Power Flow Example.ipynb`](http://localhost:8888/Power%20Flow%20Example.ipynb), try to run it. 90 | 91 | ## License 92 | 93 | This project is licensed under the Mozilla Public License, version 2.0 - see [LICENSE](LICENSE) for details. 94 | 95 | ## Contributing 96 | 97 | Please read [CODE_OF_CONDUCT](https://github.com/PowerGridModel/.github/blob/main/CODE_OF_CONDUCT.md) and [CONTRIBUTING](https://github.com/PowerGridModel/.github/blob/main/CONTRIBUTING.md) for details on the process 98 | for submitting pull requests to us. 99 | 100 | Visit [Contribute](https://github.com/PowerGridModel/power-grid-model-workshop/contribute) for a list of good first issues in this repo. 101 | 102 | ## Contact 103 | 104 | Please read [SUPPORT](https://github.com/PowerGridModel/.github/blob/main/SUPPORT.md) for how to connect and get into contact with the Power Grid Model project. 105 | -------------------------------------------------------------------------------- /data/line.csv.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /data/node.csv.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /data/source.csv: -------------------------------------------------------------------------------- 1 | id,node,status,u_ref,sk 2 | 4001,0,1,1.0,1e+20 3 | -------------------------------------------------------------------------------- /data/source.csv.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /data/sym_load.csv.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /examples/Asymmetric Calculation Example.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /examples/Asymmetric Line.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "id": "89cf2628", 7 | "metadata": {}, 8 | "source": [ 9 | "# Asym Line Example\n", 10 | "\n", 11 | "In this notebook we will present examples of asymmetric lines in `power-grid-model`. \n", 12 | "\n", 13 | "Different input formats are covered. We will do one-time power flow calculation and one-time state estimation.\n", 14 | "\n", 15 | "This notebook serves as an example of how to use the Python API. For detailed API documentation, refer to\n", 16 | "[Python API reference](https://power-grid-model.readthedocs.io/en/stable/api_reference/python-api-reference.html)\n", 17 | "and [Native Data Interface](https://power-grid-model.readthedocs.io/en/stable/advanced_documentation/native-data-interface.html).\n", 18 | "\n", 19 | "## Asym Line\n", 20 | "\n", 21 | "Asym Line is described as a pi model in `power-grid-model`, and it belongs to the `branch` component type which connects two nodes with possibly different voltage levels.\n", 22 | "\n", 23 | "### Example Network\n", 24 | "\n", 25 | "We use a simple network with 3 nodes, 1 source, 1 load and 2 asym lines. As shown below:\n", 26 | "\n", 27 | "```txt\n", 28 | " source_1 --- node_2 --- asym_line_3 --- node_4 --- asym_line_5 --- node_6 --- load_7\n", 29 | "```" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 1, 35 | "id": "ae11dc9a", 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "# some basic imports\n", 40 | "import numpy as np\n", 41 | "import pandas as pd\n", 42 | "\n", 43 | "from power_grid_model import LoadGenType, DatasetType, ComponentType\n", 44 | "from power_grid_model import PowerGridModel, CalculationMethod, CalculationType, MeasuredTerminalType\n", 45 | "from power_grid_model import initialize_array" 46 | ] 47 | }, 48 | { 49 | "attachments": {}, 50 | "cell_type": "markdown", 51 | "id": "f983cef7", 52 | "metadata": {}, 53 | "source": [ 54 | "### Input Dataset\n", 55 | "\n", 56 | "We create an input dataset by using the helper function `initialize_array`. \n", 57 | "\n", 58 | "Please refer to [Components](https://power-grid-model.readthedocs.io/en/stable/user_manual/components.html) for detailed explanation of all component types and their input/output attributes." 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 2, 64 | "id": "6f008736", 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "# node\n", 69 | "node = initialize_array(DatasetType.input, ComponentType.node, 3)\n", 70 | "node[\"id\"] = np.array([2, 4, 6])\n", 71 | "node[\"u_rated\"] = [1e3, 1e3, 1e3]\n", 72 | "\n", 73 | "# load\n", 74 | "asym_load = initialize_array(DatasetType.input, ComponentType.asym_load, 1)\n", 75 | "asym_load[\"id\"] = [7]\n", 76 | "asym_load[\"node\"] = [6]\n", 77 | "asym_load[\"status\"] = [1]\n", 78 | "asym_load[\"type\"] = [LoadGenType.const_power]\n", 79 | "asym_load[\"p_specified\"] = [[1000.0, 2000.0, 3000.0]]\n", 80 | "asym_load[\"q_specified\"] = [[1000.0, 2000.0, 3000.0]]\n", 81 | "\n", 82 | "# source\n", 83 | "source = initialize_array(DatasetType.input, ComponentType.source, 1)\n", 84 | "source[\"id\"] = [1]\n", 85 | "source[\"node\"] = [2]\n", 86 | "source[\"status\"] = [1]\n", 87 | "source[\"u_ref\"] = [1.0]\n", 88 | "\n", 89 | "# asym_line\n", 90 | "asym_line = initialize_array(DatasetType.input, ComponentType.asym_line, 2)\n", 91 | "asym_line[\"id\"] = [3, 5]\n", 92 | "asym_line[\"from_node\"] = [2, 4]\n", 93 | "asym_line[\"to_node\"] = [4, 6]\n", 94 | "asym_line[\"from_status\"] = [1, 1]\n", 95 | "asym_line[\"to_status\"] = [1, 1]\n", 96 | "asym_line[\"r_aa\"] = [0.6904, 0.6904]\n", 97 | "asym_line[\"r_ba\"] = [0.0495, 0.0495]\n", 98 | "asym_line[\"r_bb\"] = [0.6904, 0.6904]\n", 99 | "asym_line[\"r_ca\"] = [0.0492, 0.0492]\n", 100 | "asym_line[\"r_cb\"] = [0.0495, 0.0495]\n", 101 | "asym_line[\"r_cc\"] = [0.6904, 0.6904]\n", 102 | "asym_line[\"r_na\"] = [0.0495, np.nan]\n", 103 | "asym_line[\"r_nb\"] = [0.0492, np.nan]\n", 104 | "asym_line[\"r_nc\"] = [0.0495, np.nan]\n", 105 | "asym_line[\"r_nn\"] = [0.6904, np.nan]\n", 106 | "asym_line[\"x_aa\"] = [0.8316, 0.8316]\n", 107 | "asym_line[\"x_ba\"] = [0.7559, 0.7559]\n", 108 | "asym_line[\"x_bb\"] = [0.8316, 0.8316]\n", 109 | "asym_line[\"x_ca\"] = [0.7339, 0.7339]\n", 110 | "asym_line[\"x_cb\"] = [0.7559, 0.7559]\n", 111 | "asym_line[\"x_cc\"] = [0.8316, 0.8316]\n", 112 | "asym_line[\"x_na\"] = [0.7559, np.nan]\n", 113 | "asym_line[\"x_nb\"] = [0.7339, np.nan]\n", 114 | "asym_line[\"x_nc\"] = [0.7559, np.nan]\n", 115 | "asym_line[\"x_nn\"] = [0.8316, np.nan]\n", 116 | "asym_line[\"c0\"] = [0.32e-9, np.nan]\n", 117 | "asym_line[\"c1\"] = [0.54e-9, np.nan]\n", 118 | "asym_line[\"c_aa\"] = [np.nan, 0.3200e-09]\n", 119 | "asym_line[\"c_ba\"] = [np.nan, 0.5400e-09]\n", 120 | "asym_line[\"c_bb\"] = [np.nan, 0.3200e-09]\n", 121 | "asym_line[\"c_ca\"] = [np.nan, 0.7600e-09]\n", 122 | "asym_line[\"c_cb\"] = [np.nan, 0.5400e-09]\n", 123 | "asym_line[\"c_cc\"] = [np.nan, 0.3200e-09]\n", 124 | "asym_line[\"i_n\"] = [1000, 1000]\n", 125 | "\n", 126 | "# all\n", 127 | "input_data = {\n", 128 | " ComponentType.node: node,\n", 129 | " ComponentType.asym_line: asym_line,\n", 130 | " ComponentType.asym_load: asym_load,\n", 131 | " ComponentType.source: source,\n", 132 | "}" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "id": "d16f9dea", 138 | "metadata": {}, 139 | "source": [ 140 | "**We can print the input dataset by converting the numpy array to dataframe.**" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": 3, 146 | "id": "37749c7c", 147 | "metadata": {}, 148 | "outputs": [ 149 | { 150 | "name": "stdout", 151 | "output_type": "stream", 152 | "text": [ 153 | " id from_node to_node from_status to_status r_aa r_ba r_bb \\\n", 154 | "0 3 2 4 1 1 0.6904 0.0495 0.6904 \n", 155 | "1 5 4 6 1 1 0.6904 0.0495 0.6904 \n", 156 | "\n", 157 | " r_ca r_cb ... x_nn c_aa c_ba c_bb \\\n", 158 | "0 0.0492 0.0495 ... 0.8316 NaN NaN NaN \n", 159 | "1 0.0492 0.0495 ... NaN 3.200000e-10 5.400000e-10 3.200000e-10 \n", 160 | "\n", 161 | " c_ca c_cb c_cc c0 c1 \\\n", 162 | "0 NaN NaN NaN 3.200000e-10 5.400000e-10 \n", 163 | "1 7.600000e-10 5.400000e-10 3.200000e-10 NaN NaN \n", 164 | "\n", 165 | " i_n \n", 166 | "0 1000.0 \n", 167 | "1 1000.0 \n", 168 | "\n", 169 | "[2 rows x 34 columns]\n" 170 | ] 171 | } 172 | ], 173 | "source": [ 174 | "print(pd.DataFrame(input_data[ComponentType.asym_line]))" 175 | ] 176 | }, 177 | { 178 | "cell_type": "markdown", 179 | "id": "47a9c257", 180 | "metadata": {}, 181 | "source": [ 182 | "### One-time Power Flow Calculation\n", 183 | "\n", 184 | "You can call the method `calculate_power_flow` to do a one-time calculation based on the current network data in the model.\n", 185 | "\n", 186 | "For detailed explanation of the arguments, batch calculations and asymmetric calculations, we refer to the [Power Flow Example](./Power%20Flow%20Example.ipynb) and [Asymmetric Calculation Example](./Asymmetric%20Calculation%20Example.ipynb). " 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": 4, 192 | "id": "7bb0f998", 193 | "metadata": {}, 194 | "outputs": [ 195 | { 196 | "name": "stdout", 197 | "output_type": "stream", 198 | "text": [ 199 | "------node voltage result------\n", 200 | " 0 1 2\n", 201 | "0 577.350081 577.349890 577.349692\n", 202 | "1 577.543188 574.815533 571.914289\n", 203 | "2 579.346994 570.159376 567.087326\n", 204 | "------node angle result------\n", 205 | " 0 1 2\n", 206 | "0 -2.686835e-07 -2.094396 2.094394\n", 207 | "1 4.811479e-05 -2.087729 2.097964\n", 208 | "2 2.919948e-03 -2.079969 2.097696\n" 209 | ] 210 | } 211 | ], 212 | "source": [ 213 | "# validation (optional)\n", 214 | "from power_grid_model.validation import assert_valid_input_data\n", 215 | "\n", 216 | "assert_valid_input_data(input_data=input_data, calculation_type=CalculationType.power_flow)\n", 217 | "\n", 218 | "# construction\n", 219 | "model = PowerGridModel(input_data)\n", 220 | "\n", 221 | "# one-time power flow calculation\n", 222 | "output_data = model.calculate_power_flow(\n", 223 | " symmetric=False, error_tolerance=1e-8, max_iterations=20, calculation_method=CalculationMethod.newton_raphson\n", 224 | ")\n", 225 | "\n", 226 | "# result dataset\n", 227 | "print(\"------node voltage result------\")\n", 228 | "print(pd.DataFrame(output_data[ComponentType.node][\"u\"]))\n", 229 | "print(\"------node angle result------\")\n", 230 | "print(pd.DataFrame(output_data[ComponentType.node][\"u_angle\"]))" 231 | ] 232 | }, 233 | { 234 | "attachments": {}, 235 | "cell_type": "markdown", 236 | "id": "682c1c48", 237 | "metadata": {}, 238 | "source": [ 239 | "### One-time State Estimation\n", 240 | "Below we present a simple example of state estimation for a network with two asym lines. \n", 241 | "\n", 242 | "NOTE: In `power-grid-model`, asym lines belong to `branch` component type, therefore the `measured_terminal_type` of power sensors should be assigned to `MeasuredTerminalType.branch_from/_to`.\n", 243 | "\n", 244 | "For detailed explanation of the arguments, batch calculations and asymmetric calculations, we refer to the [State Estimation Example](./State%20Estimation%20Example.ipynb) and [Asymmetric Calculation Example](./Asymmetric%20Calculation%20Example.ipynb)." 245 | ] 246 | }, 247 | { 248 | "cell_type": "code", 249 | "execution_count": 5, 250 | "id": "f0c8c3e8", 251 | "metadata": {}, 252 | "outputs": [ 253 | { 254 | "name": "stdout", 255 | "output_type": "stream", 256 | "text": [ 257 | "------node result------\n", 258 | " 0 1 2\n", 259 | "0 1000.000001 999.999991 1000.000007\n", 260 | "1 1000.000019 999.999988 999.999997\n", 261 | "2 1000.000001 999.999999 999.999999\n" 262 | ] 263 | } 264 | ], 265 | "source": [ 266 | "# voltage sensor\n", 267 | "asym_voltage_sensor = initialize_array(DatasetType.input, ComponentType.asym_voltage_sensor, 1)\n", 268 | "asym_voltage_sensor[\"id\"] = [8]\n", 269 | "asym_voltage_sensor[\"measured_object\"] = [6]\n", 270 | "asym_voltage_sensor[\"u_sigma\"] = [1.0]\n", 271 | "asym_voltage_sensor[\"u_measured\"] = [[1000, 1000, 1000]]\n", 272 | "\n", 273 | "# power sensor\n", 274 | "asym_power_sensor = initialize_array(DatasetType.input, ComponentType.asym_power_sensor, 4)\n", 275 | "asym_power_sensor[\"id\"] = [9, 10, 11, 12]\n", 276 | "asym_power_sensor[\"measured_object\"] = [3, 3, 5, 5]\n", 277 | "asym_power_sensor[\"measured_terminal_type\"] = [\n", 278 | " MeasuredTerminalType.branch_from,\n", 279 | " MeasuredTerminalType.branch_to,\n", 280 | " MeasuredTerminalType.branch_from,\n", 281 | " MeasuredTerminalType.branch_to,\n", 282 | "]\n", 283 | "asym_power_sensor[\"power_sigma\"] = [500.0, 500.0, 500.0, 500.0]\n", 284 | "asym_power_sensor[\"p_measured\"] = [[1000, 2000, 3000], [1000, 2000, 3000], [1000, 2000, 3000], [1000, 2000, 3000]]\n", 285 | "asym_power_sensor[\"q_measured\"] = [[1000, 2000, 3000], [1000, 2000, 3000], [1000, 2000, 3000], [1000, 2000, 3000]]\n", 286 | "\n", 287 | "# use components from former input dataset cell.\n", 288 | "input_data2 = {\n", 289 | " ComponentType.node: node,\n", 290 | " ComponentType.asym_line: asym_line,\n", 291 | " ComponentType.asym_load: asym_load,\n", 292 | " ComponentType.source: source,\n", 293 | " ComponentType.asym_voltage_sensor: asym_voltage_sensor,\n", 294 | " ComponentType.asym_power_sensor: asym_power_sensor,\n", 295 | "}\n", 296 | "\n", 297 | "# validation (optional)\n", 298 | "from power_grid_model.validation import assert_valid_input_data\n", 299 | "\n", 300 | "assert_valid_input_data(input_data=input_data2, calculation_type=CalculationType.state_estimation)\n", 301 | "\n", 302 | "# construction\n", 303 | "model2 = PowerGridModel(input_data2)\n", 304 | "\n", 305 | "# one-time state estimation\n", 306 | "output_data2 = model2.calculate_state_estimation(\n", 307 | " symmetric=False, error_tolerance=1e-8, max_iterations=20, calculation_method=CalculationMethod.iterative_linear\n", 308 | ")\n", 309 | "\n", 310 | "# result dataset\n", 311 | "print(\"------node result------\")\n", 312 | "print(pd.DataFrame(output_data2[ComponentType.node][\"u\"]))" 313 | ] 314 | } 315 | ], 316 | "metadata": { 317 | "kernelspec": { 318 | "display_name": "venv", 319 | "language": "python", 320 | "name": "python3" 321 | }, 322 | "language_info": { 323 | "codemirror_mode": { 324 | "name": "ipython", 325 | "version": 3 326 | }, 327 | "file_extension": ".py", 328 | "mimetype": "text/x-python", 329 | "name": "python", 330 | "nbconvert_exporter": "python", 331 | "pygments_lexer": "ipython3", 332 | "version": "3.12.0" 333 | } 334 | }, 335 | "nbformat": 4, 336 | "nbformat_minor": 5 337 | } 338 | -------------------------------------------------------------------------------- /examples/Asymmetric Line.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /examples/Generic Branch Example.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /examples/Make Test Dataset.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "78e3457b", 6 | "metadata": {}, 7 | "source": [ 8 | "# Make Test Dataset\n", 9 | "\n", 10 | "When you encounter unexpected errors in the `power-grid-model`, you would like certainly to report the issue and debug (maybe by another developer) the calculation core with certain dataset. To make this possible, we have implemented a generic mechanism to export/import the dataset to/from JSON files, and to debug the calculation core in both Python and C++ with the test dataset. \n", 11 | "\n", 12 | "In this notebook we will learn how test datasets are made in this repository, including:\n", 13 | "\n", 14 | "* Structure of validation test datasets in this repository\n", 15 | "* Format of test datasets (JSON)\n", 16 | "* Use of helper functions to save and load the datasets" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "id": "05a5bd6e", 22 | "metadata": {}, 23 | "source": [ 24 | "## Structure of Validation Datasets\n", 25 | "\n", 26 | "All validation test datasets are located in the [tests/data](https://github.com/PowerGridModel/power-grid-model/tree/main/tests/data) folder. The structure of the folder is as follows:\n", 27 | "\n", 28 | "```\n", 29 | "data\n", 30 | " |\n", 31 | " |\n", 32 | " - power_flow\n", 33 | " |\n", 34 | " - power_flow_testset_1\n", 35 | " - power_flow_testset_2\n", 36 | " - ...\n", 37 | " - state_estimation\n", 38 | " |\n", 39 | " - state_estimation_testset_1\n", 40 | " - state_estimation_testset_2\n", 41 | " - ...\n", 42 | "```\n", 43 | "\n", 44 | "The testsets are separated in two types of calculations: `power_flow` and `state_estimation`. In each folder there are subfolders for individual testset. The test datasets are used in both Python and C++ unit tests. Therefore, once you create extra test datasets in the folder, you can debug the program in both Python and C++." 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "id": "4315ddc8", 50 | "metadata": {}, 51 | "source": [ 52 | "### Individual Dataset\n", 53 | "\n", 54 | "An individual dataset folder (in either `power_flow` or `state_estimation`) will consists of following files:\n", 55 | "\n", 56 | "* `params.json`: calculation parameters, mandatory\n", 57 | "* `input.json`: input data, mandatory\n", 58 | "* `sym_output.json`: reference symmetric output\n", 59 | "* `asym_output.json`: reference asymmetric output\n", 60 | "* `update_batch.json`: update batch data, mandatory if `sym_output_batch.json` or `asym_output_batch.json` exists.\n", 61 | "* `sym_output_batch.json`: reference symmetric batch output\n", 62 | "* `asym_output_batch.json`: reference asymmetric batch output\n", 63 | "\n", 64 | "The `params.json` and `input.json` are always needed. The test program (in Python and C++) will detect other files and instantiate relevant test calculations. For example, if `sym_output.json` exists, the test program will run a one-time symmetric calculation and compare the results to the reference results in `sym_output.json`." 65 | ] 66 | }, 67 | { 68 | "attachments": {}, 69 | "cell_type": "markdown", 70 | "id": "e5460d14", 71 | "metadata": {}, 72 | "source": [ 73 | "#### Test Parameters\n", 74 | "\n", 75 | "The `params.json` looks something like this:\n", 76 | "\n", 77 | "```json\n", 78 | "{\n", 79 | " \"calculation_method\": \"iterative_linear\",\n", 80 | " \"rtol\": 1e-8,\n", 81 | " \"atol\": {\n", 82 | " \"default\": 1e-8,\n", 83 | " \".+_residual\": 1e-4\n", 84 | " }\n", 85 | "}\n", 86 | "```\n", 87 | "\n", 88 | "You need to specify the method to use for the calculation, the relative and absolute tolerance to compare the calculation results with the reference results. For `rtol` you always give one number. For `atol` you can also give one number, or you can give a dictionary with regular expressions to match the attribute names. In this way you can have fine control of individual tolerance for each attribut (e.g. active/reactive power). In the example it has an absolute tolerance of `1e-4` for attributes which ends with `_residual` and `1e-8` for everything else.\n", 89 | "\n", 90 | "The `calculation_method` can be one string or list of strings. In the latter case, the test program will run the validation test mutilple times using all the specified methods." 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "id": "43d027cd", 96 | "metadata": {}, 97 | "source": [ 98 | "### JSON Data Format\n", 99 | "\n", 100 | "The data format is well explained in these resources\n", 101 | "[Serialization documentation](https://power-grid-model.readthedocs.io/en/stable/user_manual/serialization.html) and some examples of Serialization are given in [Serialization notebook](./Serialization%20Example.ipynb)" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "id": "ef0e663c", 107 | "metadata": {}, 108 | "source": [ 109 | "### Empty Result File\n", 110 | "\n", 111 | "If you encounter a crash for a certain dataset. You can also create the input data into JSON files. In this case you might not have any reference result to compare, because you just need to find where the crash happens. You still need an empty (dictionary) result file to trigger the calculation.\n", 112 | "\n", 113 | "For `sym_output.json`:\n", 114 | "\n", 115 | "```json\n", 116 | "{\n", 117 | " \"attributes\": {},\n", 118 | " \"data\": {},\n", 119 | " \"is_batch\": false,\n", 120 | " \"type\": \"sym_output\",\n", 121 | " \"version\": \"1.0\"\n", 122 | "}\n", 123 | "```\n", 124 | "\n", 125 | "For `sym_output_batch.json`:\n", 126 | "\n", 127 | "```json\n", 128 | "{\n", 129 | " \"attributes\": {},\n", 130 | " \"data\": [{}, {}, {}],\n", 131 | " \"is_batch\": true,\n", 132 | " \"type\": \"sym_output\",\n", 133 | " \"version\": \"1.0\"\n", 134 | "}\n", 135 | "```\n", 136 | "\n", 137 | " \n" 138 | ] 139 | }, 140 | { 141 | "cell_type": "markdown", 142 | "id": "99220dfe", 143 | "metadata": {}, 144 | "source": [ 145 | "## Helper Functions to Import and Export\n", 146 | "\n", 147 | "In the module `power_grid_model.utils` we have some helper functions to import a json file to a `power-grid-model` compatible dataset, or the other way around. \n", 148 | "\n", 149 | "Please refer to the [documentation](https://power-grid-model.readthedocs.io/en/stable/api_reference/python-api-reference.html) for detailed function signature.\n", 150 | "\n", 151 | "In this notebook we export the example network from [Power Flow](./Power%20Flow%20Example.ipynb) to json. " 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": 1, 157 | "id": "b158e92f", 158 | "metadata": {}, 159 | "outputs": [], 160 | "source": [ 161 | "# first build the network\n", 162 | "\n", 163 | "import numpy as np\n", 164 | "import pandas as pd\n", 165 | "\n", 166 | "from power_grid_model import LoadGenType, ComponentType, DatasetType\n", 167 | "from power_grid_model import PowerGridModel\n", 168 | "from power_grid_model import initialize_array\n", 169 | "\n", 170 | "# network\n", 171 | "\n", 172 | "# node\n", 173 | "node = initialize_array(DatasetType.input, ComponentType.node, 3)\n", 174 | "node[\"id\"] = [1, 2, 6]\n", 175 | "node[\"u_rated\"] = [10.5e3, 10.5e3, 10.5e3]\n", 176 | "\n", 177 | "# line\n", 178 | "line = initialize_array(DatasetType.input, ComponentType.line, 3)\n", 179 | "line[\"id\"] = [3, 5, 8]\n", 180 | "line[\"from_node\"] = [1, 2, 1]\n", 181 | "line[\"to_node\"] = [2, 6, 6]\n", 182 | "line[\"from_status\"] = [1, 1, 1]\n", 183 | "line[\"to_status\"] = [1, 1, 1]\n", 184 | "line[\"r1\"] = [0.25, 0.25, 0.25]\n", 185 | "line[\"x1\"] = [0.2, 0.2, 0.2]\n", 186 | "line[\"c1\"] = [10e-6, 10e-6, 10e-6]\n", 187 | "line[\"tan1\"] = [0.0, 0.0, 0.0]\n", 188 | "line[\"i_n\"] = [1000, 1000, 1000]\n", 189 | "\n", 190 | "# load\n", 191 | "sym_load = initialize_array(DatasetType.input, ComponentType.sym_load, 2)\n", 192 | "sym_load[\"id\"] = [4, 7]\n", 193 | "sym_load[\"node\"] = [2, 6]\n", 194 | "sym_load[\"status\"] = [1, 1]\n", 195 | "sym_load[\"type\"] = [LoadGenType.const_power, LoadGenType.const_power]\n", 196 | "sym_load[\"p_specified\"] = [20e6, 10e6]\n", 197 | "sym_load[\"q_specified\"] = [5e6, 2e6]\n", 198 | "\n", 199 | "# source\n", 200 | "source = initialize_array(DatasetType.input, ComponentType.source, 1)\n", 201 | "source[\"id\"] = [10]\n", 202 | "source[\"node\"] = [1]\n", 203 | "source[\"status\"] = [1]\n", 204 | "source[\"u_ref\"] = [1.0]\n", 205 | "\n", 206 | "# all\n", 207 | "input_data = {\n", 208 | " ComponentType.node: node,\n", 209 | " ComponentType.line: line,\n", 210 | " ComponentType.sym_load: sym_load,\n", 211 | " ComponentType.source: source,\n", 212 | "}" 213 | ] 214 | }, 215 | { 216 | "cell_type": "markdown", 217 | "id": "73dba42b", 218 | "metadata": {}, 219 | "source": [ 220 | "### Export to JSON\n", 221 | "\n", 222 | "We can use the fuction `json_serialize_to_file` to export the input data to a json file." 223 | ] 224 | }, 225 | { 226 | "cell_type": "code", 227 | "execution_count": 2, 228 | "id": "724e098a", 229 | "metadata": {}, 230 | "outputs": [], 231 | "source": [ 232 | "from power_grid_model.utils import json_serialize_to_file\n", 233 | "import tempfile\n", 234 | "from pathlib import Path\n", 235 | "\n", 236 | "temp_path = Path(tempfile.gettempdir())\n", 237 | "json_serialize_to_file(temp_path / \"input.json\", input_data)" 238 | ] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "execution_count": 3, 243 | "id": "071c790a", 244 | "metadata": {}, 245 | "outputs": [ 246 | { 247 | "name": "stdout", 248 | "output_type": "stream", 249 | "text": [ 250 | "{\n", 251 | " \"version\": \"1.0\",\n", 252 | " \"type\": \"input\",\n", 253 | " \"is_batch\": false,\n", 254 | " \"attributes\": {},\n", 255 | " \"data\": {\n", 256 | " \"node\": [\n", 257 | " {\"id\": 1, \"u_rated\": 10500},\n", 258 | " {\"id\": 2, \"u_rated\": 10500},\n", 259 | " {\"id\": 6, \"u_rated\": 10500}\n", 260 | " ],\n", 261 | " \"line\": [\n", 262 | " {\"id\": 3, \"from_node\": 1, \"to_node\": 2, \"from_status\": 1, \"to_status\": 1, \"r1\": 0.25, \"x1\": 0.2, \"c1\": 1e-05, \"tan1\": 0, \"i_n\": 1000},\n", 263 | " {\"id\": 5, \"from_node\": 2, \"to_node\": 6, \"from_status\": 1, \"to_status\": 1, \"r1\": 0.25, \"x1\": 0.2, \"c1\": 1e-05, \"tan1\": 0, \"i_n\": 1000},\n", 264 | " {\"id\": 8, \"from_node\": 1, \"to_node\": 6, \"from_status\": 1, \"to_status\": 1, \"r1\": 0.25, \"x1\": 0.2, \"c1\": 1e-05, \"tan1\": 0, \"i_n\": 1000}\n", 265 | " ],\n", 266 | " \"sym_load\": [\n", 267 | " {\"id\": 4, \"node\": 2, \"status\": 1, \"type\": 0, \"p_specified\": 20000000, \"q_specified\": 5000000},\n", 268 | " {\"id\": 7, \"node\": 6, \"status\": 1, \"type\": 0, \"p_specified\": 10000000, \"q_specified\": 2000000}\n", 269 | " ],\n", 270 | " \"source\": [\n", 271 | " {\"id\": 10, \"node\": 1, \"status\": 1, \"u_ref\": 1}\n", 272 | " ]\n", 273 | " }\n", 274 | "}\n" 275 | ] 276 | } 277 | ], 278 | "source": [ 279 | "# we can display the json file\n", 280 | "\n", 281 | "with open(temp_path / \"input.json\", \"r\") as f:\n", 282 | " print(f.read())" 283 | ] 284 | }, 285 | { 286 | "cell_type": "markdown", 287 | "id": "6e85c767", 288 | "metadata": {}, 289 | "source": [ 290 | "### Import JSON\n", 291 | "\n", 292 | "We can use the fuction `json_deserialize_from_file` to import the input data from a json file." 293 | ] 294 | }, 295 | { 296 | "cell_type": "code", 297 | "execution_count": 4, 298 | "id": "c79d7216", 299 | "metadata": {}, 300 | "outputs": [ 301 | { 302 | "name": "stdout", 303 | "output_type": "stream", 304 | "text": [ 305 | " id energized u_pu u u_angle p q\n", 306 | "0 1 1 0.998988 10489.375043 -0.003039 3.121451e+07 6.991358e+06\n", 307 | "1 2 1 0.952126 9997.325181 -0.026031 -2.000000e+07 -5.000000e+06\n", 308 | "2 6 1 0.962096 10102.012975 -0.021895 -1.000000e+07 -2.000000e+06\n" 309 | ] 310 | } 311 | ], 312 | "source": [ 313 | "# round trip and run power flow\n", 314 | "\n", 315 | "from power_grid_model.utils import json_deserialize_from_file\n", 316 | "\n", 317 | "imported_data = json_deserialize_from_file(temp_path / \"input.json\")\n", 318 | "\n", 319 | "pgm = PowerGridModel(imported_data)\n", 320 | "result = pgm.calculate_power_flow()\n", 321 | "\n", 322 | "print(pd.DataFrame(result[ComponentType.node]))" 323 | ] 324 | }, 325 | { 326 | "cell_type": "markdown", 327 | "id": "f2409710", 328 | "metadata": {}, 329 | "source": [ 330 | "## Import and Export Batch Update/Result Dataset\n", 331 | "\n", 332 | "You can use the same function to import and export batch update/result dataset for batch calculation." 333 | ] 334 | }, 335 | { 336 | "cell_type": "code", 337 | "execution_count": 5, 338 | "id": "37cd5ade", 339 | "metadata": {}, 340 | "outputs": [], 341 | "source": [ 342 | "# create batch set\n", 343 | "\n", 344 | "load_profile = initialize_array(DatasetType.update, ComponentType.sym_load, (3, 2))\n", 345 | "load_profile[\"id\"] = [[4, 7]]\n", 346 | "# this is a scale of load from 0% to 100%\n", 347 | "load_profile[\"p_specified\"] = [[30e6, 15e6]] * np.linspace(0, 1, 3).reshape(-1, 1)\n", 348 | "\n", 349 | "\n", 350 | "time_series_mutation = {ComponentType.sym_load: load_profile}" 351 | ] 352 | }, 353 | { 354 | "cell_type": "code", 355 | "execution_count": 6, 356 | "id": "89011a10", 357 | "metadata": {}, 358 | "outputs": [ 359 | { 360 | "name": "stdout", 361 | "output_type": "stream", 362 | "text": [ 363 | "{\n", 364 | " \"version\": \"1.0\",\n", 365 | " \"type\": \"update\",\n", 366 | " \"is_batch\": true,\n", 367 | " \"attributes\": {},\n", 368 | " \"data\": [\n", 369 | " {\n", 370 | " \"sym_load\": [\n", 371 | " {\"id\": 4, \"p_specified\": 0},\n", 372 | " {\"id\": 7, \"p_specified\": 0}\n", 373 | " ]\n", 374 | " },\n", 375 | " {\n", 376 | " \"sym_load\": [\n", 377 | " {\"id\": 4, \"p_specified\": 15000000},\n", 378 | " {\"id\": 7, \"p_specified\": 7500000}\n", 379 | " ]\n", 380 | " },\n", 381 | " {\n", 382 | " \"sym_load\": [\n", 383 | " {\"id\": 4, \"p_specified\": 30000000},\n", 384 | " {\"id\": 7, \"p_specified\": 15000000}\n", 385 | " ]\n", 386 | " }\n", 387 | " ]\n", 388 | "}\n" 389 | ] 390 | } 391 | ], 392 | "source": [ 393 | "# export and print\n", 394 | "\n", 395 | "json_serialize_to_file(temp_path / \"update_batch.json\", time_series_mutation)\n", 396 | "\n", 397 | "with open(temp_path / \"update_batch.json\", \"r\") as f:\n", 398 | " print(f.read())" 399 | ] 400 | }, 401 | { 402 | "cell_type": "code", 403 | "execution_count": 7, 404 | "id": "8c4ad876", 405 | "metadata": {}, 406 | "outputs": [ 407 | { 408 | "name": "stdout", 409 | "output_type": "stream", 410 | "text": [ 411 | "[[ 0. 0.]\n", 412 | " [15000000. 7500000.]\n", 413 | " [30000000. 15000000.]]\n" 414 | ] 415 | } 416 | ], 417 | "source": [ 418 | "# import round trip, calculate\n", 419 | "\n", 420 | "imported_batch_update = json_deserialize_from_file(temp_path / \"update_batch.json\")\n", 421 | "\n", 422 | "batch_result = pgm.calculate_power_flow(update_data=imported_batch_update)\n", 423 | "\n", 424 | "print(batch_result[ComponentType.sym_load][\"p\"])" 425 | ] 426 | } 427 | ], 428 | "metadata": { 429 | "kernelspec": { 430 | "display_name": "venv", 431 | "language": "python", 432 | "name": "python3" 433 | }, 434 | "language_info": { 435 | "codemirror_mode": { 436 | "name": "ipython", 437 | "version": 3 438 | }, 439 | "file_extension": ".py", 440 | "mimetype": "text/x-python", 441 | "name": "python", 442 | "nbconvert_exporter": "python", 443 | "pygments_lexer": "ipython3", 444 | "version": "3.13.0" 445 | } 446 | }, 447 | "nbformat": 4, 448 | "nbformat_minor": 5 449 | } 450 | -------------------------------------------------------------------------------- /examples/Make Test Dataset.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /examples/Power Flow Example.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /examples/Serialization Example.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /examples/Short Circuit Example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "89cf2628", 6 | "metadata": {}, 7 | "source": [ 8 | "# Short Circuit Example\n", 9 | "\n", 10 | "This notebook contains an example of a short circuit calculation using `power-grid-model`.\n", 11 | "The following points are covered\n", 12 | " * Construction of the model\n", 13 | " * Run short circuit calculation, and its relevant function arguments\n", 14 | "\n", 15 | "This notebook skips most of the common features across all calculations of power-grid-model like updating model, batch calculations and error handling.\n", 16 | "\n", 17 | "It serves as an example of how to use the Python API. For detailed API documentation, refer to\n", 18 | "[Python API reference](https://power-grid-model.readthedocs.io/en/stable/api_reference/python-api-reference.html)\n", 19 | "and [Native Data Interface](https://power-grid-model.readthedocs.io/en/stable/advanced_documentation/native-data-interface.html).\n", 20 | "\n", 21 | "\n", 22 | "## Example Network\n", 23 | "\n", 24 | "We use a simple network with 3 nodes, 1 source, 3 lines, and 2 loads. As shown below:\n", 25 | "\n", 26 | "```\n", 27 | " -----------------------line_8---------------\n", 28 | " | |\n", 29 | "node_1 ---line_3--- node_2 ----line_5---- node_6 ---- fault_11\n", 30 | " | | |\n", 31 | "source_10 sym_load_4 sym_load_7\n", 32 | "```\n", 33 | "\n", 34 | "The 3 nodes are connected in a triangular way by 3 lines." 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 1, 40 | "id": "ae11dc9a", 41 | "metadata": { 42 | "ExecuteTime": { 43 | "end_time": "2023-08-03T08:29:18.753481900Z", 44 | "start_time": "2023-08-03T08:29:17.530737800Z" 45 | } 46 | }, 47 | "outputs": [], 48 | "source": [ 49 | "# some basic imports\n", 50 | "import numpy as np\n", 51 | "import pandas as pd\n", 52 | "\n", 53 | "from power_grid_model import LoadGenType, ComponentType, DatasetType\n", 54 | "from power_grid_model import (\n", 55 | " PowerGridModel,\n", 56 | " CalculationMethod,\n", 57 | " CalculationType,\n", 58 | " FaultType,\n", 59 | " FaultPhase,\n", 60 | " ShortCircuitVoltageScaling,\n", 61 | ")\n", 62 | "from power_grid_model import initialize_array" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "id": "f983cef7", 68 | "metadata": {}, 69 | "source": [ 70 | "## Input Dataset\n", 71 | "\n", 72 | "Please refer to [Components](https://power-grid-model.readthedocs.io/en/stable/user_manual/components.html) for detailed explanation of all component types and their input/output attributes." 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": 2, 78 | "id": "6f008736", 79 | "metadata": { 80 | "ExecuteTime": { 81 | "end_time": "2023-08-03T08:29:18.763488Z", 82 | "start_time": "2023-08-03T08:29:18.758492200Z" 83 | } 84 | }, 85 | "outputs": [], 86 | "source": [ 87 | "# node\n", 88 | "node = initialize_array(DatasetType.input, ComponentType.node, 3)\n", 89 | "node[\"id\"] = np.array([1, 2, 6])\n", 90 | "node[\"u_rated\"] = [10.5e3, 10.5e3, 10.5e3]\n", 91 | "\n", 92 | "# line\n", 93 | "line = initialize_array(DatasetType.input, ComponentType.line, 3)\n", 94 | "line[\"id\"] = [3, 5, 8]\n", 95 | "line[\"from_node\"] = [1, 2, 1]\n", 96 | "line[\"to_node\"] = [2, 6, 6]\n", 97 | "line[\"from_status\"] = [1, 1, 1]\n", 98 | "line[\"to_status\"] = [1, 1, 1]\n", 99 | "line[\"r1\"] = [0.25, 0.25, 0.25]\n", 100 | "line[\"x1\"] = [0.2, 0.2, 0.2]\n", 101 | "line[\"c1\"] = [10e-6, 10e-6, 10e-6]\n", 102 | "line[\"tan1\"] = [0.0, 0.0, 0.0]\n", 103 | "line[\"i_n\"] = [1000, 1000, 1000]\n", 104 | "\n", 105 | "# load\n", 106 | "sym_load = initialize_array(DatasetType.input, ComponentType.sym_load, 2)\n", 107 | "sym_load[\"id\"] = [4, 7]\n", 108 | "sym_load[\"node\"] = [2, 6]\n", 109 | "sym_load[\"status\"] = [1, 1]\n", 110 | "sym_load[\"type\"] = [LoadGenType.const_power, LoadGenType.const_power]\n", 111 | "sym_load[\"p_specified\"] = [20e6, 10e6]\n", 112 | "sym_load[\"q_specified\"] = [5e6, 2e6]\n", 113 | "\n", 114 | "# source\n", 115 | "source = initialize_array(DatasetType.input, ComponentType.source, 1)\n", 116 | "source[\"id\"] = [10]\n", 117 | "source[\"node\"] = [1]\n", 118 | "source[\"status\"] = [1]\n", 119 | "source[\"u_ref\"] = [1.0]\n", 120 | "\n", 121 | "# fault\n", 122 | "fault = initialize_array(DatasetType.input, ComponentType.fault, 1)\n", 123 | "fault[\"id\"] = [11]\n", 124 | "fault[\"status\"] = [1]\n", 125 | "fault[\"fault_object\"] = [6]\n", 126 | "fault[\"fault_type\"] = [FaultType.three_phase]\n", 127 | "fault[\"fault_phase\"] = [FaultPhase.abc]\n", 128 | "fault[\"r_f\"] = [0.1]\n", 129 | "fault[\"x_f\"] = [0.1]\n", 130 | "\n", 131 | "# all\n", 132 | "input_data = {\n", 133 | " ComponentType.node: node,\n", 134 | " ComponentType.line: line,\n", 135 | " ComponentType.sym_load: sym_load,\n", 136 | " ComponentType.source: source,\n", 137 | " ComponentType.fault: fault,\n", 138 | "}" 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "id": "039711c7", 144 | "metadata": { 145 | "collapsed": false, 146 | "jupyter": { 147 | "outputs_hidden": false 148 | } 149 | }, 150 | "source": [ 151 | "\n", 152 | "**Note: During a single calculation, all types of faults should be similar.**" 153 | ] 154 | }, 155 | { 156 | "cell_type": "markdown", 157 | "id": "68a38330", 158 | "metadata": {}, 159 | "source": [ 160 | "## Validation (optional)\n", 161 | "For efficiency reasons, most of the data is not explicitly validated in the power grid model. However, in most cases, a power flow calculation will fail/crash if the data is invalid. Often with a low level error message that is hard to relate to the original objects. Therfore, it is recommended to always validate your data before constructing a PowerGridModel instance.\n", 162 | "\n", 163 | "The simplest and most effective way to validate your data is by using `assert_valid_input_data()` which will throw an error if it encounters any invalid data. See [Validation Examples](./Validation%20Examples.ipynb) for more detailed information on the validation functions." 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": 3, 169 | "id": "40509eaf", 170 | "metadata": { 171 | "ExecuteTime": { 172 | "end_time": "2023-08-03T08:29:18.811133300Z", 173 | "start_time": "2023-08-03T08:29:18.764503700Z" 174 | } 175 | }, 176 | "outputs": [], 177 | "source": [ 178 | "from power_grid_model.validation import assert_valid_input_data\n", 179 | "\n", 180 | "assert_valid_input_data(input_data=input_data, calculation_type=CalculationType.short_circuit)" 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "id": "afae0224", 186 | "metadata": {}, 187 | "source": [ 188 | "## Construction\n", 189 | "\n", 190 | "The construction of the model is just calling the constructor of `PowerGridModel`.\n" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": 4, 196 | "id": "7ef134e9", 197 | "metadata": { 198 | "ExecuteTime": { 199 | "end_time": "2023-08-03T08:29:18.811133300Z", 200 | "start_time": "2023-08-03T08:29:18.804083800Z" 201 | } 202 | }, 203 | "outputs": [], 204 | "source": [ 205 | "model = PowerGridModel(input_data)" 206 | ] 207 | }, 208 | { 209 | "cell_type": "markdown", 210 | "id": "e3605c3e", 211 | "metadata": {}, 212 | "source": [ 213 | "## One-time Short circuit Calculation\n", 214 | "\n", 215 | "You can call the method `calculate_short_circuit` to do a one-time calculation based on the current network data in the model.\n", 216 | "\n", 217 | "The short circuit calculation has the following settings as arguments:\n", 218 | " * calculation_method: CalculationMethod.iec60909,\n", 219 | " * short_circuit_voltage_scaling: ShortCircuitVoltageScaling.maximum\n", 220 | "\n", 221 | "Currently, there is only one calculation method for short-circuit which calculates as per IEC 60909. The `short_circuit_voltage_scaling` is a scaling of source voltages based on the nominal node voltages.\n" 222 | ] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "execution_count": 5, 227 | "id": "44c2de63", 228 | "metadata": { 229 | "ExecuteTime": { 230 | "end_time": "2023-08-03T08:29:18.823792200Z", 231 | "start_time": "2023-08-03T08:29:18.811133300Z" 232 | }, 233 | "scrolled": true 234 | }, 235 | "outputs": [], 236 | "source": [ 237 | "output_data = model.calculate_short_circuit(\n", 238 | " calculation_method=CalculationMethod.iec60909, short_circuit_voltage_scaling=ShortCircuitVoltageScaling.maximum\n", 239 | ")" 240 | ] 241 | }, 242 | { 243 | "cell_type": "markdown", 244 | "id": "d08aaf45", 245 | "metadata": {}, 246 | "source": [ 247 | "### Result Dataset\n", 248 | "\n", 249 | "The short circuit calculation results are always asymmetric. This means we cannot convert them to dataframes\n", 250 | "\n", 251 | "The following code prints some result attributs of nodes, faults, and lines in arrays.\n" 252 | ] 253 | }, 254 | { 255 | "cell_type": "code", 256 | "execution_count": 6, 257 | "id": "a581a36e", 258 | "metadata": { 259 | "ExecuteTime": { 260 | "end_time": "2023-08-03T08:29:18.895579800Z", 261 | "start_time": "2023-08-03T08:29:18.827799800Z" 262 | } 263 | }, 264 | "outputs": [ 265 | { 266 | "name": "stdout", 267 | "output_type": "stream", 268 | "text": [ 269 | "\n", 270 | "------fault result: id------\n", 271 | "[11]\n", 272 | "------fault result: i_f------\n", 273 | "[[18404.40044631 18404.40044631 18404.40044631]]\n", 274 | "\n", 275 | "------node result: id------\n", 276 | "[1 2 6]\n", 277 | "------node result: u_pu------\n", 278 | "[[1.07539348 1.07539348 1.07539348]\n", 279 | " [0.75227027 0.75227027 0.75227027]\n", 280 | " [0.42934657 0.42934657 0.42934657]]\n", 281 | "\n", 282 | "------line result: id------\n", 283 | "[3 5 8]\n", 284 | "------line result: u_pu------\n", 285 | "[[ 6119.72533835 6119.72533835 6119.72533835]\n", 286 | " [ 6131.26794632 6131.26794632 6131.26794632]\n", 287 | " [12255.79671336 12255.79671336 12255.79671336]]\n" 288 | ] 289 | } 290 | ], 291 | "source": [ 292 | "print(\"\\n------fault result: id------\")\n", 293 | "print(output_data[ComponentType.fault][\"id\"])\n", 294 | "print(\"------fault result: i_f------\")\n", 295 | "print(output_data[ComponentType.fault][\"i_f\"])\n", 296 | "\n", 297 | "print(\"\\n------node result: id------\")\n", 298 | "print(output_data[ComponentType.node][\"id\"])\n", 299 | "print(\"------node result: u_pu------\")\n", 300 | "print(output_data[ComponentType.node][\"u_pu\"])\n", 301 | "\n", 302 | "print(\"\\n------line result: id------\")\n", 303 | "print(output_data[ComponentType.line][\"id\"])\n", 304 | "print(\"------line result: u_pu------\")\n", 305 | "print(output_data[ComponentType.line][\"i_from\"])" 306 | ] 307 | }, 308 | { 309 | "cell_type": "markdown", 310 | "id": "1884ebd4", 311 | "metadata": { 312 | "collapsed": false, 313 | "jupyter": { 314 | "outputs_hidden": false 315 | } 316 | }, 317 | "source": [ 318 | "## Batch Calculations\n", 319 | "\n", 320 | "The batch calculations are mentioned in detail in the [Power Flow Example](./Power%20Flow%20Example.ipynb). Short circuit batch calculations are carried out in similar way." 321 | ] 322 | } 323 | ], 324 | "metadata": { 325 | "kernelspec": { 326 | "display_name": "venv", 327 | "language": "python", 328 | "name": "python3" 329 | }, 330 | "language_info": { 331 | "codemirror_mode": { 332 | "name": "ipython", 333 | "version": 3 334 | }, 335 | "file_extension": ".py", 336 | "mimetype": "text/x-python", 337 | "name": "python", 338 | "nbconvert_exporter": "python", 339 | "pygments_lexer": "ipython3", 340 | "version": "3.13.0" 341 | } 342 | }, 343 | "nbformat": 4, 344 | "nbformat_minor": 5 345 | } 346 | -------------------------------------------------------------------------------- /examples/Short Circuit Example.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /examples/State Estimation Example.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /examples/Transformer Examples.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /examples/Validation Examples.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "89cf2628", 6 | "metadata": {}, 7 | "source": [ 8 | "# Validation Examples\n", 9 | "\n", 10 | "As a result of optimizations, and the low level nature of the Power Grid Model's mathematical core, the core exceptions may not always be clear to the user. Therefore an optional validation mechanism is supplied, which validates data structures and values off-line. It is recommended to always validate your data before constructing a PowerGridModel instance. An alternative approach would be to validate only when an exception is raised, but be aware that not all data errors will raise exceptions: most of them will just yield invalid results without warning." 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "id": "3cbdd654", 16 | "metadata": {}, 17 | "source": [ 18 | "The basic methods and class definitions are available in the `power_grid_model.validation` module:\n", 19 | "\n", 20 | "```python\n", 21 | "# Manual validation\n", 22 | "# validate_input_data() assumes that you won't be using update data in your calculation.\n", 23 | "# validate_batch_data() validates input_data in combination with batch/update data.\n", 24 | "validate_input_data(input_data, calculation_type, symmetric) -> list[ValidationError]\n", 25 | "validate_batch_data(input_data, update_data, calculation_type, symmetric) -> dict[int, list[ValidationError]]\n", 26 | "\n", 27 | "# Assertions\n", 28 | "# assert_valid_input_data() and assert_valid_batch_data() raise a ValidationException,\n", 29 | "# containing the list/dict of errors, when the data is invalid.\n", 30 | "assert_valid_input_data(input_data, calculation_type, symmetric) raises ValidationException\n", 31 | "assert_valid_batch_data(input_data, calculation_type, update_data, symmetric) raises ValidationException\n", 32 | "\n", 33 | "# Utilities\n", 34 | "# errors_to_string() converts a set of errors to a human readable (multi-line) string representation\n", 35 | "errors_to_string(errors, name, details)\n", 36 | "```\n", 37 | "\n", 38 | "Each validation error is an object which can be converted to a compact human-readable message using `str(error)`. It\n", 39 | "contains three member variables `component`, `field` and `ids`, which can be used to gather more specific information about the validation error, e.g. which object IDs are involved.\n", 40 | "\n", 41 | "```python\n", 42 | "class ValidationError:\n", 43 | " \n", 44 | " # Component(s): e.g. ComponentType.node or [ComponentType.node, ComponentType.line]\n", 45 | " component: ComponentType | list[ComponentType]\n", 46 | " \n", 47 | " # Field(s): e.g. \"id\" or [\"line_from\", \"line_to\"] or [(ComponentType.node, \"id\"), (ComponentType.line, \"id\")]\n", 48 | " field: str | list[str] | list[tuple[ComponentType, str]]\n", 49 | "\n", 50 | " # IDs: e.g. [1, 2, 3] or [(ComponentType.node, 1), (ComponentType.line, 1)]\n", 51 | " ids: list[int] | list[tuple[ComponentType, int]] = [] \n", 52 | " \n", 53 | "```\n", 54 | "\n", 55 | "Note: The data types of `input_data` and `update_data` are the same as expected by the power grid model." 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 1, 61 | "id": "d122ee22", 62 | "metadata": {}, 63 | "outputs": [], 64 | "source": [ 65 | "from power_grid_model import PowerGridModel, DatasetType, ComponentType, initialize_array\n", 66 | "\n", 67 | "# A power grid containing several errors\n", 68 | "\n", 69 | "# node\n", 70 | "node_error = initialize_array(DatasetType.input, ComponentType.node, 3)\n", 71 | "node_error[\"id\"] = [1, 2, 3]\n", 72 | "node_error[\"u_rated\"] = [10.5e3]\n", 73 | "\n", 74 | "# line\n", 75 | "line_error = initialize_array(DatasetType.input, ComponentType.line, 3)\n", 76 | "line_error[\"id\"] = [4, 5, 6]\n", 77 | "line_error[\"from_node\"] = [1, 2, 3]\n", 78 | "line_error[\"to_node\"] = [2, 3, 4]\n", 79 | "line_error[\"from_status\"] = [True]\n", 80 | "line_error[\"to_status\"] = [True]\n", 81 | "line_error[\"r1\"] = [0.25]\n", 82 | "line_error[\"x1\"] = [0.2]\n", 83 | "line_error[\"c1\"] = [10e-6]\n", 84 | "line_error[\"tan1\"] = [0.0]\n", 85 | "\n", 86 | "# Power Sensor\n", 87 | "sensor_error = initialize_array(DatasetType.input, ComponentType.sym_power_sensor, 2)\n", 88 | "sensor_error[\"id\"] = [6, 7]\n", 89 | "sensor_error[\"measured_object\"] = [3, 4]\n", 90 | "sensor_error[\"measured_terminal_type\"] = [0, 2]\n", 91 | "sensor_error[\"p_measured\"] = [0]\n", 92 | "sensor_error[\"q_measured\"] = [0]\n", 93 | "sensor_error[\"power_sigma\"] = [0]\n", 94 | "\n", 95 | "error_data = {\n", 96 | " ComponentType.node: node_error,\n", 97 | " ComponentType.line: line_error,\n", 98 | " ComponentType.sym_power_sensor: sensor_error,\n", 99 | "}" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": 2, 105 | "id": "d997e738", 106 | "metadata": {}, 107 | "outputs": [ 108 | { 109 | "ename": "IDWrongType", 110 | "evalue": "Wrong type for object with id 4\n\nTry validate_input_data() or validate_batch_data() to validate your data.\n", 111 | "output_type": "error", 112 | "traceback": [ 113 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 114 | "\u001b[0;31mIDWrongType\u001b[0m Traceback (most recent call last)", 115 | "Cell \u001b[0;32mIn[2], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# Without validation\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m model \u001b[38;5;241m=\u001b[39m \u001b[43mPowerGridModel\u001b[49m\u001b[43m(\u001b[49m\u001b[43merror_data\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 3\u001b[0m output_data \u001b[38;5;241m=\u001b[39m model\u001b[38;5;241m.\u001b[39mcalculate_state_estimation(symmetric\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n", 116 | "File \u001b[0;32m~/pgm/power-grid-model/src/power_grid_model/_core/power_grid_model.py:125\u001b[0m, in \u001b[0;36mPowerGridModel.__init__\u001b[0;34m(self, input_data, system_frequency)\u001b[0m\n\u001b[1;32m 123\u001b[0m prepared_input \u001b[38;5;241m=\u001b[39m prepare_input_view(_map_to_component_types(input_data))\n\u001b[1;32m 124\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_model_ptr \u001b[38;5;241m=\u001b[39m pgc\u001b[38;5;241m.\u001b[39mcreate_model(system_frequency, input_data\u001b[38;5;241m=\u001b[39mprepared_input\u001b[38;5;241m.\u001b[39mget_dataset_ptr())\n\u001b[0;32m--> 125\u001b[0m \u001b[43massert_no_error\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 126\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_all_component_count \u001b[38;5;241m=\u001b[39m {k: v \u001b[38;5;28;01mfor\u001b[39;00m k, v \u001b[38;5;129;01min\u001b[39;00m prepared_input\u001b[38;5;241m.\u001b[39mget_info()\u001b[38;5;241m.\u001b[39mtotal_elements()\u001b[38;5;241m.\u001b[39mitems() \u001b[38;5;28;01mif\u001b[39;00m v \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m0\u001b[39m}\n", 117 | "File \u001b[0;32m~/pgm/power-grid-model/src/power_grid_model/_core/error_handling.py:169\u001b[0m, in \u001b[0;36massert_no_error\u001b[0;34m(batch_size, decode_error)\u001b[0m\n\u001b[1;32m 167\u001b[0m error \u001b[38;5;241m=\u001b[39m find_error(batch_size\u001b[38;5;241m=\u001b[39mbatch_size, decode_error\u001b[38;5;241m=\u001b[39mdecode_error)\n\u001b[1;32m 168\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m error \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m--> 169\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m error\n", 118 | "\u001b[0;31mIDWrongType\u001b[0m: Wrong type for object with id 4\n\nTry validate_input_data() or validate_batch_data() to validate your data.\n" 119 | ] 120 | } 121 | ], 122 | "source": [ 123 | "# Without validation\n", 124 | "model = PowerGridModel(error_data)\n", 125 | "output_data = model.calculate_state_estimation(symmetric=True)" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": 3, 131 | "id": "fd84be3c", 132 | "metadata": {}, 133 | "outputs": [ 134 | { 135 | "ename": "ValidationException", 136 | "evalue": "There are 5 validation errors in input_data:\n 1. Fields line.id and sym_power_sensor.id are not unique for 2 lines/sym_power_sensors.\n 2. Field 'to_node' does not contain a valid node id for 1 line.\n 3. Field 'power_sigma' is not greater than zero for 2 sym_power_sensors.\n 4. Field 'measured_object' does not contain a valid line/generic_branch/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n 5. Field 'measured_object' does not contain a valid source id for 1 sym_power_sensor. (measured_terminal_type=source)", 137 | "output_type": "error", 138 | "traceback": [ 139 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 140 | "\u001b[0;31mValidationException\u001b[0m Traceback (most recent call last)", 141 | "Cell \u001b[0;32mIn[3], line 4\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mpower_grid_model\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mvalidation\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m assert_valid_input_data\n\u001b[1;32m 3\u001b[0m \u001b[38;5;66;03m# Assert valid data\u001b[39;00m\n\u001b[0;32m----> 4\u001b[0m \u001b[43massert_valid_input_data\u001b[49m\u001b[43m(\u001b[49m\u001b[43merror_data\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msymmetric\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 5\u001b[0m model \u001b[38;5;241m=\u001b[39m PowerGridModel(error_data)\n\u001b[1;32m 6\u001b[0m output_data \u001b[38;5;241m=\u001b[39m model\u001b[38;5;241m.\u001b[39mcalculate_state_estimation(symmetric\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n", 142 | "File \u001b[0;32m~/pgm/power-grid-model/src/power_grid_model/validation/assertions.py:57\u001b[0m, in \u001b[0;36massert_valid_input_data\u001b[0;34m(input_data, calculation_type, symmetric)\u001b[0m\n\u001b[1;32m 53\u001b[0m validation_errors \u001b[38;5;241m=\u001b[39m validate_input_data(\n\u001b[1;32m 54\u001b[0m input_data\u001b[38;5;241m=\u001b[39minput_data, calculation_type\u001b[38;5;241m=\u001b[39mcalculation_type, symmetric\u001b[38;5;241m=\u001b[39msymmetric\n\u001b[1;32m 55\u001b[0m )\n\u001b[1;32m 56\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m validation_errors:\n\u001b[0;32m---> 57\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ValidationException(validation_errors, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124minput_data\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", 143 | "\u001b[0;31mValidationException\u001b[0m: There are 5 validation errors in input_data:\n 1. Fields line.id and sym_power_sensor.id are not unique for 2 lines/sym_power_sensors.\n 2. Field 'to_node' does not contain a valid node id for 1 line.\n 3. Field 'power_sigma' is not greater than zero for 2 sym_power_sensors.\n 4. Field 'measured_object' does not contain a valid line/generic_branch/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n 5. Field 'measured_object' does not contain a valid source id for 1 sym_power_sensor. (measured_terminal_type=source)" 144 | ] 145 | } 146 | ], 147 | "source": [ 148 | "from power_grid_model.validation import assert_valid_input_data\n", 149 | "\n", 150 | "# Assert valid data\n", 151 | "assert_valid_input_data(error_data, symmetric=True)\n", 152 | "model = PowerGridModel(error_data)\n", 153 | "output_data = model.calculate_state_estimation(symmetric=True)" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 4, 159 | "id": "6286ece0", 160 | "metadata": {}, 161 | "outputs": [ 162 | { 163 | "name": "stdout", 164 | "output_type": "stream", 165 | "text": [ 166 | "MultiComponentNotUniqueError [, ] : [(, np.int32(6)), (, np.int32(6))]\n", 167 | "InvalidIdError ComponentType.line : [6]\n", 168 | "NotGreaterThanError ComponentType.sym_power_sensor : [6, 7]\n", 169 | "InvalidIdError ComponentType.sym_power_sensor : [6]\n", 170 | "InvalidIdError ComponentType.sym_power_sensor : [7]\n" 171 | ] 172 | } 173 | ], 174 | "source": [ 175 | "from power_grid_model.validation import ValidationException\n", 176 | "\n", 177 | "# Assert valid data and display component ids\n", 178 | "try:\n", 179 | " assert_valid_input_data(error_data, symmetric=True)\n", 180 | " model = PowerGridModel(error_data)\n", 181 | " output_data = model.calculate_state_estimation(symmetric=True)\n", 182 | "except ValidationException as ex:\n", 183 | " for error in ex.errors:\n", 184 | " print(type(error).__name__, error.component, \":\", error.ids)" 185 | ] 186 | }, 187 | { 188 | "cell_type": "code", 189 | "execution_count": 5, 190 | "id": "1e4ca721", 191 | "metadata": {}, 192 | "outputs": [ 193 | { 194 | "name": "stdout", 195 | "output_type": "stream", 196 | "text": [ 197 | "There are 5 validation errors in the data:\n", 198 | " 1. Fields line.id and sym_power_sensor.id are not unique for 2 lines/sym_power_sensors.\n", 199 | " 2. Field 'to_node' does not contain a valid node id for 1 line.\n", 200 | " 3. Field 'power_sigma' is not greater than zero for 2 sym_power_sensors.\n", 201 | " 4. Field 'measured_object' does not contain a valid line/generic_branch/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n", 202 | " 5. Field 'measured_object' does not contain a valid source id for 1 sym_power_sensor. (measured_terminal_type=source)\n" 203 | ] 204 | } 205 | ], 206 | "source": [ 207 | "from power_grid_model.validation import validate_input_data, errors_to_string\n", 208 | "\n", 209 | "# Validation only as exception handling\n", 210 | "try:\n", 211 | " model = PowerGridModel(error_data)\n", 212 | " output_data = model.calculate_state_estimation(symmetric=True)\n", 213 | "except RuntimeError as ex:\n", 214 | " errors = validate_input_data(error_data, symmetric=True)\n", 215 | " print(errors_to_string(errors))" 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": 6, 221 | "id": "91a1aff6", 222 | "metadata": {}, 223 | "outputs": [ 224 | { 225 | "name": "stdout", 226 | "output_type": "stream", 227 | "text": [ 228 | "There are 5 validation errors in the data:\n", 229 | "\n", 230 | "\tFields line.id and sym_power_sensor.id are not unique for 2 lines/sym_power_sensors.\n", 231 | "\t\tcomponent: line/sym_power_sensor\n", 232 | "\t\tfield: line.id and sym_power_sensor.id\n", 233 | "\t\tids: [(, np.int32(6)), (, np.int32(6))]\n", 234 | "\n", 235 | "\tField 'to_node' does not contain a valid node id for 1 line.\n", 236 | "\t\tids: [6]\n", 237 | "\t\tref_components: node\n", 238 | "\t\tfilters: \n", 239 | "\t\tcomponent: line\n", 240 | "\t\tfield: 'to_node'\n", 241 | "\n", 242 | "\tField 'power_sigma' is not greater than zero for 2 sym_power_sensors.\n", 243 | "\t\tcomponent: sym_power_sensor\n", 244 | "\t\tfield: 'power_sigma'\n", 245 | "\t\tids: [6, 7]\n", 246 | "\t\tref_value: zero\n", 247 | "\n", 248 | "\tField 'measured_object' does not contain a valid line/generic_branch/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n", 249 | "\t\tids: [6]\n", 250 | "\t\tref_components: line/generic_branch/transformer\n", 251 | "\t\tfilters: (measured_terminal_type=branch_from)\n", 252 | "\t\tcomponent: sym_power_sensor\n", 253 | "\t\tfield: 'measured_object'\n", 254 | "\n", 255 | "\tField 'measured_object' does not contain a valid source id for 1 sym_power_sensor. (measured_terminal_type=source)\n", 256 | "\t\tids: [7]\n", 257 | "\t\tref_components: source\n", 258 | "\t\tfilters: (measured_terminal_type=source)\n", 259 | "\t\tcomponent: sym_power_sensor\n", 260 | "\t\tfield: 'measured_object'\n", 261 | "\n" 262 | ] 263 | } 264 | ], 265 | "source": [ 266 | "# Manual checking and display detailed information about the invalid data\n", 267 | "errors = validate_input_data(error_data, symmetric=True)\n", 268 | "print(errors_to_string(errors, details=True))" 269 | ] 270 | } 271 | ], 272 | "metadata": { 273 | "kernelspec": { 274 | "display_name": "venv", 275 | "language": "python", 276 | "name": "python3" 277 | }, 278 | "language_info": { 279 | "codemirror_mode": { 280 | "name": "ipython", 281 | "version": 3 282 | }, 283 | "file_extension": ".py", 284 | "mimetype": "text/x-python", 285 | "name": "python", 286 | "nbconvert_exporter": "python", 287 | "pygments_lexer": "ipython3", 288 | "version": "3.13.0" 289 | } 290 | }, 291 | "nbformat": 4, 292 | "nbformat_minor": 5 293 | } 294 | -------------------------------------------------------------------------------- /examples/Validation Examples.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /examples/data/serialized_input.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": { 3 | "node": [ 4 | "id", 5 | "u_rated" 6 | ], 7 | "sym_load": [ 8 | "id", 9 | "node", 10 | "status", 11 | "type", 12 | "p_specified", 13 | "q_specified" 14 | ], 15 | "source": [ 16 | "id", 17 | "node", 18 | "status", 19 | "u_ref", 20 | "sk" 21 | ] 22 | }, 23 | "data": { 24 | "node": [ 25 | [ 26 | 1, 27 | 10.5e3 28 | ], 29 | [ 30 | 2, 31 | 10.5e3 32 | ], 33 | [ 34 | 3, 35 | 10.5e3 36 | ] 37 | ], 38 | "line": [ 39 | { 40 | "id": 4, 41 | "from_node": 1, 42 | "to_node": 2, 43 | "from_status": 1, 44 | "to_status": 1, 45 | "r1": 0.11, 46 | "x1": 0.12, 47 | "c1": 4e-05, 48 | "tan1": 0.1, 49 | "i_n": 500.0 50 | }, 51 | { 52 | "id": 5, 53 | "from_node": 2, 54 | "to_node": 3, 55 | "from_status": 1, 56 | "to_status": 1, 57 | "r1": 0.15, 58 | "x1": 0.16, 59 | "c1": 5e-05, 60 | "tan1": 0.12, 61 | "i_n": 550.0 62 | } 63 | ], 64 | "source": [ 65 | [ 66 | 15, 67 | 1, 68 | 1, 69 | 1.03, 70 | 1e20 71 | ], 72 | [ 73 | 16, 74 | 1, 75 | 1, 76 | 1.04, 77 | null 78 | ], 79 | { 80 | "id": 17, 81 | "node": 1, 82 | "status": 1, 83 | "u_ref": 1.03, 84 | "sk": 1e10, 85 | "rx_ratio": 0.2 86 | } 87 | ], 88 | "sym_load": [ 89 | [ 90 | 7, 91 | 2, 92 | 1, 93 | 0, 94 | 1.01e6, 95 | 0.21e6 96 | ], 97 | [ 98 | 8, 99 | 3, 100 | 1, 101 | 0, 102 | 1.02e6, 103 | 0.22e6 104 | ] 105 | ] 106 | }, 107 | "is_batch": false, 108 | "type": "input", 109 | "version": "1.0" 110 | } -------------------------------------------------------------------------------- /examples/data/serialized_input.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-flow-assignment/Power Flow Assignment with Solutions.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-flow-assignment/Power Flow Assignment.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "id": "a727ce38", 7 | "metadata": {}, 8 | "source": [ 9 | "# Introduction\n", 10 | "\n", 11 | "In this assignment you will be given a series of tasks about using the library `power-grid-model`. The tasks include:\n", 12 | "\n", 13 | "1. [Load input](#Assignment-1:-Load-Input-Data)\n", 14 | "2. [Validate Input Data](#Assignment-2:-Validate-Input-Data)\n", 15 | "3. [Construct Model](#Assignment-3:-Construct-Model)\n", 16 | "4. [Calculate One Time Power Flow](#Assignment-4:-Calculate-One-Time-Power-Flow)\n", 17 | "5. [Time Series Batch Calculation](#Assignment-5:-Time-Series-Batch-Calculation)\n", 18 | "6. [N 1 Scenario-Batch-Calculation](#Assignment-6:-N-1-Scenario-Batch-Calculation)\n", 19 | "\n", 20 | "The input data are CSV files in the `data/` folder:\n", 21 | "* `node.csv`\n", 22 | "* `line.csv`\n", 23 | "* `source.csv`\n", 24 | "* `sym_load.csv`\n" 25 | ] 26 | }, 27 | { 28 | "attachments": {}, 29 | "cell_type": "markdown", 30 | "id": "b18d109c", 31 | "metadata": {}, 32 | "source": [ 33 | "# Preparation\n", 34 | "\n", 35 | "First import everything we need for this workshop:" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": null, 41 | "id": "fbc50a6d", 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "import time\n", 46 | "from typing import Dict\n", 47 | "\n", 48 | "import numpy as np\n", 49 | "import pandas as pd\n", 50 | "import matplotlib.pyplot as plt\n", 51 | "\n", 52 | "from power_grid_model import (\n", 53 | " PowerGridModel,\n", 54 | " CalculationType,\n", 55 | " CalculationMethod,\n", 56 | " ComponentType,\n", 57 | " DatasetType,\n", 58 | " initialize_array\n", 59 | ")\n", 60 | "\n", 61 | "from power_grid_model.validation import (\n", 62 | " assert_valid_input_data,\n", 63 | " assert_valid_batch_data\n", 64 | ")" 65 | ] 66 | }, 67 | { 68 | "attachments": {}, 69 | "cell_type": "markdown", 70 | "id": "50065790", 71 | "metadata": {}, 72 | "source": [ 73 | "Let's define a timer class to easily benchmark the calculations:" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": null, 79 | "id": "760a38b1", 80 | "metadata": {}, 81 | "outputs": [], 82 | "source": [ 83 | "class Timer:\n", 84 | " def __init__(self, name: str):\n", 85 | " self.name = name\n", 86 | " self.start = None\n", 87 | "\n", 88 | " def __enter__(self):\n", 89 | " self.start = time.perf_counter()\n", 90 | "\n", 91 | " def __exit__(self, *args):\n", 92 | " print(f'Execution time for {self.name} is {(time.perf_counter() - self.start):0.6f} s')" 93 | ] 94 | }, 95 | { 96 | "attachments": {}, 97 | "cell_type": "markdown", 98 | "id": "0f33163a", 99 | "metadata": {}, 100 | "source": [ 101 | "The following example measures the time for a simple add operation of two numpy arrays." 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": null, 107 | "id": "a61540cd", 108 | "metadata": {}, 109 | "outputs": [], 110 | "source": [ 111 | "a = np.random.rand(1000000)\n", 112 | "b = np.random.rand(1000000)\n", 113 | "with Timer(\"Add Operation\"):\n", 114 | " c = a + b" 115 | ] 116 | }, 117 | { 118 | "attachments": {}, 119 | "cell_type": "markdown", 120 | "id": "824a3c36", 121 | "metadata": {}, 122 | "source": [ 123 | "# Assignment 1: Load Input Data\n", 124 | "\n", 125 | "The following function loads the CSV data files from folder `../data/` and convert them into one dictionary of numpy structured arrays. The returned dictionary is a compatible input for the constructor of `PowerGridModel`. Please complete the function to construct the input data which is compatible with `PowerGridModel`." 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": null, 131 | "id": "2baee3cd", 132 | "metadata": {}, 133 | "outputs": [], 134 | "source": [ 135 | "def load_input_data() -> Dict[str, np.ndarray]:\n", 136 | " input_data = {}\n", 137 | " for component in [ComponentType.node, ComponentType.line, ComponentType.source, ComponentType.sym_load]:\n", 138 | " \n", 139 | " # Use pandas to read CSV data\n", 140 | " df = pd.read_csv(f'../data/{component.value}.csv')\n", 141 | "\n", 142 | " # TODO: Initialize array\n", 143 | " input_data[component] = ...\n", 144 | "\n", 145 | " # TODO: Fill the attributes\n", 146 | " for attr ...:\n", 147 | " input_data[component][attr] = ...\n", 148 | "\n", 149 | " # Print some debug info\n", 150 | " print(f\"{component:9s}: {len(input_data[component]):4d}\")\n", 151 | "\n", 152 | " return input_data\n", 153 | "\n", 154 | "# TODO: Load input data\n", 155 | "with Timer(\"Loading Input Data\"):\n", 156 | " input_data = ...\n" 157 | ] 158 | }, 159 | { 160 | "attachments": {}, 161 | "cell_type": "markdown", 162 | "id": "728db4b8", 163 | "metadata": {}, 164 | "source": [ 165 | "# Assignment 2: Validate Input Data\n", 166 | "\n", 167 | "It is recommended to validate your data before constructing the `PowerGridModel`. If you are confident about your input data, you can skip this step for performance reasons. The easiest way to validate your input data is using `assert_valid_input_data`, which will raise an exception if there are any errors in your data. Please have a look at the [Validation Examples](https://github.com/PowerGridModel/power-grid-model/blob/main/examples/Validation%20Examples.ipynb) for more detailed information on the validation functions." 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": null, 173 | "id": "b703c8aa", 174 | "metadata": {}, 175 | "outputs": [], 176 | "source": [ 177 | "# TODO: Validate input data\n", 178 | "with Timer(\"Validating Input Data\"):\n", 179 | " assert_valid_input_data(...)" 180 | ] 181 | }, 182 | { 183 | "attachments": {}, 184 | "cell_type": "markdown", 185 | "id": "940d48be", 186 | "metadata": {}, 187 | "source": [ 188 | "# Assignment 3: Construct Model\n", 189 | "\n", 190 | "Create an instance of `PowerGridModel` using the input data. Benchmark the construction time." 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": null, 196 | "id": "71b39571", 197 | "metadata": {}, 198 | "outputs": [], 199 | "source": [ 200 | "# TODO: Construct model\n", 201 | "with Timer(\"Model Construction\"):\n", 202 | " model = PowerGridModel(...)\n", 203 | "\n", 204 | "# Print the number of objects\n", 205 | "print(model.all_component_count)" 206 | ] 207 | }, 208 | { 209 | "attachments": {}, 210 | "cell_type": "markdown", 211 | "id": "2b013fa7", 212 | "metadata": {}, 213 | "source": [ 214 | "# Assignment 4: Calculate One-Time Power Flow\n", 215 | "\n", 216 | "* Calculate one-time power flow, print the highest and lowest loading of the lines.\n", 217 | "* Try with Newton-Raphson and linear method, compare the results and speed." 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": null, 223 | "id": "88034903", 224 | "metadata": {}, 225 | "outputs": [], 226 | "source": [ 227 | "# TODO: Newton-Raphson Power Flow\n", 228 | "with Timer(\"Newton-Raphson Power Flow\"):\n", 229 | " result = ...\n", 230 | " \n", 231 | "# TODO: Print min and max line loading\n", 232 | "print(\"Min line loading:\", ...)\n", 233 | "print(\"Max line loading:\", ...)" 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": null, 239 | "id": "a8d298d5", 240 | "metadata": {}, 241 | "outputs": [], 242 | "source": [ 243 | "# TODO: Linear Power Flow\n", 244 | "with Timer(\"Linear Power Flow\"):\n", 245 | " result = ...\n", 246 | " \n", 247 | "# TODO: Print min and max line loading\n", 248 | "print(\"Min line loading:\", ...)\n", 249 | "print(\"Max line loading:\", ...)" 250 | ] 251 | }, 252 | { 253 | "attachments": {}, 254 | "cell_type": "markdown", 255 | "id": "14fcc242", 256 | "metadata": {}, 257 | "source": [ 258 | "# Assignment 5: Time Series Batch Calculation\n", 259 | "\n", 260 | "## Load Profile\n", 261 | "\n", 262 | "Below we randomly generate a dataframe of load profile. \n", 263 | "\n", 264 | "* The column names are the IDs of `sym_load`\n", 265 | "* Each row is one scenario\n", 266 | "* Each entry specifies the active power of the load\n", 267 | "* The reactive power is zero\n" 268 | ] 269 | }, 270 | { 271 | "cell_type": "code", 272 | "execution_count": null, 273 | "id": "b58045cc", 274 | "metadata": {}, 275 | "outputs": [], 276 | "source": [ 277 | "# Generate random load profile of hourly data\n", 278 | "n_scenarios = 1000\n", 279 | "n_loads = len(input_data[ComponentType.sym_load]) \n", 280 | "load_id = input_data[ComponentType.sym_load][\"id\"]\n", 281 | "load_p = input_data[ComponentType.sym_load][\"p_specified\"]\n", 282 | "profile = np.tile(load_p, (n_scenarios, 1)) + 5e5 * np.random.randn(n_scenarios, n_loads)\n", 283 | "dti = pd.date_range(\"2022-01-01\", periods=n_scenarios, freq=\"h\")\n", 284 | "df_load_profile = pd.DataFrame(profile, columns=load_id, index=dti)\n", 285 | "display(df_load_profile)" 286 | ] 287 | }, 288 | { 289 | "attachments": {}, 290 | "cell_type": "markdown", 291 | "id": "6074de68", 292 | "metadata": {}, 293 | "source": [ 294 | "## Run Time Series Calculation\n", 295 | "\n", 296 | "We want to run a time-series load flow batch calculation using the dataframe.\n", 297 | "\n", 298 | "* Convert the load profile to the compatible batch update dataset.\n", 299 | "* Run the batch calculation.\n", 300 | "* Compare the calculation methods `newton_raphson` and `linear`." 301 | ] 302 | }, 303 | { 304 | "cell_type": "code", 305 | "execution_count": null, 306 | "id": "921310a7", 307 | "metadata": {}, 308 | "outputs": [], 309 | "source": [ 310 | "# TODO: Initialize an empty load profile\n", 311 | "load_profile = initialize_array(..., ..., ...)\n", 312 | "\n", 313 | "# TODO: Set the attributes for the batch calculation (assume q_specified = 0.0)\n", 314 | "load_profile[\"id\"] = ...\n", 315 | "load_profile[\"p_specified\"] = ...\n", 316 | "load_profile[\"q_specified\"] = ...\n", 317 | "\n", 318 | "# Construct the update data\n", 319 | "update_data = {ComponentType.sym_load: load_profile}" 320 | ] 321 | }, 322 | { 323 | "cell_type": "code", 324 | "execution_count": null, 325 | "id": "9f88ba5f", 326 | "metadata": {}, 327 | "outputs": [], 328 | "source": [ 329 | "# Validating batch data can take a long time.\n", 330 | "# It is recommended to only validate batch data when you run into trouble.\n", 331 | "with Timer(\"Validating Batch Data\"):\n", 332 | " assert_valid_batch_data(input_data=input_data, update_data=update_data, calculation_type=CalculationType.power_flow)" 333 | ] 334 | }, 335 | { 336 | "cell_type": "code", 337 | "execution_count": null, 338 | "id": "e7ee773c", 339 | "metadata": {}, 340 | "outputs": [], 341 | "source": [ 342 | "# TODO: Run Newton Raphson power flow (this may take a minute...)\n", 343 | "with Timer(\"Batch Calculation using Newton-Raphson\"):\n", 344 | " output_data = model.calculate_power_flow(...)" 345 | ] 346 | }, 347 | { 348 | "cell_type": "code", 349 | "execution_count": null, 350 | "id": "251e2edd", 351 | "metadata": {}, 352 | "outputs": [], 353 | "source": [ 354 | "# TODO: Run linear power flow\n", 355 | "with Timer(\"Batch Calculation using linear calculation\"):\n", 356 | " output_data_linear = model.calculate_power_flow(...)" 357 | ] 358 | }, 359 | { 360 | "attachments": {}, 361 | "cell_type": "markdown", 362 | "id": "40bbfb51", 363 | "metadata": {}, 364 | "source": [ 365 | "### Plotting batch results\n", 366 | "\n", 367 | "Lets say we wish to plot the loading of the `line with id 2007` vs time. We can use matplotlib to do so.\n", 368 | "**Note:** The grid and results are randomly generated so dont be alarmed to see loading >100% or any other unrealistic results." 369 | ] 370 | }, 371 | { 372 | "cell_type": "code", 373 | "execution_count": null, 374 | "id": "9ff1aca3", 375 | "metadata": {}, 376 | "outputs": [], 377 | "source": [ 378 | "# TODO: Prepare data to be plotted. We wish to plot the loading of line with id 2007 vs time.\n", 379 | "line_2007_idx = np.where(... == 2007)\n", 380 | "result_loading = output_data[ComponentType.line][\"loading\"][...]\n", 381 | "plt.plot(result_loading)\n", 382 | "plt.title('Loading of line with id 2007')\n", 383 | "plt.xlabel('Time')\n", 384 | "plt.ylabel('Loading')\n", 385 | "plt.show()" 386 | ] 387 | }, 388 | { 389 | "attachments": {}, 390 | "cell_type": "markdown", 391 | "id": "2f433789", 392 | "metadata": {}, 393 | "source": [ 394 | "### Indexing the results\n", 395 | "\n", 396 | "Also find the time stamps where loading in `line with id 2007` is greater than `90%`" 397 | ] 398 | }, 399 | { 400 | "cell_type": "code", 401 | "execution_count": null, 402 | "id": "36842570", 403 | "metadata": {}, 404 | "outputs": [], 405 | "source": [ 406 | "# TODO: Fill condition to find times when loading of line with id 2007 when its greater than 90%\n", 407 | "ind = np.where(...)\n", 408 | "df_load_profile.index[ind]" 409 | ] 410 | }, 411 | { 412 | "attachments": {}, 413 | "cell_type": "markdown", 414 | "id": "eb7ab8b5", 415 | "metadata": {}, 416 | "source": [ 417 | "# Assignment 6: N-1 Scenario Batch Calculation\n", 418 | "\n", 419 | "We want to run a N-1 Scenario analysis. For each batch calculation, one `line` is disconnected at from- and to-side." 420 | ] 421 | }, 422 | { 423 | "cell_type": "code", 424 | "execution_count": null, 425 | "id": "28b46e3a", 426 | "metadata": {}, 427 | "outputs": [], 428 | "source": [ 429 | "n_lines = len(input_data[ComponentType.line])\n", 430 | "\n", 431 | "# TODO: Initialize an empty line profile\n", 432 | "line_profile = initialize_array(..., ..., ...)\n", 433 | "\n", 434 | "# TODO: Set the attributes for the batch calculation\n", 435 | "line_profile[\"id\"] = ...\n", 436 | "line_profile[\"from_status\"] = ...\n", 437 | "line_profile[\"to_status\"] = ...\n", 438 | "\n", 439 | "# Construct the update data\n", 440 | "update_data = {ComponentType.line: line_profile}" 441 | ] 442 | }, 443 | { 444 | "cell_type": "code", 445 | "execution_count": null, 446 | "id": "5fa9c6ca", 447 | "metadata": {}, 448 | "outputs": [], 449 | "source": [ 450 | "# Validating batch data can take a long time.\n", 451 | "# It is recommended to only validate batch data when you run into trouble.\n", 452 | "with Timer(\"Validating Batch Data\"):\n", 453 | " assert_valid_batch_data(input_data=input_data, update_data=update_data, calculation_type=CalculationType.power_flow)" 454 | ] 455 | }, 456 | { 457 | "cell_type": "code", 458 | "execution_count": null, 459 | "id": "3337a5cb", 460 | "metadata": {}, 461 | "outputs": [], 462 | "source": [ 463 | "# TODO: Run Newton Raphson power flow (this may take a minute...)\n", 464 | "with Timer(\"Batch Calculation using Newton-Raphson\"):\n", 465 | " model.calculate_power_flow(...)" 466 | ] 467 | }, 468 | { 469 | "cell_type": "code", 470 | "execution_count": null, 471 | "id": "f7ba9924", 472 | "metadata": {}, 473 | "outputs": [], 474 | "source": [ 475 | "# TODO: Run linear power flow\n", 476 | "with Timer(\"Batch Calculation using linear calculation\"):\n", 477 | " model.calculate_power_flow(...)" 478 | ] 479 | }, 480 | { 481 | "attachments": {}, 482 | "cell_type": "markdown", 483 | "id": "6af43b69", 484 | "metadata": {}, 485 | "source": [ 486 | "## Parallel processing\n", 487 | "The `calculate_power_flow` method has an optional `threading` argument to define the number of threads ran in parallel. Experiment with different threading values and compare the results..." 488 | ] 489 | }, 490 | { 491 | "cell_type": "code", 492 | "execution_count": null, 493 | "id": "9f1a3d88", 494 | "metadata": {}, 495 | "outputs": [], 496 | "source": [ 497 | "# By default, sequential threading is used\n", 498 | "with Timer(\"Sequential\"):\n", 499 | " model.calculate_power_flow(update_data=update_data)\n", 500 | "\n", 501 | "# TODO: Single thread, this is essentially the same as running a single thread\n", 502 | "with Timer(\"Single thread\"):\n", 503 | " model.calculate_power_flow(update_data=update_data, threading=...)\n", 504 | "\n", 505 | "# TODO: Two threads should be faster \n", 506 | "with Timer(\"Two threads in parallel\"):\n", 507 | " model.calculate_power_flow(update_data=update_data, threading=...)\n", 508 | "\n", 509 | "# TODO: Four threads should be even faster \n", 510 | "with Timer(\"Four threads in parallel\"):\n", 511 | " model.calculate_power_flow(update_data=update_data, threading=...)\n", 512 | "\n", 513 | "# TODO: Use number of threads based the machine hardware \n", 514 | "with Timer(\"Use number of threads based the machine hardware\"):\n", 515 | " model.calculate_power_flow(update_data=update_data, threading=...)" 516 | ] 517 | } 518 | ], 519 | "metadata": { 520 | "kernelspec": { 521 | "display_name": "Python 3 (ipykernel)", 522 | "language": "python", 523 | "name": "python3" 524 | }, 525 | "language_info": { 526 | "codemirror_mode": { 527 | "name": "ipython", 528 | "version": 3 529 | }, 530 | "file_extension": ".py", 531 | "mimetype": "text/x-python", 532 | "name": "python", 533 | "nbconvert_exporter": "python", 534 | "pygments_lexer": "ipython3", 535 | "version": "3.11.2" 536 | } 537 | }, 538 | "nbformat": 4, 539 | "nbformat_minor": 5 540 | } 541 | -------------------------------------------------------------------------------- /power-flow-assignment/Power Flow Assignment.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Power Grid Model DS Workshop 8 | 9 | Welcome to the **Power Grid Model DS Workshop** — an introductory guide to the [Power Grid Model DS](https://github.com/PowerGridModel/power-grid-model-ds) project. This workshop is designed to help you get familiar with the PGM-DS interface and explore its capabilities through hands-on exercises. 10 | 11 | ## Workshop Structure 12 | 13 | The workshop includes two Jupyter notebooks: 14 | 15 | 1. **Introduction Notebook** 16 | A beginner-friendly guide that walks you through the basics of using the Power Grid Model DS interface. Ideal for first-time users. 17 | 18 | 2. **Advanced Notebook** 19 | A more in-depth exploration featuring a grid contingency scenario. You'll apply your knowledge to solve a real-world power grid challenge using PGM-DS. 20 | 21 | ## How to Load Solutions 22 | 23 | Each notebook includes optional solution cells. To reveal a solution, simply run the appropriate `%load` command. For example: 24 | 25 | ```python 26 | %load solutions/introduction_1_1_define_array_extensions 27 | -------------------------------------------------------------------------------- /power-grid-model-ds/advanced.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "59d49e29", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "!pip install 'pandas' 'power-grid-model-ds[visualizer]' --quiet" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "id": "d5e1958f", 16 | "metadata": {}, 17 | "source": [ 18 | "# ⚡ Advanced PGM-DS Workshop: Solving an Overload Scenario\n", 19 | "\n", 20 | "You're a senior grid analyst at GridNova Utilities, responsible for operating a legacy distribution grid in a rural area. The grid is radially operated, with some cables inactive as back-up in case failures. Recently, customer load growth has increased dramatically, particularly in areas served by several long feeders. This has pushed some branches past their capacity, triggering repeated overloads.\n", 21 | "\n", 22 | "Your task: upgrade the grid by adding a second substation and relieving the overloaded feeder through new connections to the new substation.\n", 23 | "\n", 24 | "This hands-on simulation walks you through each step of diagnosing, planning, and solving this overload using the Power Grid Model Data Science library.\n", 25 | "\n", 26 | "## 🎯 Workshop Goals\n", 27 | "- Detect a line overload using PGM load flow calculations.\n", 28 | "- Find a suitable node to create a connection to the new substation.\n", 29 | "- Strategically open a line to reroute power and relieve the feeder.\n" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "id": "cf475775", 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "import numpy as np\n", 40 | "\n", 41 | "from dataclasses import dataclass\n", 42 | "from power_grid_model_ds import Grid, GraphContainer\n", 43 | "from power_grid_model_ds.arrays import LineArray, NodeArray, SourceArray\n", 44 | "from power_grid_model_ds.enums import NodeType\n", 45 | "from power_grid_model_ds.visualizer import visualize" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "id": "8a4a9dbb", 51 | "metadata": {}, 52 | "source": [ 53 | "# 🧪 Step 1: Extend the Data Model\n", 54 | "Goal: Add coordinate fields and tracking for simulated voltages and line currents. This allows us to store and analyse metadata of the grid needed to to decide where to invest in the grid.\n", 55 | "\n", 56 | "You’ll subclass NodeArray and LineArray to add:\n", 57 | "\n", 58 | "- x, y coordinates for spatial logic and plotting\n", 59 | "- u for node voltage results\n", 60 | "- i_from for line currents\n", 61 | "- A computed .is_overloaded property for easy filtering\n", 62 | "\n", 63 | "This shows how the Grid can be extended to suit the needs of a specific project." 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": null, 69 | "id": "37f4bd29", 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "# 📦 Extend the grid with x, y, u (node) and i_from (line)\n", 74 | "from numpy.typing import NDArray\n", 75 | "\n", 76 | "class MyNodeArray(NodeArray):\n", 77 | " _defaults = {\"x\": 0.0, \"y\": 0.0, \"u\": 0.0}\n", 78 | " x: NDArray[np.float64]\n", 79 | " y: NDArray[np.float64]\n", 80 | " u: NDArray[np.float64]\n", 81 | "\n", 82 | "class MyLineArray(LineArray):\n", 83 | " _defaults = {\"i_from\": 0.0, \"overload_status\": 0}\n", 84 | " i_from: NDArray[np.float64]\n", 85 | " overload_status: NDArray[np.int8]\n", 86 | "\n", 87 | " def set_overload_status(self):\n", 88 | " \"\"\"Set the overload status based on the current and nominal current.\"\"\"\n", 89 | " self.overload_status = np.where(self.i_from > self.i_n, 1, 0)\n", 90 | "\n", 91 | " @property\n", 92 | " def is_overloaded(self) -> NDArray[np.bool_]:\n", 93 | " \"\"\"Check if the line is overloaded.\"\"\"\n", 94 | " self.set_overload_status()\n", 95 | " return self.overload_status == 1\n", 96 | "\n", 97 | "@dataclass\n", 98 | "class MyGrid(Grid):\n", 99 | " node: MyNodeArray\n", 100 | " line: MyLineArray\n", 101 | " graphs: GraphContainer" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "id": "8e2d5fbe", 107 | "metadata": {}, 108 | "source": [ 109 | "# 🏗️ Step 2: Load and Prepare the Grid\n", 110 | "Goal: Load a synthetic medium-voltage grid from the provided data\n", 111 | "(Code is already given in helper.py file, take a look to see how the grid data is loaded!)" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": null, 117 | "id": "78d0639e", 118 | "metadata": {}, 119 | "outputs": [], 120 | "source": [ 121 | "from helper import load_dummy_grid\n", 122 | "\n", 123 | "grid = load_dummy_grid(MyGrid)" 124 | ] 125 | }, 126 | { 127 | "cell_type": "markdown", 128 | "id": "4d2df7f4", 129 | "metadata": {}, 130 | "source": [ 131 | "We now loaded our the network visualised here\n", 132 | "\n", 133 | "![input_network.png](input_network.png)" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "id": "6ac4eb43", 139 | "metadata": {}, 140 | "source": [ 141 | "# 🧯 Step 3: Detect the Overload\n", 142 | "This is your first excercise.\n", 143 | "\n", 144 | "Goal: Identify which line(s) are exceeding their rated current (the `i_n` property).\n", 145 | "\n", 146 | "You can do this step by step (don't forget to check the PGM-DS documentation):\n", 147 | "\n", 148 | "1. Use the PowerGridModelInterface to calculate power flow\n", 149 | "2. Update the Grid object with the calculated values\n", 150 | "3. Return the lines (LineArray) that are overloaded\n", 151 | "\n", 152 | "**💡 Hint**: You can use the `is_overloaded` property of the `MyLineArray` class to check for overloaded lines.\n", 153 | "\n", 154 | "**💡 Hint**: https://power-grid-model-ds.readthedocs.io/en/stable/quick_start.html#performing-power-flow-calculations" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": null, 160 | "id": "206be67b", 161 | "metadata": {}, 162 | "outputs": [], 163 | "source": [ 164 | "def check_for_capacity_issues(grid: Grid) -> LineArray:\n", 165 | " \"\"\"Check for capacity issues on the grid.\n", 166 | " Return the lines that with capacity issues.\n", 167 | " \"\"\"\n", 168 | "\n", 169 | "print(check_for_capacity_issues(grid))" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": null, 175 | "id": "18973302-105f-42c3-9ffd-1408b90aeb10", 176 | "metadata": {}, 177 | "outputs": [], 178 | "source": [ 179 | "# %load solutions/advanced_3_check_for_capacity_issues.py" 180 | ] 181 | }, 182 | { 183 | "cell_type": "markdown", 184 | "id": "4bd3aea9", 185 | "metadata": {}, 186 | "source": [ 187 | "We can use PGM-DSs visualization function to explore the resulting grid. Check out the highlighting parts of the grid based on it's attributes to find out where the overload occurs" 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "execution_count": null, 193 | "id": "1774170b", 194 | "metadata": {}, 195 | "outputs": [], 196 | "source": [ 197 | "visualize(grid)" 198 | ] 199 | }, 200 | { 201 | "cell_type": "markdown", 202 | "id": "008fafb9", 203 | "metadata": {}, 204 | "source": [ 205 | "# 🧭 Step 4: Plan a Relief Strategy\n", 206 | "\n", 207 | "If you visualize the grid and highlight the overloaded cables, this is what you will see:\n", 208 | "\n", 209 | "![input_network_with_overload.png](input_network_with_overload.png)\n", 210 | "\n", 211 | "We found out the north-east part of the area is overloaded.\n", 212 | "Goal: Place a second substation near the overloaded path. In the next steps we will use this substation to relieve overloaded cables.\n", 213 | "\n", 214 | "You’ll:\n", 215 | "- Create a new substations using the NodeArrayobject at the correct location.\n", 216 | "\n", 217 | "This substation will act as a new injection point for rerouting load.\n" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": null, 223 | "id": "ae5d93b7", 224 | "metadata": {}, 225 | "outputs": [], 226 | "source": [ 227 | "# Check the introduction workshop on adding a substation\n", 228 | "\n", 229 | "def build_new_substation(grid: Grid, location: tuple[float, float]) -> NodeArray:\n", 230 | " \"\"\"Build a new substation at the given location.\n", 231 | " Return the new substation.\n", 232 | " \"\"\"" 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": null, 238 | "id": "bff1e30f-5dd3-4774-977b-5707322a8f59", 239 | "metadata": {}, 240 | "outputs": [], 241 | "source": [ 242 | "# %load solutions/advanced_4_build_new_substation.py" 243 | ] 244 | }, 245 | { 246 | "cell_type": "markdown", 247 | "id": "8f2b17e3", 248 | "metadata": {}, 249 | "source": [ 250 | "# 🔗 Step 5: Analyze and Connect the Overloaded Route\n", 251 | "Goal: Identify the best way to connect the new substation to the overloaded routes.\n", 252 | "\n", 253 | "You’ll:\n", 254 | "- Compute which routes (/feeders) are overloaded to see where we need to intervene.\n", 255 | "- Find which node on an overloaded route is geographically closed to the new substation.\n", 256 | "- Create a new cable to connect the closest node to the new substation.\n", 257 | "\n", 258 | "**💡 Hint**: The lines have been extended with extra properties in Step 1\n", 259 | "\n", 260 | "**💡 Hint**: The arrays in the grid have a filter option, https://power-grid-model-ds.readthedocs.io/en/stable/examples/model/array_examples.html#using-filters" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": null, 266 | "id": "1bfe4e6e", 267 | "metadata": {}, 268 | "outputs": [], 269 | "source": [ 270 | "def get_all_congested_routes(grid: Grid) -> list[NodeArray]:\n", 271 | " \"\"\"Get all nodes on routes that contain an overloaded line.\"\"\"" 272 | ] 273 | }, 274 | { 275 | "cell_type": "code", 276 | "execution_count": null, 277 | "id": "dc494d36-5ee7-401c-8a3b-59e701777f8d", 278 | "metadata": {}, 279 | "outputs": [], 280 | "source": [ 281 | "# %load solutions/advanced_5_1_get_all_congested_routes.py" 282 | ] 283 | }, 284 | { 285 | "cell_type": "markdown", 286 | "id": "ee84e60c", 287 | "metadata": {}, 288 | "source": [ 289 | "Next we will use the nodes x and y coordinates to find a suitable node to connect to the new substation. You will create a find_connection_point function that return the Node in a route which is closest to the new_substation." 290 | ] 291 | }, 292 | { 293 | "cell_type": "code", 294 | "execution_count": null, 295 | "id": "32b32015", 296 | "metadata": {}, 297 | "outputs": [], 298 | "source": [ 299 | "def find_connection_point(route: NodeArray, new_substation: NodeArray) -> NodeArray:\n", 300 | " \"\"\"Calculate the connection point for the new route.\n", 301 | " This should be the geographically closest node to the new substation.\n", 302 | " \"\"\"\n", 303 | " # Calculate the distance of each node in the route to the new_substation\n", 304 | " # Return the closest one" 305 | ] 306 | }, 307 | { 308 | "cell_type": "code", 309 | "execution_count": null, 310 | "id": "8ce16108-4580-4c32-9e18-60239144ae4f", 311 | "metadata": {}, 312 | "outputs": [], 313 | "source": [ 314 | "# %load solutions/advanced_5_2_find_connection_point.py" 315 | ] 316 | }, 317 | { 318 | "cell_type": "markdown", 319 | "id": "a75e9b73", 320 | "metadata": {}, 321 | "source": [ 322 | "Finally we build a function that creates a new line between the connection point and the new substation.\n", 323 | "\n", 324 | "❗ **IMPORTANT** ❗ The new line should first be created with an open connection; we will optimize the location of the line opening in the next step.\n", 325 | "\n", 326 | "**💡 Hint**: In the introduction you learned how to add a LineArray to the grid." 327 | ] 328 | }, 329 | { 330 | "cell_type": "code", 331 | "execution_count": null, 332 | "id": "263818f6", 333 | "metadata": {}, 334 | "outputs": [], 335 | "source": [ 336 | "def connect_to_route(grid: Grid, connection_point: NodeArray, new_substation: NodeArray) -> None:\n", 337 | " \"\"\"Connect the new substation node to the connection point.\n", 338 | " \"\"\"" 339 | ] 340 | }, 341 | { 342 | "cell_type": "code", 343 | "execution_count": null, 344 | "id": "e711bc3c-a09c-4454-b3a5-be4bf15fa077", 345 | "metadata": {}, 346 | "outputs": [], 347 | "source": [ 348 | "# %load solutions/advanced_5_3_connect_to_route.py" 349 | ] 350 | }, 351 | { 352 | "cell_type": "markdown", 353 | "id": "4a9ef582", 354 | "metadata": {}, 355 | "source": [ 356 | "# 🔌 Step 6: Open the Right Line\n", 357 | "Goal: Find the optimal line to open to relieve the original overloaded feeder.\n", 358 | "\n", 359 | "You’ll:\n", 360 | "- Trace a path from the newly created cable to the old substation\n", 361 | "- Evaluate each line on the path by running `check_for_capacity_issues()` and find the optimal line to open\n", 362 | "- Open the correct line\n", 363 | "- Confirm the overload is resolved\n", 364 | "\n", 365 | "This final step demonstrates how network topology can be programmatically optimized using the Power Grid Model Data Science toolkit!\n", 366 | "\n" 367 | ] 368 | }, 369 | { 370 | "cell_type": "code", 371 | "execution_count": null, 372 | "id": "a494d54c", 373 | "metadata": {}, 374 | "outputs": [], 375 | "source": [ 376 | "def optimize_route_transfer(grid: Grid, connection_point: NodeArray, new_substation: NodeArray) -> None:\n", 377 | " \"\"\"Attempt to optimize the route transfer moving the normally open point (NOP) upstream towards the old substation.\n", 378 | " This way, the new substation will take over more nodes of the original route.\n", 379 | " \"\"\"\n", 380 | " # Get the path from the connection point to the old substation\n", 381 | " ...\n", 382 | "\n", 383 | " # filter the first branch in the path\n", 384 | " ...\n", 385 | "\n", 386 | " # Iterate over the path and check if the route is still overloaded\n", 387 | " for from_node, to_node in zip(path[0:-1], path[1:]):\n", 388 | " # Check if the route is still overloaded\n", 389 | " ...\n", 390 | " \n", 391 | " # Move the Open Point (NOP) upstream\n", 392 | " ...\n", 393 | " \n", 394 | " grid.set_feeder_ids()" 395 | ] 396 | }, 397 | { 398 | "cell_type": "code", 399 | "execution_count": null, 400 | "id": "471b9ff0-43ca-432c-9ce9-71fc06d95b86", 401 | "metadata": {}, 402 | "outputs": [], 403 | "source": [ 404 | "# %load solutions/advanced_6_optimize_route_transfer.py" 405 | ] 406 | }, 407 | { 408 | "cell_type": "markdown", 409 | "id": "dbf3243e", 410 | "metadata": {}, 411 | "source": [ 412 | "Now we combine the functions you created to solve the issues in the network" 413 | ] 414 | }, 415 | { 416 | "cell_type": "code", 417 | "execution_count": null, 418 | "id": "cb4092a1", 419 | "metadata": {}, 420 | "outputs": [], 421 | "source": [ 422 | "def transfer_routes(grid: Grid, new_substation: NodeArray) -> NodeArray:\n", 423 | " \"\"\"Migrate a subset of the routes of the old substation to the new substation.\n", 424 | " Each route can be migrated fully or partially.\n", 425 | "\n", 426 | " \"\"\"\n", 427 | " congested_routes = get_all_congested_routes(grid)\n", 428 | "\n", 429 | " for route in congested_routes:\n", 430 | " closest_node = find_connection_point(\n", 431 | " route=route,\n", 432 | " new_substation=new_substation\n", 433 | " )\n", 434 | "\n", 435 | " connect_to_route(\n", 436 | " grid=grid,\n", 437 | " connection_point=closest_node,\n", 438 | " new_substation=new_substation,\n", 439 | " )\n", 440 | "\n", 441 | " optimize_route_transfer(\n", 442 | " grid=grid,\n", 443 | " connection_point=closest_node,\n", 444 | " new_substation=new_substation)\n", 445 | " \n", 446 | " print(f\"Connected new substation to node {closest_node.id}\")\n", 447 | "\n", 448 | "transfer_routes(grid=grid, new_substation=new_substation)" 449 | ] 450 | }, 451 | { 452 | "cell_type": "markdown", 453 | "id": "82546c38", 454 | "metadata": {}, 455 | "source": [ 456 | "Check we resolved all contingencies" 457 | ] 458 | }, 459 | { 460 | "cell_type": "code", 461 | "execution_count": null, 462 | "id": "8226a9c1", 463 | "metadata": {}, 464 | "outputs": [], 465 | "source": [ 466 | "print(check_for_capacity_issues(grid))" 467 | ] 468 | }, 469 | { 470 | "cell_type": "code", 471 | "execution_count": null, 472 | "id": "83099a5e", 473 | "metadata": {}, 474 | "outputs": [], 475 | "source": [ 476 | "visualize(grid) " 477 | ] 478 | }, 479 | { 480 | "cell_type": "markdown", 481 | "id": "a2532f9d-60a9-4fcb-ac23-af6bd3ba47a2", 482 | "metadata": {}, 483 | "source": [ 484 | "*Note: Jupyter notebook only supports one visualizer instance at a time. You might need to restart the kernel and re-run some cells for this final visualizer to work properly. If you do, make sure to not run earlier cells that contain `visualize(grid)`*" 485 | ] 486 | }, 487 | { 488 | "cell_type": "markdown", 489 | "id": "1c2de102", 490 | "metadata": {}, 491 | "source": [ 492 | "# ✅ Wrap-Up\n", 493 | "You’ve just:\n", 494 | "\n", 495 | "- Loaded a grid topology and grid loads from a file\n", 496 | "- Analyse grid components that are or will soon be overloaded using load flow analysis\n", 497 | "- Automatically optimize a solution to relieve (future) congestions on the energy grid\n", 498 | "\n", 499 | "We hope you enjoyed working with Power Grid Model DS and would love to hear your feedback" 500 | ] 501 | } 502 | ], 503 | "metadata": { 504 | "kernelspec": { 505 | "display_name": "Python 3 (ipykernel)", 506 | "language": "python", 507 | "name": "python3" 508 | }, 509 | "language_info": { 510 | "codemirror_mode": { 511 | "name": "ipython", 512 | "version": 3 513 | }, 514 | "file_extension": ".py", 515 | "mimetype": "text/x-python", 516 | "name": "python", 517 | "nbconvert_exporter": "python", 518 | "pygments_lexer": "ipython3", 519 | "version": "3.13.2" 520 | } 521 | }, 522 | "nbformat": 4, 523 | "nbformat_minor": 5 524 | } 525 | -------------------------------------------------------------------------------- /power-grid-model-ds/advanced.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/data/lines.csv: -------------------------------------------------------------------------------- 1 | id,from_node,to_node,from_status,to_status,i_from,r1,x1,c1,tan1,i_n 2 | 301,101,1,1,1,0,0.04172331553871052,0.052980486528544264,0.0,0.0,210.28427536474146 3 | 302,1,2,1,1,0,0.15725110380730004,0.022290287012795527,0.0,0.0,105.94268839245518 4 | 303,2,3,1,1,0,0.15725110380730004,0.022290287012795527,0.0,0.0,105.94268839245518 5 | 304,3,4,1,1,0,0.025752163570459393,0.017975680453095257,0.0,0.0,252.81330598432984 6 | 305,4,5,1,1,0,0.184580749945473,0.05679935115815177,0.0,0.0,308.29168286950096 7 | 306,5,6,1,1,0,0.09247280056072502,0.04061706379498719,0.0,0.0,146.12039826984656 8 | 307,6,7,1,1,0,0.09247280056072502,0.04061706379498719,0.0,0.0,146.12039826984656 9 | 308,7,101,1,0,0,0.13774802878276293,0.023316760013206598,0.0,0.0,435.44293210319637 10 | 309,101,8,1,1,0,0.13774802878276293,0.023316760013206598,0.0,0.0,435.44293210319637 11 | 310,8,9,1,1,0,0.03155099592582141,0.1684585911623038,0.0,0.0,386.9416504130695 12 | 311,9,10,1,1,0,0.38045029098187605,0.0063983451879243095,0.0,0.0,595.6252329736612 13 | 312,10,11,1,1,0,0.38045029098187605,0.0063983451879243095,0.0,0.0,595.6252329736612 14 | 313,11,12,1,0,0,0.2711350129219581,0.0239320695659479,0.0,0.0,109.56302502830336 15 | 314,12,13,1,1,0,0.7512185900820556,0.016025736292976843,0.0,0.0,320.73771673448846 16 | 315,13,14,1,1,0,0.2332922408038184,0.010579215551474988,0.0,0.0,221.24699130886745 17 | 316,14,15,1,1,0,0.2884431875887676,0.016206534923606564,0.0,0.0,305.34636498186455 18 | 317,15,16,1,1,0,0.06893013265387658,0.002501770712404082,0.0,0.0,129.78347503297758 19 | 318,16,101,1,1,0,0.17466926178911735,0.04197366431254047,0.0,0.0,223.462291908927 20 | 319,101,17,1,1,0,0.0014230825354318865,0.005366123634368465,0.0,0.0,143.19692487769424 21 | 320,17,18,1,1,0,0.05535894789343593,0.015888270665417342,0.0,0.0,348.82546139151896 22 | 321,18,19,1,1,0,1.1336902294005273,0.0034372075909574274,0.0,0.0,109.4472896415015 23 | 322,19,20,1,1,0,0.02756399437186089,0.001442850824658642,0.0,0.0,315.15332070790726 24 | 323,20,21,1,1,0,0.15000233423633422,0.012783549273682825,0.0,0.0,505.6064937043503 25 | 324,21,101,1,0,0,0.4527663172740473,0.010942584326900677,0.0,0.0,235.29434874845845 26 | -------------------------------------------------------------------------------- /power-grid-model-ds/data/lines.csv.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/data/loads.csv: -------------------------------------------------------------------------------- 1 | id,node,p_specified,q_specified 2 | 201,1,75559.96424922015,2119.7561886191743 3 | 202,2,3897910.78949213034,-8632.07774532976 4 | 203,3,95478.41471359684,18229.00620748075 5 | 204,4,-63249.17346947564,-712.0333973476627 6 | 205,5,-64900.69141993268,14743.624463381835 7 | 206,6,348049.77201387816,36026.00482909736 8 | 207,7,21984.741221515593,24426.02990667891 9 | 208,8,-67789.02445002584,32094.560258638798 10 | 209,9,194810.9302324328,4783.500971894942 11 | 210,10,-65128.11331122106,-4447.890651114186 12 | 211,11,143731.66910079535,5949.794993746957 13 | 212,12,224756.36715177132,39976.29538075794 14 | 213,13,80358.3966250818,22745.972387370846 15 | 214,14,313227.5266984018,23551.04963072692 16 | 215,15,234550.7662600798,19216.914424467115 17 | 216,16,293012.8372641854,-9912.346527315487 18 | 217,17,386582.49982988625,-2654.9452654344077 19 | 218,18,-68922.11726350724,13959.630269749312 20 | 219,19,329471.2892289656,-5256.568898739658 21 | 220,20,3828501.06297316383,27023.499817300326 22 | 221,21,55155.355929427635,37523.58862659218 23 | -------------------------------------------------------------------------------- /power-grid-model-ds/data/loads.csv.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/data/nodes.csv: -------------------------------------------------------------------------------- 1 | id,u_rated,node_type,x,y 2 | 1,10500.0,0,0,200 3 | 2,10500.0,0,0,400 4 | 3,10500.0,0,0,600 5 | 4,10500.0,0,0,800 6 | 5,10500.0,0,-200,800 7 | 6,10500.0,0,-400,600 8 | 7,10500.0,0,-400,400 9 | 8,10500.0,0,-200,0 10 | 9,10500.0,0,-400,0 11 | 10,10500.0,0,-600,0 12 | 11,10500.0,0,-600,-200 13 | 12,10500.0,0,-600,-400 14 | 13,10500.0,0,-400,-400 15 | 14,10500.0,0,-200,-400 16 | 15,10500.0,0,0,-400 17 | 16,10500.0,0,0,-200 18 | 17,10500.0,0,200,0 19 | 18,10500.0,0,400,0 20 | 19,10500.0,0,600,200 21 | 20,10500.0,0,400,200 22 | 21,10500.0,0,200,200 23 | 101,10500.0,1,0,0 24 | -------------------------------------------------------------------------------- /power-grid-model-ds/data/nodes.csv.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/helper.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | import pandas as pd 6 | from power_grid_model_ds.arrays import SymLoadArray, SourceArray 7 | 8 | def load_dummy_grid(grid_class): 9 | nodes_csv = pd.read_csv("data/nodes.csv") 10 | loads_csv = pd.read_csv("data/loads.csv") 11 | lines_csv = pd.read_csv("data/lines.csv") 12 | 13 | grid = grid_class.empty() 14 | 15 | nodes = grid.node.__class__( 16 | id=nodes_csv.id.tolist(), 17 | u_rated=nodes_csv.u_rated.tolist(), 18 | node_type=nodes_csv.node_type.tolist(), 19 | x=nodes_csv.x.tolist(), 20 | y=nodes_csv.y.tolist(), 21 | u=[-1.0] * len(nodes_csv), 22 | ) 23 | loads = SymLoadArray( 24 | id=loads_csv.id.tolist(), 25 | node=loads_csv.node.tolist(), 26 | status=[1] * len(loads_csv), 27 | type=[0] * len(loads_csv), 28 | p_specified=loads_csv.p_specified.tolist(), 29 | q_specified=loads_csv.q_specified.tolist(), 30 | ) 31 | lines = grid.line.__class__( 32 | id=lines_csv.id.tolist(), 33 | from_node=lines_csv.from_node.tolist(), 34 | to_node=lines_csv.to_node.tolist(), 35 | from_status=lines_csv.from_status.tolist(), # 1 means active connection from-side, 36 | to_status=lines_csv.to_status.tolist(), # 1 means active connection to-side 37 | i_n=lines_csv.i_n.tolist(), # line max current capacity (e.g., 200 A) 38 | r1=lines_csv.r1.tolist(), # line resistance 39 | x1=lines_csv.x1.tolist(), # line reactance 40 | c1=lines_csv.c1.tolist(), # line capacitance 41 | tan1=lines_csv.tan1.tolist() # line loss tangent 42 | ) 43 | sources = SourceArray( 44 | id=[401], 45 | node=[101], 46 | status=[1], 47 | u_ref=[1.0], 48 | ) 49 | 50 | grid.append(nodes, check_max_id=False) 51 | grid.append(loads, check_max_id=False) 52 | grid.append(lines, check_max_id=False) 53 | grid.append(sources, check_max_id=False) 54 | 55 | grid.set_feeder_ids() 56 | 57 | return grid -------------------------------------------------------------------------------- /power-grid-model-ds/input_network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerGridModel/power-grid-model-workshop/240cc449c225461e0558d63915b8a0ba3925b81f/power-grid-model-ds/input_network.png -------------------------------------------------------------------------------- /power-grid-model-ds/input_network.png.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/input_network_with_overload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerGridModel/power-grid-model-workshop/240cc449c225461e0558d63915b8a0ba3925b81f/power-grid-model-ds/input_network_with_overload.png -------------------------------------------------------------------------------- /power-grid-model-ds/input_network_with_overload.png.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/introduction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "e708f200", 6 | "metadata": {}, 7 | "source": [ 8 | "# Hands-on Workshop: Extending and Modifying a Power Grid Model DS Grid\n", 9 | "\n", 10 | "## Introduction & Setup\n", 11 | "Power Grid Model DS (PGM-DS) is an open-source Python toolkit for modeling electric power grids. It extends the core power-grid-model engine with a user-friendly interface for data science applications​. In PGM-DS, a power network is represented by a Grid dataclass that manages all network components (nodes, lines, transformers, loads, etc.) and ensures their consistency​.\n", 12 | "\n", 13 | "This library provides a graph-based representation of the network and an API to modify the network structure (e.g. adding or removing equipment), which is very useful for simulation studies​. In this workshop, you will perform a series of short exercises to get hands-on experience with two key features of PGM-DS:\n", 14 | "- Extending a grid with custom properties – how to subclass PGM-DS data structures to add your own fields.\n", 15 | "- Modifying a grid object – how to build and alter a grid (add nodes, lines, etc.) programmatically.\n", 16 | "\n", 17 | "We assume you are comfortable with Python and Jupyter notebooks. Before we begin, make sure you have PGM-DS installed:\n", 18 | "\n", 19 | "```bash\n", 20 | "pip install power-grid-model-ds[visualizer]\n", 21 | "```" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "id": "86c14151", 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "!pip install 'power-grid-model-ds[visualizer]' --quiet" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "id": "fd2741c2", 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "# ⚙️ Setup\n", 42 | "import numpy as np\n", 43 | "from dataclasses import dataclass\n", 44 | "\n", 45 | "from power_grid_model_ds import Grid\n", 46 | "from power_grid_model_ds.arrays import NodeArray, LineArray, SymLoadArray, SourceArray\n", 47 | "from power_grid_model_ds.enums import NodeType\n", 48 | "\n", 49 | "from numpy.typing import NDArray" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "id": "b53605bc", 55 | "metadata": {}, 56 | "source": [ 57 | "## Exercise 1: Extending the Grid with Custom Properties\n", 58 | "Context: By default, the PGM-DS Grid includes standard attributes for each component (like node voltage ratings, line impedances, etc.). However, certain project-specific data (for example, simulation results or custom labels) are not included out of the box​. PGM-DS allows you to extend the grid data model by subclassing its array classes to add new columns. In other words, you can inherit the existing functionality and add your own fields​.\n", 59 | "\n", 60 | "In this exercise, we will extend the grid to include an output voltage u for nodes and an output current i_from for lines, which are not present in the basic grid.\n", 61 | "\n", 62 | "### Step 1: Define Extended Node and Line arrays\n", 63 | "We create subclasses of NodeArray and LineArray that include the new properties. We'll call them MyNodeArray and MyLineArray. Each subclass defines a class attribute for the new column and (optionally) a default value for that column via a _defaults dictionary.\n", 64 | "\n", 65 | "**⚙️ Assignment**: Create two Array extensions to hold the x, y, u (extending NodeArray) and i_from (extending LineArray) attributes\n", 66 | "\n", 67 | "**💡 Hint**: https://power-grid-model-ds.readthedocs.io/en/stable/examples/model/grid_extensions_examples.html" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "id": "7cfe11a7-80ed-4fe9-8c7f-0daf0ec1c27a", 74 | "metadata": {}, 75 | "outputs": [], 76 | "source": [ 77 | "# Build your solution here..." 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "id": "6ada3e60-8bd7-4687-9f29-3ae5ff029228", 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [ 87 | "# %load solutions/introduction_1_1_define_array_extensions" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "id": "8ef5b9fa", 93 | "metadata": {}, 94 | "source": [ 95 | "### Step 2: Create an Extended Grid class\n", 96 | "Now we'll integrate these new arrays into a custom Grid class. We do this by subclassing the PGM-DS Grid and specifying that our grid should use MyNodeArray and MyLineArray instead of the default NodeArray and LineArray. We'll use Python's dataclass to define the new Grid schema:\n", 97 | "\n", 98 | "**⚙️ Assignment**: Create a new grid class that uses the extended arrays.\n", 99 | "\n", 100 | "**💡 Hint**: https://power-grid-model-ds.readthedocs.io/en/stable/examples/model/grid_extensions_examples.html#adding-the-new-arrays-to-the-grid\n", 101 | "\n", 102 | "**💡 Hint**: Make sure to add the `@dataclass` decorator to your grid." 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "id": "de236a67-1632-4f81-9145-7a904c63746b", 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "# Build your solution here..." 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": null, 118 | "id": "a7512d0d-5d85-44b6-b42e-11be0fa6802a", 119 | "metadata": {}, 120 | "outputs": [], 121 | "source": [ 122 | "# %load solutions/introduction_1_2_define_my_grid" 123 | ] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "id": "75264a22", 128 | "metadata": {}, 129 | "source": [ 130 | "This ExtendedGrid class inherits all the behavior of Grid but with our extended node and line definitions​. Essentially, we've informed the Grid that whenever it creates or manipulates the node or line arrays, it should use the extended versions that include the extra columns." 131 | ] 132 | }, 133 | { 134 | "cell_type": "markdown", 135 | "id": "430f88b5", 136 | "metadata": {}, 137 | "source": [ 138 | "### Step 3: Initialize an Extended Grid\n", 139 | "With the classes defined, let's create an instance of our extended grid. PGM-DS provides a convenient class method Grid.empty() to initialize an empty grid. We'll call this on our ExtendedGrid:\n", 140 | "\n", 141 | "**⚙️ Assignment**: Instantiate an empty extended grid\n", 142 | "\n", 143 | "**💡 Hint**: https://power-grid-model-ds.readthedocs.io/en/stable/examples/model/grid_examples.html#creating-an-empty-grid" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": null, 149 | "id": "c6040e5b", 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [ 153 | "# Build your solution here..." 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": null, 159 | "id": "8d5464bf-5689-4919-a85b-b6548b475537", 160 | "metadata": {}, 161 | "outputs": [], 162 | "source": [ 163 | "# %load solutions/introduction_1_3_grid_empty" 164 | ] 165 | }, 166 | { 167 | "cell_type": "markdown", 168 | "id": "06983bc4", 169 | "metadata": {}, 170 | "source": [ 171 | "Verification: To ensure our extended properties exist, you can access the new attributes:\n", 172 | "\n", 173 | "**⚙️ Assignment**: Print some information about the grid.\n", 174 | "\n", 175 | "**💡 Hint**: Be creative! You can use the grid's attributes and methods to get information about the grid. Using `print()` on an array will format it for better readability." 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": null, 181 | "id": "ee1afd73", 182 | "metadata": {}, 183 | "outputs": [], 184 | "source": [ 185 | "# Build your solution here..." 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": null, 191 | "id": "b4d131c6-094a-49a2-a283-5bdbd05e961d", 192 | "metadata": {}, 193 | "outputs": [], 194 | "source": [ 195 | "# %load solutions/introduction_1_3_grid_verification" 196 | ] 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "id": "7eb8b79f", 201 | "metadata": {}, 202 | "source": [ 203 | "Since we haven't added any nodes or lines yet, these arrays are empty" 204 | ] 205 | }, 206 | { 207 | "cell_type": "markdown", 208 | "id": "c0b7b9e7", 209 | "metadata": {}, 210 | "source": [ 211 | "## Exercise 2: Building and Modifying the Grid Structure\n", 212 | "\n", 213 | "Context: The Grid object allows you to add, remove, and manipulate grid components in-memory. We will now construct a simple network step-by-step using our ext_grid. This will demonstrate how to modify a grid object by adding nodes and branches. In practice, you can start with an empty grid and programmatically add substations, lines, transformers, loads, sources, etc., as needed​. We will create a minimal example with two nodes (a source node and a load node) connected by a line, and verify that the grid is updated accordingly. (For brevity, we'll use a very small grid, but the same methods apply to larger networks.)\n", 214 | "\n", 215 | "Note: PGM-DS typically distinguishes node types (e.g., substation vs. regular node) and requires unique IDs for each element. We will manually specify IDs for clarity. The library’s Grid.append() method will be used to add new component records to the grid​.\n" 216 | ] 217 | }, 218 | { 219 | "cell_type": "markdown", 220 | "id": "6b2d3489", 221 | "metadata": {}, 222 | "source": [ 223 | "### Step 1: Add a substation node\n", 224 | "First, let's add a substation node to the grid. We create an MyNodeArray with one entry representing the substation. We need to provide at least an id, a rated voltage (u_rated), and a node type.\n", 225 | "We will use the enum NodeType.SUBSTATION_NODE for the type.\n", 226 | "In this example, we will assign the substation an ID of 101 and a rated voltage of 10500.0 (which could represent 10.5 kV):\n", 227 | "\n", 228 | "**⚙️ Assignment**: Add a substation to the grid.\n", 229 | "\n", 230 | "**💡 Hint**: https://power-grid-model-ds.readthedocs.io/en/stable/examples/model/grid_examples.html#adding-substations" 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": null, 236 | "id": "c03c85f4", 237 | "metadata": {}, 238 | "outputs": [], 239 | "source": [ 240 | "# Build your solution here..." 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": null, 246 | "id": "5c641c07-1cc7-44bc-87ff-49bed38e7d5c", 247 | "metadata": {}, 248 | "outputs": [], 249 | "source": [ 250 | "# %load solutions/introduction_2_1_add_substation" 251 | ] 252 | }, 253 | { 254 | "cell_type": "markdown", 255 | "id": "91ca3ea2", 256 | "metadata": {}, 257 | "source": [ 258 | "Here we constructed a MyNodeArray with one record and then appended it to grid. We set check_max_id=False to disable internal ID checks since we're manually managing IDs in this exercise. After running this, the grid now contains one node. Verification: Check that the node was added. For example:" 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "execution_count": null, 264 | "id": "8695367f", 265 | "metadata": {}, 266 | "outputs": [], 267 | "source": [ 268 | "print(\"Nodes in the grid:\")\n", 269 | "print(grid.node)" 270 | ] 271 | }, 272 | { 273 | "cell_type": "markdown", 274 | "id": "88cd0bb5", 275 | "metadata": {}, 276 | "source": [ 277 | "### Step 2: Add a second node\n", 278 | "Next, we'll add another node to represent a load or another bus in the grid. This node will be of a generic type (we'll use NodeType.UNSPECIFIED, which equals 0 in the enum). We'll give it an ID of 102 and the same base voltage (10.5 kV).\n", 279 | "\n", 280 | "**⚙️ Assignment**: Add a node to the grid. " 281 | ] 282 | }, 283 | { 284 | "cell_type": "code", 285 | "execution_count": null, 286 | "id": "780b1c35", 287 | "metadata": {}, 288 | "outputs": [], 289 | "source": [ 290 | "# Build your solution here..." 291 | ] 292 | }, 293 | { 294 | "cell_type": "code", 295 | "execution_count": null, 296 | "id": "3f987c7c-ea3b-472c-b9f9-f379a478bc4e", 297 | "metadata": {}, 298 | "outputs": [], 299 | "source": [ 300 | "# %load solutions/introduction_2_2_add_node" 301 | ] 302 | }, 303 | { 304 | "cell_type": "code", 305 | "execution_count": null, 306 | "id": "3791cef2", 307 | "metadata": {}, 308 | "outputs": [], 309 | "source": [ 310 | "print(\"Nodes in the grid:\")\n", 311 | "print(grid.node)" 312 | ] 313 | }, 314 | { 315 | "cell_type": "markdown", 316 | "id": "a6f17195", 317 | "metadata": {}, 318 | "source": [ 319 | "### Step 3: Add a line connecting the two nodes\n", 320 | "Now that we have two nodes, we will connect them with a line. We'll use our MyLineArray to create a single line record. We need to specify an ID for the line (let's use 201), the from_node and to_node it connects (101 to 102), and statuses to indicate the line is active. We should also provide line electrical parameters (resistance, reactance, etc.) – we'll use some placeholder values here for demonstration:\n", 321 | "\n", 322 | "**⚙️ Assignment**: Add a line to the grid.\n", 323 | "\n", 324 | "**💡 Hint**: https://power-grid-model-ds.readthedocs.io/en/stable/examples/model/grid_examples.html#adding-lines" 325 | ] 326 | }, 327 | { 328 | "cell_type": "code", 329 | "execution_count": null, 330 | "id": "b4baa9b6", 331 | "metadata": {}, 332 | "outputs": [], 333 | "source": [ 334 | "# Build your solution here..." 335 | ] 336 | }, 337 | { 338 | "cell_type": "code", 339 | "execution_count": null, 340 | "id": "0211f7ec-0890-4880-b78a-65610e1eaf10", 341 | "metadata": {}, 342 | "outputs": [], 343 | "source": [ 344 | "# %load solutions/introduction_2_3_add_line" 345 | ] 346 | }, 347 | { 348 | "cell_type": "markdown", 349 | "id": "fa1e3f2f", 350 | "metadata": {}, 351 | "source": [ 352 | "This adds a line (ID 201) connecting node 101 to 102. We marked the line as active by setting both from_status and to_status to 1. We also provided some dummy impedance values. The approach of constructing a LineArray (or in our case, MyLineArray) with the necessary fields and appending it to the grid is shown in the official examples​. Verification: Check that the line was added to the grid:" 353 | ] 354 | }, 355 | { 356 | "cell_type": "code", 357 | "execution_count": null, 358 | "id": "e2914592", 359 | "metadata": {}, 360 | "outputs": [], 361 | "source": [ 362 | "print(grid.line)" 363 | ] 364 | }, 365 | { 366 | "cell_type": "markdown", 367 | "id": "f7a5bc49", 368 | "metadata": {}, 369 | "source": [ 370 | "### Step 4: Add a load to the second node\n", 371 | "We'll now add a load connected to node 102. PGM-DS uses a SymLoadArray for symmetrical loads. We will create a single load with an ID of 401 at node 102. We need to specify the node it is attached to, a load type code (we'll use 1 for a basic load type), the specified active (p_specified) and reactive (q_specified) power (let's say 1e6 each, representing 1 MW and 1 Mvar for example), and set its status to active (1):\n", 372 | "\n", 373 | "**⚙️ Assignment**: Add a load to the grid.\n", 374 | "\n", 375 | "**💡 Hint**: https://power-grid-model-ds.readthedocs.io/en/stable/examples/model/grid_examples.html#adding-loads" 376 | ] 377 | }, 378 | { 379 | "cell_type": "code", 380 | "execution_count": null, 381 | "id": "ddd3445c", 382 | "metadata": {}, 383 | "outputs": [], 384 | "source": [ 385 | "# Build your solution here..." 386 | ] 387 | }, 388 | { 389 | "cell_type": "code", 390 | "execution_count": null, 391 | "id": "5499a30c-8820-4066-a322-ee03d5cfb8cf", 392 | "metadata": {}, 393 | "outputs": [], 394 | "source": [ 395 | "# %load solutions/introduction_2_4_add_load" 396 | ] 397 | }, 398 | { 399 | "cell_type": "markdown", 400 | "id": "972efb50", 401 | "metadata": {}, 402 | "source": [ 403 | "This adds one load to node 102. In practice, adding loads ensures that node 102 will be consuming power in any simulation. Verification: Check that the load appears in the grid:" 404 | ] 405 | }, 406 | { 407 | "cell_type": "code", 408 | "execution_count": null, 409 | "id": "a6e900eb", 410 | "metadata": {}, 411 | "outputs": [], 412 | "source": [ 413 | "print(grid.sym_load)" 414 | ] 415 | }, 416 | { 417 | "cell_type": "markdown", 418 | "id": "da53ba14", 419 | "metadata": {}, 420 | "source": [ 421 | "### Step 5: Add a source to the substation node\n", 422 | "Finally, we'll add a power source to supply the grid at the substation (node 101). We'll use SourceArray for this. We'll create a source with ID 501 at node 101, status active (1), and set a reference voltage u_ref. Typically, u_ref might be the slack/reference voltage magnitude or angle; we'll use 0.0 as a reference angle (assuming the default usage):\n", 423 | "\n", 424 | "**⚙️ Assignment**: Add a source to the grid.\n", 425 | "\n", 426 | "**💡 Hint**: https://power-grid-model-ds.readthedocs.io/en/stable/examples/model/grid_examples.html#adding-a-source" 427 | ] 428 | }, 429 | { 430 | "cell_type": "code", 431 | "execution_count": null, 432 | "id": "e741dec4", 433 | "metadata": {}, 434 | "outputs": [], 435 | "source": [ 436 | "# Build your solution here..." 437 | ] 438 | }, 439 | { 440 | "cell_type": "code", 441 | "execution_count": null, 442 | "id": "1ab72aec-acc5-42ac-b429-18f608d6bd38", 443 | "metadata": {}, 444 | "outputs": [], 445 | "source": [ 446 | "# %load solutions/introduction_2_5_add_source" 447 | ] 448 | }, 449 | { 450 | "cell_type": "markdown", 451 | "id": "bda9752e", 452 | "metadata": {}, 453 | "source": [ 454 | "This adds a source (e.g., a generator or slack source) at node 101, so the grid now has a supply. Verification: Check the source:" 455 | ] 456 | }, 457 | { 458 | "cell_type": "code", 459 | "execution_count": null, 460 | "id": "3047ecf6", 461 | "metadata": {}, 462 | "outputs": [], 463 | "source": [ 464 | "print(grid.source)" 465 | ] 466 | }, 467 | { 468 | "cell_type": "markdown", 469 | "id": "e5e6eb1b", 470 | "metadata": {}, 471 | "source": [ 472 | "You should see [501] as the source ID and [101] as the node, indicating the source is at node 101. The count of sources should be 1. Now we have built a simple grid with 2 nodes (101 and 102), 1 line connecting them, 1 load at node 102, and 1 source at node 101. It's good practice to ensure all IDs are unique and there are no inconsistencies. PGM-DS provides a method grid.check_ids() to validate this.\n", 473 | "\n", 474 | "**⚙️ Assignment**: Check whether all IDs are correct.\n", 475 | "\n", 476 | "**💡 Hint**: The grid has a method for that!" 477 | ] 478 | }, 479 | { 480 | "cell_type": "code", 481 | "execution_count": null, 482 | "id": "b756a4d5", 483 | "metadata": {}, 484 | "outputs": [], 485 | "source": [ 486 | "# Build your solution here..." 487 | ] 488 | }, 489 | { 490 | "cell_type": "code", 491 | "execution_count": null, 492 | "id": "403f00f3-d0bd-497a-8b93-8eaba629d68b", 493 | "metadata": {}, 494 | "outputs": [], 495 | "source": [ 496 | "# %load solutions/introduction_2_6_check_ids" 497 | ] 498 | }, 499 | { 500 | "cell_type": "markdown", 501 | "id": "26f831f3", 502 | "metadata": {}, 503 | "source": [ 504 | "If everything is correct, this should execute without errors (it will raise an exception if any duplicate or conflicting IDs were found). We expect no issues since we chose unique IDs for each element type. For a final summary, let's print out the contents of our grid's key components:" 505 | ] 506 | }, 507 | { 508 | "cell_type": "code", 509 | "execution_count": null, 510 | "id": "c94ab378", 511 | "metadata": {}, 512 | "outputs": [], 513 | "source": [ 514 | "print(\"Nodes:\", grid.node.id) # Expect [101 102]\n", 515 | "print(\"Lines:\", grid.line.id) # Expect [201]\n", 516 | "print(\"Loads:\", grid.sym_load.id) # Expect [401]\n", 517 | "print(\"Sources:\", grid.source.id) # Expect [501]" 518 | ] 519 | }, 520 | { 521 | "cell_type": "markdown", 522 | "id": "a1abb080", 523 | "metadata": {}, 524 | "source": [ 525 | "Now we can visualize the resulting network" 526 | ] 527 | }, 528 | { 529 | "cell_type": "code", 530 | "execution_count": null, 531 | "id": "49c3e647", 532 | "metadata": {}, 533 | "outputs": [], 534 | "source": [ 535 | "from power_grid_model_ds.visualizer import visualize\n", 536 | "\n", 537 | "visualize(grid)" 538 | ] 539 | } 540 | ], 541 | "metadata": { 542 | "kernelspec": { 543 | "display_name": ".venv", 544 | "language": "python", 545 | "name": "python3" 546 | }, 547 | "language_info": { 548 | "codemirror_mode": { 549 | "name": "ipython", 550 | "version": 3 551 | }, 552 | "file_extension": ".py", 553 | "mimetype": "text/x-python", 554 | "name": "python", 555 | "nbconvert_exporter": "python", 556 | "pygments_lexer": "ipython3", 557 | "version": "3.12.6" 558 | } 559 | }, 560 | "nbformat": 4, 561 | "nbformat_minor": 5 562 | } 563 | -------------------------------------------------------------------------------- /power-grid-model-ds/introduction.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/advanced_3_check_for_capacity_issues.py: -------------------------------------------------------------------------------- 1 | from power_grid_model_ds import PowerGridModelInterface 2 | 3 | def check_for_capacity_issues(grid: Grid) -> LineArray: 4 | """Check for capacity issues on the grid. 5 | Return the lines that with capacity issues. 6 | """ 7 | pgm_interface = PowerGridModelInterface(grid) 8 | pgm_interface.calculate_power_flow() 9 | pgm_interface.update_grid() 10 | 11 | return grid.line[grid.line.is_overloaded] 12 | 13 | print(check_for_capacity_issues(grid)) 14 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/advanced_3_check_for_capacity_issues.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/advanced_4_build_new_substation.py: -------------------------------------------------------------------------------- 1 | def build_new_substation(grid: Grid, location: tuple[float, float]) -> NodeArray: 2 | """Build a new substation at the given location. 3 | Return the new substation. 4 | """ 5 | new_substation = MyNodeArray( 6 | node_type=[NodeType.SUBSTATION_NODE.value], 7 | u_rated=[10500.0], 8 | x=[location[0]], 9 | y=[location[1]] 10 | ) 11 | grid.append(new_substation) 12 | 13 | new_source = SourceArray( 14 | node=[new_substation.id.item()], 15 | status=[1], 16 | u_ref=[1], 17 | ) 18 | grid.append(new_source) 19 | return new_substation 20 | 21 | # 🏗️ Add a new substation at (400, 400) 22 | new_substation = build_new_substation(grid, (400, 400)) -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/advanced_4_build_new_substation.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/advanced_5_1_get_all_congested_routes.py: -------------------------------------------------------------------------------- 1 | def get_all_congested_routes(grid: Grid) -> list[NodeArray]: 2 | """Get all nodes on routes that contain an overloaded line.""" 3 | grid.set_feeder_ids() 4 | lines_with_congestion = check_for_capacity_issues(grid) 5 | feeder_branch_ids_with_congestion = np.unique(lines_with_congestion['feeder_branch_id']) 6 | return [grid.node.filter(feeder_branch_id=branch_id) for branch_id in feeder_branch_ids_with_congestion] 7 | 8 | congested_routes = get_all_congested_routes(grid) -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/advanced_5_1_get_all_congested_routes.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/advanced_5_2_find_connection_point.py: -------------------------------------------------------------------------------- 1 | def find_connection_point(route: NodeArray, new_substation: NodeArray) -> NodeArray: 2 | """Calculate the connection point for the new route. 3 | This should be the geographically closest node to the new substation. 4 | """ 5 | x_difference = route.x - new_substation.x 6 | y_difference = route.y - new_substation.y 7 | distances = (x_difference**2 + y_difference**2) ** 0.5 8 | 9 | idx_closest_node = np.argmin(distances) 10 | closest_node = route[idx_closest_node] 11 | return closest_node -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/advanced_5_2_find_connection_point.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/advanced_5_3_connect_to_route.py: -------------------------------------------------------------------------------- 1 | def connect_to_route(grid: Grid, connection_point: NodeArray, new_substation: NodeArray) -> None: 2 | """Connect the new substation node to the connection point. 3 | """ 4 | # Create a new line that connects the two nodes 5 | new_line = MyLineArray( 6 | from_node=[new_substation.id], 7 | to_node=[connection_point.id], 8 | from_status=[0], # status is 0 to make sure the line is not active 9 | to_status=[1], 10 | i_n=[360.0], 11 | r1=[0.05], x1=[0.01], c1=[0.0], tan1=[0.0] 12 | ) 13 | grid.append(new_line) 14 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/advanced_5_3_connect_to_route.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/advanced_6_optimize_route_transfer.py: -------------------------------------------------------------------------------- 1 | def optimize_route_transfer(grid: Grid, connection_point: NodeArray, new_substation: NodeArray) -> None: 2 | """Attempt to optimize the route transfer moving the naturally open point (NOP) upstream towards the old substation. 3 | This way, the new substation will take over more nodes of the original route. 4 | """ 5 | old_substation_node_id = connection_point.feeder_node_id.item() 6 | path, _ = grid.graphs.active_graph.get_shortest_path(connection_point.id.item(), old_substation_node_id) 7 | print("Path from overload to old substation:", path) 8 | 9 | current_branch = grid.line.filter( 10 | from_node=[connection_point.id, new_substation.id], 11 | to_node=[connection_point.id, new_substation.id] 12 | ) 13 | for from_node, to_node in zip(path[0:-1], path[1:]): 14 | # Check if the route is still overloaded 15 | capacity_issues = check_for_capacity_issues(grid) 16 | route_capacity_issues = capacity_issues.filter(feeder_branch_id=connection_point.feeder_branch_id) 17 | if not any(route_capacity_issues): 18 | break 19 | 20 | grid.make_active(current_branch) 21 | current_branch = grid.line.filter(from_node=[from_node, to_node], to_node=[from_node, to_node]) 22 | grid.make_inactive(current_branch) 23 | 24 | grid.set_feeder_ids() -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/advanced_6_optimize_route_transfer.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_1_1_define_array_extensions.py: -------------------------------------------------------------------------------- 1 | class MyNodeArray(NodeArray): 2 | _defaults = {"x": 0.0, "y": 0.0, "u": 0.0} 3 | x: NDArray[np.float64] 4 | y: NDArray[np.float64] 5 | u: NDArray[np.float64] 6 | 7 | class MyLineArray(LineArray): 8 | _defaults = {"i_from": 0.0} 9 | i_from: NDArray[np.float64] -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_1_1_define_array_extensions.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_1_2_define_my_grid.py: -------------------------------------------------------------------------------- 1 | @dataclass 2 | class ExtendedGrid(Grid): 3 | node: MyNodeArray 4 | line: MyLineArray -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_1_2_define_my_grid.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_1_3_grid_empty.py: -------------------------------------------------------------------------------- 1 | grid = ExtendedGrid.empty() 2 | grid -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_1_3_grid_empty.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_1_3_grid_verification.py: -------------------------------------------------------------------------------- 1 | print("Node 'u' field exists?", hasattr(grid.node, "u")) 2 | print("Line 'i_from' field exists?", hasattr(grid.line, "i_from")) 3 | print("Node array:", grid.node) -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_1_3_grid_verification.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_2_1_add_substation.py: -------------------------------------------------------------------------------- 1 | # Create a substation node entry 2 | substation_node = MyNodeArray( 3 | id=[101], 4 | u_rated=[10500.0], # e.g., 10.5 kV base voltage 5 | node_type=[NodeType.SUBSTATION_NODE.value] # type = 1 (Substation node) 6 | ) 7 | # Append this node to the grid 8 | grid.append(substation_node, check_max_id=False) -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_2_1_add_substation.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_2_2_add_node.py: -------------------------------------------------------------------------------- 1 | # Create another node 2 | load_node = MyNodeArray( 3 | id=[102], 4 | u_rated=[10500.0], 5 | node_type=[NodeType.UNSPECIFIED.value] 6 | ) 7 | grid.append(load_node, check_max_id=False) -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_2_2_add_node.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_2_3_add_line.py: -------------------------------------------------------------------------------- 1 | # Create a line between node 101 and 102 2 | new_line = MyLineArray( 3 | id=[201], 4 | from_node=[101], 5 | to_node=[102], 6 | from_status=[1], # 1 = active from-side, 7 | to_status=[1], # 1 = active to-side (both ends active) 8 | i_n=[200.0], # line current capacity (e.g., 200 A) 9 | r1=[0.1], x1=[0.03], # line resistance and reactance 10 | c1=[0.0], tan1=[0.0] # line capacitance and loss tangent (0 for simplicity) 11 | ) 12 | grid.append(new_line, check_max_id=False) -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_2_3_add_line.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_2_4_add_load.py: -------------------------------------------------------------------------------- 1 | # Create a load at node 102 2 | load = SymLoadArray( 3 | id=[401], 4 | node=[102], 5 | type=[1], # load type (e.g., 1 for constant power load) 6 | p_specified=[1_000_000.0], # 1e6 W = 1 MW 7 | q_specified=[1_000_000.0], # 1e6 VAR 8 | status=[1] # active 9 | ) 10 | grid.append(load, check_max_id=False) -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_2_4_add_load.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_2_5_add_source.py: -------------------------------------------------------------------------------- 1 | # Create a source at node 101 2 | source = SourceArray( 3 | id=[501], 4 | node=[101], 5 | status=[1], 6 | u_ref=[1.0] 7 | ) 8 | grid.append(source, check_max_id=False) -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_2_5_add_source.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_2_6_check_ids.py: -------------------------------------------------------------------------------- 1 | grid.check_ids() -------------------------------------------------------------------------------- /power-grid-model-ds/solutions/introduction_2_6_check_ids.py.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | # 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | power-grid-model[doc] >= 1.9 6 | jupyter 7 | matplotlib 8 | -------------------------------------------------------------------------------- /state-estimation-assignment/State Estimation Assignment with Solutions.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | -------------------------------------------------------------------------------- /state-estimation-assignment/State Estimation Assignment.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "id": "a727ce38", 7 | "metadata": {}, 8 | "source": [ 9 | "# Introduction\n", 10 | "\n", 11 | "In this assignment you will be given a series of tasks about using the library `power-grid-model` and performing a\n", 12 | "**state estimation**. In this assignment we will use the output data of a power flow calculation as input for our sensors\n", 13 | "so we can perform a state estimation. The tasks include:\n", 14 | "\n", 15 | "* Think about the number of sensors needed\n", 16 | "* Print the required loadflow output\n", 17 | "* Initializing only the required sensors and performing a state estimation\n", 18 | "* Comparing the state estimation results to the loadflow output\n", 19 | "* Adding more sensors to make the calculation more accurate\n", 20 | "\n", 21 | "The network we'll be using for troughout this workshop consists of three nodes, two lines, two loads and a source as shown below:\n", 22 | "\n", 23 | "```\n", 24 | " node_1 ------ node_2 ------ node_3\n", 25 | " | | |\n", 26 | "source_8 load_6 load_7\n", 27 | "```" 28 | ] 29 | }, 30 | { 31 | "attachments": {}, 32 | "cell_type": "markdown", 33 | "id": "9cd12445", 34 | "metadata": {}, 35 | "source": [ 36 | "# Preparation\n", 37 | "\n", 38 | "First import everything we need for this workshop:" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": null, 44 | "id": "2bc7de1e", 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "from typing import Dict\n", 49 | "\n", 50 | "import numpy as np\n", 51 | "import pandas as pd\n", 52 | "\n", 53 | "from IPython.display import display, Markdown\n", 54 | "\n", 55 | "from power_grid_model import (\n", 56 | " PowerGridModel,\n", 57 | " CalculationType,\n", 58 | " CalculationMethod,\n", 59 | " ComponentType,\n", 60 | " DatasetType,\n", 61 | " LoadGenType,\n", 62 | " initialize_array,\n", 63 | " \n", 64 | ")\n", 65 | "\n", 66 | "from power_grid_model.validation import (\n", 67 | " assert_valid_input_data,\n", 68 | " assert_valid_batch_data\n", 69 | ")" 70 | ] 71 | }, 72 | { 73 | "attachments": {}, 74 | "cell_type": "markdown", 75 | "id": "50065790", 76 | "metadata": {}, 77 | "source": [ 78 | "## Generate example data\n", 79 | "\n", 80 | "In order to use realistic data in out workshop, we use the results of a power flow calculation, which gives us voltages, voltage angles, active powers, reactive powers, etc. Below all components of the network are initialized, a model is composed and a power flow calculation is executed." 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": null, 86 | "id": "760a38b1", 87 | "metadata": {}, 88 | "outputs": [], 89 | "source": [ 90 | "# Initialize 3 nodes\n", 91 | "node = initialize_array(DatasetType.input, ComponentType.node, 3)\n", 92 | "node[\"id\"] = [1, 2, 3]\n", 93 | "node[\"u_rated\"] = [10.5e3, 10.5e3, 10.5e3]\n", 94 | "\n", 95 | "# Initialize 2 lines between the 3 nodes\n", 96 | "line = initialize_array(DatasetType.input, ComponentType.line, 2)\n", 97 | "line[\"id\"] = [4, 5]\n", 98 | "line[\"from_node\"] = [1, 2]\n", 99 | "line[\"to_node\"] = [2, 3]\n", 100 | "line[\"from_status\"] = [1, 1]\n", 101 | "line[\"to_status\"] = [1, 1]\n", 102 | "line[\"r1\"] = [0.25, 0.25]\n", 103 | "line[\"x1\"] = [0.2, 0.2]\n", 104 | "line[\"c1\"] = [10e-6, 10e-6]\n", 105 | "line[\"tan1\"] = [0.0, 0.0]\n", 106 | "line[\"i_n\"] = [1000, 1000]\n", 107 | "\n", 108 | "# Initialize 2 loads, each connected to a different node\n", 109 | "sym_load = initialize_array(DatasetType.input, ComponentType.sym_load, 2)\n", 110 | "sym_load[\"id\"] = [6, 7]\n", 111 | "sym_load[\"node\"] = [2, 3]\n", 112 | "sym_load[\"status\"] = [1, 1]\n", 113 | "sym_load[\"type\"] = [LoadGenType.const_power, LoadGenType.const_power]\n", 114 | "sym_load[\"p_specified\"] = [20e6, 10e6]\n", 115 | "sym_load[\"q_specified\"] = [5e6, 2e6]\n", 116 | "\n", 117 | "# Initialize 1 source, connected to a different node than the loads\n", 118 | "source = initialize_array(DatasetType.input, ComponentType.source, 1)\n", 119 | "source[\"id\"] = [8]\n", 120 | "source[\"node\"] = [1]\n", 121 | "source[\"status\"] = [1]\n", 122 | "source[\"u_ref\"] = [1.0]\n", 123 | "\n", 124 | "# Construct the input data\n", 125 | "input_data = {\n", 126 | " ComponentType.node: node,\n", 127 | " ComponentType.line: line,\n", 128 | " ComponentType.sym_load: sym_load,\n", 129 | " ComponentType.source: source\n", 130 | "}\n", 131 | "\n", 132 | "# Validate the input data\n", 133 | "assert_valid_input_data(input_data)\n", 134 | "\n", 135 | "# Create a power grid model\n", 136 | "model = PowerGridModel(input_data)\n", 137 | "\n", 138 | "# Run a (Newton Raphson) power flow calculation\n", 139 | "pf_output_data = model.calculate_power_flow(\n", 140 | " symmetric=True, \n", 141 | " error_tolerance=1e-8, \n", 142 | " max_iterations=20, \n", 143 | " calculation_method=CalculationMethod.newton_raphson\n", 144 | ")" 145 | ] 146 | }, 147 | { 148 | "attachments": {}, 149 | "cell_type": "markdown", 150 | "id": "0c2bad59", 151 | "metadata": {}, 152 | "source": [ 153 | "### View example data" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": null, 159 | "id": "02130221", 160 | "metadata": {}, 161 | "outputs": [], 162 | "source": [ 163 | "# Display the results\n", 164 | "for component, data in pf_output_data.items():\n", 165 | " display(Markdown(f\"### {component.title()}s (power flow):\"))\n", 166 | " display(pd.DataFrame(data))" 167 | ] 168 | }, 169 | { 170 | "attachments": {}, 171 | "cell_type": "markdown", 172 | "id": "0234cab7", 173 | "metadata": {}, 174 | "source": [ 175 | "# Assignment 1: Number of sensors\n", 176 | "\n", 177 | "In order to perform a state estimation some voltage and power sensors need to be added to the model.\n", 178 | "For the calculation to be successful a minumum number of sensors is required.\n", 179 | "\n", 180 | "- What is the minimum number of sensors to perform a state estimation on the given network?\n", 181 | "- How many of those should be voltage sensors?\n" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": null, 187 | "id": "2baee3cd", 188 | "metadata": {}, 189 | "outputs": [], 190 | "source": [ 191 | "# Fill in the minimal required number of sensors below\n", 192 | "n_sensors = ...\n", 193 | "n_voltage_sensors = ...\n" 194 | ] 195 | }, 196 | { 197 | "attachments": {}, 198 | "cell_type": "markdown", 199 | "id": "940d48be", 200 | "metadata": {}, 201 | "source": [ 202 | "# Assignment 2: Collecting voltage sensor data\n", 203 | "\n", 204 | "In this assignment print the output of the assets in the the loadflow calculation, which is needed as input for the voltage sensors. \n", 205 | "\n", 206 | "Hint: convert to a pandas DataFrame before printing for a better overview" 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": null, 212 | "id": "71b39571", 213 | "metadata": {}, 214 | "outputs": [], 215 | "source": [ 216 | "# Print the assets in the output data that we need for the voltage sensors\n", 217 | "display(pd.DataFrame(pf_output_data[ComponentType.node]))" 218 | ] 219 | }, 220 | { 221 | "attachments": {}, 222 | "cell_type": "markdown", 223 | "id": "2b013fa7", 224 | "metadata": {}, 225 | "source": [ 226 | "# Assignment 3: Initialize the sensors\n", 227 | "\n", 228 | "In this assignment we will perform a state estimation based on three voltage sensors that only measure the voltage. \n", 229 | "If you look closely to the data, you'll notice that the number of measurements (3) is not larger than or equal to the number of unknowns (6). \n", 230 | "So the system is not *fully observable* and you might expect the state estimation to fail. \n", 231 | "However, the linear state estimation algorithm will assume the voltage angles (3) to be zero if no value is given. \n", 232 | "In other words, the mathematical core will give us a faulty result, without any warning! \n", 233 | "To prevent this, we need an observability check, which is complex, but will be added to the validation functions in the future.\n", 234 | "\n", 235 | "- initialize the voltage sensors\n", 236 | "- extend the input data set, with the voltage sensors\n", 237 | "- construct a new model with the new input data\n", 238 | "- run the state estimation calculation" 239 | ] 240 | }, 241 | { 242 | "cell_type": "code", 243 | "execution_count": null, 244 | "id": "88034903", 245 | "metadata": {}, 246 | "outputs": [], 247 | "source": [ 248 | "# TODO: Initialize 3 symmetric voltage sensors, each connected to a different node\n", 249 | "sym_voltage_sensor = initialize_array(..., ..., ...)\n", 250 | "sym_voltage_sensor[\"id\"] = ...\n", 251 | "sym_voltage_sensor[\"measured_object\"] = ...\n", 252 | "sym_voltage_sensor[\"u_sigma\"] = 10.0\n", 253 | "sym_voltage_sensor[\"u_measured\"] = ...\n", 254 | "\n", 255 | "# TODO: Add the sensors to the input data\n", 256 | "input_data[...] = sym_voltage_sensor\n", 257 | "\n", 258 | "# TODO: Validate the input data\n", 259 | "assert_valid_input_data(..., calculation_type=..., symmetric=...)\n", 260 | "\n", 261 | "# Create a power grid model\n", 262 | "model = PowerGridModel(input_data)\n", 263 | "\n", 264 | "# Run the (iterative linear) state estimation\n", 265 | "se_output_data = model.calculate_state_estimation(\n", 266 | " symmetric=True, \n", 267 | " error_tolerance=1e-8, \n", 268 | " max_iterations=20, \n", 269 | " calculation_method=CalculationMethod.iterative_linear)" 270 | ] 271 | }, 272 | { 273 | "attachments": {}, 274 | "cell_type": "markdown", 275 | "id": "f4736cc1", 276 | "metadata": {}, 277 | "source": [ 278 | "# Assignment 4: Compare the results between the loadflow and state estimation\n", 279 | "\n", 280 | "For all nodes:\n", 281 | "- print the difference in `u` between `se_output_data` and `pf_output_data`\n", 282 | "\n", 283 | "For all lines:\n", 284 | "- print the difference in `p_from` between `se_output_data` and `pf_output_data`\n", 285 | "- print the difference in `p_to` between `se_output_data` and `pf_output_data`\n", 286 | "- print the difference in `q_from` between `se_output_data` and `pf_output_data`\n", 287 | "- print the difference in `q_to` between `se_output_data` and `pf_output_data`\n", 288 | "\n", 289 | "You should see that while the voltages match quite precisely (in the order of microvolts), the *p* and *q* are way off (in the order of megawatts / mega VARs). This is as expected because we used voltage angles of 0.0." 290 | ] 291 | }, 292 | { 293 | "cell_type": "code", 294 | "execution_count": null, 295 | "id": "a8d298d5", 296 | "metadata": { 297 | "scrolled": true 298 | }, 299 | "outputs": [], 300 | "source": [ 301 | "# TODO: Print the delta u for all nodes (pf_output_data - se_output_data)\n", 302 | "print(\"-------------- nodes --------------\")\n", 303 | "print(\"delta_u:\", ...)\n", 304 | "\n", 305 | "# TODO: Print the delta p and q for all lines (pf_output_data - se_output_data)\n", 306 | "print(\"-------------- lines --------------\")\n", 307 | "print(\"delta_p_from:\", ...)\n", 308 | "print(\"delta_p_to:\", ...)\n", 309 | "print(\"delta_q_from:\", ...)\n", 310 | "print(\"delta_q_to:\", ...)" 311 | ] 312 | }, 313 | { 314 | "attachments": {}, 315 | "cell_type": "markdown", 316 | "id": "683bb610", 317 | "metadata": {}, 318 | "source": [ 319 | "# Assignment 5: Add voltage angle measurements\n", 320 | "\n", 321 | "Now we will update the model by adding voltage angles to the voltage sensors.\n", 322 | "We could alter the `input_data` and construct a new Model, but for the purpose of this workshop (and efficiency) we'll supply the voltage angles as `update_data`, which could potentially be a *batch* calculation in other usecases.\n", 323 | "\n", 324 | "- initialize an update voltage sensor array\n", 325 | "- create an update dataset\n", 326 | "- perform a state estimation, using the update dataset\n", 327 | "- compare the results (as in assignment 4)\n", 328 | "\n", 329 | "You should see that the voltages match quite precisely (in the order of microvolts), the *p* and *q* do too (in the order of 0.01 watts / VARs), because we used the exact voltage angles from the power flow calculation." 330 | ] 331 | }, 332 | { 333 | "cell_type": "code", 334 | "execution_count": null, 335 | "id": "8628b888", 336 | "metadata": {}, 337 | "outputs": [], 338 | "source": [ 339 | "# TODO: Initialize a voltage sensor update array for 3 sensors\n", 340 | "update_sym_voltage_sensor = initialize_array(..., ..., ...)\n", 341 | "update_sym_voltage_sensor[\"id\"] = ...\n", 342 | "update_sym_voltage_sensor[\"u_angle_measured\"] = ...\n", 343 | "\n", 344 | "# TODO: Create an update dataset \n", 345 | "update_data = {\n", 346 | " ...\n", 347 | "}\n", 348 | "\n", 349 | "# TODO: Validate the update data\n", 350 | "assert_valid_batch_data(..., ..., calculation_type=..., symmetric=...)\n", 351 | "\n", 352 | "# Run the (iterative linear) state estimation\n", 353 | "se_output_data_u_angle = model.calculate_state_estimation(\n", 354 | " update_data = update_data,\n", 355 | " symmetric=True,\n", 356 | " error_tolerance=1e-8, \n", 357 | " max_iterations=20, \n", 358 | " calculation_method=CalculationMethod.iterative_linear)\n", 359 | "\n", 360 | "# TODO: Print the delta u for all nodes (se_output_data_u_angle - pf_output_data)\n", 361 | "print(\"-------------- nodes --------------\")\n", 362 | "print(\"delta_u:\", ...)\n", 363 | "\n", 364 | "# TODO: Print the delta p and q for all lines (se_output_data_u_angle - pf_output_data)\n", 365 | "print(\"-------------- lines --------------\")\n", 366 | "print(\"delta_p_from:\", ...)\n", 367 | "print(\"delta_p_to:\", ...)\n", 368 | "print(\"delta_q_from:\", ...)\n", 369 | "print(\"delta_q_to:\", ...)" 370 | ] 371 | }, 372 | { 373 | "attachments": {}, 374 | "cell_type": "markdown", 375 | "id": "7b054f55", 376 | "metadata": {}, 377 | "source": [ 378 | "# Assignment 6: Add power sensors to the model\n", 379 | "\n", 380 | "In common power grids most voltage sensors only measure the voltage magnitude; not the angle. In this assigment we will again use the `input_data` of assignment 3 (with unknown voltage angles) and we will connect power sensors to the model.\n", 381 | "\n", 382 | "In our network it would be possible to connect power sensors to the lines, the loads and the source. To assign realistic measurement values to the power sensors we can use the powerflow output.\n", 383 | "\n", 384 | "- Print the powerflow output of the lines, loads and source\n", 385 | "- Initialize as many `sym_power_sensors` as you like (think about which data you use for which type of power sensor)\n", 386 | "- Create a new input data set, including both voltage and power sensors\n", 387 | "- Use the print statements of assignment 4 to compare the results\n", 388 | "\n", 389 | "You should see that the voltages match quite precisely (in the order of microvolts), the *p* and *q* do too (in the order of watts / VARs)." 390 | ] 391 | }, 392 | { 393 | "cell_type": "code", 394 | "execution_count": null, 395 | "id": "da6e4dbe", 396 | "metadata": {}, 397 | "outputs": [], 398 | "source": [ 399 | "# Print the lines, loads and sources\n", 400 | "print(\"Lines:\")\n", 401 | "display(...)\n", 402 | "print(\"Sources:\")\n", 403 | "display(...)\n", 404 | "print(\"Loads:\")\n", 405 | "display(...)" 406 | ] 407 | }, 408 | { 409 | "cell_type": "code", 410 | "execution_count": null, 411 | "id": "7db83085", 412 | "metadata": {}, 413 | "outputs": [], 414 | "source": [ 415 | "# TODO: Initialize as many power sensors as you like.\n", 416 | "# Note that the sensors must added to the `input_data`, not `update_data`, as they don't exist in the model yet.sym_power_sensor = initialize_array(..., ..., ...)\n", 417 | "sym_power_sensor = initialize_array(..., ..., ...)\n", 418 | "sym_power_sensor[\"id\"] = ...\n", 419 | "sym_power_sensor[\"measured_object\"] = ...\n", 420 | "sym_power_sensor[\"measured_terminal_type\"] = ...\n", 421 | "sym_power_sensor[\"power_sigma\"] = ...\n", 422 | "sym_power_sensor[\"p_measured\"] = ...\n", 423 | "sym_power_sensor[\"q_measured\"] = ...\n", 424 | "\n", 425 | "# TODO: Add the sensors to the input data\n", 426 | "input_data[...] = sym_power_sensor\n", 427 | "# TODO: Validate the input data\n", 428 | "assert_valid_input_data(..., calculation_type=..., symmetric=...)\n", 429 | "\n", 430 | "# TODO: Create a new power grid model\n", 431 | "model = PowerGridModel(...)\n", 432 | "\n", 433 | "# Run the (iterative linear) state estimation\n", 434 | "se_output_data_power = model.calculate_state_estimation(\n", 435 | " symmetric=True, \n", 436 | " error_tolerance=1e-8, \n", 437 | " max_iterations=20, \n", 438 | " calculation_method=CalculationMethod.iterative_linear)\n", 439 | "\n", 440 | "# TODO: Print the delta u for all nodes (se_output_data_u_angle - pf_output_data)\n", 441 | "print(\"-------------- nodes --------------\")\n", 442 | "print(\"delta_u\", ...\n", 443 | "\n", 444 | "# TODO: Print the delta p and q for all lines (se_output_data_u_angle - pf_output_data)\n", 445 | "print(\"-------------- lines --------------\")\n", 446 | "print(\"delta_p_from:\", ...)\n", 447 | "print(\"delta_p_to:\", ...)\n", 448 | "print(\"delta_q_from:\", ...)\n", 449 | "print(\"delta_q_to:\", ...)" 450 | ] 451 | }, 452 | { 453 | "attachments": {}, 454 | "cell_type": "markdown", 455 | "id": "1afd00f1", 456 | "metadata": {}, 457 | "source": [ 458 | "It is interesting to analyze the calculated `u_angle` as well. One thing to notice is that angles should be interpreted relatively.\n", 459 | "A common way to do this, is to set the voltage angle of the first node to 0.0 radians and shift the rest accordingly." 460 | ] 461 | }, 462 | { 463 | "cell_type": "code", 464 | "execution_count": null, 465 | "id": "da21d37c", 466 | "metadata": {}, 467 | "outputs": [], 468 | "source": [ 469 | "# Copy the angles from the powerflow output and the last state estimation output\n", 470 | "pf_u_angles = pf_output_data[ComponentType.node][\"u_angle\"].copy()\n", 471 | "se_u_angles = se_output_data_power[ComponentType.node][\"u_angle\"].copy()\n", 472 | "\n", 473 | "# Print the angles\n", 474 | "print(\"\\nu_angle\")\n", 475 | "print(\"pf:\", pf_u_angles)\n", 476 | "print(\"se:\", se_u_angles)\n", 477 | "\n", 478 | "# Align the angles\n", 479 | "pf_u_angles = pf_u_angles - pf_u_angles[0]\n", 480 | "se_u_angles = se_u_angles - se_u_angles[0]\n", 481 | "\n", 482 | "# Print the angles again\n", 483 | "print(\"\\nu_angle'\")\n", 484 | "print(\"pf:\", pf_u_angles)\n", 485 | "print(\"se:\", se_u_angles)\n", 486 | "\n", 487 | "# Print the deltas\n", 488 | "print(\"\\ndelta_u_angle\")\n", 489 | "print(se_u_angles - pf_u_angles)" 490 | ] 491 | }, 492 | { 493 | "attachments": {}, 494 | "cell_type": "markdown", 495 | "id": "4e48d946", 496 | "metadata": {}, 497 | "source": [ 498 | "# Assignment 7: Time Series Batch Calculation\n", 499 | "\n", 500 | "Sometimes, it is desirable to see what the state of the power grid was for a number of measurements at different points in time. A typical use case is to see if the voltage or power requirements were not met over the past day.\n", 501 | "\n", 502 | "## Voltage measurements\n", 503 | "\n", 504 | "Let's say, we have voltage sensors with a much better temporal resolution than our power sensors. To simulate such a situation, we generate some random voltage measurements based on the input data used before, but re-use the power sensor readings." 505 | ] 506 | }, 507 | { 508 | "cell_type": "code", 509 | "execution_count": null, 510 | "id": "9324206e", 511 | "metadata": {}, 512 | "outputs": [], 513 | "source": [ 514 | "n_scenarios = 96\n", 515 | "n_sensors = len(input_data[ComponentType.sym_voltage_sensor])\n", 516 | "sensor_id = input_data[ComponentType.sym_voltage_sensor][\"id\"]\n", 517 | "sensor_u_measured = sym_voltage_sensor[\"u_measured\"]\n", 518 | "measurements = np.tile(sensor_u_measured, (n_scenarios, 1)) + np.random.normal(scale=100, size=(n_scenarios, n_sensors))\n", 519 | "dti = pd.date_range(\"2022-01-01\", periods=n_scenarios, freq=\"15min\")\n", 520 | "df_voltage_measurements = pd.DataFrame(measurements, columns=sensor_id, index=dti)\n", 521 | "display(df_voltage_measurements)" 522 | ] 523 | }, 524 | { 525 | "attachments": {}, 526 | "cell_type": "markdown", 527 | "id": "4720b175", 528 | "metadata": {}, 529 | "source": [ 530 | "## Run Time Series Calculation\n", 531 | "\n", 532 | "We want to run a time-series state estimation using the dataframe.\n", 533 | "\n", 534 | "* Convert the measurements to the compatible batch update dataset.\n", 535 | "* Run the batch calculation" 536 | ] 537 | }, 538 | { 539 | "cell_type": "code", 540 | "execution_count": null, 541 | "id": "26c0e877", 542 | "metadata": {}, 543 | "outputs": [], 544 | "source": [ 545 | "# TODO: Initialize empty measurements\n", 546 | "sym_voltage_measurements = initialize_array(..., ..., ...)\n", 547 | "\n", 548 | "# TODO: Set the attributes for the batch calculation\n", 549 | "# (assume u_sigma and u_angle_measurement are as before)\n", 550 | "sym_voltage_measurements[\"id\"] = ...\n", 551 | "sym_voltage_measurements[\"u_measured\"] = ...\n", 552 | "\n", 553 | "update_data = {ComponentType.sym_voltage_sensor: sym_voltage_measurements}" 554 | ] 555 | }, 556 | { 557 | "cell_type": "code", 558 | "execution_count": null, 559 | "id": "c64781f3", 560 | "metadata": {}, 561 | "outputs": [], 562 | "source": [ 563 | "# Validating batch data can take a long time.\n", 564 | "# It is recommended to only validate batch data when you run into trouble.\n", 565 | "assert_valid_batch_data(\n", 566 | " input_data=input_data,\n", 567 | " update_data=update_data,\n", 568 | " calculation_type=CalculationType.state_estimation,\n", 569 | ")" 570 | ] 571 | }, 572 | { 573 | "cell_type": "code", 574 | "execution_count": null, 575 | "id": "8f9208c5", 576 | "metadata": {}, 577 | "outputs": [], 578 | "source": [ 579 | "output_data = model.calculate_state_estimation(...)" 580 | ] 581 | }, 582 | { 583 | "attachments": {}, 584 | "cell_type": "markdown", 585 | "id": "fb3bf501", 586 | "metadata": {}, 587 | "source": [ 588 | "### Extracting load information from batch results\n", 589 | "\n", 590 | "We are trying to determine whether any user had a significant fluctuation in load requirements over the course of this day.\n", 591 | "\n", 592 | "* Determine the minimal and maximal power load and their ratio.\n", 593 | "* Was the fluctuation in power requirements large during this simulated day?" 594 | ] 595 | }, 596 | { 597 | "cell_type": "code", 598 | "execution_count": null, 599 | "id": "3bcfdf84", 600 | "metadata": {}, 601 | "outputs": [], 602 | "source": [ 603 | "# TODO: Extract the power field\n", 604 | "power_load = output_data[ComponentType.sym_load][...]\n", 605 | "# TODO: Calculate the max and min for each user\n", 606 | "max_p = power_load.max(...)\n", 607 | "min_p = power_load.min(...)\n", 608 | "print(\"max power load:\", max_p)\n", 609 | "print(\"min power load:\", min_p)\n", 610 | "print(\"ratio:\", max_p / min_p)" 611 | ] 612 | }, 613 | { 614 | "attachments": {}, 615 | "cell_type": "markdown", 616 | "id": "27e42d99", 617 | "metadata": {}, 618 | "source": [ 619 | "### Plotting batch results\n", 620 | "\n", 621 | "Lets say we wish to plot the loading of the `line with id 4` vs time. We can use matplotlib to do so.\n", 622 | "\n", 623 | "**Note:** The grid and results are randomly generated so dont be alarmed to see loading >100% or any other unrealistic results." 624 | ] 625 | }, 626 | { 627 | "cell_type": "code", 628 | "execution_count": null, 629 | "id": "936373ec", 630 | "metadata": {}, 631 | "outputs": [], 632 | "source": [ 633 | "from matplotlib import pyplot as plt\n", 634 | "\n", 635 | "# TODO: Prepare data to be plotted. We wish to plot the loading of line with id 4 vs time.\n", 636 | "line_4_idx = np.where(... == 4)\n", 637 | "result_loading = output_data[ComponentType.line][\"loading\"][...]\n", 638 | "\n", 639 | "plt.plot(result_loading)\n", 640 | "plt.title('Loading of line with id 2007')\n", 641 | "plt.xlabel('Time')\n", 642 | "plt.ylabel('Loading')\n", 643 | "plt.show()" 644 | ] 645 | } 646 | ], 647 | "metadata": { 648 | "kernelspec": { 649 | "display_name": "Python 3 (ipykernel)", 650 | "language": "python", 651 | "name": "python3" 652 | }, 653 | "language_info": { 654 | "codemirror_mode": { 655 | "name": "ipython", 656 | "version": 3 657 | }, 658 | "file_extension": ".py", 659 | "mimetype": "text/x-python", 660 | "name": "python", 661 | "nbconvert_exporter": "python", 662 | "pygments_lexer": "ipython3", 663 | "version": "3.11.2" 664 | } 665 | }, 666 | "nbformat": 4, 667 | "nbformat_minor": 5 668 | } 669 | -------------------------------------------------------------------------------- /state-estimation-assignment/State Estimation Assignment.ipynb.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Contributors to the Power Grid Model project 2 | 3 | SPDX-License-Identifier: MPL-2.0 4 | --------------------------------------------------------------------------------