diff --git a/.github/dco.yml b/.github/dco.yml index 1b6bb09e..d3d2275f 100644 --- a/.github/dco.yml +++ b/.github/dco.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a65864ed..0bcd8fe4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/.github/workflows/build-test-and-sonar.yml b/.github/workflows/build-test-and-sonar.yml index 62fc4bda..8963d4e0 100644 --- a/.github/workflows/build-test-and-sonar.yml +++ b/.github/workflows/build-test-and-sonar.yml @@ -1,70 +1,172 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 -name: Build, Test and Sonar +name: Build, Test, Sonar and Publish on: push: branches: - main + - 'release/**' # run pipeline on pull request pull_request: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: + build-python: - permissions: - contents: write - env: - TWINE_USERNAME: ${{ secrets.PYPI_USER }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASS }} + if: (github.event_name == 'push') || (github.event_name == 'workflow_dispatch') || !startsWith(github.head_ref, 'release') runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - uses: actions/setup-python@v4 + - name: Checkout source code + uses: actions/checkout@v3 + + - name: Setup Python 3.10 + uses: actions/setup-python@v4 with: python-version: "3.10" - - name: Build (Python 3.10) + - name: Build run: pip wheel -v --no-deps -w wheelhouse . - name: Save version id: version - run: | - echo "::set-output name=version::$(cat PYPI_VERSION)" + run: echo "version=$(cat PYPI_VERSION)" >> $GITHUB_OUTPUT - - name: Test and Coverage for built wheel file - run: | - pip install power-grid-model-io[dev]==${{ steps.version.outputs.version }} --find-links=wheelhouse - pytest + - name: Store built wheel file + uses: actions/upload-artifact@v3 + with: + name: power-grid-model-io + path: wheelhouse/ - - name: Validation tests - run: pytest tests/validation --no-cov --verbose + sonar-cloud: + # only run sonar server in push event or pull request event from own repo + if: (github.event_name == 'push') || (github.event_name == 'workflow_dispatch') || (!startsWith(github.head_ref, 'release') && (github.event.pull_request.head.repo.owner.login == 'alliander-opensource')) + permissions: + contents: write + runs-on: ubuntu-latest + steps: + + - name: Checkout source code + uses: actions/checkout@v3 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + + - name: Setup Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: "3.10" - - name: Test and Coverage for sonar cloud in develop mode + - name: Install in develop mode run: | - pip uninstall -y power-grid-model-io pip install -e .[dev] - pytest --cov-report=xml:coverage.xml + + - name: Test and Coverage + run: | + pytest --cov-report=xml:coverage.xml --cov-fail-under=0 # Fix relative paths in coverage file # Known bug: https://community.sonarsource.com/t/sonar-on-github-actions-with-python-coverage-source-issue/36057 sed -i 's@/home/runner/work/power-grid-model-io/power-grid-model-io@/github/workspace@g' coverage.xml - name: SonarCloud Scan - # only run sonar server in push event or pull request event from own repo - if: ${{ (github.event_name == 'push') || (github.event.pull_request.head.repo.owner.login == 'alliander-opensource') }} uses: SonarSource/sonarcloud-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + unit-tests: + needs: build-python + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python: ["3.8", "3.9", "3.10", "3.11"] + fail-fast: false + runs-on: ${{ matrix.os }} + + steps: + + - name: Checkout source code + uses: actions/checkout@v3 + + - name: Setup Python ${{ matrix.python }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + + - name: Load built wheel file + uses: actions/download-artifact@v3 + with: + name: power-grid-model-io + path: wheelhouse/ + + - name: Install built wheel file + run: pip install power-grid-model-io[dev]==${{ needs.build-python.outputs.version }} --find-links=wheelhouse + + - name: Unit test and coverage + run: pytest --verbose + + validation-tests: + needs: build-python + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python: ["3.8", "3.9", "3.10", "3.11"] + fail-fast: false + runs-on: ${{ matrix.os }} + + steps: + + - name: Checkout source code + uses: actions/checkout@v3 + + - name: Setup Python ${{ matrix.python }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + + - name: Load built wheel file + uses: actions/download-artifact@v3 + with: + name: power-grid-model-io + path: wheelhouse/ + + - name: Install built wheel file + run: pip install power-grid-model-io[dev]==${{ needs.build-python.outputs.version }} --find-links=wheelhouse + + - name: Validation tests + run: pytest tests/validation --no-cov --verbose + + publish: + needs: + - build-python + - unit-tests + - validation-tests + - sonar-cloud + permissions: + contents: write + env: + TWINE_USERNAME: ${{ secrets.PYPI_USER }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASS }} + runs-on: ubuntu-latest + steps: + - name: Setup Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: "3.10" + + - name: Load built wheel file + uses: actions/download-artifact@v3 + with: + name: power-grid-model-io + path: wheelhouse/ + - name: Upload wheels if: (github.event_name == 'push') || (github.event_name == 'workflow_dispatch') run: | @@ -73,12 +175,12 @@ jobs: twine upload --verbose wheelhouse/* - name: Release - uses: softprops/action-gh-release@v1 if: (github.event_name == 'push') || (github.event_name == 'workflow_dispatch') + uses: softprops/action-gh-release@v1 with: files: | ./wheelhouse/*.whl - tag_name: v${{ steps.version.outputs.version }} - prerelease: ${{ contains(steps.version.outputs.version, 'rc') || contains(steps.version.outputs.version, 'a') }} + tag_name: v${{ needs.build-python.outputs.version }} + prerelease: ${{ contains(needs.build-python.outputs.version, 'rc') || contains(needs.build-python.outputs.version, 'a') }} generate_release_notes: true target_commitish: ${{ github.sha }} diff --git a/.github/workflows/check-blocking-labels.yml b/.github/workflows/check-blocking-labels.yml index c3c5d6dc..213cde10 100644 --- a/.github/workflows/check-blocking-labels.yml +++ b/.github/workflows/check-blocking-labels.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/.github/workflows/check-code-quality.yml b/.github/workflows/check-code-quality.yml index d4225b75..0f398470 100644 --- a/.github/workflows/check-code-quality.yml +++ b/.github/workflows/check-code-quality.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/.github/workflows/reuse-compliance.yml b/.github/workflows/reuse-compliance.yml index 3b08e034..415d5fd4 100644 --- a/.github/workflows/reuse-compliance.yml +++ b/.github/workflows/reuse-compliance.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/.gitignore b/.gitignore index 2ed10574..826d071e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 @@ -10,6 +10,7 @@ wheelhouse/ # temporary_files __pycache__/ +*.stackdump .coverage .ipynb_checkpoints/ .mypy_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 00c57c1b..62de5d8b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index edc9ec01..91f7542b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,5 @@ diff --git a/PROJECT_GOVERNANCE.md b/PROJECT_GOVERNANCE.md index 93b436f6..fb3fd56b 100644 --- a/PROJECT_GOVERNANCE.md +++ b/PROJECT_GOVERNANCE.md @@ -1,5 +1,5 @@ diff --git a/README.md b/README.md index d7ca6626..9252d45a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ @@ -27,7 +27,6 @@ For detailed documentation, see [Read the Docs](https://power-grid-model-io.read # Examples * [PGM JSON Example](docs/examples/pgm_json_example.ipynb) -* [Vision/Gaia Example](docs/examples/vision_gaia_example.ipynb) # License diff --git a/RELEASE.md b/RELEASE.md index 66bfbd51..0c63fb2a 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,5 @@ diff --git a/SUPPORT.md b/SUPPORT.md index 7f23abe6..1a7f9faa 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,5 +1,5 @@ diff --git a/VERSION b/VERSION index 9f8e9b69..b123147e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0 \ No newline at end of file +1.1 \ No newline at end of file diff --git a/VERSION.license b/VERSION.license index e6220e93..ebe16ce8 100644 --- a/VERSION.license +++ b/VERSION.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/docs/converters/converter.md b/docs/converters/converter.md index bb706640..86474c5a 100644 --- a/docs/converters/converter.md +++ b/docs/converters/converter.md @@ -1,5 +1,5 @@ @@ -12,15 +12,15 @@ Here, we shall discuss their basic structure and guidelines for building a custo Use the examples notebooks to understand how to convert data from the respective formats. - **PGM JSON Converter:** Refer to the [PGM JSON Example](../examples/pgm_json_example.ipynb) -- **VisonExcelConverter** and **GaiaExcelConverter:** Refer to the [Vision and Gaia Example](../examples/vision_gaia_example.ipynb) +- **VisonExcelConverter** Refer to the [Vision Example](../examples/vision_example.ipynb) - **Pandapower Converter:** Converts [pandapower network](https://pandapower.readthedocs.io/en/stable/elements.html), which is a dictionary of dataframes, to power-grid-model data. Refer to [converters](../power_grid_model_io.md#converters) in API documentation for more details ## Structure -`VisonExcelConverter` and `GaiaExcelConverter` are inherited from [tabular converters](tabular_converter.md) for excel exports of vision and gaia respectively. -All 4 converters are derived from the base {py:class}`power_grid_model_io.converters.base_converter`. +The `VisonExcelConverter` extends the [tabular converters](tabular_converter.md) for Excel exports of Vision. +All converters are derived from the base {py:class}`power_grid_model_io.converters.base_converter`. The usable functions for loading, saving and converting the data are located in the base class. The private functions (`_load_data`, `_parse_data` and `_serialize_data`) are overloaded based on the specific type of converter (ie. excel, json or pandapower). It is recommended to create any custom converter in a similar way. diff --git a/docs/converters/tabular_converter.md b/docs/converters/tabular_converter.md index 294c017b..7058f82f 100644 --- a/docs/converters/tabular_converter.md +++ b/docs/converters/tabular_converter.md @@ -1,5 +1,5 @@ @@ -17,16 +17,27 @@ grid: Nodes: node: - id: Number + auto_id: + key: Number u_rated: Unom extra: ID Cables: line: - id: Number - from_node: From.Number + id: + auto_id: + key: Number + from_node: + auto_id: + table: Nodes + key: + Number: From.Number from_status: From.Switch state - to_node: To.Number + to_node: + auto_id: + table: Nodes + key: + Number: To.Number to_status: To.Switch state units: @@ -54,51 +65,67 @@ You can use the following `column` definitions: ```yaml from_node: From.Number ``` - * First matching column name `str` + * First matching column name that exists in the data `str` ```yaml p_specified: Inverter.Pnom | Inverter.Snom ``` - * Reference to a column on another sheet (using `!` notation as in Excel) `str` + * Automatic IDs `Dict[str, Dict[str, Any]]` with single key `reference`, required attribute `key` and optinal + attributes `table` and `name`. More extensive examples are shown in the section [AutoID Mapping](##autois-mapping). ```yaml - r1: CableProperties!R[Shortname=Type short] + id: + auto_id: + key: Number ``` - You may also specify the reference more explicitly: + + * Reference to a column on another sheet `Dict[str, Dict[str, Any]]` with single key `reference` and the ```yaml - r1: CableProperties!R[CableProperties!Shortname=Cables!Type short] + r1: + reference: + query_column: Shortname + other_table: Cable Properties + key_column: Type short + value_column: R ``` - * Constant value `int | float` + * Constant value `int` or `float` ```yaml from_status: 1 tan1: 0.0 ``` - * Functions `Dict[str, List[Any]` + * Pandas DataFrame functions `Dict[str, List[Any]]` + (`prod`, `sum`, `min`, `max`, etc and the alias `multiply` which translates to `prod`) ```yaml p_specified: min: - Pnom - Inverter.Pnom ``` + * Custom functions `Dict[str, Dict[str, Any]]` + ```yaml + g0: + power_grid_model_io.functions.complex_inverse_real_part: + real: R0 + imag: X0 + ``` * Nested definitions: ```yaml q_specified: - power_grid_model_io.filters.phase_to_phase.reactive_power: - - min: + power_grid_model_io.functions.phase_to_phase.reactive_power: + p: + min: - Pnom - Inverter.Pnom | Inverter.Snom - - Inverter.cos phi - - 1.0 + cos_phi: Inverter.cos phi ``` - Is similar to: + Is similar to something like: ```python - from power_grid_model_io.filters.phase_to_phase import reactive_power + from power_grid_model_io.functions.phase_to_phase import reactive_power q_specified = reactive_power( - min( + p=min( table["Pnom"], table["Inverter.Pnom"] if "Inverter.Pnom" in table else table["Inverter.Snom"] ), - table["Inverter.Snom"], - 1.0 + cos_phi=1.0 ) ``` ## Units @@ -163,24 +190,116 @@ item = auto_id[1] # item = "Bravo" See also {py:class}`power_grid_model_io.utils.AutoID` -### Vision and Gaia -For Vision and Gaia files, an extra trick is applied. Let's assume this mapping: - +## AutoID Mapping +Let's consider a very common example of the usage of `auto_id` in a mapping file. +(Note that we're focussing on the ids and references, so the other attributes have been disregarded.) ```yaml -grid: Nodes: node: - id: Number - ... - Cables: - line: - id: Number - from_node: From.Number - ... + id: + auto_id: + key: Number + Cables: + line: + id: + auto_id: + key: Number + from_node: + auto_id: + table: Nodes + key: + Number: From_Number + to_node: + auto_id: + table: Nodes + key: + Number: To_Number +``` +This basically reads as: +* For each row in the Nodes table, a PGM node instance is created. + * For each node instance, a numerical id is generated, which is unique for each value in the Number column. This + assumes that the Number column is unique in the source table. Let's say tha values of the Number column in that + Nodes source table are `[101, 102, 103]`, then the generated IDs will be `[0, 1, 2]`. However, if the source + column is not unique, the pgm ids won't be unique as well: `[101, 102, 103, 101] -> [0, 1, 2, 0]`. + * Under the hood, the table name `Nodes` and the column name `Number` are used to generate these IDs: + * `{"table": "Nodes", "key" {"Number": 101} -> 0` + * `{"table": "Nodes", "key" {"Number": 102} -> 1` + * `{"table": "Nodes", "key" {"Number": 103} -> 2` +* For each row in the Cables table, a PGM line instance is created. + * For each line instance, a numerical id is generated, just like for the nodes. + Let's say there are two Cables `[201, 202]` and the corresponding lines will have IDs `[3, 4]`. + * `{"table": "Cables", "key" {"Number": 201} -> 3` + * `{"table": "Cables", "key" {"Number": 202} -> 4` + * A Cable connects to two Nodes. + In this example Cable `201` connects Node `101` and `102`, and Cable `201` connects Node `102` and `103`. + These Node Numbers are stored in the columns `From_Number` and `To_Number`. + In order to retrieve the right PGM IDs, we have to explicitly state that the table in which the Nodes are + defined is called `Nodes` and the original column storing the Node Numbers is called `Number`. + * On the 'from' side of the cables: + * `{"table": "Nodes", "key" {"Number": 101} -> 0` + * `{"table": "Nodes", "key" {"Number": 102} -> 1` + * On the 'to' side of the cables: + * `{"table": "Nodes", "key" {"Number": 102} -> 1` + * `{"table": "Nodes", "key" {"Number": 103} -> 2` + + +## Advanced AutoID Mapping +In some cases, multiple components have to be created for each row in a source table. +In such cases, the `name` attribute may be necessary to create multiple PGM IDs for a single row. Let's consider +this example: +```yaml + Transformer loads: + transformer: + id: + auto_id: + name: transformer + key: + - Node_Number + - Subnumber + from_node: + auto_id: + table: Nodes + key: + Number: Node_Number + to_node: + auto_id: + name: internal_node + key: + - Node_Number + - Subnumber + node: + id: + auto_id: + name: internal_node + key: + - Node_Number + - Subnumber + sym_load: + id: + auto_id: + name: load + key: + - Node_Number + - Subnumber + node: + auto_id: + name: internal_node + key: + - Node_Number + - Subnumber ``` -The PGM `node["id"]` will be a number, based on the values in the `Nodes!Number` column. -The PGM `line["from_node"]` (which ends with `node`) will be based on the values in the `Nodes!From.Number`. -Originally this didn't work, due to the hashing function, as the column names differ: `"Number" != "From.Number"`. -Therefore, the Vision and Gaia converters overload the `_id_lookup()` method. -They split the column name on `.` so that the `Number` matches `From.Number`. \ No newline at end of file +Let's say we have one Transformer Load connected to the Node Number 103 and it's Subnumber is 1. +Then the following IDs will be generated / retrieved: +* `transformer.id`: + `{"table": "Transformer loads", "name": "transformer", "key" {"Node_Number": 103, "Subnumber": 1} -> 5` +* `transformer.from_node`: + `{"table": "Nodes", "key" {"Number": 103} -> 2` +* `transformer.to_node`: + `{"table": "Transformer loads", "name": "internal_node", "key" {"Node_Number": 103, "Subnumber": 1} -> 6` +* `node.id`: + `{"table": "Transformer loads", "name": "internal_node", "key" {"Node_Number": 103, "Subnumber": 1} -> 6` +* `sym_load.id`: + `{"table": "Transformer loads", "name": "load", "key" {"Node_Number": 103, "Subnumber": 1} -> 7` +* `sym_load.node`: + `{"table": "Transformer loads", "name": "internal_node", "key" {"Node_Number": 103, "Subnumber": 1} -> 6` diff --git a/docs/data_store.md b/docs/data_store.md index 3aaf8bb4..00e022a9 100644 --- a/docs/data_store.md +++ b/docs/data_store.md @@ -1,5 +1,5 @@ @@ -15,9 +15,8 @@ The inheritance structure of data stores is as follows: - Json file store - Excel file store - Vision-excel file store - - Gaia-excel file store -Of these, JSON file store, Vision-excel and Gaia-excel file stores are used in their respective converters. +Of these, JSON file store, Vision-excel file stores are used in their respective converters. A custom data store can be based on any of these existing stores. It has to convert the data to the specific data type. eg. tabular data. ## JSON file store @@ -37,7 +36,3 @@ Also refer {py:class}`power_grid_model_io.data_stores.ExcelFileStore` for specif The vision excel export specific operations in conversion are carried out. Eg. Vision exports has the units on the 2nd row. -#### Gaia file store - -The gaia excel export specific operations are in this file store. -Gaia also exports are in multiple excel files. \ No newline at end of file diff --git a/docs/examples/data/vision/example.png b/docs/examples/data/vision/example.png new file mode 100644 index 00000000..4b833a91 Binary files /dev/null and b/docs/examples/data/vision/example.png differ diff --git a/docs/examples/vision_gaia_example.ipynb.license b/docs/examples/data/vision/example.png.license similarity index 100% rename from docs/examples/vision_gaia_example.ipynb.license rename to docs/examples/data/vision/example.png.license diff --git a/docs/examples/data/vision/example.vnf b/docs/examples/data/vision/example.vnf new file mode 100644 index 00000000..79074b77 --- /dev/null +++ b/docs/examples/data/vision/example.vnf @@ -0,0 +1,57 @@ +V9.4 +NETWORK + +[OPTIONS] +Currency=€ +[] + +[SHEET] +#1 1 'Blad 1' $00C0C0C0 $00000000 0 0 0 0 0 0 0 0 0 0 +[] + +[NODE] +#1 1 0 44908 44908 0 'First Node' '' '' 0,4 1 '' '' 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +#2 0 0 0 0 0 +#5 0 0 0 0 0 0,5 0 1 1 1 0 0 0 0 0 +#9 1 15000 14980 1 $00000000 2 4 0 $00000000 10 'Arial' 0 0 0 0 0 -30 -20 20 5 5 0 50 +#1 2 0 44908 44908 0 'Second Node' '' '' 0,4 1 '' '' 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +#2 0 0 0 0 0 +#5 0 0 0 0 0 0,5 0 1 1 1 0 0 0 0 0 +#9 1 15140 14980 1 $00000000 4 4 0 $00000000 10 'Arial' 0 0 0 0 0 -50 -20 20 5 5 0 50 +#1 3 0 44908 44908 0 'Third Node' '' '' 0,4 1 '' '' 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +#2 0 0 0 0 0 +#5 0 0 0 0 0 0,5 0 1 1 1 0 0 0 0 0 +#9 1 15280 14920 1 $00000000 4 4 0 $00000000 10 'Arial' 0 0 0 0 0 -50 -20 20 5 5 0 50 +#1 4 0 44908 44908 0 'Fourth Node' '' '' 0,4 1 '' '' 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +#2 0 0 0 0 0 +#5 0 0 0 0 0 0,5 0 1 1 1 0 0 0 0 0 +#9 1 15280 15040 1 $00000000 4 4 0 $00000000 10 'Arial' 0 0 0 0 0 -50 -20 20 5 5 0 50 +[] + +[CABLE] +#1 4 0 0 44109 0 1 2 '' 1 1 '' '' 0 '' '' 0 0 0 0 0 0 P 1 0 +#2 100 1 '' 2 1 '' +#3 '150 AL GPLK' 0,75 0 0,206 0,079 0,072 0,003 0,82749 0,38573 0,43 220 0 330 0 300 0 275 0 20 0 0 50 0 +#9 1 $00000000 1 1 0 $00000000 10 'Arial' 0 0 0 12 6 -15 6 0 4 -20 20 0 5 0 0 # 15000 14980 ## 15140 14980 +#1 5 0 0 44109 0 2 3 '' 1 1 '' '' 0 '' '' 0 0 0 0 0 0 P 1 0 +#2 100 1 '' 2 1 '' +#3 '150 AL GPLK' 0,75 0 0,206 0,079 0,072 0,003 0,82749 0,38573 0,43 220 0 330 0 300 0 275 0 20 0 0 50 0 +#9 1 $00000000 1 1 0 $00000000 10 'Arial' 0 0 0 12 6 -15 6 4 0 -20 20 0 5 0 0 # 15140 14960 15220 14960 ## 15280 14920 15220 14920 +#1 6 0 0 44109 0 2 4 '' 1 1 '' '' 0 '' '' 0 0 0 0 0 0 P 1 0 +#2 100 1 '' 2 1 '' +#3 '150 AL GPLK' 0,75 0 0,206 0,079 0,072 0,003 0,82749 0,38573 0,43 220 0 330 0 300 0 275 0 20 0 0 50 0 +#9 1 $00000000 1 1 0 $00000000 10 'Arial' 0 0 0 12 6 -15 6 4 0 -20 20 5 0 0 0 # 15140 15000 15220 15000 ## 15280 15040 15220 15040 +[] + +[SOURCE] +#1 1 1 0 44908 44908 0 'Grid' 1 '' 0 0 0 0 0 0 1 0 2300 2300 2600 0,1 3 0 0 0 0 0 +#9 1 14920 14980 $00000000 1 1 0 $00000000 10 'Arial' 0 0 0 -15 6 -125 0 5 5 0 +[] + +[LOAD] +#1 3 1 0 0 44109 0 '' 1 '' 0 0 0 0 0 0 0,06 0 0 0 0,33333333 1 0,33333333 1 0,33333333 1 -1 0 0 0 0 0 0 0 0 0 '' 1 +#9 1 15360 14920 $00000000 1 1 0 $00000000 10 'Arial' 0 0 0 12 6 125 0 -5 5 0 +#1 4 1 0 0 44109 0 '' 1 '' 0 0 0 0 0 0 0,03 0,01 1 0 1 0 0 0 0 1 -1 0 0 0 0 0 0 0 0 0 '' 1 +#9 1 15360 15040 $00000000 1 1 0 $00000000 10 'Arial' 0 0 0 12 6 125 0 -5 5 0 +[] + diff --git a/tests/data/vision/vision_validation.json.license b/docs/examples/data/vision/example.vnf.license similarity index 100% rename from tests/data/vision/vision_validation.json.license rename to docs/examples/data/vision/example.vnf.license diff --git a/docs/examples/data/vision/example.xlsx b/docs/examples/data/vision/example.xlsx index 0f8a3a87..ba1bcedb 100644 Binary files a/docs/examples/data/vision/example.xlsx and b/docs/examples/data/vision/example.xlsx differ diff --git a/docs/examples/data/vision/sym_output.json b/docs/examples/data/vision/sym_output.json new file mode 100644 index 00000000..44fb84ee --- /dev/null +++ b/docs/examples/data/vision/sym_output.json @@ -0,0 +1,24 @@ +{ + "line": + [ + {"id": 4, "energized": 1, "loading": 0.15473990756817002, "p_from": 30133.178503728053, "q_from": 10050.721853721689, "i_from": 46.421809438989094, "s_from": 31765.16104977932, "p_to": -29999.9999999991, "q_to": -10000.000000000018, "i_to": 46.42197227045101, "s_to": 31622.776601682945, "id_reference": {"table": "Cables", "key": {"Number": 6}}}, + {"id": 5, "energized": 1, "loading": 0.2946347177166794, "p_from": 60482.835089115535, "q_from": 184.81468538821002, "i_from": 88.39041452675093, "s_from": 60483.11745342729, "p_to": -60000.00000000256, "q_to": -2.981555974335137e-10, "i_to": 88.3904153150038, "s_to": 60000.00000000256, "id_reference": {"table": "Cables", "key": {"Number": 5}}}, + {"id": 6, "energized": 1, "loading": 0.44422983507391905, "p_from": 91713.619010016, "q_from": 10656.105417735627, "i_from": 133.26889142389211, "s_from": 92330.60432266358, "p_to": -90616.01359284009, "q_to": -10235.536539109033, "i_to": 133.26895052217571, "s_to": 91192.25914353265, "id_reference": {"table": "Cables", "key": {"Number": 4}}} + ], + "node": + [ + {"id": 0, "energized": 1, "u_pu": 0.999991421302051, "u": 399.9965685208204, "u_angle": -3.921691889422987e-05, "id_reference": {"table": "Nodes", "key": {"Number": 1}}, "ID": 101, "Name": "First Node"}, + {"id": 1, "energized": 1, "u_pu": 0.983231522194849, "u": 393.2926088779396, "u_angle": -0.003434581394274798, "id_reference": {"table": "Nodes", "key": {"Number": 4}}, "ID": 104, "Name": "Fourth Node"}, + {"id": 2, "energized": 1, "u_pu": 0.9876620765421245, "u": 395.0648306168498, "u_angle": -0.003235042286685305, "id_reference": {"table": "Nodes", "key": {"Number": 2}}, "ID": 102, "Name": "Second Node"}, + {"id": 3, "energized": 1, "u_pu": 0.9797729773054616, "u": 391.9091909221847, "u_angle": -0.006296501518419363, "id_reference": {"table": "Nodes", "key": {"Number": 3}}, "ID": 103, "Name": "Third Node"} + ], + "source": + [ + {"id": 7, "energized": 1, "p": 91713.61901002181, "q": 10656.105417760706, "i": 133.26889142390465, "s": 92330.60432267224, "pf": 0.9933176510954675, "id_reference": {"table": "Sources", "key": {"Node.Number": 1, "Subnumber": 1}}, "Name": "Grid"} + ], + "sym_load": + [ + {"id": 8, "energized": 1, "p": 60000.0, "q": -0.0, "i": 88.39041531500003, "s": 60000.0, "pf": 1.0, "id_reference": {"table": "Loads", "key": {"Node.Number": 3, "Subnumber": 1}}}, + {"id": 9, "energized": 1, "p": 30000.0, "q": 10000.0, "i": 46.421972270452244, "s": 31622.776601683792, "pf": 0.9486832980505139, "id_reference": {"table": "Loads", "key": {"Node.Number": 4, "Subnumber": 1}}} + ] +} diff --git a/tests/data/vision/vision_validation.xlsx.license b/docs/examples/data/vision/sym_output.json.license similarity index 100% rename from tests/data/vision/vision_validation.xlsx.license rename to docs/examples/data/vision/sym_output.json.license diff --git a/docs/examples/pgm_json_example.ipynb b/docs/examples/pgm_json_example.ipynb index 85bdd72c..0f7ff034 100644 --- a/docs/examples/pgm_json_example.ipynb +++ b/docs/examples/pgm_json_example.ipynb @@ -56,7 +56,7 @@ { "data": { "text/markdown": [ - "
{\n",
+       "
{\n",
        "  \"node\":\n",
        "    [\n",
        "      {\"id\": 1, \"u_rated\": 10500.0, \"name\": \"First Node\"},\n",
@@ -93,7 +93,7 @@
        "      {\"id\": 701, \"measured_object\": 7, \"measured_terminal_type\": 4, \"power_sigma\": 10316.0, \"p_measured\": 1010000.0, \"q_measured\": 210000.0},\n",
        "      {\"id\": 801, \"measured_object\": 8, \"measured_terminal_type\": 4, \"power_sigma\": 10435.0, \"p_measured\": 1020000.0, \"q_measured\": 220000.0}\n",
        "    ]\n",
-       "}"
+       "}
" ], "text/plain": [ "" @@ -108,7 +108,7 @@ "from IPython.display import display, Markdown\n", "\n", "with Path(source_file).open() as json_file:\n", - " display(Markdown(f\"
{json_file.read()}\"))"
+    "    display(Markdown(f\"
{json_file.read()}
\"))" ] }, { @@ -398,7 +398,7 @@ { "data": { "text/markdown": [ - "
{\n",
+       "
{\n",
        "  \"line\":\n",
        "    [\n",
        "      {\"id\": 4, \"energized\": 1, \"loading\": 0.4073282784816704, \"p_from\": 2412359.29761995, \"q_from\": -3024028.8865554463, \"i_from\": 207.7374220256519, \"s_from\": 3868362.455553408, \"p_to\": -2240426.3899862496, \"q_to\": 1532195.103337908, \"i_to\": 145.73743890160117, \"s_to\": 2714246.1648935755},\n",
@@ -436,7 +436,7 @@
        "      {\"id\": 301, \"energized\": 1, \"u_residual\": 8.038902876705833e-08, \"u_angle_residual\": -2.7651492207070305e-15}\n",
        "    ]\n",
        "}\n",
-       ""
+       "
" ], "text/plain": [ "" @@ -451,7 +451,7 @@ "from IPython.display import display, Markdown\n", "\n", "with Path(destination_file).open() as json_file:\n", - " display(Markdown(f\"
{json_file.read()}\"))"
+    "    display(Markdown(f\"
{json_file.read()}
\"))" ] }, { diff --git a/docs/examples/vision_gaia_example.ipynb b/docs/examples/vision_example.ipynb similarity index 69% rename from docs/examples/vision_gaia_example.ipynb rename to docs/examples/vision_example.ipynb index bd9c4241..6de932aa 100644 --- a/docs/examples/vision_gaia_example.ipynb +++ b/docs/examples/vision_example.ipynb @@ -4,9 +4,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Vision and Gaia Conversion\n", + "# Vision Conversion\n", "\n", - "This example illustrates conversion from Vision or Gaia excel export to power-grid-model input data. \n", + "This example illustrates conversion from Vision excel export to power-grid-model input data.\n", "They function in a similar way since both are [Tabular Converters](../converters/tabular_converter.md).\n", "We can then calculate power-flow with it or convert to a different formats like PGM JSON." ] @@ -16,6 +16,7 @@ "metadata": {}, "source": [ "## Vision conversion\n", + "![Vision example](data/vision/example.png)\n", "\n", "### 1. Load the Vision data\n", "\n", @@ -142,7 +143,7 @@ { "data": { "text/plain": [ - "{0: 'First Node', 1: 'Second Node', 2: 'Third Node', 3: 'Fourth Node'}" + "{0: 'First Node', 1: 'Fourth Node', 2: 'Second Node', 3: 'Third Node'}" ] }, "metadata": {}, @@ -230,32 +231,32 @@ " 0\n", " 1\n", " 0.999991\n", - " 399.996506\n", - " -0.000040\n", + " 399.996569\n", + " -0.000039\n", " \n", " \n", " 1\n", " 1\n", " 1\n", - " 0.985602\n", - " 394.240830\n", - " -0.003033\n", + " 0.983232\n", + " 393.292609\n", + " -0.003435\n", " \n", " \n", " 2\n", " 2\n", " 1\n", - " 0.971222\n", - " 388.488784\n", - " -0.006115\n", + " 0.987662\n", + " 395.064831\n", + " -0.003235\n", " \n", " \n", " 3\n", " 3\n", " 1\n", - " 0.966073\n", - " 386.429212\n", - " -0.006102\n", + " 0.979773\n", + " 391.909191\n", + " -0.006297\n", " \n", " \n", "\n", @@ -263,10 +264,10 @@ ], "text/plain": [ " id energized u_pu u u_angle\n", - "0 0 1 0.999991 399.996506 -0.000040\n", - "1 1 1 0.985602 394.240830 -0.003033\n", - "2 2 1 0.971222 388.488784 -0.006115\n", - "3 3 1 0.966073 386.429212 -0.006102" + "0 0 1 0.999991 399.996569 -0.000039\n", + "1 1 1 0.983232 393.292609 -0.003435\n", + "2 2 1 0.987662 395.064831 -0.003235\n", + "3 3 1 0.979773 391.909191 -0.006297" ] }, "metadata": {}, @@ -299,14 +300,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "PGM object #0: {'table': 'Nodes', 'key': {'Number': 111}}\n", - "PGM object #4: {'table': 'Cables', 'key': {'Number': 444}}\n", - "PGM object #7: {'table': 'Sources', 'key': {'Node.Number': 111, 'Subnumber': 1}}\n", - "PGM object #9: {'table': 'Loads', 'key': {'Node.Number': 444, 'Subnumber': 1}}\n", - "Node with Number=111: 0\n", - "Cables with Number=444: 4\n", - "Source with Node.Number=111 and Subnumber=1: 7\n", - "Loads with Node.Number=444 and Subnumber=1: 9\n" + "PGM object #0: {'table': 'Nodes', 'key': {'Number': 1}}\n", + "PGM object #4: {'table': 'Cables', 'key': {'Number': 6}}\n", + "PGM object #7: {'table': 'Sources', 'key': {'Node.Number': 1, 'Subnumber': 1}}\n", + "PGM object #9: {'table': 'Loads', 'key': {'Node.Number': 4, 'Subnumber': 1}}\n", + "Node with Number=1: 0\n", + "Cables with Number=6: 4\n", + "Source with Node.Number=1 and Subnumber=1: 7\n", + "Loads with Node.Number=4 and Subnumber=1: 9\n" ] } ], @@ -317,23 +318,23 @@ "print(\"PGM object #9:\", converter.lookup_id(9))\n", "\n", "print(\n", - " \"Node with Number=111:\",\n", - " converter.get_node_id(number=111)\n", + " \"Node with Number=1:\",\n", + " converter.get_node_id(number=1)\n", ")\n", "\n", "print(\n", - " \"Cables with Number=444:\",\n", - " converter.get_branch_id(table=\"Cables\", number=444)\n", + " \"Cables with Number=6:\",\n", + " converter.get_branch_id(table=\"Cables\", number=6)\n", ")\n", "\n", "print(\n", - " \"Source with Node.Number=111 and Subnumber=1:\",\n", - " converter.get_appliance_id(table=\"Sources\", node_number=111, sub_number=1)\n", + " \"Source with Node.Number=1 and Subnumber=1:\",\n", + " converter.get_appliance_id(table=\"Sources\", node_number=1, sub_number=1)\n", ")\n", "\n", "print(\n", - " \"Loads with Node.Number=444 and Subnumber=1:\",\n", - " converter.get_appliance_id(table=\"Loads\", node_number=444, sub_number=1)\n", + " \"Loads with Node.Number=4 and Subnumber=1:\",\n", + " converter.get_appliance_id(table=\"Loads\", node_number=4, sub_number=1)\n", ")" ] }, @@ -372,31 +373,31 @@ { "data": { "text/markdown": [ - "
{\n",
+       "
{\n",
        "  \"line\":\n",
        "    [\n",
-       "      {\"id\": 4, \"energized\": 1, \"loading\": 0.4494033468128292, \"p_from\": 92766.12651731551, \"q_from\": 10913.43660532122, \"i_from\": 134.82094421436187, \"s_from\": 93405.87415984583, \"p_to\": -91463.07558984967, \"q_to\": -10483.005663479944, \"i_to\": 134.82100404384875, \"s_to\": 92061.86835001291, \"id_reference\": {\"table\": \"Cables\", \"key\": {\"Number\": 444}}},\n",
-       "      {\"id\": 5, \"energized\": 1, \"loading\": 0.44940353822035317, \"p_from\": 91463.07558984793, \"q_from\": 10483.005663480168, \"i_from\": 134.82100404384624, \"s_from\": 92061.86835001121, \"p_to\": -90160.02352913805, \"q_to\": -10052.564084910864, \"i_to\": 134.82106146610596, \"s_to\": 90718.70748338496, \"id_reference\": {\"table\": \"Cables\", \"key\": {\"Number\": 555}}},\n",
-       "      {\"id\": 6, \"energized\": 1, \"loading\": 0.15748825414612666, \"p_from\": 30160.023529136957, \"q_from\": 10052.564084910147, \"i_from\": 47.24631623927688, \"s_from\": 31791.21048276269, \"p_to\": -29999.99999999949, \"q_to\": -9999.999999999573, \"i_to\": 47.246476243837996, \"s_to\": 31622.776601683174, \"id_reference\": {\"table\": \"Cables\", \"key\": {\"Number\": 666}}}\n",
+       "      {\"id\": 4, \"energized\": 1, \"loading\": 0.15473990756817002, \"p_from\": 30133.178503728053, \"q_from\": 10050.721853721689, \"i_from\": 46.421809438989094, \"s_from\": 31765.16104977932, \"p_to\": -29999.9999999991, \"q_to\": -10000.000000000018, \"i_to\": 46.42197227045101, \"s_to\": 31622.776601682945, \"id_reference\": {\"table\": \"Cables\", \"key\": {\"Number\": 6}}},\n",
+       "      {\"id\": 5, \"energized\": 1, \"loading\": 0.2946347177166794, \"p_from\": 60482.835089115535, \"q_from\": 184.81468538821002, \"i_from\": 88.39041452675093, \"s_from\": 60483.11745342729, \"p_to\": -60000.00000000256, \"q_to\": -2.981555974335137e-10, \"i_to\": 88.3904153150038, \"s_to\": 60000.00000000256, \"id_reference\": {\"table\": \"Cables\", \"key\": {\"Number\": 5}}},\n",
+       "      {\"id\": 6, \"energized\": 1, \"loading\": 0.44422983507391905, \"p_from\": 91713.619010016, \"q_from\": 10656.105417735627, \"i_from\": 133.26889142389211, \"s_from\": 92330.60432266358, \"p_to\": -90616.01359284009, \"q_to\": -10235.536539109033, \"i_to\": 133.26895052217571, \"s_to\": 91192.25914353265, \"id_reference\": {\"table\": \"Cables\", \"key\": {\"Number\": 4}}}\n",
        "    ],\n",
        "  \"node\":\n",
        "    [\n",
-       "      {\"id\": 0, \"energized\": 1, \"u_pu\": 0.9999912644198532, \"u\": 399.9965057679413, \"u_angle\": -3.96611370590559e-05, \"id_reference\": {\"table\": \"Nodes\", \"key\": {\"Number\": 111}}, \"Name\": \"First Node\"},\n",
-       "      {\"id\": 1, \"energized\": 1, \"u_pu\": 0.9856020738558822, \"u\": 394.24082954235286, \"u_angle\": -0.003033170356205173, \"id_reference\": {\"table\": \"Nodes\", \"key\": {\"Number\": 222}}, \"Name\": \"Second Node\"},\n",
-       "      {\"id\": 2, \"energized\": 1, \"u_pu\": 0.9712219592491987, \"u\": 388.4887836996795, \"u_angle\": -0.006115298015992432, \"id_reference\": {\"table\": \"Nodes\", \"key\": {\"Number\": 333}}, \"Name\": \"Third Node\"},\n",
-       "      {\"id\": 3, \"energized\": 1, \"u_pu\": 0.966073029937675, \"u\": 386.42921197506996, \"u_angle\": -0.006102268973727995, \"id_reference\": {\"table\": \"Nodes\", \"key\": {\"Number\": 444}}, \"Name\": \"Fourth Node\"}\n",
+       "      {\"id\": 0, \"energized\": 1, \"u_pu\": 0.999991421302051, \"u\": 399.9965685208204, \"u_angle\": -3.921691889422987e-05, \"id_reference\": {\"table\": \"Nodes\", \"key\": {\"Number\": 1}}, \"ID\": 101, \"Name\": \"First Node\"},\n",
+       "      {\"id\": 1, \"energized\": 1, \"u_pu\": 0.983231522194849, \"u\": 393.2926088779396, \"u_angle\": -0.003434581394274798, \"id_reference\": {\"table\": \"Nodes\", \"key\": {\"Number\": 4}}, \"ID\": 104, \"Name\": \"Fourth Node\"},\n",
+       "      {\"id\": 2, \"energized\": 1, \"u_pu\": 0.9876620765421245, \"u\": 395.0648306168498, \"u_angle\": -0.003235042286685305, \"id_reference\": {\"table\": \"Nodes\", \"key\": {\"Number\": 2}}, \"ID\": 102, \"Name\": \"Second Node\"},\n",
+       "      {\"id\": 3, \"energized\": 1, \"u_pu\": 0.9797729773054616, \"u\": 391.9091909221847, \"u_angle\": -0.006296501518419363, \"id_reference\": {\"table\": \"Nodes\", \"key\": {\"Number\": 3}}, \"ID\": 103, \"Name\": \"Third Node\"}\n",
        "    ],\n",
        "  \"source\":\n",
        "    [\n",
-       "      {\"id\": 7, \"energized\": 1, \"p\": 92766.12651730014, \"q\": 10913.436605231671, \"i\": 134.82094421432475, \"s\": 93405.8741598201, \"pf\": 0.9931508842642452, \"id_reference\": {\"table\": \"Sources\", \"key\": {\"Node.Number\": 111, \"Subnumber\": 1}}, \"Name\": \"Netvoeding\"}\n",
+       "      {\"id\": 7, \"energized\": 1, \"p\": 91713.61901002181, \"q\": 10656.105417760706, \"i\": 133.26889142390465, \"s\": 92330.60432267224, \"pf\": 0.9933176510954675, \"id_reference\": {\"table\": \"Sources\", \"key\": {\"Node.Number\": 1, \"Subnumber\": 1}}, \"Name\": \"Grid\"}\n",
        "    ],\n",
        "  \"sym_load\":\n",
        "    [\n",
-       "      {\"id\": 8, \"energized\": 1, \"p\": 60000.0, \"q\": -0.0, \"i\": 89.16863910839885, \"s\": 60000.0, \"pf\": 1.0, \"id_reference\": {\"table\": \"Loads\", \"key\": {\"Node.Number\": 333, \"Subnumber\": 1}}},\n",
-       "      {\"id\": 9, \"energized\": 1, \"p\": 30000.0, \"q\": 10000.0, \"i\": 47.246476243838906, \"s\": 31622.776601683792, \"pf\": 0.9486832980505139, \"id_reference\": {\"table\": \"Loads\", \"key\": {\"Node.Number\": 444, \"Subnumber\": 1}}}\n",
+       "      {\"id\": 8, \"energized\": 1, \"p\": 60000.0, \"q\": -0.0, \"i\": 88.39041531500003, \"s\": 60000.0, \"pf\": 1.0, \"id_reference\": {\"table\": \"Loads\", \"key\": {\"Node.Number\": 3, \"Subnumber\": 1}}},\n",
+       "      {\"id\": 9, \"energized\": 1, \"p\": 30000.0, \"q\": 10000.0, \"i\": 46.421972270452244, \"s\": 31622.776601683792, \"pf\": 0.9486832980505139, \"id_reference\": {\"table\": \"Loads\", \"key\": {\"Node.Number\": 4, \"Subnumber\": 1}}}\n",
        "    ]\n",
        "}\n",
-       ""
+       "
" ], "text/plain": [ "" @@ -411,14 +412,15 @@ "from IPython.display import display, Markdown\n", "\n", "with Path(destination_file).open() as json_file:\n", - " display(Markdown(f\"
{json_file.read()}\"))"
+    "    display(Markdown(f\"
{json_file.read()}
\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Summary" + "## Other languages\n", + "Currently Dutch is the only extra language that is supported for conversion." ] }, { @@ -429,52 +431,42 @@ "source": [ "%%capture cap --no-stderr\n", "\n", - "from power_grid_model import PowerGridModel, CalculationType\n", - "from power_grid_model.validation import assert_valid_input_data\n", - "from power_grid_model_io.converters import VisionExcelConverter, PgmJsonConverter\n", - "\n", - "source_file = \"data/vision/example.xlsx\"\n", - "destination_file = \"data/vision/sym_output.json\"\n", - "\n", - "converter = VisionExcelConverter(source_file=source_file)\n", - "input_data, extra_info = converter.load_input_data()\n", - "assert_valid_input_data(input_data, calculation_type=CalculationType.power_flow, symmetric=True)\n", - "pgm = PowerGridModel(input_data=input_data)\n", - "output_data = pgm.calculate_power_flow()\n", - "json_converter = PgmJsonConverter(destination_file=destination_file)\n", - "json_converter.save(data=output_data, extra_info=extra_info)" + "converter = VisionExcelConverter(source_file=source_file, language=\"nl\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Gaia Conversion\n", - "\n", - "```{note}\n", - "Gaia Conversion is under development currently and will not work as intended\n", - "```\n", - "\n", - "Please refer to the Gaia manual from the software for instructions on Excel exports.\n", - "Gaia Conversion is similar to Vision conversion, so only a brief code example is given:\n" + "## Summary" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, + "execution_count": 10, + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [], "source": [ - "from power_grid_model_io.converters.gaia_excel_converter import GaiaExcelConverter\n", + "%%capture cap --no-stderr\n", + "\n", + "from power_grid_model import PowerGridModel, CalculationType\n", + "from power_grid_model.validation import assert_valid_input_data\n", + "from power_grid_model_io.converters import VisionExcelConverter, PgmJsonConverter\n", "\n", - "source_file = \"data/gaia/example.xlsx\"\n", - "types_file = \"data/gaia/types.xlsx\"\n", + "source_file = \"data/vision/example.xlsx\"\n", + "destination_file = \"data/vision/sym_output.json\"\n", "\n", - "gaia_converter = GaiaExcelConverter(source_file=source_file, types_file=types_file)\n", - "input_data, extra_info = gaia_converter.load_input_data()\n", + "converter = VisionExcelConverter(source_file=source_file)\n", + "input_data, extra_info = converter.load_input_data()\n", "assert_valid_input_data(input_data, calculation_type=CalculationType.power_flow, symmetric=True)\n", - "model = PowerGridModel(input_data=input_data)\n", - "output_data = model.calculate_power_flow()" + "pgm = PowerGridModel(input_data=input_data)\n", + "output_data = pgm.calculate_power_flow()\n", + "json_converter = PgmJsonConverter(destination_file=destination_file)\n", + "json_converter.save(data=output_data, extra_info=extra_info)" ] } ], @@ -504,4 +496,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/docs/examples/vision_example.ipynb.license b/docs/examples/vision_example.ipynb.license new file mode 100644 index 00000000..f58b56c6 --- /dev/null +++ b/docs/examples/vision_example.ipynb.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 diff --git a/docs/index.md b/docs/index.md index 30075d0d..6527c0f4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,7 +17,7 @@ The documentation is under heavy development ``` Power Grid Model IO is a tool to convert grid data to and from the native data format of [power-grid-model](https://github.com/alliander-opensource/power-grid-model). -Currently, conversions from Vision and Gaia excel exports is possible. Pandapower conversions are under development. +Currently, conversions from Vision excel exports is possible. Pandapower conversions are under development. Detailed contents of the documentation are structured as follows. @@ -39,7 +39,7 @@ converters/tabular_converter.md :caption: "Examples" :maxdepth: 2 examples/pgm_json_example.ipynb -examples/vision_gaia_example.ipynb +examples/vision_example.ipynb ``` diff --git a/docs/power_grid_model_io.md b/docs/power_grid_model_io.md index c00f2a4d..fa880d08 100644 --- a/docs/power_grid_model_io.md +++ b/docs/power_grid_model_io.md @@ -18,7 +18,6 @@ SPDX-License-Identifier: MPL-2.0 ```{eval-rst} .. automodule:: power_grid_model_io.converters.base_converter .. automodule:: power_grid_model_io.converters.tabular_converter -.. automodule:: power_grid_model_io.converters.gaia_excel_converter .. automodule:: power_grid_model_io.converters.vision_excel_converter .. automodule:: power_grid_model_io.converters.pgm_json_converter ``` @@ -28,7 +27,6 @@ SPDX-License-Identifier: MPL-2.0 ```{eval-rst} .. automodule:: power_grid_model_io.data_stores.base_data_store .. automodule:: power_grid_model_io.data_stores.excel_file_store -.. automodule:: power_grid_model_io.data_stores.gaia_excel_file_store .. automodule:: power_grid_model_io.data_stores.json_file_store .. automodule:: power_grid_model_io.data_stores.vision_excel_file_store ``` @@ -39,10 +37,11 @@ SPDX-License-Identifier: MPL-2.0 .. automodule:: power_grid_model_io.data_types.tabular_data ``` -## filters +## functions ```{eval-rst} -.. automodule:: power_grid_model_io.filters.phase_to_phase +.. automodule:: power_grid_model_io.functions._functions +.. automodule:: power_grid_model_io.functions.phase_to_phase ``` ## mappings diff --git a/pyproject.toml b/pyproject.toml index 2ab5455e..2d31d143 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 @@ -27,7 +27,7 @@ classifiers=[ "Operating System :: MacOS", "Topic :: Scientific/Engineering :: Physics", ] -requires-python = ">=3.9" +requires-python = ">=3.8" dependencies = [ "numpy>=1.20", "openpyxl", diff --git a/setup.py b/setup.py index 137640b7..84a4199b 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/sonar-project.properties b/sonar-project.properties index 30a6e435..6efc3958 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/src/power_grid_model_io/__init__.py b/src/power_grid_model_io/__init__.py index 7f6cad5b..19f65c87 100644 --- a/src/power_grid_model_io/__init__.py +++ b/src/power_grid_model_io/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/src/power_grid_model_io/config/__init__.py b/src/power_grid_model_io/config/__init__.py index 7f6cad5b..19f65c87 100644 --- a/src/power_grid_model_io/config/__init__.py +++ b/src/power_grid_model_io/config/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/src/power_grid_model_io/config/excel/__init__.py b/src/power_grid_model_io/config/excel/__init__.py index 7f6cad5b..19f65c87 100644 --- a/src/power_grid_model_io/config/excel/__init__.py +++ b/src/power_grid_model_io/config/excel/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/src/power_grid_model_io/config/excel/gaia_en.yaml b/src/power_grid_model_io/config/excel/gaia_en.yaml deleted file mode 100644 index c3dc0b29..00000000 --- a/src/power_grid_model_io/config/excel/gaia_en.yaml +++ /dev/null @@ -1,158 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project -# -# SPDX-License-Identifier: MPL-2.0 ---- -grid: - Nodes: - node: - id: Number - u_rated: Unom - extra: ID - Cables: - line: - id: Number - from_node: From.Number - from_status: From.Switch state - to_node: To.Number - to_status: To.Switch state - r1: 'Cables!R[Shortname=Type short]' - x1: 'Cables!X[Shortname=Type short]' - c1: 'Cables!C[Shortname=Type short]' - tan1: 'Cables!Tan_delta[Shortname=Type short]' - r0: 'Cables!R0[Shortname=Type short]' - x0: 'Cables!X0[Shortname=Type short]' - c0: 'Cables!C0[Shortname=Type short]' -# tan0: Tan_delta # is dit ook tan_Delta? Gaia expert? - i_n: Inom' - extra: - - Name - - ID -# Links: -# link: -# id: Number -# from_node: From.Number -# from_status: From.Switch state -# to_node: To.Number -# to_status: To.Switch state -# -# Transformers: -# transformer: -# id: Number -# from_node: From.Number -# from_status: From.Switch state -# to_node: To.Number -# to_status: To.Switch state -# u1: Unom1 -# u2: Unom2 -# sn: Snom # Cross reference Type r 47 tm 49 -# uk: uk -# pk: Pk -# -# i0: -# power_grid_model_io.filters.phase_to_phase.relative_no_load_current: -# - Inul # cross reference type r53 tm 55 -# - Pnul -# - Snom -# - Unom2 -# p0: Pnul -# winding_from: -# power_grid_model.conversion.filters.get_winding_from: -# - s1 # cross reference type -# - N1 #Navragen bij Gaia expert -# winding_to: -# power_grid_model.conversion.filters.get_winding_to: -# - s2 # cross reference type -# - N2 #Navragen bij Gaia expert -# clock: -# power_grid_model.conversion.filters.get_clock: -# - Clock # cross reference type -# tap_side: Tap side # cross reference type -# tap_pos: Tap -# tap_min: Tap min # cross reference type -# tap_max: Tap max # cross reference type -# tap_nom: Tap nom # cross reference type -# tap_size: Tap size # cross reference type -# uk_min: uk # cross reference type -# uk_max: uk # cross reference type -# pk_min: Pk # cross reference type -# pk_max: Pk # cross reference type -# r_grounding_from: Re1 # kan alleen Re vinden -# x_grounding_from: Xe1 # niet gevonden -# r_grounding_to: Re2 # kan alleen Re vinden -# x_grounding_to: Xe2 # niet gevonden -# extra: -# - Name -# - ID -# -# Sources: -# source: -# id: -# - Node.Number -# - Subnumber -# node: Node.Number -# status: Switch state -# u_ref: -# power_grid_model.conversion.filters.multiply: -# - Uprofile -# - urated -# u_ref: Uref # Uprofile / urated van node -# sk: Sk"nom -# -# Connections: # van connections naar loads, maak cable, node eind vd cable, load verbonden met de node bram vragen -# -# line: -# id: -# - Node.Number -# - Subnumber -# from_node: Node.Number -# to_node: -# - Node.Number -# - Subnumber -# node: -# id: -# - Node.Number -# - Subnumber -# sym_load: -# id: -# - Node.Number # niet gevonden -# - Subnumber # niet gevonden -# node: -# - Node.Number -# - Subnumber -# status: Switch state # niet gevonden -# type: Behaviour # niet gevonden -# p_specified: Load.P -# q_specified: -# Load.Q -# extra: -# - Name -# - ID # niet gevonden - -substitutions: #met bram doorlopen - Switch state: - 'off': 'off' #checken - in: 'in' #checken - N1: - none: false #niet gevonden - own: true #niet gevonden - N2: - none: false #niet gevonden - own: true #niet gevonden - Behaviour: #niet gevonden - Constant admittance: 1 - Constant impedance: 1 - ~Constant current: 2 - Constant power: 0 - Default: 0 - Industry: 0 - Business: 0 - Residential: 0 - Living: 0 - Tap side: - '1': 1 - '2': 2 - Q: # Q-regeling? 0,1,2 gaia manual - absorb: -1 - supply: 1 - Short type: - 4x50A: 50AL+25 diff --git a/src/power_grid_model_io/config/excel/vision_en.yaml b/src/power_grid_model_io/config/excel/vision_en.yaml index 8b28b83b..f99a0b23 100644 --- a/src/power_grid_model_io/config/excel/vision_en.yaml +++ b/src/power_grid_model_io/config/excel/vision_en.yaml @@ -142,23 +142,23 @@ grid: uk: uk pk: Pk i0: - power_grid_model_io.filters.phase_to_phase.relative_no_load_current: - - Io - - Po - - Snom - - Unom2 + power_grid_model_io.functions.phase_to_phase.relative_no_load_current: + i_0: Io + p_0: Po + s_nom: Snom + u_nom: Unom2 p0: Po winding_from: - power_grid_model_io.filters.phase_to_phase.get_winding_from: - - Connection - - N1 + power_grid_model_io.functions.phase_to_phase.get_winding_from: + conn_str: Connection + neutral_grounding: N1 winding_to: - power_grid_model_io.filters.phase_to_phase.get_winding_to: - - Connection - - N2 + power_grid_model_io.functions.phase_to_phase.get_winding_to: + conn_str: Connection + neutral_grounding: N2 clock: - power_grid_model_io.filters.phase_to_phase.get_clock: - - Connection + power_grid_model_io.functions.phase_to_phase.get_clock: + conn_str: Connection tap_side: Tap side tap_pos: Tap tap_min: Tap min @@ -199,11 +199,11 @@ grid: uk: uknom pk: Pknom i0: - power_grid_model_io.filters.phase_to_phase.relative_no_load_current: - - Io - - Po - - Snom - - Unom2 + power_grid_model_io.functions.phase_to_phase.relative_no_load_current: + i_0: Io + p_0: Po + s_nom: Snom + u_nom: Unom2 p0: Po winding_from: 0 winding_to: 0 @@ -253,17 +253,20 @@ grid: pk: Pk p0: Po i0: - power_grid_model_io.filters.phase_to_phase.relative_no_load_current: - - 0 - - Po - - Snom - - Unom2 + power_grid_model_io.functions.phase_to_phase.relative_no_load_current: + i_0: 0 + p_0: Po + s_nom: Snom + u_nom: Unom2 winding_from: - power_grid_model_io.filters.phase_to_phase.get_winding_from: Connection + power_grid_model_io.functions.phase_to_phase.get_winding_from: + conn_str: Connection winding_to: - power_grid_model_io.filters.phase_to_phase.get_winding_to: Connection + power_grid_model_io.functions.phase_to_phase.get_winding_to: + conn_str: Connection clock: - power_grid_model_io.filters.phase_to_phase.get_clock: Connection + power_grid_model_io.functions.phase_to_phase.get_clock: + conn_str: Connection tap_side: Tap side tap_pos: Tap tap_min: Tap min @@ -308,13 +311,21 @@ grid: status: Switch state type: Behaviour p_specified: - power_grid_model_io.filters.multiply: + multiply: - Load.P - - Nodes!Simultaneity[Number=Node.Number] + - reference: + query_column: Node.Number + other_table: Nodes + key_column: Number + value_column: Simultaneity q_specified: - power_grid_model_io.filters.multiply: + multiply: - Load.Q - - Nodes!Simultaneity[Number=Node.Number] + - reference: + query_column: Node.Number + other_table: Nodes + key_column: Number + value_column: Simultaneity extra: - ID - Name @@ -354,9 +365,9 @@ grid: type: 0 p_specified: PV.Pnom q_specified: - power_grid_model_io.filters.phase_to_phase.reactive_power: - - PV.Pnom - - 1 + power_grid_model_io.functions.phase_to_phase.reactive_power: + p: PV.Pnom + cos_phi: 1 extra: - ID - Name @@ -396,10 +407,10 @@ grid: type: 0 p_specified: Pref q_specified: - power_grid_model_io.filters.multiply: - - power_grid_model_io.filters.phase_to_phase.reactive_power: - - Pref - - cos phi + multiply: + - power_grid_model_io.functions.phase_to_phase.reactive_power: + p: Pref + cos_phi: cos phi - Q extra: - ID @@ -419,19 +430,22 @@ grid: status: Switch state type: 1 p_specified: - power_grid_model_io.filters.value_or_default: - - Pref - - power_grid_model_io.filters.phase_to_phase.power_wind_speed: - - Pnom - - Wind speed + power_grid_model_io.functions.value_or_default: + value: Pref + default: + power_grid_model_io.functions.phase_to_phase.power_wind_speed: + p_nom: Pnom + wind_speed: Wind speed q_specified: - power_grid_model_io.filters.phase_to_phase.reactive_power: - - power_grid_model_io.filters.value_or_default: - - Pref - - power_grid_model_io.filters.phase_to_phase.power_wind_speed: - - Pnom - - Wind speed - - cos phi + power_grid_model_io.functions.phase_to_phase.reactive_power: + p: + power_grid_model_io.functions.value_or_default: + value: Pref + default: + power_grid_model_io.functions.phase_to_phase.power_wind_speed: + p_nom: Pnom + wind_speed: Wind speed + cos_phi: cos phi extra: - ID - Name @@ -450,13 +464,21 @@ grid: status: Switch state type: Behaviour p_specified: - power_grid_model_io.filters.multiply: + multiply: - P - - Nodes!Simultaneity[Number=Node.Number] + - reference: + query_column: Node.Number + other_table: Nodes + key_column: Number + value_column: Simultaneity q_specified: - power_grid_model_io.filters.multiply: + multiply: - Q - - Nodes!Simultaneity[Number=Node.Number] + - reference: + query_column: Node.Number + other_table: Nodes + key_column: Number + value_column: Simultaneity extra: - ID - Name @@ -476,13 +498,13 @@ grid: g1: 0 b1: 0 g0: - power_grid_model_io.filters.complex_inverse_real_part: - - R0 - - X0 + power_grid_model_io.functions.complex_inverse_real_part: + real: R0 + imag: X0 b0: - power_grid_model_io.filters.complex_inverse_imaginary_part: - - R0 - - X0 + power_grid_model_io.functions.complex_inverse_imaginary_part: + real: R0 + imag: X0 extra: - ID - Name @@ -501,9 +523,9 @@ grid: status: Switch state g1: 0 b1: - power_grid_model_io.filters.phase_to_phase.reactive_power_to_susceptance: - - Q - - Unom + power_grid_model_io.functions.phase_to_phase.reactive_power_to_susceptance: + q: Q + u_nom: Unom g0: 0 b0: 0 extra: @@ -524,10 +546,10 @@ grid: status: Switch state g1: 0 b1: - power_grid_model_io.filters.multiply: - - power_grid_model_io.filters.phase_to_phase.reactive_power_to_susceptance: - - Q - - Unom + multiply: + - power_grid_model_io.functions.phase_to_phase.reactive_power_to_susceptance: + q: Q + u_nom: Unom - -1 g0: 0 b0: 0 @@ -549,38 +571,42 @@ grid: status: Switch state type: 0 p_specified: - power_grid_model_io.filters.multiply: + multiply: - min: - - Pnom - - Inverter.Pnom | Inverter.Snom + - Pnom + - Inverter.Pnom | Inverter.Snom - Scaling q_specified: - power_grid_model_io.filters.multiply: - - power_grid_model_io.filters.phase_to_phase.reactive_power: - - min: - - Pnom - - Inverter.Pnom | Inverter.Snom - - Inverter.cos phi + multiply: + - power_grid_model_io.functions.phase_to_phase.reactive_power: + p: + min: + - Pnom + - Inverter.Pnom | Inverter.Snom + cos_phi: Inverter.cos phi - Scaling extra: - ID - Name units: - A: + A: null F: µF: 0.000_001 V: kV: 1_000.0 VA: + kVA: 1_000.0 MVA: 1_000_000.0 VAR: + kvar: 1_000.0 Mvar: 1_000_000.0 W: kW: 1_000.0 MW: 1_000_000.0 Wp: + kWp: 1_000.0 MWp: 1_000_000.0 - m/s: + m/s: null ohm: Ohm: 1.0 ohm/m: @@ -589,7 +615,6 @@ units: pu: 1.0 "%": 0.01 "‰": 0.001 - substitutions: ".*Switch state": "off": 0 diff --git a/src/power_grid_model_io/config/excel/vision_nl.yaml b/src/power_grid_model_io/config/excel/vision_nl.yaml new file mode 100644 index 00000000..5b6af837 --- /dev/null +++ b/src/power_grid_model_io/config/excel/vision_nl.yaml @@ -0,0 +1,643 @@ +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project +# +# SPDX-License-Identifier: MPL-2.0 +--- +grid: + Knooppunten: + node: + id: + auto_id: + key: Nummer + u_rated: Unom + extra: + - ID + - Naam + Kabels: + line: + id: + auto_id: + key: Nummer + from_node: + auto_id: + table: Knooppunten + key: + Nummer: Van.Nummer + from_status: Van.Schakelstand + to_node: + auto_id: + table: Knooppunten + key: + Nummer: Naar.Nummer + to_status: Naar.Schakelstand + r1: R + x1: X + c1: C + tan1: 0 + r0: R0 + x0: X0 + c0: C0 + tan0: 0 + i_n: Inom' + extra: + - ID + - Naam + Verbindingen: + line: + id: + auto_id: + key: Nummer + from_node: + auto_id: + table: Knooppunten + key: + Nummer: Van.Nummer + from_status: Van.Schakelstand + to_node: + auto_id: + table: Knooppunten + key: + Nummer: Naar.Nummer + to_status: Naar.Schakelstand + r1: R + x1: X + c1: C + tan1: 0 + r0: R0 + x0: X0 + c0: C0 + tan0: 0 + i_n: Inom' + extra: + - ID + - Naam + Links: + link: + id: + auto_id: + key: Nummer + from_node: + auto_id: + table: Knooppunten + key: + Nummer: Van.Nummer + from_status: Van.Schakelstand + to_node: + auto_id: + table: Knooppunten + key: + Nummer: Naar.Nummer + to_status: Naar.Schakelstand + extra: + - ID + - Naam + Smoorspoelen: + line: + id: + auto_id: + key: Nummer + from_node: + auto_id: + table: Knooppunten + key: + Nummer: Van.Nummer + from_status: Van.Schakelstand + to_node: + auto_id: + table: Knooppunten + key: + Nummer: Naar.Nummer + to_status: Naar.Schakelstand + r1: R + x1: X + c1: 0 + tan1: 0 + r0: R0 + x0: X0 + c0: 0 + tan0: 0 + i_n: Inom + extra: + - ID + - Naam + Transformatoren: + transformer: + id: + auto_id: + key: Nummer + from_node: + auto_id: + table: Knooppunten + key: + Nummer: Van.Nummer + from_status: Van.Schakelstand + to_node: + auto_id: + table: Knooppunten + key: + Nummer: Naar.Nummer + to_status: Naar.Schakelstand + u1: Unom1 + u2: Unom2 + sn: Snom + uk: uk + pk: Pk + i0: + power_grid_model_io.functions.phase_to_phase.relative_no_load_current: + i_0: Inul + p_0: Pnul + s_nom: Snom + u_nom: Unom2 + p0: Pnul + winding_from: + power_grid_model_io.functions.phase_to_phase.get_winding_from: + conn_str: Schakeling + neutral_grounding: N1 + winding_to: + power_grid_model_io.functions.phase_to_phase.get_winding_to: + conn_str: Schakeling + neutral_grounding: N1 + clock: + power_grid_model_io.functions.phase_to_phase.get_clock: + conn_str: Schakeling + tap_side: Trapzijde + tap_pos: Trapstand + tap_min: Trapmin + tap_max: Trapmax + tap_nom: Trapnom + tap_size: Trapgrootte + uk_min: uk + uk_max: uk + pk_min: Pk + pk_max: Pk + r_grounding_from: Ra1 + x_grounding_from: Xa1 + r_grounding_to: Ra2 + x_grounding_to: Xa2 + extra: + - ID + - Naam + Speciale transformatoren: + transformer: + id: + auto_id: + key: Nummer + from_node: + auto_id: + table: Knooppunten + key: + Nummer: Van.Nummer + from_status: Van.Schakelstand + to_node: + auto_id: + table: Knooppunten + key: + Nummer: Naar.Nummer + to_status: Naar.Schakelstand + u1: Unom1 + u2: Unom2 + sn: Snom + uk: uknom + pk: Pknom + i0: + power_grid_model_io.functions.phase_to_phase.relative_no_load_current: + i_0: Inul + p_0: Pnul + s_nom: Snom + u_nom: Unom2 + p0: Pnul + winding_from: 0 + winding_to: 0 + clock: 0 + tap_side: Trapzijde + tap_pos: Trapstand + tap_min: Trapmin + tap_max: Trapmax + tap_nom: Trapnom + tap_size: Trapgrootte + uk_min: ukmin + uk_max: ukmax + pk_min: Pkmin + pk_max: Pkmax + r_grounding_from: 0 + x_grounding_from: 0 + r_grounding_to: 0 + x_grounding_to: 0 + extra: + - ID + - Naam + Transformatorbelastingen: + transformer: + id: + auto_id: + name: transformer + key: + - Knooppunt.Nummer + - Subnummer + from_node: + auto_id: + table: Knooppunten + key: + Nummer: Knooppunt.Nummer + to_node: + auto_id: + name: internal_node + key: + - Knooppunt.Nummer + - Subnummer + from_status: Schakelstand + to_status: 1 + u1: Unom1 + u2: Unom2 + sn: Snom + uk: uk + pk: Pk + p0: Pnul + i0: + power_grid_model_io.functions.phase_to_phase.relative_no_load_current: + i_0: 0 + p_0: Pnul + s_nom: Snom + u_nom: Unom2 + winding_from: + power_grid_model_io.functions.phase_to_phase.get_winding_from: + conn_str: Schakeling + winding_to: + power_grid_model_io.functions.phase_to_phase.get_winding_to: + conn_str: Schakeling + clock: + power_grid_model_io.functions.phase_to_phase.get_clock: + conn_str: Schakeling + tap_side: Trapzijde + tap_pos: Trapstand + tap_min: Trapmin + tap_max: Trapmax + tap_nom: Trapnom + tap_size: Trapgrootte + uk_min: uk + uk_max: uk + pk_min: Pk + pk_max: Pk + r_grounding_from: 0 + x_grounding_from: 0 + r_grounding_to: 0 + x_grounding_to: 0 + extra: + - ID + - Naam + node: + id: + auto_id: + name: internal_node + key: + - Knooppunt.Nummer + - Subnummer + u_rated: Unom2 + extra: + - ID + - Naam + sym_load: + id: + auto_id: + name: load + key: + - Knooppunt.Nummer + - Subnummer + node: + auto_id: + name: internal_node + key: + - Knooppunt.Nummer + - Subnummer + status: Schakelstand + type: Gedrag + p_specified: + multiply: + - Belasting.P + - reference: + query_column: Knooppunt.Nummer + other_table: Knooppunten + key_column: Nummer + value_column: Gelijktijdigheid + q_specified: + multiply: + - Belasting.Q + - reference: + query_column: Knooppunt.Nummer + other_table: Knooppunten + key_column: Nummer + value_column: Gelijktijdigheid + extra: + - ID + - Naam + sym_gen: + - id: + auto_id: + name: generation + key: + - Knooppunt.Nummer + - Subnummer + node: + auto_id: + name: internal_node + key: + - Knooppunt.Nummer + - Subnummer + status: Schakelstand + type: 0 + p_specified: Opwekking.P + q_specified: Opwekking.Q + extra: + - ID + - Naam + - id: + auto_id: + name: pv_generation + key: + - Knooppunt.Nummer + - Subnummer + node: + auto_id: + name: internal_node + key: + - Knooppunt.Nummer + - Subnummer + status: Schakelstand + type: 0 + p_specified: PV.Pnom + q_specified: + power_grid_model_io.functions.phase_to_phase.reactive_power: + p: PV.Pnom + cos_phi: 1 + extra: + - ID + - Naam + Netvoedingen: + source: + id: + auto_id: + key: + - Knooppunt.Nummer + - Subnummer + node: + auto_id: + table: Knooppunten + key: + Nummer: Knooppunt.Nummer + status: Schakelstand + u_ref: Uref + sk: Sk"nom + rx_ratio: R/X + z01_ratio: Z0/Z1 + extra: + - ID + - Naam + Synchrone generatoren: + sym_gen: + id: + auto_id: + key: + - Knooppunt.Nummer + - Subnummer + node: + auto_id: + table: Knooppunten + key: + Nummer: Knooppunt.Nummer + status: Schakelstand + type: 0 + p_specified: Pref + q_specified: + multiply: + - power_grid_model_io.functions.phase_to_phase.reactive_power: + p: Pref + cos_phi: cos phi + - Q + extra: + - ID + - Naam + Windturbines: + sym_gen: + id: + auto_id: + key: + - Knooppunt.Nummer + - Subnummer + node: + auto_id: + table: Knooppunten + key: + Nummer: Knooppunt.Nummer + status: Schakelstand + type: 0 + p_specified: + power_grid_model_io.functions.value_or_default: + value: Pref + default: + power_grid_model_io.functions.phase_to_phase.power_wind_speed: + p_nom: Pnom + wind_speed: Windsnelheid + q_specified: + power_grid_model_io.functions.phase_to_phase.reactive_power: + p: + power_grid_model_io.functions.value_or_default: + value: Pref + default: + power_grid_model_io.functions.phase_to_phase.power_wind_speed: + p_nom: Pnom + wind_speed: Windsnelheid + cos_phi: cos phi + extra: + - ID + - Naam + + Belastingen: + sym_load: + id: + auto_id: + key: + - Knooppunt.Nummer + - Subnummer + node: + auto_id: + table: Knooppunten + key: + Nummer: Knooppunt.Nummer + status: Schakelstand + type: Gedrag + p_specified: + multiply: + - P + - reference: + query_column: Knooppunt.Nummer + other_table: Knooppunten + key_column: Nummer + value_column: Gelijktijdigheid + q_specified: + multiply: + - Q + - reference: + query_column: Knooppunt.Nummer + other_table: Knooppunten + key_column: Nummer + value_column: Gelijktijdigheid + extra: + - ID + - Naam + Nulpuntstransformatoren: + shunt: + id: + auto_id: + key: + - Knooppunt.Nummer + - Subnummer + node: + auto_id: + table: Knooppunten + key: + Nummer: Knooppunt.Nummer + status: Schakelstand + g1: 0 + b1: 0 + g0: + power_grid_model_io.functions.complex_inverse_real_part: + real: R0 + imag: X0 + b0: + power_grid_model_io.functions.complex_inverse_imaginary_part: + real: R0 + imag: X0 + extra: + - ID + - Naam + Condensatoren: + shunt: + id: + auto_id: + key: + - Knooppunt.Nummer + - Subnummer + node: + auto_id: + table: Knooppunten + key: + Nummer: Knooppunt.Nummer + status: Schakelstand + g1: 0 + b1: + power_grid_model_io.functions.phase_to_phase.reactive_power_to_susceptance: + q: Q + u_nom: Unom + g0: 0 + b0: 0 + extra: + - ID + - Naam + Spoelen: + shunt: + id: + auto_id: + key: + - Knooppunt.Nummer + - Subnummer + node: + auto_id: + table: Knooppunten + key: + Nummer: Knooppunt.Nummer + status: Schakelstand + g1: 0 + b1: + multiply: + - power_grid_model_io.functions.phase_to_phase.reactive_power_to_susceptance: + q: Q + u_nom: Unom + - -1 + g0: 0 + b0: 0 + extra: + - ID + - Naam + Pv's: + sym_gen: + id: + auto_id: + key: + - Knooppunt.Nummer + - Subnummer + node: + auto_id: + table: Knooppunten + key: + Nummer: Knooppunt.Nummer + status: Schakelstand + type: 0 + p_specified: + multiply: + - min: + - Pnom + - Inverter.Pnom | Inverter.Snom + - Schaling + q_specified: + multiply: + - power_grid_model_io.functions.phase_to_phase.reactive_power: + p: + min: + - Pnom + - Inverter.Pnom | Inverter.Snom + cos_phi: Inverter.cos phi + - Schaling + extra: + - ID + - Naam +units: + A: + F: + µF: 0.000_001 + V: + kV: 1_000.0 + VA: + kVA: 1_000.0 + MVA: 1_000_000.0 + VAR: + kvar: 1_000.0 + Mvar: 1_000_000.0 + W: + kW: 1_000.0 + MW: 1_000_000.0 + Wp: + kWp: 1_000.0 + MWp: 1_000_000.0 + m/s: + ohm: + Ohm: 1.0 + ohm/m: + ohm/km: 0.001 + one: + pu: 1.0 + "%": 0.01 + "‰": 0.001 + +substitutions: + ".*Schakelstand": + "uit": 0 + "in": 1 + N1: + geen: false + eigen: true + N2: + geen: false + eigen: true + Gedrag: + Constante impedantie: 1 + ~Constante stroom: 2 + Constant vermogen: 0 + Default: 0 + Industrie: 0 + Zakelijk: 0 + Wonen: 0 + Trapzijde: + 1: 0 + 2: 1 + Synchrone generatoren.Q: + terugleveren: -1 + leveren: 1 \ No newline at end of file diff --git a/src/power_grid_model_io/converters/__init__.py b/src/power_grid_model_io/converters/__init__.py index 11124bc0..73baae0d 100644 --- a/src/power_grid_model_io/converters/__init__.py +++ b/src/power_grid_model_io/converters/__init__.py @@ -1,10 +1,9 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ Converters """ -from power_grid_model_io.converters.gaia_excel_converter import GaiaExcelConverter from power_grid_model_io.converters.pgm_json_converter import PgmJsonConverter from power_grid_model_io.converters.vision_excel_converter import VisionExcelConverter diff --git a/src/power_grid_model_io/converters/base_converter.py b/src/power_grid_model_io/converters/base_converter.py index e2842851..1b4dc884 100644 --- a/src/power_grid_model_io/converters/base_converter.py +++ b/src/power_grid_model_io/converters/base_converter.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ diff --git a/src/power_grid_model_io/converters/gaia_excel_converter.py b/src/power_grid_model_io/converters/gaia_excel_converter.py deleted file mode 100644 index c9dd5105..00000000 --- a/src/power_grid_model_io/converters/gaia_excel_converter.py +++ /dev/null @@ -1,35 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project -# -# SPDX-License-Identifier: MPL-2.0 -""" -Gaia Excel Converter: Load data from a Gaia Excel export file and use a mapping file to convert the data to PGM -""" - -from pathlib import Path -from typing import Optional, Union - -from power_grid_model_io.converters.tabular_converter import TabularConverter -from power_grid_model_io.data_stores.gaia_excel_file_store import GaiaExcelFileStore - -DEFAULT_MAPPING_FILE = Path(__file__).parent.parent / "config" / "excel" / "gaia_{language:s}.yaml" - - -class GaiaExcelConverter(TabularConverter): - """ - Gaia Excel Converter: Load data from a Gaia Excel export file and use a mapping file to convert the data to PGM - """ - - def __init__( - self, - source_file: Optional[Union[Path, str]] = None, - types_file: Optional[Union[Path, str]] = None, - language: str = "en", - ): - mapping_file = Path(str(DEFAULT_MAPPING_FILE).format(language=language)) - if not mapping_file.exists(): - raise FileNotFoundError(f"No Gaia Excel mapping available for language '{language}'") - source = None - if source_file: - types_file = Path(types_file) if types_file else None - source = GaiaExcelFileStore(file_path=Path(source_file), types_file=types_file) - super().__init__(mapping_file=mapping_file, source=source) diff --git a/src/power_grid_model_io/converters/pgm_json_converter.py b/src/power_grid_model_io/converters/pgm_json_converter.py index ba3736f0..ea2ad9ca 100644 --- a/src/power_grid_model_io/converters/pgm_json_converter.py +++ b/src/power_grid_model_io/converters/pgm_json_converter.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ @@ -233,8 +233,14 @@ def _serialize_dataset(data: SingleDataset, extra_info: Optional[ExtraInfoLookup # For example: {"node": [{"id": 0, ...}, {"id": 1, ...}], "line": [{"id": 2, ...}]} return { component: [ - {attribute: obj[attribute].tolist() for attribute in objects.dtype.names if not is_nan(obj[attribute])} - | extra_info.get(obj["id"], {}) + dict( + **{ + attribute: obj[attribute].tolist() + for attribute in objects.dtype.names + if not is_nan(obj[attribute]) + }, + **extra_info.get(obj["id"], {}), + ) for obj in objects ] for component, objects in data.items() diff --git a/src/power_grid_model_io/converters/tabular_converter.py b/src/power_grid_model_io/converters/tabular_converter.py index 1606cc93..7b6c04de 100644 --- a/src/power_grid_model_io/converters/tabular_converter.py +++ b/src/power_grid_model_io/converters/tabular_converter.py @@ -1,11 +1,10 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ Tabular Data Converter: Load data from multiple tables and use a mapping file to convert the data to PGM """ -import re from pathlib import Path from typing import Any, Dict, List, Literal, Optional, Union, cast @@ -24,29 +23,6 @@ from power_grid_model_io.mappings.value_mapping import ValueMapping, Values from power_grid_model_io.utils.modules import get_function -COL_REF_RE = re.compile(r"^([^!]+)!([^\[]+)\[(([^!]+)!)?([^=]+)=(([^!]+)!)?([^]]+)]$") -r""" -Regular expressions to match patterns like: - OtherTable!ValueColumn[IdColumn=RefColumn] -and: - OtherTable!ValueColumn[OtherTable!IdColumn=ThisTable!RefColumn] - -^ Start of the string -([^!]+) OtherTable -! separator -([^\[]+) ValueColumn -[ separator -([^\[]+) ValueColumn -(([^!]+)!)? OtherTable + separator! (optional) -([^=]+) IdColumn -= separator -(([^!]+)!)? ThisTable + separator! (optional) -= separator -([^]]+) -] separator -$ End of the string -""" - MappingFile = Dict[Literal["multipliers", "grid", "units", "substitutions"], Union[Multipliers, Tables, Units, Values]] @@ -133,7 +109,7 @@ def _parse_data(self, data: TabularData, data_type: str, extra_info: Optional[Ex # For each table in the mapping for table in self._mapping.tables(): if table not in data or len(data[table]) == 0: - continue + continue # pragma: no cover (bug in python 3.9) for component, attributes in self._mapping.instances(table=table): component_data = self._convert_table_to_component( data=data, @@ -367,8 +343,6 @@ def _parse_col_def( if isinstance(col_def, (int, float)): return self._parse_col_def_const(data=data, table=table, col_def=col_def) if isinstance(col_def, str): - if COL_REF_RE.fullmatch(col_def) is not None: - return self._parse_col_def_column_reference(data=data, table=table, col_def=col_def) return self._parse_col_def_column_name(data=data, table=table, col_def=col_def) if isinstance(col_def, dict): return self._parse_col_def_filter(data=data, table=table, col_def=col_def, extra_info=extra_info) @@ -434,40 +408,29 @@ def _apply_multiplier(self, table: str, column: str, data: pd.Series) -> pd.Seri except KeyError: return data - def _parse_col_def_column_reference(self, data: TabularData, table: str, col_def: str) -> pd.DataFrame: - """Find and extract a column from a different table. + def _parse_reference( + self, data: TabularData, table: str, other_table: str, query_column: str, key_column: str, value_column: str + ) -> pd.DataFrame: + """ + Find and extract a column from a different table. Args: - data: TabularData: - table: str: - col_def: str: + data: The data + table: The current table named + other_table: The table in which we would like to find a value + key_column: The column in the current table that stores the keys + query_column: The column in the other table in which we should look for the keys + value_column: The column in the other table which stores the values that we would like to return Returns: """ - # pylint: disable=too-many-locals - assert isinstance(col_def, str) - match = COL_REF_RE.fullmatch(col_def) - if match is None: - raise ValueError( - f"Invalid column reference '{col_def}' " "(should be 'OtherTable!ValueColumn[IdColumn=RefColumn])" - ) - other_table, value_col_name, _, other_table_, id_col_name, _, this_table_, ref_col_name = match.groups() - if (other_table_ is not None and other_table_ != other_table) or ( - this_table_ is not None and this_table_ != table - ): - raise ValueError( - f"Invalid column reference '{col_def}'.\n" - "It should be something like " - f"{other_table}!{value_col_name}[{other_table}!{{id_column}}={table}!{{ref_column}}] " - f"or simply {other_table}!{value_col_name}[{{id_column}}={{ref_column}}]" - ) - ref_column = self._parse_col_def_column_name(data=data, table=table, col_def=ref_col_name) - id_column = self._parse_col_def_column_name(data=data, table=other_table, col_def=id_col_name) - val_column = self._parse_col_def_column_name(data=data, table=other_table, col_def=value_col_name) - other = pd.concat([id_column, val_column], axis=1) - result = ref_column.merge(other, how="left", left_on=ref_col_name, right_on=id_col_name) - return result[[value_col_name]] + queries = self._parse_col_def_column_name(data=data, table=table, col_def=query_column) + keys = self._parse_col_def_column_name(data=data, table=other_table, col_def=key_column) + values = self._parse_col_def_column_name(data=data, table=other_table, col_def=value_column) + other = pd.concat([keys, values], axis=1) + result = queries.merge(other, how="left", left_on=query_column, right_on=key_column) + return result[[value_column]] def _parse_col_def_filter( self, data: TabularData, table: str, col_def: Dict[str, Any], extra_info: Optional[ExtraInfoLookup] @@ -480,8 +443,12 @@ def _parse_col_def_filter( for name, sub_def in col_def.items(): if name == "auto_id": # Check that "key" is in the definition and no other keys than "table" and "name" - if "key" not in sub_def or len(set(sub_def.keys()) & {"table", "name"}) > 2: - raise ValueError(f"Invalid auto_id definition: {sub_def}") + if ( + not isinstance(sub_def, dict) + or "key" not in sub_def + or len(set(sub_def.keys()) & {"table", "name"}) > 2 + ): + raise ValueError(f"Invalid {name} definition: {sub_def}") col_data = self._parse_auto_id( data=data, table=table, @@ -490,16 +457,29 @@ def _parse_col_def_filter( key_col_def=sub_def["key"], extra_info=extra_info, ) - elif name == "multiply": - raise NotImplementedError(f"Column filter '{name}' not implemented") - elif name == "function": - raise NotImplementedError(f"Column filter '{name}' not implemented") elif name == "reference": - raise NotImplementedError(f"Column filter '{name}' not implemented") - else: # For now, fall back to legacy function definition - col_data = self._parse_col_def_function(data=data, table=table, col_def={name: sub_def}) - # TODO: raise NotImplementedError(f"Column filter '{name}' not implemented") if "function" is - # implemented + # Check that (only) the required keys are in the definition + if not isinstance(sub_def, dict) or { + "other_table", + "query_column", + "key_column", + "value_column", + } != set(sub_def.keys()): + raise ValueError(f"Invalid {name} definition: {sub_def}") + return self._parse_reference( + data=data, + table=table, + other_table=sub_def["other_table"], + query_column=sub_def["query_column"], + key_column=sub_def["key_column"], + value_column=sub_def["value_column"], + ) + elif isinstance(sub_def, list): + col_data = self._parse_pandas_function(data=data, table=table, function=name, col_def=sub_def) + elif isinstance(sub_def, dict): + col_data = self._parse_function(data=data, table=table, function=name, col_def=sub_def) + else: + raise TypeError(f"Invalid {name} definition: {sub_def}") data_frames.append(col_data) return pd.concat(data_frames, axis=1) @@ -566,29 +546,60 @@ def auto_id(row: np.ndarray): return col_data.apply(auto_id, axis=1, raw=True) - def _parse_col_def_function(self, data: TabularData, table: str, col_def: Dict[str, Any]) -> pd.DataFrame: - """Import the function by name and apply it to each row. The column definition may contain multiple functions, - a DataFrame with one column per function will be returned. + def _parse_pandas_function(self, data: TabularData, table: str, function: str, col_def: List[Any]) -> pd.DataFrame: + """Special vectorized functions. Args: - data: TabularData: - table: str: - col_def: Dict[str: - str]: + data: The data + table: The name of the current table + function: The name of the function. + col_def: The definition of the function arguments + + Returns: + + """ + assert isinstance(col_def, list) + + # "multiply" is an alias for "prod" + if function == "multiply": + function = "prod" + + col_data = self._parse_col_def(data=data, table=table, col_def=col_def, extra_info=None) + + try: + fn_ptr = getattr(col_data, function) + except AttributeError as ex: + raise ValueError(f"Pandas DataFrame has no function '{function}'") from ex + + try: + return pd.DataFrame(fn_ptr(axis=1)) + except TypeError as ex: + raise ValueError(f"Invalid pandas function DataFrame.{function}") from ex + + def _parse_function(self, data: TabularData, table: str, function: str, col_def: Dict[str, Any]) -> pd.DataFrame: + """Import the function by name and apply it to each row. + + Args: + data: The data + table: The name of the current table + function: The name (or path) of the function. + col_def: The definition of the function keyword arguments Returns: """ assert isinstance(col_def, dict) - data_frame = [] - for fn_name, sub_def in col_def.items(): - fn_ptr = get_function(fn_name) - col_data = self._parse_col_def(data=data, table=table, col_def=sub_def, extra_info=None) - if col_data.empty: - raise ValueError(f"Cannot apply function {fn_name} to an empty DataFrame") - col_data = col_data.apply(lambda row, fn=fn_ptr: fn(*row), axis=1, raw=True) - data_frame.append(col_data) - return pd.concat(data_frame, axis=1) + + fn_ptr = get_function(function) + key_words = list(col_def.keys()) + sub_def = list(col_def.values()) + col_data = self._parse_col_def(data=data, table=table, col_def=sub_def, extra_info=None) + + if col_data.empty: + raise ValueError(f"Cannot apply function {function} to an empty DataFrame") + + col_data = col_data.apply(lambda row, fn=fn_ptr: fn(**dict(zip(key_words, row))), axis=1, raw=True) + return pd.DataFrame(col_data) def _parse_col_def_composite(self, data: TabularData, table: str, col_def: list) -> pd.DataFrame: """Select multiple columns (each is created from a column definition) and return them as a new DataFrame. diff --git a/src/power_grid_model_io/converters/vision_excel_converter.py b/src/power_grid_model_io/converters/vision_excel_converter.py index 2aa9e3b9..632234df 100644 --- a/src/power_grid_model_io/converters/vision_excel_converter.py +++ b/src/power_grid_model_io/converters/vision_excel_converter.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ diff --git a/src/power_grid_model_io/data_stores/__init__.py b/src/power_grid_model_io/data_stores/__init__.py index 7f6cad5b..19f65c87 100644 --- a/src/power_grid_model_io/data_stores/__init__.py +++ b/src/power_grid_model_io/data_stores/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/src/power_grid_model_io/data_stores/base_data_store.py b/src/power_grid_model_io/data_stores/base_data_store.py index 4fcd0af6..23b3b90d 100644 --- a/src/power_grid_model_io/data_stores/base_data_store.py +++ b/src/power_grid_model_io/data_stores/base_data_store.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ diff --git a/src/power_grid_model_io/data_stores/excel_file_store.py b/src/power_grid_model_io/data_stores/excel_file_store.py index b85c3d2c..172568db 100644 --- a/src/power_grid_model_io/data_stores/excel_file_store.py +++ b/src/power_grid_model_io/data_stores/excel_file_store.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ diff --git a/src/power_grid_model_io/data_stores/gaia_excel_file_store.py b/src/power_grid_model_io/data_stores/gaia_excel_file_store.py deleted file mode 100644 index 29169834..00000000 --- a/src/power_grid_model_io/data_stores/gaia_excel_file_store.py +++ /dev/null @@ -1,33 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project -# -# SPDX-License-Identifier: MPL-2.0 -""" -Gaia Excel file store -""" - -from pathlib import Path -from typing import Optional - -from power_grid_model_io.data_stores.excel_file_store import ExcelFileStore - - -class GaiaExcelFileStore(ExcelFileStore): - """ - Gaia Excel file store - - Gaia Excel exports are quite similar to Vision Excel exports. The names of the sheets and columns are quite - different though, but that will be solved in the mapping file for the TabularConverter. Another difference is - that Vision exports only one Excel file, where Gaia outputs a separate Excel file containing cable types etc. - """ - - def __init__(self, file_path: Path, types_file: Optional[Path] = None): - """ - Args: - file_path: The main Gaia Excel export file - types_file: The Excel file storing cable types etc. - """ - if types_file is None: - super().__init__(file_path) - else: - super().__init__(file_path, types=types_file) - self._header_rows.append(1) # Units are stored in the row below the column names diff --git a/src/power_grid_model_io/data_stores/json_file_store.py b/src/power_grid_model_io/data_stores/json_file_store.py index 7f305b48..f307e9fe 100644 --- a/src/power_grid_model_io/data_stores/json_file_store.py +++ b/src/power_grid_model_io/data_stores/json_file_store.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ diff --git a/src/power_grid_model_io/data_stores/vision_excel_file_store.py b/src/power_grid_model_io/data_stores/vision_excel_file_store.py index 5f0b5d81..20df8cd6 100644 --- a/src/power_grid_model_io/data_stores/vision_excel_file_store.py +++ b/src/power_grid_model_io/data_stores/vision_excel_file_store.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ diff --git a/src/power_grid_model_io/data_types/__init__.py b/src/power_grid_model_io/data_types/__init__.py index e1b82393..a0a45c0a 100644 --- a/src/power_grid_model_io/data_types/__init__.py +++ b/src/power_grid_model_io/data_types/__init__.py @@ -1,8 +1,8 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ -Common data types used in the Power Grid Model IO project +Common data types used in the Power Grid Model project """ from power_grid_model_io.data_types._data_types import ExtraInfo, ExtraInfoLookup, StructuredData diff --git a/src/power_grid_model_io/data_types/_data_types.py b/src/power_grid_model_io/data_types/_data_types.py index 141e5699..ae8d0a30 100644 --- a/src/power_grid_model_io/data_types/_data_types.py +++ b/src/power_grid_model_io/data_types/_data_types.py @@ -1,8 +1,8 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ -Common data types used in the Power Grid Model IO project +Common data types used in the Power Grid Model project """ from typing import Any, Dict, List, Union diff --git a/src/power_grid_model_io/data_types/tabular_data.py b/src/power_grid_model_io/data_types/tabular_data.py index 38fbb902..a4186502 100644 --- a/src/power_grid_model_io/data_types/tabular_data.py +++ b/src/power_grid_model_io/data_types/tabular_data.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ diff --git a/src/power_grid_model_io/filters/__init__.py b/src/power_grid_model_io/functions/__init__.py similarity index 69% rename from src/power_grid_model_io/filters/__init__.py rename to src/power_grid_model_io/functions/__init__.py index 94dfe471..f49d06c7 100644 --- a/src/power_grid_model_io/filters/__init__.py +++ b/src/power_grid_model_io/functions/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 @@ -6,17 +6,13 @@ These functions can be used in the mapping files to apply functions to tabular data """ -from power_grid_model_io.filters._filters import ( - all_true, - any_true, +from power_grid_model_io.functions._functions import ( complex_inverse_imaginary_part, complex_inverse_real_part, degrees_to_clock, get_winding, has_value, is_greater_than, - multiply, - subtract, value_or_default, value_or_zero, ) diff --git a/src/power_grid_model_io/filters/_filters.py b/src/power_grid_model_io/functions/_functions.py similarity index 76% rename from src/power_grid_model_io/filters/_filters.py rename to src/power_grid_model_io/functions/_functions.py index 3aab7489..144d0568 100644 --- a/src/power_grid_model_io/filters/_filters.py +++ b/src/power_grid_model_io/functions/_functions.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 @@ -6,7 +6,6 @@ These functions can be used in the mapping files to apply functions to tabular data """ -import math from typing import Any, Optional, TypeVar, cast import numpy as np @@ -23,13 +22,6 @@ } -def multiply(*args: float): - """ - Multiply all arguments. - """ - return math.prod(args) - - def has_value(value: Any) -> bool: """ Return True if the value is not None, NaN or empty string. @@ -89,29 +81,6 @@ def degrees_to_clock(degrees: float) -> int: return int(round(degrees / 30.0)) % 12 -def subtract(value: float, *args: float) -> float: - """ - Return a value after subtracting all the arguments from the first argument - """ - for arg in args: - value -= arg - return value - - -def all_true(*args) -> bool: - """ - Return true if all values are true - """ - return all(args) and not any(np.isnan(x) for x in args) - - -def any_true(*args) -> bool: - """ - Return true if at least one of the values is true - """ - return any(x and not np.isnan(x) for x in args) - - def is_greater_than(left_side, right_side) -> bool: """ Return true if the first argument is greater than the second diff --git a/src/power_grid_model_io/filters/phase_to_phase.py b/src/power_grid_model_io/functions/phase_to_phase.py similarity index 77% rename from src/power_grid_model_io/filters/phase_to_phase.py rename to src/power_grid_model_io/functions/phase_to_phase.py index 52eee685..ebe194f7 100644 --- a/src/power_grid_model_io/filters/phase_to_phase.py +++ b/src/power_grid_model_io/functions/phase_to_phase.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ @@ -11,7 +11,7 @@ from power_grid_model import WindingType -from power_grid_model_io.filters import get_winding +from power_grid_model_io.functions import get_winding CONNECTION_PATTERN = re.compile(r"(Y|YN|D|Z|ZN)(y|yn|d|z|zn)(\d|1[0-2])") @@ -35,11 +35,11 @@ def reactive_power(p: float, cos_phi: float) -> float: def power_wind_speed( # pylint: disable=too-many-arguments p_nom: float, - wind_speed_m_s: float, - cut_in_wind_speed_m_s: float = 3.0, - nominal_wind_speed_m_s: float = 14.0, - cutting_out_wind_speed_m_s: float = 25.0, - cut_out_wind_speed_m_s: float = 30.0, + wind_speed: float, + cut_in_wind_speed: float = 3.0, + nominal_wind_speed: float = 14.0, + cutting_out_wind_speed: float = 25.0, + cut_out_wind_speed: float = 30.0, ) -> float: """ Estimate p_ref based on p_nom and wind_speed. @@ -48,23 +48,23 @@ def power_wind_speed( # pylint: disable=too-many-arguments """ # At a wind speed below cut-in, the power is zero. - if wind_speed_m_s < cut_in_wind_speed_m_s: + if wind_speed < cut_in_wind_speed: return 0.0 # At a wind speed between cut-in and nominal, the power is a third power function of the wind speed. - if wind_speed_m_s < nominal_wind_speed_m_s: - factor = wind_speed_m_s - cut_in_wind_speed_m_s - max_factor = nominal_wind_speed_m_s - cut_in_wind_speed_m_s + if wind_speed < nominal_wind_speed: + factor = wind_speed - cut_in_wind_speed + max_factor = nominal_wind_speed - cut_in_wind_speed return ((factor / max_factor) ** 3) * p_nom # At a wind speed between nominal and cutting-out, the power is the nominal power. - if wind_speed_m_s < cutting_out_wind_speed_m_s: + if wind_speed < cutting_out_wind_speed: return p_nom # At a wind speed between cutting-out and cut-out, the power decreases from nominal to zero. - if wind_speed_m_s < cut_out_wind_speed_m_s: - factor = wind_speed_m_s - cutting_out_wind_speed_m_s - max_factor = cut_out_wind_speed_m_s - cutting_out_wind_speed_m_s + if wind_speed < cut_out_wind_speed: + factor = wind_speed - cutting_out_wind_speed + max_factor = cut_out_wind_speed - cutting_out_wind_speed return (1.0 - factor / max_factor) * p_nom # Above cut-out speed, the power is zero. @@ -95,11 +95,11 @@ def get_clock(conn_str: str) -> int: return clock -def reactive_power_to_susceptance(q_var: float, u_nom: float) -> float: +def reactive_power_to_susceptance(q: float, u_nom: float) -> float: """ Calculate susceptance, b1 from reactive power Q with nominal voltage """ - return q_var / u_nom / u_nom + return q / u_nom / u_nom def _split_connection_string(conn_str: str) -> Tuple[str, str, int]: diff --git a/src/power_grid_model_io/mappings/__init__.py b/src/power_grid_model_io/mappings/__init__.py index 7f6cad5b..19f65c87 100644 --- a/src/power_grid_model_io/mappings/__init__.py +++ b/src/power_grid_model_io/mappings/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/src/power_grid_model_io/mappings/field_mapping.py b/src/power_grid_model_io/mappings/field_mapping.py index 9542840e..3cda6fde 100644 --- a/src/power_grid_model_io/mappings/field_mapping.py +++ b/src/power_grid_model_io/mappings/field_mapping.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ diff --git a/src/power_grid_model_io/mappings/multiplier_mapping.py b/src/power_grid_model_io/mappings/multiplier_mapping.py index 37fe00f6..4c37ed68 100644 --- a/src/power_grid_model_io/mappings/multiplier_mapping.py +++ b/src/power_grid_model_io/mappings/multiplier_mapping.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ diff --git a/src/power_grid_model_io/mappings/tabular_mapping.py b/src/power_grid_model_io/mappings/tabular_mapping.py index b3db6c9e..643e542f 100644 --- a/src/power_grid_model_io/mappings/tabular_mapping.py +++ b/src/power_grid_model_io/mappings/tabular_mapping.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ diff --git a/src/power_grid_model_io/mappings/unit_mapping.py b/src/power_grid_model_io/mappings/unit_mapping.py index 80d7bc77..f222ba92 100644 --- a/src/power_grid_model_io/mappings/unit_mapping.py +++ b/src/power_grid_model_io/mappings/unit_mapping.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ @@ -61,7 +61,7 @@ def set_mapping(self, mapping: Units): raise ValueError( f"Invalid unit definition for '{unit}': 1{unit} cannot be {multiplier}{si_unit}" ) - continue + continue # pragma: no cover (bug in python 3.9) self._mapping[unit] = (multiplier, si_unit) self._log.debug( "Set unit definitions", n_units=len(self._si_units | self._mapping.keys()), n_si_units=len(self._si_units) diff --git a/src/power_grid_model_io/mappings/value_mapping.py b/src/power_grid_model_io/mappings/value_mapping.py index e973b1b5..152e0152 100644 --- a/src/power_grid_model_io/mappings/value_mapping.py +++ b/src/power_grid_model_io/mappings/value_mapping.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ diff --git a/src/power_grid_model_io/utils/__init__.py b/src/power_grid_model_io/utils/__init__.py index 7f6cad5b..19f65c87 100644 --- a/src/power_grid_model_io/utils/__init__.py +++ b/src/power_grid_model_io/utils/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/src/power_grid_model_io/utils/auto_id.py b/src/power_grid_model_io/utils/auto_id.py index f49a11ed..cad39182 100644 --- a/src/power_grid_model_io/utils/auto_id.py +++ b/src/power_grid_model_io/utils/auto_id.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ diff --git a/src/power_grid_model_io/utils/json.py b/src/power_grid_model_io/utils/json.py index 22845784..c6a6fa88 100644 --- a/src/power_grid_model_io/utils/json.py +++ b/src/power_grid_model_io/utils/json.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ diff --git a/src/power_grid_model_io/utils/modules.py b/src/power_grid_model_io/utils/modules.py index 9dc9d5e1..d39b0ad7 100644 --- a/src/power_grid_model_io/utils/modules.py +++ b/src/power_grid_model_io/utils/modules.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 """ diff --git a/tests/__init__.py b/tests/__init__.py index 7f6cad5b..19f65c87 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/data/vision/vision_validation.json b/tests/data/vision/pgm_input_data_en.json similarity index 100% rename from tests/data/vision/vision_validation.json rename to tests/data/vision/pgm_input_data_en.json diff --git a/tests/data/vision/pgm_input_data_en.json.license b/tests/data/vision/pgm_input_data_en.json.license new file mode 100644 index 00000000..f58b56c6 --- /dev/null +++ b/tests/data/vision/pgm_input_data_en.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 diff --git a/tests/data/vision/pgm_input_data_nl.json b/tests/data/vision/pgm_input_data_nl.json new file mode 100644 index 00000000..2f2bff48 --- /dev/null +++ b/tests/data/vision/pgm_input_data_nl.json @@ -0,0 +1,51 @@ +{ + "node": + [ + {"id": 0, "id_reference": {"table": "Knooppunten", "key": {"Nummer": 1}}, "Naam": "node1"}, + {"id": 1, "id_reference": {"table": "Knooppunten", "key": {"Nummer": 2}}, "Naam": "node2"}, + {"id": 2, "id_reference": {"table": "Knooppunten", "key": {"Nummer": 3}}, "Naam": "node3"}, + {"id": 3, "id_reference": {"table": "Knooppunten", "key": {"Nummer": 4}}, "Naam": "node4"}, + {"id": 4, "id_reference": {"table": "Knooppunten", "key": {"Nummer": 5}}}, + {"id": 12, "id_reference": {"table": "Transformatorbelastingen", "name": "internal_node", "key": {"Knooppunt.Nummer": 3, "Subnummer": 4}}, "Naam": "transformer_load_1"} + ], + "line": + [ + {"id": 5, "from_node": 0, "to_node": 1, "id_reference": {"table": "Kabels", "key": {"Nummer": 1}}, "Naam": "cable1"}, + {"id": 6, "from_node": 0, "to_node": 1, "id_reference": {"table": "Verbindingen", "key": {"Nummer": 1}}, "Naam": "line1"}, + {"id": 8, "from_node": 0, "to_node": 1, "id_reference": {"table": "Smoorspoelen", "key": {"Nummer": 1}}, "Naam": "rcoil1"} + ], + "link": + [ + {"id": 7, "from_node": 0, "to_node": 1, "id_reference": {"table": "Links", "key": {"Nummer": 1}}, "Naam": "link1"} + ], + "transformer": + [ + {"id": 9, "from_node": 1, "to_node": 2, "id_reference": {"table": "Transformatoren", "key": {"Nummer": 1}}, "Naam": "transformer1"}, + {"id": 10, "from_node": 1, "to_node": 3, "id_reference": {"table": "Speciale transformatoren", "key": {"Nummer": 1}}, "Naam": "special_trans1"}, + {"id": 11, "from_node": 2, "to_node": 12, "id_reference": {"table": "Transformatorbelastingen", "name": "transformer", "key": {"Knooppunt.Nummer": 3, "Subnummer": 4}}, "Naam": "transformer_load_1"} + ], + "sym_load": + [ + {"id": 13, "node": 12, "id_reference": {"table": "Transformatorbelastingen", "name": "load", "key": {"Knooppunt.Nummer": 3, "Subnummer": 4}}, "Naam": "transformer_load_1"}, + {"id": 19, "node": 2, "id_reference": {"table": "Belastingen", "key": {"Knooppunt.Nummer": 3, "Subnummer": 6}}, "Naam": "load1"} + ], + "sym_gen": + [ + {"id": 14, "node": 12, "id_reference": {"table": "Transformatorbelastingen", "name": "generation", "key": {"Knooppunt.Nummer": 3, "Subnummer": 4}}, "Naam": "transformer_load_1"}, + {"id": 14, "node": 12, "id_reference": {"table": "Transformatorbelastingen", "name": "generation", "key": {"Knooppunt.Nummer": 3, "Subnummer": 4}}, "Naam": "transformer_load_1"}, + {"id": 15, "node": 12, "id_reference": {"table": "Transformatorbelastingen", "name": "pv_generation", "key": {"Knooppunt.Nummer": 3, "Subnummer": 4}}, "Naam": "transformer_load_1"}, + {"id": 17, "node": 3, "id_reference": {"table": "Synchrone generatoren", "key": {"Knooppunt.Nummer": 4, "Subnummer": 2}}, "Naam": "syngen1"}, + {"id": 18, "node": 2, "id_reference": {"table": "Windturbines", "key": {"Knooppunt.Nummer": 3, "Subnummer": 3}}, "Naam": "wind1"}, + {"id": 23, "node": 2, "id_reference": {"table": "Pv's", "key": {"Knooppunt.Nummer": 3, "Subnummer": 1}}, "Naam": "pv1"} + ], + "source": + [ + {"id": 16, "node": 0, "id_reference": {"table": "Netvoedingen", "key": {"Knooppunt.Nummer": 1, "Subnummer": 1}}, "Naam": "source1"} + ], + "shunt": + [ + {"id": 20, "node": 2, "id_reference": {"table": "Nulpuntstransformatoren", "key": {"Knooppunt.Nummer": 3, "Subnummer": 5}}, "Naam": "zztrans1"}, + {"id": 21, "node": 3, "id_reference": {"table": "Condensatoren", "key": {"Knooppunt.Nummer": 4, "Subnummer": 9}}, "Naam": "shunt1"}, + {"id": 22, "node": 3, "id_reference": {"table": "Spoelen", "key": {"Knooppunt.Nummer": 4, "Subnummer": 1}}} + ] +} diff --git a/tests/data/vision/pgm_input_data_nl.json.license b/tests/data/vision/pgm_input_data_nl.json.license new file mode 100644 index 00000000..f58b56c6 --- /dev/null +++ b/tests/data/vision/pgm_input_data_nl.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 diff --git a/tests/data/vision/vision_validation.xlsx b/tests/data/vision/vision_en.xlsx similarity index 100% rename from tests/data/vision/vision_validation.xlsx rename to tests/data/vision/vision_en.xlsx diff --git a/tests/data/vision/vision_en.xlsx.license b/tests/data/vision/vision_en.xlsx.license new file mode 100644 index 00000000..f58b56c6 --- /dev/null +++ b/tests/data/vision/vision_en.xlsx.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 diff --git a/tests/data/vision/vision_nl.xlsx b/tests/data/vision/vision_nl.xlsx new file mode 100644 index 00000000..ed835ca6 Binary files /dev/null and b/tests/data/vision/vision_nl.xlsx differ diff --git a/tests/data/vision/vision_nl.xlsx.license b/tests/data/vision/vision_nl.xlsx.license new file mode 100644 index 00000000..f58b56c6 --- /dev/null +++ b/tests/data/vision/vision_nl.xlsx.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py index 7f6cad5b..19f65c87 100644 --- a/tests/unit/__init__.py +++ b/tests/unit/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/converters/__init__.py b/tests/unit/converters/__init__.py index 7f6cad5b..19f65c87 100644 --- a/tests/unit/converters/__init__.py +++ b/tests/unit/converters/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/converters/test_base_converter.py b/tests/unit/converters/test_base_converter.py index 6d9b596a..592115d1 100644 --- a/tests/unit/converters/test_base_converter.py +++ b/tests/unit/converters/test_base_converter.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/converters/test_gaia_excel_converter.py b/tests/unit/converters/test_gaia_excel_converter.py deleted file mode 100644 index 376e11e7..00000000 --- a/tests/unit/converters/test_gaia_excel_converter.py +++ /dev/null @@ -1,33 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project -# -# SPDX-License-Identifier: MPL-2.0 -from unittest.mock import patch - -import pandas as pd -import pytest - -from power_grid_model_io.converters.gaia_excel_converter import GaiaExcelConverter - - -@pytest.fixture -def converter(): - converter = GaiaExcelConverter() - return converter - - -def test_initialization(): - with pytest.raises(FileNotFoundError, match="No Gaia Excel mapping available for language 'abcde'"): - GaiaExcelConverter(language="abcde") - - with patch( - "power_grid_model_io.converters.tabular_converter.TabularConverter.set_mapping_file" - ) as mock_set_mapping_file: - GaiaExcelConverter() - mock_set_mapping_file.assert_called_once() - - with patch("power_grid_model_io.converters.gaia_excel_converter.GaiaExcelFileStore") as MockFileStore: - GaiaExcelConverter() - MockFileStore.assert_not_called() - - GaiaExcelConverter(source_file="source_file") - MockFileStore.assert_called_once() diff --git a/tests/unit/converters/test_mapping_file_existence.py b/tests/unit/converters/test_mapping_file_existence.py deleted file mode 100644 index 8f7e6f63..00000000 --- a/tests/unit/converters/test_mapping_file_existence.py +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project -# -# SPDX-License-Identifier: MPL-2.0 - - -from pathlib import Path - -from power_grid_model_io.converters.gaia_excel_converter import DEFAULT_MAPPING_FILE as gaia_mapping -from power_grid_model_io.converters.vision_excel_converter import DEFAULT_MAPPING_FILE as vision_mapping - - -def test_mapping_files_exist(): - gf = Path(str(gaia_mapping).format(language="en")) - vf = Path(str(vision_mapping).format(language="en")) - assert gf.exists() - assert vf.exists() diff --git a/tests/unit/converters/test_pgm_json_converter.py b/tests/unit/converters/test_pgm_json_converter.py index 91c767b6..6bf31527 100644 --- a/tests/unit/converters/test_pgm_json_converter.py +++ b/tests/unit/converters/test_pgm_json_converter.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 @@ -134,7 +134,7 @@ def test_is_batch( # Sparse batch dataset assert converter._is_batch(pgm_sparse_batch_data) # Wrong dataset with both single and batch data - combined_input_batch = pgm_input_data | pgm_batch_data + combined_input_batch = dict(**pgm_input_data, **pgm_batch_data) with pytest.raises(ValueError, match=r"Mixed non-batch data with batch data \(line\)."): converter._is_batch(combined_input_batch) diff --git a/tests/unit/converters/test_tabular_converter.py b/tests/unit/converters/test_tabular_converter.py index c9ee93ce..e03b60db 100644 --- a/tests/unit/converters/test_tabular_converter.py +++ b/tests/unit/converters/test_tabular_converter.py @@ -1,8 +1,8 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 from pathlib import Path -from typing import Optional, Tuple +from typing import Tuple from unittest.mock import MagicMock, call, patch import numpy as np @@ -12,51 +12,13 @@ from power_grid_model import initialize_array, power_grid_meta_data from power_grid_model.data_types import SingleDataset -from power_grid_model_io.converters.tabular_converter import COL_REF_RE, TabularConverter +from power_grid_model_io.converters.tabular_converter import TabularConverter from power_grid_model_io.data_types import ExtraInfoLookup, TabularData from power_grid_model_io.mappings.tabular_mapping import InstanceAttributes MAPPING_FILE = Path(__file__).parents[2] / "data" / "config" / "mapping.yaml" -def ref_cases(): - yield "OtherTable!ValueColumn[IdColumn=RefColumn]", ( - "OtherTable", - "ValueColumn", - None, - None, - "IdColumn", - None, - None, - "RefColumn", - ) - - yield "OtherTable!ValueColumn[OtherTable!IdColumn=ThisTable!RefColumn]", ( - "OtherTable", - "ValueColumn", - "OtherTable!", - "OtherTable", - "IdColumn", - "ThisTable!", - "ThisTable", - "RefColumn", - ) - - yield "OtherTable.ValueColumn[IdColumn=RefColumn]", None - yield "ValueColumn[IdColumn=RefColumn]", None - yield "OtherTable![IdColumn=RefColumn]", None - - -@pytest.mark.parametrize("value,groups", ref_cases()) -def test_col_ref_pattern(value: str, groups: Optional[Tuple[Optional[str]]]): - match = COL_REF_RE.fullmatch(value) - if groups is None: - assert match is None - else: - assert match is not None - assert match.groups() == groups - - @pytest.fixture def converter(): converter = TabularConverter(mapping_file=MAPPING_FILE) @@ -95,7 +57,7 @@ def tabular_data(): @pytest.fixture def tabular_data_no_units_no_substitutions(): nodes = pd.DataFrame(data=[[1, 10.5e3], [2, 400.0]], columns=["id_number", "u_nom"]) - lines = pd.DataFrame(data=[[1, 100], [2, 200]], columns=["id_number", "from_node_side"]) + lines = pd.DataFrame(data=[[1, 2], [3, 1]], columns=["id_number", "from_node_side"]) tabular_data_no_units_no_substitutions = TabularData(nodes=nodes, lines=lines) return tabular_data_no_units_no_substitutions @@ -325,18 +287,28 @@ def test_parse_col_def(converter: TabularConverter, tabular_data_no_units_no_sub # type(col_def) == str (regular expression) with patch( - "power_grid_model_io.converters.tabular_converter.TabularConverter._parse_col_def_column_reference" - ) as mock_parse_col_column_reference: + "power_grid_model_io.converters.tabular_converter.TabularConverter._parse_reference" + ) as mock_parse_reference: converter._parse_col_def( data=tabular_data_no_units_no_substitutions, - table="nodes", - col_def="OtherTable!ValueColumn[IdColumn=RefColumn]", + table="lines", + col_def={ + "reference": { + "query_column": "from_node_side", + "other_table": "nodes", + "key_column": "id_number", + "value_column": "u_nom", + } + }, extra_info=None, ) - mock_parse_col_column_reference.assert_called_once_with( + mock_parse_reference.assert_called_once_with( data=tabular_data_no_units_no_substitutions, - table="nodes", - col_def="OtherTable!ValueColumn[IdColumn=RefColumn]", + table="lines", + other_table="nodes", + query_column="from_node_side", + key_column="id_number", + value_column="u_nom", ) # type(col_def) == str @@ -410,99 +382,142 @@ def test_parse_col_def_column_name(converter: TabularConverter, tabular_data_no_ ) -def test_parse_col_def_column_reference( - converter: TabularConverter, tabular_data_no_units_no_substitutions: TabularData -): - with pytest.raises(AssertionError): - converter._parse_col_def_column_reference( - data=tabular_data_no_units_no_substitutions, table="nodes", col_def=1 # type: ignore - ) - with pytest.raises( - ValueError, - match="Invalid column reference 'some_column' " r"\(should be 'OtherTable!ValueColumn\[IdColumn=RefColumn\]\)", - ): - converter._parse_col_def_column_reference( - data=tabular_data_no_units_no_substitutions, table="nodes", col_def="some_column" - ) - with pytest.raises( - ValueError, - match=r"Invalid column reference 'lines!id_number\[abc!id_number=def!u_nom\]'.\n" - "It should be something like " - r"lines!id_number\[lines!{id_column}=nodes!{ref_column}\] " - r"or simply lines!id_number\[{id_column}={ref_column}\]", - ): - converter._parse_col_def_column_reference( - data=tabular_data_no_units_no_substitutions, - table="nodes", - col_def="lines!id_number[abc!id_number=def!u_nom]", - ) - +def test_parse_reference(converter: TabularConverter, tabular_data_no_units_no_substitutions: TabularData): # get lines.from_nodes where line id == node id - df_lines_from_node_long = converter._parse_col_def_column_reference( + df_lines_from_node_long = converter._parse_reference( data=tabular_data_no_units_no_substitutions, - table="nodes", - col_def="lines!from_node_side[lines!id_number=nodes!id_number]", - ) - assert_frame_equal(df_lines_from_node_long, pd.DataFrame([100, 200], columns=["from_node_side"])) - - df_lines_from_node_short = converter._parse_col_def_column_reference( - data=tabular_data_no_units_no_substitutions, table="nodes", col_def="lines!from_node_side[id_number=id_number]" + table="lines", + other_table="nodes", + query_column="from_node_side", + key_column="id_number", + value_column="u_nom", ) - assert_frame_equal(df_lines_from_node_long, df_lines_from_node_short) + assert_frame_equal(df_lines_from_node_long, pd.DataFrame([400.0, 10.5e3], columns=["u_nom"])) -def test_parse_col_def_filter(converter: TabularConverter, tabular_data_no_units_no_substitutions: TabularData): - # wrong col_def instance +def test_parse_col_def_filter(converter: TabularConverter): + # Act/Assert: with pytest.raises(AssertionError): converter._parse_col_def_filter( data=tabular_data_no_units_no_substitutions, table="", col_def=[], extra_info=None # type: ignore ) - # not implemented filters - with pytest.raises(NotImplementedError, match="Column filter 'multiply' not implemented"): - converter._parse_col_def_filter( - data=tabular_data_no_units_no_substitutions, table="", col_def={"multiply": ""}, extra_info=None - ) - with pytest.raises(NotImplementedError, match="Column filter 'function' not implemented"): - converter._parse_col_def_filter( - data=tabular_data_no_units_no_substitutions, table="", col_def={"function": ""}, extra_info=None - ) - with pytest.raises(NotImplementedError, match="Column filter 'reference' not implemented"): - converter._parse_col_def_filter( - data=tabular_data_no_units_no_substitutions, table="", col_def={"reference": ""}, extra_info=None - ) + with pytest.raises(TypeError, match="Invalid foo definition: 123"): + converter._parse_col_def_filter(data=MagicMock(), table="", col_def={"foo": 123}, extra_info=None) - with patch( - "power_grid_model_io.converters.tabular_converter.TabularConverter._parse_col_def_function" - ) as mock_parse_col_def_function: - mock_parse_col_def_function.return_value = pd.DataFrame([1, 2]) - df = converter._parse_col_def_filter( - data=tabular_data_no_units_no_substitutions, table="", col_def={"a": "str"}, extra_info=None - ) - mock_parse_col_def_function.assert_called_once_with( - data=tabular_data_no_units_no_substitutions, table="", col_def={"a": "str"} - ) - assert_frame_equal(df, pd.DataFrame([1, 2])) - # auto id +@patch("power_grid_model_io.converters.tabular_converter.TabularConverter._parse_function") +def test_parse_col_def_filter__function(mock_parse_function: MagicMock, converter: TabularConverter): + # Arrange + data = MagicMock() + function_result = pd.DataFrame([1, 2]) + mock_parse_function.return_value = function_result + + # Act + result = converter._parse_col_def_filter( + data=data, + table="nodes", + col_def={"path.to.function": {"foo": "id_number", "bar": "u_nom"}}, + extra_info=None, + ) + + # Assert + mock_parse_function.assert_called_once_with( + data=data, + table="nodes", + function="path.to.function", + col_def={"foo": "id_number", "bar": "u_nom"}, + ) + pd.testing.assert_frame_equal(result, function_result) + + +@patch("power_grid_model_io.converters.tabular_converter.TabularConverter._parse_pandas_function") +def test_parse_col_def_filter__pandas_function(mock_parse_function: MagicMock, converter: TabularConverter): + # Arrange + data = MagicMock() + function_result = pd.DataFrame([1, 2]) + mock_parse_function.return_value = function_result + + # Act + result = converter._parse_col_def_filter( + data=data, table="nodes", col_def={"multiply": ["id_number", "u_nom"]}, extra_info=None + ) + + # Assert + mock_parse_function.assert_called_once_with( + data=data, table="nodes", function="multiply", col_def=["id_number", "u_nom"] + ) + pd.testing.assert_frame_equal(result, function_result) + + +@patch("power_grid_model_io.converters.tabular_converter.TabularConverter._parse_auto_id") +def test_parse_col_def_filter__auto_id(mock_parse_auto_id: MagicMock, converter: TabularConverter): + # Arrange + data = MagicMock() + auto_id_result = pd.DataFrame([1, 2]) + extra_info = MagicMock() + mock_parse_auto_id.return_value = auto_id_result + + # Act + result = converter._parse_col_def_filter( + data=data, + table="lines", + col_def={"auto_id": {"table": "nodes", "name": "dummy", "key": "from_node_side"}}, + extra_info=extra_info, + ) + + # Assert + mock_parse_auto_id.assert_called_once_with( + data=data, + table="lines", + ref_table="nodes", + ref_name="dummy", + key_col_def="from_node_side", + extra_info=extra_info, + ) + pd.testing.assert_frame_equal(result, auto_id_result) + + # Act/Assert: with pytest.raises(ValueError, match="Invalid auto_id definition: {'a': 1, 'b': 2}"): - converter._parse_col_def_filter( - data=tabular_data_no_units_no_substitutions, - table="", - col_def={"auto_id": {"a": 1, "b": 2}}, - extra_info=None, - ) - with patch( - "power_grid_model_io.converters.tabular_converter.TabularConverter._parse_auto_id" - ) as mock_parse_auto_id: - mock_parse_auto_id.return_value = pd.DataFrame([3, 4]) - df = converter._parse_col_def_filter( - data=tabular_data_no_units_no_substitutions, - table="nodes", - col_def={"auto_id": {"table": "id", "key": "id_number"}}, - extra_info=None, - ) - assert_frame_equal(df, pd.DataFrame([3, 4])) + converter._parse_col_def_filter(data=data, table="", col_def={"auto_id": {"a": 1, "b": 2}}, extra_info=None) + + +@patch("power_grid_model_io.converters.tabular_converter.TabularConverter._parse_reference") +def test_parse_col_def_filter__reference(mock_parse_reference: MagicMock, converter: TabularConverter): + # Arrange + data = MagicMock() + reference_result = MagicMock() + mock_parse_reference.return_value = reference_result + + # Act + result = converter._parse_col_def_filter( + data=data, + table="lines", + col_def={ + "reference": { + "query_column": "from_node_side", + "other_table": "nodes", + "key_column": "id_number", + "value_column": "u_nom", + } + }, + extra_info=None, + ) + + # Assert + mock_parse_reference.assert_called_once_with( + data=data, + table="lines", + other_table="nodes", + query_column="from_node_side", + key_column="id_number", + value_column="u_nom", + ) + assert result is reference_result + + # Act/Assert: + with pytest.raises(ValueError, match="Invalid reference definition: {'a': 1, 'b': 2}"): + converter._parse_col_def_filter(data=data, table="", col_def={"reference": {"a": 1, "b": 2}}, extra_info=None) @patch("power_grid_model_io.converters.tabular_converter.TabularConverter._get_id") @@ -562,7 +577,7 @@ def test_parse_auto_id__reference_column( extra_info=extra_info, ) mock_get_id.assert_has_calls( - [call(table="nodes", key={"id_number": 100}, name=None), call(table="nodes", key={"id_number": 200}, name=None)] + [call(table="nodes", key={"id_number": 2}, name=None), call(table="nodes", key={"id_number": 1}, name=None)] ) assert len(extra_info) == 0 @@ -634,12 +649,12 @@ def test_parse_auto_id__named_keys( ) mock_get_id.assert_has_calls( [ - call(table="lines", key={"id": 1, "node": 100}, name=None), - call(table="lines", key={"id": 2, "node": 200}, name=None), + call(table="lines", key={"id": 1, "node": 2}, name=None), + call(table="lines", key={"id": 3, "node": 1}, name=None), ] ) - assert extra_info[101] == {"id_reference": {"table": "lines", "key": {"id": 1, "node": 100}}} - assert extra_info[102] == {"id_reference": {"table": "lines", "key": {"id": 2, "node": 200}}} + assert extra_info[101] == {"id_reference": {"table": "lines", "key": {"id": 1, "node": 2}}} + assert extra_info[102] == {"id_reference": {"table": "lines", "key": {"id": 3, "node": 1}}} def test_parse_auto_id__invalid_key_definition( @@ -656,9 +671,73 @@ def test_parse_auto_id__invalid_key_definition( ) +@pytest.mark.parametrize( + ("function", "expected"), + [ + ("multiply", (1 * 1, 2 * 10, 3 * 100)), + ("prod", (1 * 1, 2 * 10, 3 * 100)), + ("sum", (1 + 1, 2 + 10, 3 + 100)), + ("min", (1, 2, 3)), + ("max", (1, 10, 100)), + ], +) +@patch("power_grid_model_io.converters.tabular_converter.TabularConverter._parse_col_def") +def test_parse_pandas_function( + mock_parse_col_def: MagicMock, converter: TabularConverter, function: str, expected: Tuple[int, int, int] +): + # Arrange + data = MagicMock() + col_def = ["a", "b"] + parse_col_def_data = pd.DataFrame([[1, 1], [2, 10], [3, 100]], columns=["a", "b"]) + mock_parse_col_def.return_value = parse_col_def_data + + # Act + result = converter._parse_pandas_function(data=data, table="foo", function=function, col_def=col_def) + + # Assert + mock_parse_col_def.assert_called_once_with(data=data, table="foo", col_def=col_def, extra_info=None) + pd.testing.assert_frame_equal(result, pd.DataFrame(expected)) + + +@patch("power_grid_model_io.converters.tabular_converter.TabularConverter._parse_col_def") +def test_parse_pandas_function__no_data(mock_parse_col_def: MagicMock, converter: TabularConverter): + # Arrange + data = MagicMock() + col_def = ["a", "b"] + parse_col_def_data = pd.DataFrame([], columns=["a", "b"]) + mock_parse_col_def.return_value = parse_col_def_data + + # Act + result = converter._parse_pandas_function(data=data, table="foo", function="multiply", col_def=col_def) + + # Assert + mock_parse_col_def.assert_called_once_with(data=data, table="foo", col_def=col_def, extra_info=None) + assert result.empty + + +@patch("power_grid_model_io.converters.tabular_converter.TabularConverter._parse_col_def") +def test_parse_pandas_function__invalid(mock_parse_col_def: MagicMock, converter: TabularConverter): + # Arrange + mock_parse_col_def.return_value = pd.DataFrame() + + # Act / Assert + with pytest.raises(AssertionError): + converter._parse_pandas_function( + data=MagicMock(), table="foo", function="multiply", col_def=123 # type: ignore + ) + + # Act / Assert + with pytest.raises(ValueError, match="Pandas DataFrame has no function 'bar'"): + converter._parse_pandas_function(data=MagicMock(), table="foo", function="bar", col_def=[]) + + # Act / Assert + with pytest.raises(ValueError, match=f"Invalid pandas function DataFrame.apply"): + converter._parse_pandas_function(data=MagicMock(), table="foo", function="apply", col_def=[]) + + @patch("power_grid_model_io.converters.tabular_converter.get_function") @patch("power_grid_model_io.converters.tabular_converter.TabularConverter._parse_col_def") -def test_parse_col_def_function( +def test_parse_function( mock_parse_col_def: MagicMock, mock_get_function: MagicMock, converter: TabularConverter, @@ -670,15 +749,18 @@ def multiply_by_two(value: int): mock_get_function.return_value = multiply_by_two mock_parse_col_def.return_value = pd.DataFrame([2, 4, 5]) - multiplied_data = converter._parse_col_def_function( - data=tabular_data_no_units_no_substitutions, table="nodes", col_def={"multiply_by_two": "u_nom"} + multiplied_data = converter._parse_function( + data=tabular_data_no_units_no_substitutions, + table="nodes", + function="multiply_by_two", + col_def={"value": "u_nom"}, ) assert_frame_equal(multiplied_data, pd.DataFrame([4, 8, 10])) @patch("power_grid_model_io.converters.tabular_converter.get_function") @patch("power_grid_model_io.converters.tabular_converter.TabularConverter._parse_col_def") -def test_parse_col_def_function__no_data( +def test_parse_function__no_data( mock_parse_col_def: MagicMock, mock_get_function: MagicMock, converter: TabularConverter, @@ -690,10 +772,11 @@ def multiply_by_two(value: int): mock_parse_col_def.return_value = pd.DataFrame() with pytest.raises(ValueError, match="multiply_by_two.*empty DataFrame"): - converter._parse_col_def_function( + converter._parse_function( data=TabularData(nodes=pd.DataFrame([], columns=["u_nom"])), table="nodes", - col_def={"multiply_by_two": "u_nom"}, + function="multiply_by_two", + col_def={"value": "u_nom"}, ) diff --git a/tests/unit/converters/test_vision_excel_converter.py b/tests/unit/converters/test_vision_excel_converter.py index 2358f7f6..696ad3fd 100644 --- a/tests/unit/converters/test_vision_excel_converter.py +++ b/tests/unit/converters/test_vision_excel_converter.py @@ -1,11 +1,12 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 +from pathlib import Path from unittest.mock import patch import pytest -from power_grid_model_io.converters.vision_excel_converter import VisionExcelConverter +from power_grid_model_io.converters.vision_excel_converter import DEFAULT_MAPPING_FILE, VisionExcelConverter @pytest.fixture @@ -31,6 +32,12 @@ def converter() -> VisionExcelConverter: return converter +@pytest.mark.parametrize("language", ["en"]) +def test_mapping_files_exist(language: str): + vf = Path(str(DEFAULT_MAPPING_FILE).format(language=language)) + assert vf.exists() + + def test_initialization(): with pytest.raises(FileNotFoundError, match="No Vision Excel mapping available for language 'abcde'"): VisionExcelConverter(language="abcde") diff --git a/tests/unit/data_stores/__init__.py b/tests/unit/data_stores/__init__.py index 7f6cad5b..19f65c87 100644 --- a/tests/unit/data_stores/__init__.py +++ b/tests/unit/data_stores/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/data_stores/test_base_data_store.py b/tests/unit/data_stores/test_base_data_store.py index c87f387c..eb7b6ef0 100644 --- a/tests/unit/data_stores/test_base_data_store.py +++ b/tests/unit/data_stores/test_base_data_store.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/data_stores/test_excel_file_store.py b/tests/unit/data_stores/test_excel_file_store.py index 3b32d942..bbf34cad 100644 --- a/tests/unit/data_stores/test_excel_file_store.py +++ b/tests/unit/data_stores/test_excel_file_store.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/data_stores/test_gaia_excel_file_store.py b/tests/unit/data_stores/test_gaia_excel_file_store.py deleted file mode 100644 index 4433ada8..00000000 --- a/tests/unit/data_stores/test_gaia_excel_file_store.py +++ /dev/null @@ -1,39 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project -# -# SPDX-License-Identifier: MPL-2.0 -from pathlib import Path -from unittest.mock import MagicMock, Mock, mock_open, patch - -from power_grid_model_io.data_stores.gaia_excel_file_store import GaiaExcelFileStore - - -@patch("power_grid_model_io.data_stores.excel_file_store.pd.read_excel") -@patch("power_grid_model_io.data_stores.excel_file_store.Path.open", mock_open()) -def test_header_rows(read_excel_mock: MagicMock): - # Arrange - store = GaiaExcelFileStore(file_path=Path("dummy.xlsx")) - read_excel_mock.return_value = {} - - # Act - store.load() - - # Assert - read_excel_mock.assert_called_once() - assert read_excel_mock.call_args_list[0].kwargs["header"] == [0, 1] - - -@patch("power_grid_model_io.data_stores.excel_file_store.pd.read_excel") -def test_types_file(read_excel_mock: MagicMock): - # Arrange - main_file = MagicMock(suffix=".xlsx") - types_file = MagicMock(suffix=".xlsx") - store = GaiaExcelFileStore(file_path=main_file, types_file=types_file) - read_excel_mock.return_value = {} - - # Act - store.load() - - # Assert - main_file.open.assert_called_once() - types_file.open.assert_called_once() - assert read_excel_mock.call_count == 2 diff --git a/tests/unit/data_stores/test_json_file_store.py b/tests/unit/data_stores/test_json_file_store.py index 91f8fe96..f3d4c796 100644 --- a/tests/unit/data_stores/test_json_file_store.py +++ b/tests/unit/data_stores/test_json_file_store.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/data_stores/test_vision_excel_file_store.py b/tests/unit/data_stores/test_vision_excel_file_store.py index 6dbc2212..4e6cab37 100644 --- a/tests/unit/data_stores/test_vision_excel_file_store.py +++ b/tests/unit/data_stores/test_vision_excel_file_store.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 from pathlib import Path diff --git a/tests/unit/data_types/__init__.py b/tests/unit/data_types/__init__.py index 7f6cad5b..19f65c87 100644 --- a/tests/unit/data_types/__init__.py +++ b/tests/unit/data_types/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/data_types/test_data_types.py b/tests/unit/data_types/test_data_types.py index 59ed36a6..a687c712 100644 --- a/tests/unit/data_types/test_data_types.py +++ b/tests/unit/data_types/test_data_types.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/data_types/test_tabular_data.py b/tests/unit/data_types/test_tabular_data.py index 78d316d4..adc33159 100644 --- a/tests/unit/data_types/test_tabular_data.py +++ b/tests/unit/data_types/test_tabular_data.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/filters/__init__.py b/tests/unit/functions/__init__.py similarity index 64% rename from tests/unit/filters/__init__.py rename to tests/unit/functions/__init__.py index 7f6cad5b..19f65c87 100644 --- a/tests/unit/filters/__init__.py +++ b/tests/unit/functions/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/filters/test_filters.py b/tests/unit/functions/test_functions.py similarity index 59% rename from tests/unit/filters/test_filters.py rename to tests/unit/functions/test_functions.py index d6950b52..d85cc08c 100644 --- a/tests/unit/filters/test_filters.py +++ b/tests/unit/functions/test_functions.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 from typing import List, Tuple, Union @@ -8,36 +8,18 @@ from power_grid_model import WindingType from pytest import approx, mark, param -from power_grid_model_io.filters import ( - all_true, - any_true, +from power_grid_model_io.functions import ( complex_inverse_imaginary_part, complex_inverse_real_part, degrees_to_clock, get_winding, has_value, is_greater_than, - multiply, - subtract, value_or_default, value_or_zero, ) -@mark.parametrize( - ("args", "expected"), - [ - ([], 1), - ([2], 2), - ([2, 3], 6), - ([2, 3, 5], 30), - ([2.0, 3.0, 5.0, 7.0], 210.0), - ], -) -def test_multiply_0(args, expected): - assert multiply(*args) == approx(expected) - - @mark.parametrize( ("value", "expected"), [ @@ -58,19 +40,19 @@ def test_has_value(value, expected): assert has_value(value) == approx(expected) -@patch("power_grid_model_io.filters.has_value") +@patch("power_grid_model_io.functions.has_value") def test_value_or_default__value(mock_has_value: MagicMock): mock_has_value.return_value = True assert value_or_default("value", "default") == "value" -@patch("power_grid_model_io.filters._filters.has_value") +@patch("power_grid_model_io.functions._functions.has_value") def test_value_or_default__default(mock_has_value: MagicMock): mock_has_value.return_value = False assert value_or_default("value", "default") == "default" -@patch("power_grid_model_io.filters._filters.value_or_default") +@patch("power_grid_model_io.functions._functions.value_or_default") def test_value_or_zero(mock_value_or_default: MagicMock): value_or_zero(1.23) mock_value_or_default.assert_called_once_with(value=1.23, default=0.0) @@ -150,62 +132,6 @@ def test_degrees_to_clock(degrees: float, expected: int): assert actual == approx(expected) or (np.isnan(actual) and np.isnan(expected)) -@mark.parametrize( - ("value", "arguments", "expected"), - [ - (360.0, [0.0, 10.0, 300.0], 50.0), - (120.0, [4.0, 15.5, 63.5], 37.0), - (180.0, [10.5, 20.5, 30.5, 40.5], 78.0), - (540.0, [50.5, 10.5, 101.0, 64.5, 3.5], 310.0), - ], -) -def test_subtract(value: float, arguments: List[float], expected: float): - actual = subtract(value, *arguments) - assert actual == approx(expected) or (np.isnan(actual) and np.isnan(expected)) - - -@mark.parametrize( - ("args", "expected"), - [ - param(tuple(), True, id="no_args"), - param((True,), True, id="single_true"), - param((False,), False, id="single_false"), - param((True, True), True, id="only_true"), - param((True, False), False, id="mixed_true_false"), - param((False, False), False, id="only_false"), - param((True, 1, 1.0), True, id="mixed_types"), - param((True, 1, 0.0), False, id="mixed_types_zero_float"), - param((True, 0, 1.0), False, id="mixed_types_zero_int"), - param((False, 1, 1.0), False, id="mixed_types_false_bool"), - param((True, 1, np.nan), False, id="mixed_types_nan_float"), - ], -) -def test_all_true(args: Tuple[Union[bool, int, float], ...], expected: bool): - actual = all_true(*args) - assert actual == expected - - -@mark.parametrize( - ("args", "expected"), - [ - param(tuple(), False, id="no_args"), - param((True,), True, id="single_true"), - param((False,), False, id="single_false"), - param((True, True), True, id="only_true"), - param((True, False), True, id="mixed_true_false"), - param((False, False), False, id="only_false"), - param((False, 0, 0.0), False, id="mixed_types"), - param((False, 0, 1.0), True, id="mixed_types_non_zero_float"), - param((False, 1, 0.0), True, id="mixed_types_non_zero_int"), - param((True, 0, 0.0), True, id="mixed_types_true_bool"), - param((False, 0, np.nan), False, id="mixed_types_nan_float"), - ], -) -def test_any_true(args: Tuple[Union[bool, int, float], ...], expected: bool): - actual = any_true(*args) - assert actual == expected - - @mark.parametrize( ("left_side", "right_side", "expected"), [ diff --git a/tests/unit/filters/test_phase_to_phase.py b/tests/unit/functions/test_phase_to_phase.py similarity index 97% rename from tests/unit/filters/test_phase_to_phase.py rename to tests/unit/functions/test_phase_to_phase.py index 2e0ac60b..51db92af 100644 --- a/tests/unit/filters/test_phase_to_phase.py +++ b/tests/unit/functions/test_phase_to_phase.py @@ -1,11 +1,11 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 import numpy as np from power_grid_model.enum import WindingType from pytest import approx, mark, raises -from power_grid_model_io.filters.phase_to_phase import ( +from power_grid_model_io.functions.phase_to_phase import ( get_clock, get_winding_from, get_winding_to, diff --git a/tests/unit/mappings/__init__.py b/tests/unit/mappings/__init__.py index 7f6cad5b..19f65c87 100644 --- a/tests/unit/mappings/__init__.py +++ b/tests/unit/mappings/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/mappings/test_multiplier_mapping.py b/tests/unit/mappings/test_multiplier_mapping.py index 13e5f327..e7eea2bc 100644 --- a/tests/unit/mappings/test_multiplier_mapping.py +++ b/tests/unit/mappings/test_multiplier_mapping.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/mappings/test_tabular_mapping.py b/tests/unit/mappings/test_tabular_mapping.py index b50a1a61..9dbf7de7 100644 --- a/tests/unit/mappings/test_tabular_mapping.py +++ b/tests/unit/mappings/test_tabular_mapping.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 import pytest diff --git a/tests/unit/mappings/test_unit_mapping.py b/tests/unit/mappings/test_unit_mapping.py index 01d50db7..6e346c1d 100644 --- a/tests/unit/mappings/test_unit_mapping.py +++ b/tests/unit/mappings/test_unit_mapping.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/mappings/test_value_mapping.py b/tests/unit/mappings/test_value_mapping.py index 38f1e2d2..20b3458a 100644 --- a/tests/unit/mappings/test_value_mapping.py +++ b/tests/unit/mappings/test_value_mapping.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/utils/__init__.py b/tests/unit/utils/__init__.py index 7f6cad5b..19f65c87 100644 --- a/tests/unit/utils/__init__.py +++ b/tests/unit/utils/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/utils/test_auto_id.py b/tests/unit/utils/test_auto_id.py index 34ac1c57..834e2e0d 100644 --- a/tests/unit/utils/test_auto_id.py +++ b/tests/unit/utils/test_auto_id.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 from pytest import raises diff --git a/tests/unit/utils/test_json.py b/tests/unit/utils/test_json.py index 7a26c4b8..0c4f5c5a 100644 --- a/tests/unit/utils/test_json.py +++ b/tests/unit/utils/test_json.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/unit/utils/test_modules.py b/tests/unit/utils/test_modules.py index b94f37a7..958871fc 100644 --- a/tests/unit/utils/test_modules.py +++ b/tests/unit/utils/test_modules.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 @@ -16,9 +16,9 @@ def test_get_function__native(): def test_get_function__custom(): - from power_grid_model_io.filters import multiply + from power_grid_model_io.functions import complex_inverse_real_part - assert get_function("power_grid_model_io.filters.multiply") == multiply + assert get_function("power_grid_model_io.functions.complex_inverse_real_part") == complex_inverse_real_part def test_get_function__module_doesnt_exist(): @@ -28,9 +28,9 @@ def test_get_function__module_doesnt_exist(): def test_get_function__function_doesnt_exist(): with raises( - AttributeError, match="Function 'unknown_function' does not exist in module 'power_grid_model_io.filters'!" + AttributeError, match="Function 'unknown_function' does not exist in module 'power_grid_model_io.functions'!" ): - assert get_function("power_grid_model_io.filters.unknown_function") + assert get_function("power_grid_model_io.functions.unknown_function") def test_get_function__builtin_doesnt_exist(): diff --git a/tests/utils.py b/tests/utils.py index 20662d81..e90fdf9f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 import sys diff --git a/tests/validation/__init__.py b/tests/validation/__init__.py index 7f6cad5b..19f65c87 100644 --- a/tests/validation/__init__.py +++ b/tests/validation/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/validation/converters/__init__.py b/tests/validation/converters/__init__.py index 7f6cad5b..19f65c87 100644 --- a/tests/validation/converters/__init__.py +++ b/tests/validation/converters/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/validation/converters/test_vision_excel_converter.py b/tests/validation/converters/test_vision_excel_converter.py index 20b74322..c08eb344 100644 --- a/tests/validation/converters/test_vision_excel_converter.py +++ b/tests/validation/converters/test_vision_excel_converter.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 @@ -18,45 +18,71 @@ from ..utils import component_attributes, component_objects, load_json_single_dataset, select_values DATA_PATH = Path(__file__).parents[2] / "data" / "vision" -SOURCE_FILE = DATA_PATH / "vision_validation.xlsx" -VALIDATION_FILE = DATA_PATH / "vision_validation.json" +SOURCE_FILE = DATA_PATH / "vision_{language:s}.xlsx" +VALIDATION_FILE = DATA_PATH / "pgm_input_data_{language:s}.json" +LANGUAGES = ["en", "nl"] +VALIDATION_EN = Path(str(VALIDATION_FILE).format(language="en")) + + +@lru_cache +def load_and_convert_excel_file(language: str) -> Tuple[SingleDataset, ExtraInfoLookup]: + """ + Read the excel file and do the conversion + """ + source_file = Path(str(SOURCE_FILE).format(language=language)) + data, extra_info = VisionExcelConverter(source_file, language=language).load_input_data() + return data, extra_info -@pytest.fixture @lru_cache -def actual() -> Tuple[SingleDataset, ExtraInfoLookup]: +def load_validation_data(language: str) -> Tuple[SingleDataset, ExtraInfoLookup]: """ - Read the excel file and do the conversion (extra info won't be used, for now) + Read the excel file and do the conversion """ - actual_data, actual_extra_info = VisionExcelConverter(SOURCE_FILE).load_input_data() - return actual_data, actual_extra_info + validation_file = Path(str(VALIDATION_FILE).format(language=language)) + data, extra_info = load_json_single_dataset(validation_file) + return data, extra_info @pytest.fixture -def expected() -> Tuple[SingleDataset, ExtraInfoLookup]: +def input_data(request) -> Tuple[SingleDataset, SingleDataset]: """ - Read the json file (extra info is currently not tested and therefore not allowed) + Read the excel file and do the conversion """ - expected_data, expected_extra_info = load_json_single_dataset(VALIDATION_FILE) - return expected_data, expected_extra_info + actual, _ = load_and_convert_excel_file(language=request.param) + expected, _ = load_validation_data(language=request.param) + return actual, expected -def test_input_data(actual, expected): +@pytest.fixture +def extra_info(request) -> Tuple[ExtraInfoLookup, ExtraInfoLookup]: + """ + Read the excel file and do the conversion + """ + _, actual = load_and_convert_excel_file(language=request.param) + _, expected = load_validation_data(language=request.param) + return actual, expected + + +@pytest.mark.parametrize("input_data", LANGUAGES, indirect=True) +def test_input_data(input_data: Tuple[SingleDataset, SingleDataset]): """ Unit test to preload the expected and actual data """ + # Arrange + actual, expected = input_data # Assert assert len(expected) <= len(actual) -@pytest.mark.parametrize(("component", "attribute"), component_attributes(VALIDATION_FILE)) -def test_attributes(actual, expected, component: str, attribute: str): +@pytest.mark.parametrize(("component", "attribute"), component_attributes(VALIDATION_EN)) +@pytest.mark.parametrize("input_data", LANGUAGES, indirect=True) +def test_attributes(input_data: Tuple[SingleDataset, SingleDataset], component: str, attribute: str): """ For each attribute, check if the actual values are consistent with the expected values """ # Arrange - actual_data, _ = actual - expected_data, _ = expected + actual_data, expected_data = input_data # Act actual_values, expected_values = select_values(actual_data, expected_data, component, attribute) @@ -67,15 +93,15 @@ def test_attributes(actual, expected, component: str, attribute: str): @pytest.mark.parametrize( ("component", "obj_ids"), - (pytest.param(component, objects, id=component) for component, objects in component_objects(VALIDATION_FILE)), + (pytest.param(component, objects, id=component) for component, objects in component_objects(VALIDATION_EN)), ) -def test_extra_info(actual, expected, component: str, obj_ids: List[int]): +@pytest.mark.parametrize("extra_info", LANGUAGES, indirect=True) +def test_extra_info(extra_info: Tuple[ExtraInfoLookup, ExtraInfoLookup], component: str, obj_ids: List[int]): """ For each object, check if the actual extra info is consistent with the expected extra info """ # Arrange - _, actual_extra_info = actual - _, expected_extra_info = expected + actual_extra_info, expected_extra_info = extra_info # Assert @@ -113,9 +139,10 @@ def test_extra_info(actual, expected, component: str, obj_ids: List[int]): raise ValueError("\n" + "\n".join(errors)) -def test_extra_info__serializable(actual): +@pytest.mark.parametrize("extra_info", LANGUAGES, indirect=True) +def test_extra_info__serializable(extra_info): # Arrange - _, extra_info = actual + actual, _expected = extra_info # Assert - json.dumps(extra_info, cls=JsonEncoder) # expect no exception + json.dumps(actual, cls=JsonEncoder) # expect no exception diff --git a/tests/validation/test_test_utils.py b/tests/validation/test_test_utils.py index 64474a9d..ee638c33 100644 --- a/tests/validation/test_test_utils.py +++ b/tests/validation/test_test_utils.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0 diff --git a/tests/validation/utils.py b/tests/validation/utils.py index 51b0da83..ce4a1c5a 100644 --- a/tests/validation/utils.py +++ b/tests/validation/utils.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model IO project +# SPDX-FileCopyrightText: 2022 Contributors to the Power Grid Model project # # SPDX-License-Identifier: MPL-2.0