diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8e4ea47..3a6e9cdc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,10 +17,10 @@ jobs: strategy: matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: lfs: false diff --git a/.github/workflows/conda.yml b/.github/workflows/conda.yml index ff47b426..7fcd9e14 100644 --- a/.github/workflows/conda.yml +++ b/.github/workflows/conda.yml @@ -20,7 +20,7 @@ jobs: python-version: ["3.9"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: lfs: false @@ -47,17 +47,19 @@ jobs: with: auto-update-conda: true python-version: ${{ matrix.python-version }} + miniforge-variant: Mambaforge + use-mamba: true - name: Conda Build id: conda-build shell: bash -l {0} run: | - conda config --add channels conda-forge - conda config --set channel_priority strict - conda install --channel conda-forge conda-build conda-verify + mamba config --add channels conda-forge + mamba config --set channel_priority strict + mamba install --channel conda-forge conda-build conda-verify mkdir -p dist/ - conda build --output-folder dist/ conda/ - echo "CONDA_ARCHIVE=$(conda build --output-folder dist/ --output conda/)" >> $GITHUB_OUTPUT + mamba build --output-folder dist/ conda/ + echo "CONDA_ARCHIVE=$(mamba build --output-folder dist/ --output conda/)" >> $GITHUB_OUTPUT - name: Upload artifact uses: actions/upload-artifact@v3 diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 3c179628..65d867c6 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -29,7 +29,7 @@ jobs: sudo apt update sudo apt -yq --no-install-suggests --no-install-recommends install pandoc make - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: lfs: false @@ -57,20 +57,20 @@ jobs: - name: Install poetry run: pipx install poetry - - name: Set up Python 3.11 + - name: Set up Python 3.12 uses: actions/setup-python@v4 with: - python-version: "3.11" + python-version: "3.12" cache: "poetry" - name: Install dependencies run: | - poetry env use "3.11" + poetry env use "3.12" poetry install --only doc - name: Build with Sphinx run: | - poetry env use "3.11" + poetry env use "3.12" cd doc && make html env: SPHINXBUILD: poetry run sphinx-build diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index c665d2d6..706a29fc 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -18,10 +18,10 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: lfs: false - uses: actions/setup-python@v4 with: - python-version: "3.11" + python-version: "3.12" - uses: pre-commit/action@v3.0.0 diff --git a/.gitignore b/.gitignore index c8dd5045..bd430e34 100644 --- a/.gitignore +++ b/.gitignore @@ -246,3 +246,6 @@ benchmark/*.mtx *.log .ruff_cache/ doc/images/*.pdf + +roseau/* +!roseau/load_flow diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 80f04937..c7a8257c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^.idea/|^conda/meta.yaml repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-builtin-literals - id: check-json @@ -13,28 +13,27 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/python-poetry/poetry - rev: 1.6.0 + rev: 1.7.0 hooks: - id: poetry-check - - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.7.0 # keep in sync with pyproject.toml - hooks: - - id: black-jupyter - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.286 # keep in sync with pyproject.toml + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.6 # keep in sync with pyproject.toml hooks: - id: ruff types_or: [python, pyi, jupyter] - args: [--fix, --exit-non-zero-on-fix] + args: [--fix] + - id: ruff-format + types_or: [python, pyi, jupyter] - repo: https://github.com/adamchainz/blacken-docs rev: 1.16.0 hooks: - id: blacken-docs entry: bash -c "blacken-docs -l 90 $(find doc/ -name '*.md')" args: [-l 90] - additional_dependencies: [black==23.7.0] # keep in sync with black above + additional_dependencies: [black==23.10.1] # keep in sync with black above - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.2 + rev: v3.1.0 hooks: - id: prettier args: ["--print-width", "120"] + exclude: ^.vscode/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 5226822d..07f32b7a 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,9 +1,10 @@ { "recommendations": [ "charliermarsh.ruff", + "esbenp.prettier-vscode", "ms-python.black-formatter", "ms-python.python", - "ms-python.vscode-pylance" + "ms-python.vscode-pylance", ], "unwantedRecommendations": [ "ms-python.flake8", // We use ruff diff --git a/.vscode/settings.json b/.vscode/settings.json index fe97411f..5c4f517a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,24 +3,27 @@ "jupyter.notebookFileRoot": "${workspaceFolder}", "notebook.formatOnSave.enabled": true, "notebook.codeActionsOnSave": { - "source.organizeImports.ruff": true + "source.organizeImports.ruff": "explicit", }, // Python "python.analysis.diagnosticSeverityOverrides": { "reportInvalidStringEscapeSequence": "warning", "reportImportCycles": "warning", - "reportUnusedImport": "warning" + "reportUnusedImport": "warning", }, - "python.formatting.provider": "none", - "python.testing.pytestArgs": [], - "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "[python]": { "editor.defaultFormatter": "ms-python.black-formatter", "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.organizeImports.ruff": true - } + "source.organizeImports.ruff": "explicit", + }, + }, + // Prettier + "prettier.printWidth": 120, + "[markdown][yaml][html][css]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, } } diff --git a/conda/environment.yml b/conda/environment.yml index a6e1ff7f..1285b8e3 100644 --- a/conda/environment.yml +++ b/conda/environment.yml @@ -12,4 +12,6 @@ dependencies: - pint >=0.21.0 - requests >=2.28.1 - typing-extensions >=4.6.2 - - rich >=13.5.2 + - rich >=13.5.1 + - matplotlib >=3.7.2 + - networkx >=3.0.0 diff --git a/conda/meta.yaml b/conda/meta.yaml index 7d15d74e..3ce83ce7 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -1,6 +1,6 @@ # prettier-ignore {% set name = "roseau-load-flow" %} -{% set version = "0.5.0" %} +{% set version = "0.6.0" %} package: name: "{{ name|lower }}" @@ -35,6 +35,8 @@ requirements: - requests >=2.28.1 - typing-extensions >=4.6.2 - rich >=13.5.1 + - matplotlib >=3.7.2 + - networkx >=3.0.0 test: imports: diff --git a/doc/Bibliography.bib b/doc/Bibliography.bib index 85fd2b2a..7e9aa0b3 100644 --- a/doc/Bibliography.bib +++ b/doc/Bibliography.bib @@ -29,3 +29,14 @@ @misc{wiki:Method_Of_Image_Charges url = {http://en.wikipedia.org/w/index.php?title=Method\%20of\%20image\%20charges&oldid=1152888135}, note = "[Online; accessed 25-August-2023]" } + +@inproceedings{Girigoudar_2019, + author = {Girigoudar, Kshitij and Molzahn, Daniel K. and Roald, Line A.}, + booktitle = {2019 North American Power Symposium (NAPS)}, + title = {On The Relationships Among Different Voltage Unbalance Definitions}, + year = {2019}, + volume = {}, + number = {}, + pages = {1-6}, + doi = {10.1109/NAPS46351.2019.9000231} +} diff --git a/doc/Changelog.md b/doc/Changelog.md index d5af9daf..6eea5d84 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -1,5 +1,41 @@ # Changelog +## Version 0.6.0 + +- {gh-pr}`149` {gh-issue}`145` Add custom pint wrapper for better handling of pint arrays. +- {gh-pr}`148` {gh-issue}`122` deprecate `LineParameters.from_name_lv()` in favor of the more generic + `LineParameters.from_geometry()`. The method will be removed in a future release. +- {gh-pr}`142` {gh-issue}`136` Add `Bus.res_voltage_unbalance()` method to get the Voltage Unbalance + Factor (VUF) as defined by the IEC standard IEC 61000-3-14. +- {gh-pr}`141` {gh-issue}`137` Add `ElectricalNetwork.to_graph()` to get a `networkx.Graph` object + representing the electrical network for graph theory studies. Install with the `"graph"` extra to + get _networkx_. + `ElectricalNetwork` also gained a new `buses_clusters` property that returns a list of sets of + IDs of buses that are connected by a line or a switch. This can be useful to isolate parts of the + network for localized analysis. For example, to study a LV subnetwork of a MV feeder. Alternatively, + to get the cluster certain bus belongs to, you can use `Bus.get_connected_buses()`. +- {gh-pr}`141` Add official support for Python 3.12. This is the last release to support Python 3.9. +- {gh-pr}`138` Add network constraints for analysis of the results. + - Buses can define minimum and maximum voltages. Use `bus.res_violated` to see if the bus has + over- or under-voltage. + - Lines can define a maximum current. Use `line.res_violated` to see if the loading of any of the + line's cables is too high. + - Transformers can define a maximum power. Use `transformer.res_violated` to see if the transformer + loading is too high. + - The new fields also appear in the data frames of the network. +- {gh-pr}`133` {gh-issue}`126` Add Qmin and Qmax limits of flexible parameters. +- {gh-pr}`132` {gh-issue}`101` Document extra utilities including converters and constants. +- {gh-pr}`131` {gh-issue}`127` Improve the documentation of the flexible loads. + - Add the method `compute_powers` method to the `FlexibleParameter` class to compute the resulting flexible powers + for a given theoretical power and a list of voltage norms. + - Add the `plot_control_p`, `plot_control_q` and `plot_pq` methods to the `FlexibleParameter` class to plot the + control curves and control trajectories. + - Add the extra `plot` to install `matplotlib` alongside `roseau-load-flow`. +- {gh-pr}`131` Correction of a bug in the error message of the powers setter method. +- {gh-pr}`130` Mark some internal attributes as private, they were previously marked as public. +- {gh-pr}`128` Add the properties `z_line`, `y_shunt` and `with_shunt` to the `Line` class. +- {gh-pr}`125` Speed-up build of conda workflow using mamba. + ## Version 0.5.0 - {gh-pr}`121` {gh-issue}`68` Improvements of the `LineParameters` constructor: diff --git a/doc/Installation.md b/doc/Installation.md index e8b7fec4..163cf59f 100644 --- a/doc/Installation.md +++ b/doc/Installation.md @@ -1,43 +1,67 @@ # Installation -## Using `pip` +Please select one of the following installation methods that best suits your workflow. + +```{note} +If you are a beginner in Python, please note that the commands below must be executed in a +**terminal**, not in the _Python console_. This is indicated by the `$` or `C:>` prompt as opposed +to the Python console prompt `>>>`. +``` + +## 1. Using `pip` `roseau-load-flow` is available on [PyPI](https://pypi.org/project/roseau-load-flow/). It can be installed using pip with: -```console -$ python -m pip install roseau-load-flow +````{tab} Windows + +```doscon +C:> python -m pip install roseau-load-flow ``` -`````{tip} -It is recommended to work in a virtual environment to isolate your project. You can create one with: +```` + +````{tab} Linux/MacOS ```console -$ python -m venv venv +$ python -m pip install roseau-load-flow ``` -A folder named `venv` will be created. To activate the virtual environment, run: +```` + +`````{tip} +It is recommended to work in a virtual environment to isolate your project. Create and activate a virtual environment before installing the package. You can create one with: ````{tab} Windows ```doscon -C:> venv\Scripts\activate +C:> python -m venv .venv ``` ```` -````{tab} Linux +````{tab} Linux/MacOS ```console -$ source venv/bin/activate +$ python -m venv .venv ``` ```` -````{tab} MacOS +A folder named `.venv` will be created. To activate the virtual environment, run: + +````{tab} Windows + +```doscon +C:> .venv\Scripts\activate +``` + +```` + +````{tab} Linux/MacOS ```console -$ . venv/bin/activate +$ source .venv/bin/activate ``` ```` @@ -46,19 +70,63 @@ $ . venv/bin/activate To upgrade to the latest version (recommended), use: +````{tab} Windows + +```doscon +C:> python -m pip install --upgrade roseau-load-flow +``` + +```` + +````{tab} Linux/MacOS + ```console $ python -m pip install --upgrade roseau-load-flow ``` -## Using `conda` +```` + +Optional dependencies can be installed using the available extras. These are only needed if you use +the corresponding functions. They can be installed with the +`python -m pip install roseau-load-flow[EXTRA]` command where `EXTRA` is one of the following: + +1. `plot`: installs _matplotlib_ for the plotting functions +2. `graph` installs _networkx_ for graph theory analysis functions + +## 2. Using `pip` in Jupyter Notebooks + +If you are using Jupyter Notebooks, you can install `roseau-load-flow` directly from a notebook +cell with: + +```ipython3 +In [1]: %pip install roseau-load-flow +``` + +This installs the package in the correct environment for the active notebook kernel. + +## 3. Using `conda` `roseau-load-flow` is also available on [conda-forge](https://anaconda.org/conda-forge/roseau-load-flow). It can be installed using conda with: +````{tab} Windows + +```doscon +C:> conda install -c conda-forge roseau-load-flow +``` + +```` + +````{tab} Linux/MacOS + ```console $ conda install -c conda-forge roseau-load-flow ``` +```` + +This installs the package and all its required and optional dependencies. + ```{tip} If you use *conda* to manage your project, it is recommended to use the `conda` package manager instead of `pip`. diff --git a/doc/_static/Control_Trajectory_PmaxU_QU.svg b/doc/_static/Control_Trajectory_PmaxU_QU.svg deleted file mode 100644 index 24045244..00000000 --- a/doc/_static/Control_Trajectory_PmaxU_QU.svg +++ /dev/null @@ -1,25138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/_static/Domain_PmaxU_QU.svg b/doc/_static/Domain_PmaxU_QU.svg deleted file mode 100644 index d6bebf95..00000000 --- a/doc/_static/Domain_PmaxU_QU.svg +++ /dev/null @@ -1,2267 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/_static/Constant_P_Projection.svg b/doc/_static/Load/FlexibleLoad/Constant_P_Projection.svg similarity index 100% rename from doc/_static/Constant_P_Projection.svg rename to doc/_static/Load/FlexibleLoad/Constant_P_Projection.svg diff --git a/doc/_static/Constant_Q_Projection.svg b/doc/_static/Load/FlexibleLoad/Constant_Q_Projection.svg similarity index 100% rename from doc/_static/Constant_Q_Projection.svg rename to doc/_static/Load/FlexibleLoad/Constant_Q_Projection.svg diff --git a/doc/_static/Control_PU_Cons.svg b/doc/_static/Load/FlexibleLoad/Control_PU_Cons.svg similarity index 100% rename from doc/_static/Control_PU_Cons.svg rename to doc/_static/Load/FlexibleLoad/Control_PU_Cons.svg diff --git a/doc/_static/Control_PU_Prod.svg b/doc/_static/Load/FlexibleLoad/Control_PU_Prod.svg similarity index 100% rename from doc/_static/Control_PU_Prod.svg rename to doc/_static/Load/FlexibleLoad/Control_PU_Prod.svg diff --git a/doc/_static/Control_QU.svg b/doc/_static/Load/FlexibleLoad/Control_QU.svg similarity index 100% rename from doc/_static/Control_QU.svg rename to doc/_static/Load/FlexibleLoad/Control_QU.svg diff --git a/doc/_static/Domain_Pconst_QU_Eucl.svg b/doc/_static/Load/FlexibleLoad/Domain_Pconst_QU_Eucl.svg similarity index 77% rename from doc/_static/Domain_Pconst_QU_Eucl.svg rename to doc/_static/Load/FlexibleLoad/Domain_Pconst_QU_Eucl.svg index 88a97d14..7f60ad40 100644 --- a/doc/_static/Domain_Pconst_QU_Eucl.svg +++ b/doc/_static/Load/FlexibleLoad/Domain_Pconst_QU_Eucl.svg @@ -1,5 +1,5 @@ - + @@ -28,40 +28,42 @@ - + - + - - - - + + + + - + - + - + - + - + - - - + + + - - + + - + - + + + diff --git a/doc/_static/Domain_Pconst_QU_P.svg b/doc/_static/Load/FlexibleLoad/Domain_Pconst_QU_P.svg similarity index 81% rename from doc/_static/Domain_Pconst_QU_P.svg rename to doc/_static/Load/FlexibleLoad/Domain_Pconst_QU_P.svg index fd6f0a61..c103b187 100644 --- a/doc/_static/Domain_Pconst_QU_P.svg +++ b/doc/_static/Load/FlexibleLoad/Domain_Pconst_QU_P.svg @@ -1,5 +1,5 @@ - + @@ -28,35 +28,35 @@ - + - - - - + + + + - + - + - + - + - + - - - + + + - - - - + + + + diff --git a/doc/_static/Domain_Pconst_QU_Q.svg b/doc/_static/Load/FlexibleLoad/Domain_Pconst_QU_Q.svg similarity index 80% rename from doc/_static/Domain_Pconst_QU_Q.svg rename to doc/_static/Load/FlexibleLoad/Domain_Pconst_QU_Q.svg index e7238144..803fe96b 100644 --- a/doc/_static/Domain_Pconst_QU_Q.svg +++ b/doc/_static/Load/FlexibleLoad/Domain_Pconst_QU_Q.svg @@ -1,5 +1,5 @@ - + @@ -28,40 +28,40 @@ - + - + - - - - + + + + - + - + - + - + - + - - - + + + - - + + - + - + diff --git a/doc/_static/Load/FlexibleLoad/Domain_Pconst_QU_Qmin_Qmax.svg b/doc/_static/Load/FlexibleLoad/Domain_Pconst_QU_Qmin_Qmax.svg new file mode 100644 index 00000000..7923fd93 --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/Domain_Pconst_QU_Qmin_Qmax.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/Domain_Pconst_Qconst.svg b/doc/_static/Load/FlexibleLoad/Domain_Pconst_Qconst.svg similarity index 82% rename from doc/_static/Domain_Pconst_Qconst.svg rename to doc/_static/Load/FlexibleLoad/Domain_Pconst_Qconst.svg index eeb01cf0..b2f13d7f 100644 --- a/doc/_static/Domain_Pconst_Qconst.svg +++ b/doc/_static/Load/FlexibleLoad/Domain_Pconst_Qconst.svg @@ -1,5 +1,5 @@ - + @@ -28,34 +28,34 @@ - + - - - - + + + + - + - + - + - + - + - - - + + + - - - + + + diff --git a/doc/_static/Load/FlexibleLoad/Domain_PmaxU_QU.svg b/doc/_static/Load/FlexibleLoad/Domain_PmaxU_QU.svg new file mode 100644 index 00000000..17136493 --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/Domain_PmaxU_QU.svg @@ -0,0 +1,3587 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/Domain_PmaxU_Qconst.svg b/doc/_static/Load/FlexibleLoad/Domain_PmaxU_Qconst.svg similarity index 81% rename from doc/_static/Domain_PmaxU_Qconst.svg rename to doc/_static/Load/FlexibleLoad/Domain_PmaxU_Qconst.svg index 6ae5ce94..19a8ae38 100644 --- a/doc/_static/Domain_PmaxU_Qconst.svg +++ b/doc/_static/Load/FlexibleLoad/Domain_PmaxU_Qconst.svg @@ -1,5 +1,5 @@ - + @@ -28,35 +28,35 @@ - + - - - - + + + + - + - + - + - + - + - - - + + + - - - - + + + + diff --git a/doc/_static/Euclidean_Projection.svg b/doc/_static/Load/FlexibleLoad/Euclidean_Projection.svg similarity index 100% rename from doc/_static/Euclidean_Projection.svg rename to doc/_static/Load/FlexibleLoad/Euclidean_Projection.svg diff --git a/doc/_static/Load/FlexibleLoad/Pconst_QU_Eucl_Control_Curve_Example.svg b/doc/_static/Load/FlexibleLoad/Pconst_QU_Eucl_Control_Curve_Example.svg new file mode 100644 index 00000000..93e9ed0c --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/Pconst_QU_Eucl_Control_Curve_Example.svg @@ -0,0 +1,578 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/Load/FlexibleLoad/Pconst_QU_Eucl_Trajectory_Example.svg b/doc/_static/Load/FlexibleLoad/Pconst_QU_Eucl_Trajectory_Example.svg new file mode 100644 index 00000000..ffa9650e --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/Pconst_QU_Eucl_Trajectory_Example.svg @@ -0,0 +1,782 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/Load/FlexibleLoad/Pconst_QU_P_Control_Curve_Example.svg b/doc/_static/Load/FlexibleLoad/Pconst_QU_P_Control_Curve_Example.svg new file mode 100644 index 00000000..b7f741e7 --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/Pconst_QU_P_Control_Curve_Example.svg @@ -0,0 +1,578 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/Load/FlexibleLoad/Pconst_QU_P_Trajectory_Example.svg b/doc/_static/Load/FlexibleLoad/Pconst_QU_P_Trajectory_Example.svg new file mode 100644 index 00000000..406aa583 --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/Pconst_QU_P_Trajectory_Example.svg @@ -0,0 +1,782 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/Load/FlexibleLoad/Pconst_QU_Q_Control_Curve_Example.svg b/doc/_static/Load/FlexibleLoad/Pconst_QU_Q_Control_Curve_Example.svg new file mode 100644 index 00000000..f0c87749 --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/Pconst_QU_Q_Control_Curve_Example.svg @@ -0,0 +1,578 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/Load/FlexibleLoad/Pconst_QU_Q_Trajectory_Example.svg b/doc/_static/Load/FlexibleLoad/Pconst_QU_Q_Trajectory_Example.svg new file mode 100644 index 00000000..9ca66dec --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/Pconst_QU_Q_Trajectory_Example.svg @@ -0,0 +1,782 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Control_Curve_Example.svg b/doc/_static/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Control_Curve_Example.svg new file mode 100644 index 00000000..47a78c22 --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Control_Curve_Example.svg @@ -0,0 +1,578 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Trajectory_Example.svg b/doc/_static/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Trajectory_Example.svg new file mode 100644 index 00000000..d9c5c0e8 --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Trajectory_Example.svg @@ -0,0 +1,782 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/Load/FlexibleLoad/PmaxU_QU_Sequential_1_Trajectory_Example.svg b/doc/_static/Load/FlexibleLoad/PmaxU_QU_Sequential_1_Trajectory_Example.svg new file mode 100644 index 00000000..c74aa94c --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/PmaxU_QU_Sequential_1_Trajectory_Example.svg @@ -0,0 +1,811 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/Load/FlexibleLoad/PmaxU_QU_Sequential_2_Trajectory_Example.svg b/doc/_static/Load/FlexibleLoad/PmaxU_QU_Sequential_2_Trajectory_Example.svg new file mode 100644 index 00000000..c66562d7 --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/PmaxU_QU_Sequential_2_Trajectory_Example.svg @@ -0,0 +1,811 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/Load/FlexibleLoad/PmaxU_QU_Simultaneous_2_Trajectory_Example.svg b/doc/_static/Load/FlexibleLoad/PmaxU_QU_Simultaneous_2_Trajectory_Example.svg new file mode 100644 index 00000000..b7719f51 --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/PmaxU_QU_Simultaneous_2_Trajectory_Example.svg @@ -0,0 +1,819 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/Load/FlexibleLoad/PmaxU_QU_Simultaneous_Trajectory_Example.svg b/doc/_static/Load/FlexibleLoad/PmaxU_QU_Simultaneous_Trajectory_Example.svg new file mode 100644 index 00000000..dbe4d207 --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/PmaxU_QU_Simultaneous_Trajectory_Example.svg @@ -0,0 +1,811 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/Load/FlexibleLoad/PmaxU_Qconst_Control_Curve_Example.svg b/doc/_static/Load/FlexibleLoad/PmaxU_Qconst_Control_Curve_Example.svg new file mode 100644 index 00000000..e2bdb5e5 --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/PmaxU_Qconst_Control_Curve_Example.svg @@ -0,0 +1,535 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/Load/FlexibleLoad/PmaxU_Qconst_Trajectory_Example.svg b/doc/_static/Load/FlexibleLoad/PmaxU_Qconst_Trajectory_Example.svg new file mode 100644 index 00000000..76c8ecd0 --- /dev/null +++ b/doc/_static/Load/FlexibleLoad/PmaxU_Qconst_Trajectory_Example.svg @@ -0,0 +1,733 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/conf.py b/doc/conf.py index bb13e336..098cb6b9 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -22,8 +22,8 @@ # author = "Benoît Vinot" # The full version, including alpha/beta/rc tags -version = "0.5" -release = "0.5.0" +version = "0.6" +release = "0.6.0" # -- General configuration --------------------------------------------------- @@ -68,6 +68,7 @@ autodoc_member_order = "bysource" autodoc_typehints = "signature" autodoc_inherit_docstrings = True +autoclass_content = "both" # show both class and __init__ docstrings # -- Options for HTML output ------------------------------------------------- @@ -129,11 +130,16 @@ "pint": ("https://pint.readthedocs.io/en/stable/", None), "typing_extensions": ("https://typing-extensions.readthedocs.io/en/stable/", None), "rich": ("https://rich.readthedocs.io/en/stable/", None), + "matplotlib": ("https://matplotlib.org/stable/", None), + "networkx": ("https://networkx.org/documentation/stable/", None), } # -- Options for sphinx_copybutton ------------------------------------------- copybutton_exclude = ".linenos, .gp, .go" copybutton_copy_empty_lines = False +# https://sphinx-copybutton.readthedocs.io/en/latest/use.html#strip-and-configure-input-prompts-for-code-cells +copybutton_prompt_text = r">>> |\.\.\. |\$ |C:> |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: " +copybutton_prompt_is_regexp = True # -- Options for sphinxcontrib.googleanalytics ------------------------------- googleanalytics_id = "G-Y9QSN78RFV" diff --git a/doc/images/Constant_P_Projection.tex b/doc/images/Constant_P_Projection.tex deleted file mode 100644 index 60d609e6..00000000 --- a/doc/images/Constant_P_Projection.tex +++ /dev/null @@ -1,75 +0,0 @@ -\input{Preambule}% - -\begin{document} -\begin{tikzpicture}[% - show background rectangle,% - tight background,% - background rectangle/.style={fill=white}% - ] - % Styles - \tikzset{fleche/.style={->, -{Latex}}}% - \tikzset{interdit/.style={pattern=north east lines, pattern color=red}}% - \tikzset{point/.pic={\filldraw[#1] (0,0) circle[radius=0.05];}, point/.default=black}% - - % Parameters - \pgfmathsetmacro{\r}{3.5}% - \pgfmathsetmacro{\R}{1.1 * \r}% - \pgfmathsetmacro{\pth}{0.8 * \r}% - \pgfmathsetmacro{\angth}{acos(\pth/\r)}% - \pgfmathsetmacro{\qth}{\r * sin(\angth)}% - \pgfmathsetmacro{\startangle}{-10}% - \pgfmathsetmacro{\endangle}{90-\startangle}% - - % Axes - \pgfmathsetmacro{\tmp}{\r*cos(90-\startangle)};% - \draw[fleche] (\tmp,0) -- (\R,0) node[below right] {$P$};% - \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% - \draw[fleche] (0,\tmp) -- (0,\R) node[above right] {$Q$};% - - % Circle - \draw (\startangle:\r) arc[start angle=\startangle, end angle=\endangle, radius=\r];% - \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% - \pgfmathsetmacro{\tmpdeux}{\r*cos(\endangle)};% - \pgfmathsetmacro{\tmptrois}{\r*sin(\endangle)};% - \fill[interdit] (0,\tmp) -- (\tmpdeux,\tmp) -- (\tmpdeux,\tmptrois) arc[start angle=\endangle, - end angle=90, radius=\r];% - \draw[fleche] (0,0) -- (20:\r) node[above, midway, sloped] {$\smax$};% - - % Rectangle - \draw (0,\r) -- (\r,\r) -- (\r,0);% - - % Theoretical power - \draw (\pth,0) -- (\pth,\r) node[below left] at (\pth,0) {$P^{\theo}$};% - \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% - \fill[interdit] (\pth,\qth) arc[start angle=\angth, end angle=\startangle, radius=\r] -- - (\pth,\tmp);% - - % Point P^{\theo} - \path (\pth,0) pic[pic type=point];% - - % Point outside the circle - \pgfmathsetmacro{\rayon}{1.15*\r}% - \pgfmathsetmacro{\anglevaleur}{55}% - \coordinate (S) at (\anglevaleur:\rayon);% - - \node[right] at (S) {$\underline{S}$};% - \path (S) pic[pic type=point];% - - % Projection - \pgfmathsetmacro{\tmp}{\rayon*cos(\anglevaleur)};% - \pgfmathsetmacro{\tmpdeux}{sqrt(pow(\r,2)-pow(\tmp,2))};% - \coordinate (S correct) at (\tmp,\tmpdeux);% - \draw[fleche, blue] (S) -- (S correct);% - \path (S correct) pic {point=blue};% - \node[below left] at (S correct) {$\underline{S^{\text{proj.}}}$};% -\end{tikzpicture} -\end{document} -% Local Variables: -% mode: latex -% TeX-engine: luatex -% TeX-source-correlate-method-active: synctex -% ispell-local-dictionary: "british" -% coding: utf-8 -% LaTeX-indent-level: 4 -% fill-column: 100 -% End: diff --git a/doc/images/Constant_Q_Projection.tex b/doc/images/Constant_Q_Projection.tex deleted file mode 100644 index 8e437c69..00000000 --- a/doc/images/Constant_Q_Projection.tex +++ /dev/null @@ -1,75 +0,0 @@ -\input{Preambule}% - -\begin{document} -\begin{tikzpicture}[% - show background rectangle,% - tight background,% - background rectangle/.style={fill=white}% - ] - % Styles - \tikzset{fleche/.style={->, -{Latex}}}% - \tikzset{interdit/.style={pattern=north east lines, pattern color=red}}% - \tikzset{point/.pic={\filldraw[#1] (0,0) circle[radius=0.05];}, point/.default=black}% - - % Paramètres - \pgfmathsetmacro{\r}{3.5}% - \pgfmathsetmacro{\R}{1.1 * \r}% - \pgfmathsetmacro{\pth}{0.8 * \r}% - \pgfmathsetmacro{\angth}{acos(\pth/\r)}% - \pgfmathsetmacro{\qth}{\r * sin(\angth)}% - \pgfmathsetmacro{\startangle}{-10}% - \pgfmathsetmacro{\endangle}{90-\startangle}% - - % Axes - \pgfmathsetmacro{\tmp}{\r*cos(90-\startangle)};% - \draw[fleche] (\tmp,0) -- (\R,0) node[below right] {$P$};% - \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% - \draw[fleche] (0,\tmp) -- (0,\R) node[above right] {$Q$};% - - % Cercle - \draw (\startangle:\r) arc[start angle=\startangle, end angle=\endangle, radius=\r];% - \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% - \pgfmathsetmacro{\tmpdeux}{\r*cos(\endangle)};% - \pgfmathsetmacro{\tmptrois}{\r*sin(\endangle)};% - \fill[interdit] (0,\tmp) -- (\tmpdeux,\tmp) -- (\tmpdeux,\tmptrois) arc[start angle=\endangle, - end angle=90, radius=\r];% - \draw[fleche] (0,0) -- (20:\r) node[above, midway, sloped] {$\smax$};% - - % Rectangle - \draw (0,\r) -- (\r,\r) -- (\r,0);% - - % Puissance théorique - \draw (\pth,0) -- (\pth,\r) node[below left] at (\pth,0) {$P^{\theo}$};% - \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% - \fill[interdit] (\pth,\qth) arc[start angle=\angth, end angle=\startangle, radius=\r] -- - (\pth,\tmp);% - - % Point noir sur P^{\theo} - \path (\pth,0) pic[pic type=point];% - - % Point en dehors du cercle - \pgfmathsetmacro{\rayon}{1.15*\r}% - \pgfmathsetmacro{\anglevaleur}{55}% - \coordinate (S) at (\anglevaleur:\rayon);% - - \node[right] at (S) {$\underline{S}$};% - \path (S) pic[pic type=point];% - - % Projection - \pgfmathsetmacro{\tmp}{\rayon*sin(\anglevaleur)};% - \pgfmathsetmacro{\tmpdeux}{sqrt(pow(\r,2)-pow(\tmp,2))};% - \coordinate (S correct) at (\tmpdeux,\tmp);% - \draw[fleche, blue] (S) -- (S correct);% - \path (S correct) pic {point=blue};% - \node[below left] at (S correct) {$\underline{S^{\text{proj.}}}$};% -\end{tikzpicture} -\end{document} -% Local Variables: -% mode: latex -% TeX-engine: luatex -% TeX-source-correlate-method-active: synctex -% ispell-local-dictionary: "british" -% coding: utf-8 -% LaTeX-indent-level: 4 -% fill-column: 100 -% End: diff --git a/doc/images/Control_PU_Cons.tex b/doc/images/Control_PU_Cons.tex deleted file mode 100644 index d8466032..00000000 --- a/doc/images/Control_PU_Cons.tex +++ /dev/null @@ -1,107 +0,0 @@ -\input{Preambule}% - -\usepackage{pgfplots}% -\pgfplotsset{compat=newest}% -\usepgfplotslibrary{groupplots, colorbrewer}% - -\begin{document} -\begin{tikzpicture}[% - show background rectangle,% - tight background,% - background rectangle/.style={fill=white}% - ] - % - % Common parameters - % - \pgfmathsetmacro{\uminvaleur}{210.0}% - \pgfmathsetmacro{\uminnormvaleur}{1.0}% - \pgfmathsetmacro{\udownvaleur}{220.0}% - \pgfmathsetmacro{\udownnormvaleur}{\udownvaleur/\uminvaleur}% - \pgfmathsetmacro{\uupvaleur}{240.0}% - \pgfmathsetmacro{\uupnormvaleur}{\uupvaleur/\uminvaleur}% - \pgfmathsetmacro{\umaxvaleur}{250.0}% - \pgfmathsetmacro{\umaxnormvaleur}{\umaxvaleur/\uminvaleur}% - \pgfmathsetmacro{\unomvaleur}{(\udownvaleur+\uupvaleur)/2.0}% - \pgfmathsetmacro{\unomnormvaleur}{\unomvaleur/\uminvaleur}% - \pgfmathsetmacro{\umidminvaleur}{(\udownvaleur+\uminvaleur)/2.0}% - \pgfmathsetmacro{\umidminnormvaleur}{\umidminvaleur/\uminvaleur}% - \pgfmathsetmacro{\umidmaxvaleur}{(\uupvaleur+\umaxvaleur)/2.0}% - \pgfmathsetmacro{\umidmaxnormvaleur}{\umidmaxvaleur/\uminvaleur}% - - \pgfmathsetmacro{\xminvaleur}{\uminvaleur - 2.5}% - \pgfmathsetmacro{\xminnormvaleur}{\xminvaleur/\uminvaleur}% - \pgfmathsetmacro{\xmaxvaleur}{\umaxvaleur + 2.5}% - \pgfmathsetmacro{\xmaxnormvaleur}{\xmaxvaleur/\uminvaleur}% - - \pgfmathsetmacro{\yminnormvaleur}{0}% - \pgfmathsetmacro{\ymaxnormvaleur}{1}% - - % - % Style - % - \tikzset{lisse/.style={line width=0.3mm, - domain=\xminnormvaleur:\xmaxnormvaleur, samples=75, mark=none}}% - \tikzset{non lisse/.style={line width=0.3mm, mark=*}}% - - \begin{axis}[% - height=7cm,% - width=0.9\textwidth,% - enlarge y limits,% - grid=major,% - xlabel={$|V_{p_1}-V_{p_2}|$},% - xtick={\uminnormvaleur,\umidminnormvaleur,\udownnormvaleur,\unomnormvaleur,\uupnormvaleur,\umidmaxnormvaleur,\umaxnormvaleur},% - xticklabels={% - $\uminnorm$,,$\udownnorm$,$\unomnorm$,$\uupnorm$,,$\umaxnorm$% - },% - y tick label style={/pgf/number format/.cd,% - set thousands separator={},% - fixed,% - % fixed zerofill,% - precision=1,% - use comma% - },% - xmin=\xminnormvaleur,% - xmax=\xmaxnormvaleur,% - ymin=\yminnormvaleur,% - ymax=\ymaxnormvaleur,% - legend columns=2,% - legend style={% - at={(0.5,-0.25)},% - anchor=north,% - nodes={text width=4cm}% - },% - cycle list/YlOrRd-5, % initialize YlOrRd-5 - cycle list name=YlOrRd-5% - ] - - % Piecewise linear function - \addplot[non lisse, red] coordinates {% - (\xminnormvaleur,0)% - (\uminnormvaleur,0)% - (\udownnormvaleur,1)% - (\xmaxnormvaleur,1)% - };% - \addlegendentry{Non-smooth control};% - - - % Soft clipping functions - \pgfplotsset{cycle list shift=-1}% Reset cycle to 0 - \foreach \alphavaleur in {50,100,200,300,400} {% - \addplot+[lisse] expression {% - 1.0/(\alphavaleur*(\udownnormvaleur-\uminnormvaleur)) * - ln((1+exp(\alphavaleur*(x-\uminnormvaleur)))/(1+exp(\alphavaleur*(x-\udownnormvaleur)))) - };% - \addlegendentryexpanded{Soft clipping ($\alpha=\num{\alphavaleur}$)};% - };% - \end{axis} -\end{tikzpicture} -\end{document} -% Local Variables: -% mode: latex -% TeX-engine: luatex -% TeX-source-correlate-method-active: synctex -% ispell-local-dictionary: "british" -% coding: utf-8 -% LaTeX-indent-level: 4 -% fill-column: 100 -% End: diff --git a/doc/images/Control_PU_Prod.tex b/doc/images/Control_PU_Prod.tex deleted file mode 100644 index 421ecb4b..00000000 --- a/doc/images/Control_PU_Prod.tex +++ /dev/null @@ -1,107 +0,0 @@ -\input{Preambule}% - -\usepackage{pgfplots}% -\pgfplotsset{compat=newest}% -\usepgfplotslibrary{groupplots, colorbrewer}% - -\begin{document} -\begin{tikzpicture}[% - show background rectangle,% - tight background,% - background rectangle/.style={fill=white}% - ] - % - % Common parameters - % - \pgfmathsetmacro{\umaxvaleur}{250.0}% - \pgfmathsetmacro{\umaxnormvaleur}{1.0}% - \pgfmathsetmacro{\uupvaleur}{240.0}% - \pgfmathsetmacro{\uupnormvaleur}{\uupvaleur/\umaxvaleur}% - \pgfmathsetmacro{\udownvaleur}{220.0}% - \pgfmathsetmacro{\udownnormvaleur}{\udownvaleur/\umaxvaleur}% - \pgfmathsetmacro{\uminvaleur}{210.0}% - \pgfmathsetmacro{\uminnormvaleur}{\uminvaleur/\umaxvaleur}% - \pgfmathsetmacro{\unomvaleur}{(\udownvaleur+\uupvaleur)/2.0}% - \pgfmathsetmacro{\unomnormvaleur}{\unomvaleur/\umaxvaleur}% - \pgfmathsetmacro{\umidminvaleur}{(\udownvaleur+\uminvaleur)/2.0}% - \pgfmathsetmacro{\umidminnormvaleur}{\umidminvaleur/\umaxvaleur}% - \pgfmathsetmacro{\umidmaxvaleur}{(\uupvaleur+\umaxvaleur)/2.0}% - \pgfmathsetmacro{\umidmaxnormvaleur}{\umidmaxvaleur/\umaxvaleur}% - - \pgfmathsetmacro{\xminvaleur}{\uminvaleur - 2.5}% - \pgfmathsetmacro{\xminnormvaleur}{\xminvaleur/\umaxvaleur}% - \pgfmathsetmacro{\xmaxvaleur}{\umaxvaleur + 2.5}% - \pgfmathsetmacro{\xmaxnormvaleur}{\xmaxvaleur/\umaxvaleur}% - - \pgfmathsetmacro{\yminnormvaleur}{0}% - \pgfmathsetmacro{\ymaxnormvaleur}{1}% - - % - % Style - % - \tikzset{lisse/.style={line width=0.3mm, domain=\xminnormvaleur:\xmaxnormvaleur, samples=75, - mark=none}}% - \tikzset{non lisse/.style={line width=0.3mm, mark=*}}% - - \begin{axis}[% - height=7cm,% - width=0.9\textwidth,% - enlarge y limits,% - grid=major,% - xlabel={$|V_{p_1}-V_{p_2}|$},% - xtick={\uminnormvaleur,\umidminnormvaleur,\udownnormvaleur,\unomnormvaleur,\uupnormvaleur,\umidmaxnormvaleur,\umaxnormvaleur},% - xticklabels={% - $\uminnorm$,,$\udownnorm$,$\unomnorm$,$\uupnorm$,,$\umaxnorm$% - },% - y tick label style={/pgf/number format/.cd,% - set thousands separator={},% - fixed,% - % fixed zerofill,% - precision=1,% - use comma% - },% - xmin=\xminnormvaleur,% - xmax=\xmaxnormvaleur,% - ymin=\yminnormvaleur,% - ymax=\ymaxnormvaleur,% - legend columns=2,% - legend style={% - at={(0.5,-0.25)},% - anchor=north,% - nodes={text width=4cm}% - },% - cycle list/YlOrRd-5, % initialize YlOrRd-5 - cycle list name=YlOrRd-5% - ] - - % Piecewise linear function - \addplot[non lisse, red] coordinates {% - (\xminnormvaleur,1)% - (\uupnormvaleur,1)% - (\umaxnormvaleur,0)% - (\xmaxnormvaleur,0)% - };% - \addlegendentry{Non-smooth control};% - - - % Soft clipping functions - \pgfplotsset{cycle list shift=-1}% Reset cycle to 0 - \foreach \alphavaleur in {50,100,200,300,400} {% - \addplot+[lisse] expression {% - 1.0 + 1.0/(\alphavaleur*(\umaxnormvaleur-\uupnormvaleur)) * - ln((1+exp(\alphavaleur*(x-\umaxnormvaleur)))/(1+exp(\alphavaleur*(x-\uupnormvaleur)))) - };% - \addlegendentryexpanded{Soft clipping ($\alpha=\num{\alphavaleur}$)};% - };% - \end{axis} -\end{tikzpicture} -\end{document} -% Local Variables: -% mode: latex -% TeX-engine: luatex -% TeX-source-correlate-method-active: synctex -% ispell-local-dictionary: "british" -% coding: utf-8 -% LaTeX-indent-level: 4 -% fill-column: 100 -% End: diff --git a/doc/images/Control_QU.tex b/doc/images/Control_QU.tex deleted file mode 100644 index b8947919..00000000 --- a/doc/images/Control_QU.tex +++ /dev/null @@ -1,116 +0,0 @@ -\input{Preambule}% - -\usepackage{pgfplots}% -\pgfplotsset{compat=newest}% -\usepgfplotslibrary{groupplots, colorbrewer}% - -\begin{document} -\begin{tikzpicture}[% - show background rectangle,% - tight background,% - background rectangle/.style={fill=white}% - ] - % - % Common parameters - % - \pgfmathsetmacro{\umaxvaleur}{250.0}% - \pgfmathsetmacro{\umaxnormvaleur}{1.0}% - \pgfmathsetmacro{\uupvaleur}{240.0}% - \pgfmathsetmacro{\uupnormvaleur}{\uupvaleur/\umaxvaleur}% - \pgfmathsetmacro{\udownvaleur}{220.0}% - \pgfmathsetmacro{\udownnormvaleur}{\udownvaleur/\umaxvaleur}% - \pgfmathsetmacro{\uminvaleur}{210.0}% - \pgfmathsetmacro{\uminnormvaleur}{\uminvaleur/\umaxvaleur}% - \pgfmathsetmacro{\unomvaleur}{(\udownvaleur+\uupvaleur)/2.0}% - \pgfmathsetmacro{\unomnormvaleur}{\unomvaleur/\umaxvaleur}% - \pgfmathsetmacro{\umidminvaleur}{(\udownvaleur+\uminvaleur)/2.0}% - \pgfmathsetmacro{\umidminnormvaleur}{\umidminvaleur/\umaxvaleur}% - \pgfmathsetmacro{\umidmaxvaleur}{(\uupvaleur+\umaxvaleur)/2.0}% - \pgfmathsetmacro{\umidmaxnormvaleur}{\umidmaxvaleur/\umaxvaleur}% - - \pgfmathsetmacro{\xminvaleur}{\uminvaleur - 2.5}% - \pgfmathsetmacro{\xminnormvaleur}{\xminvaleur/\umaxvaleur}% - \pgfmathsetmacro{\xmaxvaleur}{\umaxvaleur + 2.5}% - \pgfmathsetmacro{\xmaxnormvaleur}{\xmaxvaleur/\umaxvaleur}% - - \pgfmathsetmacro{\yminnormvaleur}{-1}% - \pgfmathsetmacro{\ymaxnormvaleur}{1}% - - \pgfmathsetmacro{\qthnormvaleur}{0.30}% - - % - % Style - % - \tikzset{lisse/.style={line width=0.3mm, domain=\xminnormvaleur:\xmaxnormvaleur, samples=75, - mark=none}}% - \tikzset{non lisse/.style={line width=0.3mm, mark=*}}% - - \begin{axis}[% - height=7cm,% - width=0.9\textwidth,% - enlarge y limits,% - grid=major,% - xlabel={$|V_{p_1}-V_{p_2}|$},% - xtick={\uminnormvaleur,\umidminnormvaleur,\udownnormvaleur,\unomnormvaleur,\uupnormvaleur,\umidmaxnormvaleur,\umaxnormvaleur},% - xticklabels={% - $\uminnorm$,,$\udownnorm$,$\unomnorm$,$\uupnorm$,,$\umaxnorm$% - },% - y tick label style={/pgf/number format/.cd,% - set thousands separator={},% - fixed,% - fixed zerofill,% - precision=1,% - use comma% - },% - ytick={\yminnormvaleur,0,\qthnormvaleur,\ymaxnormvaleur},% - yticklabels={-1,0,$\dfrac{Q^{\theo}_{p_1p_2}}{\smax_{p_1p_2}}$,1},% - xmin=\xminnormvaleur,% - xmax=\xmaxnormvaleur,% - ymin=\yminnormvaleur,% - ymax=\ymaxnormvaleur,% - % ylabel={$\alpha\left(|V_{p}|\right)$},% - % legend entries={Contrôles non lisses,Contrôles lisses},% - legend columns=2,% - legend style={% - at={(0.5,-0.25)},% - anchor=north,% - nodes={text width=4cm}% - },% - cycle list/YlOrRd-5, % initialize YlOrRd-5 - cycle list name=YlOrRd-5% - ] - - % Fonction linéaire par morceaux - \addplot[non lisse, red] coordinates {% - (\xminnormvaleur,-1)% - (\uminnormvaleur,-1)% - (\udownnormvaleur,\qthnormvaleur)% - (\uupnormvaleur,\qthnormvaleur)% - (\umaxnormvaleur,1)% - (\xmaxnormvaleur,1)% - };% - \addlegendentry{Non-smooth control};% - - % Soft clipping functions - \pgfplotsset{cycle list shift=-1}% Reset cycle to 0 - \foreach \alphavaleur in {50,100,200,300,400} {% - \addplot+[lisse] expression {% - \qthnormvaleur + ( 1.0/(\alphavaleur*(\uminnormvaleur-\udownnormvaleur)) * - ln((1+exp(\alphavaleur*(x-\udownnormvaleur)))/(1+exp(\alphavaleur*(x-\uminnormvaleur))))-1.0 - )*(1+\qthnormvaleur) + ( 1.0/(\alphavaleur*(\umaxnormvaleur-\uupnormvaleur)) * - ln((1+exp(\alphavaleur*(x-\uupnormvaleur)))/(1+exp(\alphavaleur*(x-\umaxnormvaleur)))) - )*(1-\qthnormvaleur) };% - \addlegendentryexpanded{Soft clipping ($\alpha=\num{\alphavaleur}$)};% - };% - \end{axis} -\end{tikzpicture} -\end{document} -% Local Variables: -% mode: latex -% TeX-engine: luatex -% TeX-source-correlate-method-active: synctex -% ispell-local-dictionary: "british" -% coding: utf-8 -% LaTeX-indent-level: 4 -% fill-column: 100 -% End: diff --git a/doc/images/Control_Trajectory_PmaxU_QU.tex b/doc/images/Control_Trajectory_PmaxU_QU.tex deleted file mode 100644 index 62ca29bc..00000000 --- a/doc/images/Control_Trajectory_PmaxU_QU.tex +++ /dev/null @@ -1,119 +0,0 @@ -\input{Preambule}% - -\usepackage{pgfplots}% -\pgfplotsset{compat=newest}% - -\begin{document} -\begin{tikzpicture}[% - show background rectangle,% - tight background,% - background rectangle/.style={fill=white}% - ] - % - % Common parameters - % - % Control of P - \pgfmathsetmacro{\umaxpvaleur}{250}% - \pgfmathsetmacro{\umaxpnormvaleur}{1.0}% - \pgfmathsetmacro{\uuppvaleur}{240}% - \pgfmathsetmacro{\uuppnormvaleur}{\uuppvaleur/\umaxpvaleur}% - \pgfmathsetmacro{\alphapvaleur}{400}% - - % Control of Q - \pgfmathsetmacro{\umaxqvaleur}{240}% - \pgfmathsetmacro{\umaxqnormvaleur}{1}% - \pgfmathsetmacro{\uupqvaleur}{235}% - \pgfmathsetmacro{\uupqnormvaleur}{\uupqvaleur/\umaxqvaleur}% - \pgfmathsetmacro{\udownqvaleur}{220}% - \pgfmathsetmacro{\udownqnormvaleur}{\udownqvaleur/\umaxqvaleur}% - \pgfmathsetmacro{\uminqvaleur}{210}% - \pgfmathsetmacro{\uminqnormvaleur}{\uminqvaleur/\umaxqvaleur}% - \pgfmathsetmacro{\alphaqvaleur}{400}% - - - % User-defined values - \pgfmathsetmacro{\smaxvaleur}{5000}% - \pgfmathsetmacro{\smaxnormvaleur}{1}% - \pgfmathsetmacro{\pthvaleur}{3750}% - \pgfmathsetmacro{\pthnormvaleur}{\pthvaleur/\smaxvaleur}% - \pgfmathsetmacro{\angthvaleur}{acos(\pthvaleur/\smaxvaleur)}% - \pgfmathsetmacro{\qthvaleur}{\smaxvaleur * sin(\angthvaleur)}% - \pgfmathsetmacro{\qthnormvaleur}{\qthvaleur/\smaxvaleur}% - - % - % Style - % - \tikzset{interdit/.style={pattern=north east lines, pattern color=red}}% - - \begin{axis}[% - height=9cm,% - % width=0.9\textwidth,% - axis equal=true,% - enlarge y limits,% - enlarge x limits,% - grid=major,% - xmin=-\smaxnormvaleur,% - xmax=\smaxnormvaleur,% - ymin=-\smaxnormvaleur,% - ymax=\smaxnormvaleur,% - xlabel=$P$,% - ylabel=$Q$,% - xtick={-\smaxnormvaleur,-\pthnormvaleur,0,\smaxnormvaleur},% - xticklabels={$-\smax$,$P^{\theo}$,0,$\smax$},% - ytick={-\smaxnormvaleur,0,\smaxnormvaleur},% - yticklabels={$-\smax$,0,$\smax$},% - declare function={% - fp(\t)=1.0+1.0/(\alphapvaleur*(\umaxpnormvaleur-\uuppnormvaleur)) * - ln((1+exp(\alphapvaleur*(\t-\umaxpnormvaleur)))/(1+exp(\alphapvaleur*(\t-\uuppnormvaleur))));% - p(\t)=-fp(\t/\umaxpvaleur)*\pthnormvaleur;% - fq(\t)= 1.0/(\alphaqvaleur*(\uminqnormvaleur-\udownqnormvaleur)) * - ln((1+exp(\alphaqvaleur*(\t-\udownqnormvaleur)))/(1+exp(\alphaqvaleur*(\t-\uminqnormvaleur))))-1.0;% - gq(\t)=1.0/(\alphaqvaleur*(\umaxqnormvaleur-\uupqnormvaleur)) * - ln((1+exp(\alphaqvaleur*(\t-\uupqnormvaleur)))/(1+exp(\alphaqvaleur*(\t-\umaxqnormvaleur))));% - q(\t)=\qthnormvaleur + - fq(\t/\umaxqvaleur)*(\smaxnormvaleur+\qthnormvaleur)+gq(\t/\umaxqvaleur)*(\smaxnormvaleur-\qthnormvaleur);% - pclip(\t)=p(\t);% - qclip(\t)=sign(q(\t))*sqrt(min(q(\t)^2,\smaxnormvaleur^2-p(\t)^2));% - },% - ] - % Points - \addplot+ [samples at={210,211,...,250}] ({pclip(\x)},{qclip(\x)});% - - % Cercle - \draw (axis cs:0,0) circle[radius=\smaxnormvaleur];% - - % Axes - \draw (axis cs:0,\pgfkeysvalueof{/pgfplots/ymin}) -- (axis - cs:0,\pgfkeysvalueof{/pgfplots/ymax});% - \draw (axis cs:\pgfkeysvalueof{/pgfplots/xmin},0) -- (axis - cs:\pgfkeysvalueof{/pgfplots/xmax},0);% - - % Remplissage - \fill[interdit] (axis cs:0,-\smaxnormvaleur) -- (axis cs:0,\smaxnormvaleur) arc[start - angle=90, delta angle=-180, radius=\smaxnormvaleur];% - \fill[interdit] (axis cs:-\pthnormvaleur,\qthnormvaleur) arc[start angle=180-\angthvaleur, - delta angle=2*\angthvaleur, radius=\smaxnormvaleur];% - - % Notes - \node[pin={[pin distance=1mm] right:\SI{210}{\volt}}] at ({pclip(210)},{qclip(210)}) {};% - \node[pin={[pin distance=1mm] right:\SI{215}{\volt}}] at ({pclip(215)},{qclip(215)}) {};% - \node[pin={[pin distance=1mm] below right:\SI{220}{\volt}} ] at ({pclip(220)},{qclip(220)}) - {};% - \node[pin={[pin distance=1mm] left:\SI{230}{\volt}} ] at ({pclip(230)},{qclip(230)}) {};% - \node[pin={[pin distance=1mm] right:\SI{240}{\volt}}] at ({pclip(240)},{qclip(240)}) {};% - \node[pin={[pin distance=1mm] above left:\SI{245}{\volt}}] at ({pclip(245)},{qclip(245)}) - {};% - \node[pin={[pin distance=1mm] above right:\SI{250}{\volt}}] at ({pclip(250)},{qclip(250)}) - {};% - \end{axis} -\end{tikzpicture} -\end{document} -% Local Variables: -% mode: latex -% TeX-engine: luatex -% TeX-source-correlate-method-active: synctex -% ispell-local-dictionary: "british" -% coding: utf-8 -% LaTeX-indent-level: 4 -% fill-column: 100 -% End: diff --git a/doc/images/Domain_Pconst_QU_Eucl.tex b/doc/images/Domain_Pconst_QU_Eucl.tex deleted file mode 100644 index 2529f943..00000000 --- a/doc/images/Domain_Pconst_QU_Eucl.tex +++ /dev/null @@ -1,27 +0,0 @@ -\input{Preambule}% - -\begin{document} -\begin{tikzpicture}[% - show background rectangle,% - tight background,% - background rectangle/.style={fill=white}% - ] - \input{Domain_Common.tikz}% - - % The domain is a segment with two arc circle - \draw[domaine] (-\angeuclthvaleur:\r) arc[start angle=-\angeuclthvaleur, end - angle=-\angthvaleur, radius=\r] -- (\pthvaleur,\qthmaxvaleur) arc [start angle=\angthvaleur, end - angle=\angeuclthvaleur, radius=\r];% - - \pic[domaine] at (\pthvaleur,\qthvaleur) {point};% -\end{tikzpicture} -\end{document} -% Local Variables: -% mode: latex -% TeX-engine: luatex -% TeX-source-correlate-method-active: synctex -% ispell-local-dictionary: "british" -% coding: utf-8 -% LaTeX-indent-level: 4 -% fill-column: 100 -% End: diff --git a/doc/images/Domain_Pconst_QU_P.tex b/doc/images/Domain_Pconst_QU_P.tex deleted file mode 100644 index cb528ed5..00000000 --- a/doc/images/Domain_Pconst_QU_P.tex +++ /dev/null @@ -1,25 +0,0 @@ -\input{Preambule}% - -\begin{document} -\begin{tikzpicture}[% - show background rectangle,% - tight background,% - background rectangle/.style={fill=white}% - ] - \input{Domain_Common.tikz}% - - % The domain is a segment - \draw[domaine] (\pthvaleur,-\qthmaxvaleur) -- (\pthvaleur,\qthmaxvaleur);% - - \pic[domaine] at (\pthvaleur,\qthvaleur) {point};% -\end{tikzpicture} -\end{document} -% Local Variables: -% mode: latex -% TeX-engine: luatex -% TeX-source-correlate-method-active: synctex -% ispell-local-dictionary: "british" -% coding: utf-8 -% LaTeX-indent-level: 4 -% fill-column: 100 -% End: diff --git a/doc/images/Domain_Pconst_QU_Q.tex b/doc/images/Domain_Pconst_QU_Q.tex deleted file mode 100644 index 7b1275c5..00000000 --- a/doc/images/Domain_Pconst_QU_Q.tex +++ /dev/null @@ -1,26 +0,0 @@ -\input{Preambule}% - -\begin{document} -\begin{tikzpicture}[% - show background rectangle,% - tight background,% - background rectangle/.style={fill=white}% - ] - \input{Domain_Common.tikz}% - - % The domain is a segment with two portions of circle arc - \draw[domaine] (0,-\r) arc[start angle=-90, end angle=-\angthvaleur, radius=\r] -- - (\pthvaleur,\qthmaxvaleur) arc [start angle=\angthvaleur, end angle=90, radius=\r];% - - \pic[domaine] at (\pthvaleur,\qthvaleur) {point};% -\end{tikzpicture} -\end{document} -% Local Variables: -% mode: latex -% TeX-engine: luatex -% TeX-source-correlate-method-active: synctex -% ispell-local-dictionary: "british" -% coding: utf-8 -% LaTeX-indent-level: 4 -% fill-column: 100 -% End: diff --git a/doc/images/Domain_Pconst_Qconst.tex b/doc/images/Domain_Pconst_Qconst.tex deleted file mode 100644 index 698b4d3f..00000000 --- a/doc/images/Domain_Pconst_Qconst.tex +++ /dev/null @@ -1,22 +0,0 @@ -\input{Preambule}% -\begin{document} -\begin{tikzpicture}[% - show background rectangle,% - tight background,% - background rectangle/.style={fill=white}% - ] - \input{Domain_Common.tikz}% - - % The domain is limited to a point - \pic[domaine] at (\pthvaleur,\qthvaleur) {point}; -\end{tikzpicture} -\end{document} -% Local Variables: -% mode: latex -% TeX-engine: luatex -% TeX-source-correlate-method-active: synctex -% ispell-local-dictionary: "british" -% coding: utf-8 -% LaTeX-indent-level: 4 -% fill-column: 100 -% End: diff --git a/doc/images/Domain_PmaxU_QU.tex b/doc/images/Domain_PmaxU_QU.tex deleted file mode 100644 index 452040ab..00000000 --- a/doc/images/Domain_PmaxU_QU.tex +++ /dev/null @@ -1,26 +0,0 @@ -\input{Preambule}% - -\begin{document} -\begin{tikzpicture}[% - show background rectangle,% - tight background,% - background rectangle/.style={fill=white}% - ] - \input{Domain_Common.tikz}% - - % The domain is a prtion of the circle - \filldraw[domaine hache] (0,-\r) arc[start angle=-90, end angle=-\angthvaleur, radius=\r] -- - (\pthvaleur, \qthmaxvaleur) arc[start angle=\angthvaleur, end angle=90, radius=\r] --cycle;% - - \pic[domaine] at (\pthvaleur,\qthvaleur) {point};% -\end{tikzpicture} -\end{document} -% Local Variables: -% mode: latex -% TeX-engine: luatex -% TeX-source-correlate-method-active: synctex -% ispell-local-dictionary: "british" -% coding: utf-8 -% LaTeX-indent-level: 4 -% fill-column: 100 -% End: diff --git a/doc/images/Domain_PmaxU_Qconst.tex b/doc/images/Domain_PmaxU_Qconst.tex deleted file mode 100644 index 7434c790..00000000 --- a/doc/images/Domain_PmaxU_Qconst.tex +++ /dev/null @@ -1,24 +0,0 @@ -\input{Preambule}% - -\begin{document} -\begin{tikzpicture}[% - show background rectangle,% - tight background,% - background rectangle/.style={fill=white}% - ] - \input{Domain_Common.tikz}% - - % The domain is a segment - \draw[domaine] (0,\qthvaleur) -- (\pthvaleur,\qthvaleur);% - \pic[domaine] at (\pthvaleur,\qthvaleur) {point};% -\end{tikzpicture} -\end{document} -% Local Variables: -% mode: latex -% TeX-engine: luatex -% TeX-source-correlate-method-active: synctex -% ispell-local-dictionary: "british" -% coding: utf-8 -% LaTeX-indent-level: 4 -% fill-column: 100 -% End: diff --git a/doc/images/Euclidean_Projection.tex b/doc/images/Euclidean_Projection.tex deleted file mode 100644 index c774d8ba..00000000 --- a/doc/images/Euclidean_Projection.tex +++ /dev/null @@ -1,73 +0,0 @@ -\input{Preambule}% - -\begin{document} -\begin{tikzpicture}[% - show background rectangle,% - tight background,% - background rectangle/.style={fill=white}% - ] - % Styles - \tikzset{fleche/.style={->, -{Latex}}}% - \tikzset{interdit/.style={pattern=north east lines, pattern color=red}}% - \tikzset{point/.pic={\filldraw[#1] (0,0) circle[radius=0.05];}, point/.default=black}% - - % Paramètres - \pgfmathsetmacro{\r}{3.5}% - \pgfmathsetmacro{\R}{1.1 * \r}% - \pgfmathsetmacro{\pth}{0.8 * \r}% - \pgfmathsetmacro{\angth}{acos(\pth/\r)}% - \pgfmathsetmacro{\qth}{\r * sin(\angth)}% - \pgfmathsetmacro{\startangle}{-10}% - \pgfmathsetmacro{\endangle}{90-\startangle}% - - % Axes - \pgfmathsetmacro{\tmp}{\r*cos(90-\startangle)};% - \draw[fleche] (\tmp,0) -- (\R,0) node[below right] {$P$};% - \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% - \draw[fleche] (0,\tmp) -- (0,\R) node[above right] {$Q$};% - - % Circle - \draw (\startangle:\r) arc[start angle=\startangle, end angle=\endangle, radius=\r];% - \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% - \pgfmathsetmacro{\tmpdeux}{\r*cos(\endangle)};% - \pgfmathsetmacro{\tmptrois}{\r*sin(\endangle)};% - \fill[interdit] (0,\tmp) -- (\tmpdeux,\tmp) -- (\tmpdeux,\tmptrois) arc[start angle=\endangle, - end angle=90, radius=\r];% - \draw[fleche] (0,0) -- (20:\r) node[above, midway, sloped] {$\smax$};% - - % Rectangle - \draw (0,\r) -- (\r,\r) -- (\r,0);% - - % Theoretical power - \draw (\pth,0) -- (\pth,\r) node[below left] at (\pth,0) {$P^{\theo}$};% - \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% - \fill[interdit] (\pth,\qth) arc[start angle=\angth, end angle=\startangle, radius=\r] -- - (\pth,\tmp);% - - % Point P^{\theo} - \path (\pth,0) pic[pic type=point];% - - % Point outside of the circle - \pgfmathsetmacro{\rayon}{1.15*\r}% - \pgfmathsetmacro{\anglevaleur}{55}% - \coordinate (S) at (\anglevaleur:\rayon);% - - \node[right] at (S) {$\underline{S}$};% - \path (S) pic[pic type=point];% - - % Projection - \coordinate (S correct) at (\anglevaleur:\r);% - \draw[fleche, blue] (S) -- (S correct);% - \path (S correct) pic {point=blue};% - \node[below left] at (S correct) {$\underline{S^{\text{proj.}}}$};% -\end{tikzpicture} -\end{document} -% Local Variables: -% mode: latex -% TeX-engine: luatex -% TeX-source-correlate-method-active: synctex -% ispell-local-dictionary: "british" -% coding: utf-8 -% LaTeX-indent-level: 4 -% fill-column: 100 -% End: diff --git a/doc/images/Load/FlexibleLoad/Constant_P_Projection.tex b/doc/images/Load/FlexibleLoad/Constant_P_Projection.tex new file mode 100644 index 00000000..a213d2de --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Constant_P_Projection.tex @@ -0,0 +1,75 @@ +\input{Preambule}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + % Styles + \tikzset{fleche/.style={->, -{Latex}}}% + \tikzset{interdit/.style={pattern=north east lines, pattern color=red}}% + \tikzset{point/.pic={\filldraw[#1] (0,0) circle[radius=0.05];}, point/.default=black}% + + % Parameters + \pgfmathsetmacro{\r}{3.5}% + \pgfmathsetmacro{\R}{1.1 * \r}% + \pgfmathsetmacro{\pth}{0.8 * \r}% + \pgfmathsetmacro{\angth}{acos(\pth/\r)}% + \pgfmathsetmacro{\qth}{\r * sin(\angth)}% + \pgfmathsetmacro{\startangle}{-10}% + \pgfmathsetmacro{\endangle}{90-\startangle}% + + % Axes + \pgfmathsetmacro{\tmp}{\r*cos(90-\startangle)};% + \draw[fleche] (\tmp,0) -- (\R,0) node[below right] {$P$};% + \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% + \draw[fleche] (0,\tmp) -- (0,\R) node[above right] {$Q$};% + + % Circle + \draw (\startangle:\r) arc[start angle=\startangle, end angle=\endangle, radius=\r];% + \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% + \pgfmathsetmacro{\tmpdeux}{\r*cos(\endangle)};% + \pgfmathsetmacro{\tmptrois}{\r*sin(\endangle)};% + \fill[interdit] (0,\tmp) -- (\tmpdeux,\tmp) -- (\tmpdeux,\tmptrois) arc[start angle=\endangle, + end angle=90, radius=\r];% + \draw[fleche] (0,0) -- (20:\r) node[above, midway, sloped] {$\smax$};% + + % Rectangle + \draw (0,\r) -- (\r,\r) -- (\r,0);% + + % Theoretical power + \draw (\pth,0) -- (\pth,\r) node[below left] at (\pth,0) {$P^{\theo}$};% + \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% + \fill[interdit] (\pth,\qth) arc[start angle=\angth, end angle=\startangle, radius=\r] -- + (\pth,\tmp);% + + % Point P^{\theo} + \path (\pth,0) pic[pic type=point];% + + % Point outside the circle + \pgfmathsetmacro{\rayon}{1.15*\r}% + \pgfmathsetmacro{\anglevaleur}{55}% + \coordinate (S) at (\anglevaleur:\rayon);% + + \node[right] at (S) {$\underline{S}$};% + \path (S) pic[pic type=point];% + + % Projection + \pgfmathsetmacro{\tmp}{\rayon*cos(\anglevaleur)};% + \pgfmathsetmacro{\tmpdeux}{sqrt(pow(\r,2)-pow(\tmp,2))};% + \coordinate (S correct) at (\tmp,\tmpdeux);% + \draw[fleche, blue] (S) -- (S correct);% + \path (S correct) pic {point=blue};% + \node[below left] at (S correct) {$\underline{S^{\text{proj.}}}$};% + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Constant_Q_Projection.tex b/doc/images/Load/FlexibleLoad/Constant_Q_Projection.tex new file mode 100644 index 00000000..08746307 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Constant_Q_Projection.tex @@ -0,0 +1,75 @@ +\input{Preambule}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + % Styles + \tikzset{fleche/.style={->, -{Latex}}}% + \tikzset{interdit/.style={pattern=north east lines, pattern color=red}}% + \tikzset{point/.pic={\filldraw[#1] (0,0) circle[radius=0.05];}, point/.default=black}% + + % Paramètres + \pgfmathsetmacro{\r}{3.5}% + \pgfmathsetmacro{\R}{1.1 * \r}% + \pgfmathsetmacro{\pth}{0.8 * \r}% + \pgfmathsetmacro{\angth}{acos(\pth/\r)}% + \pgfmathsetmacro{\qth}{\r * sin(\angth)}% + \pgfmathsetmacro{\startangle}{-10}% + \pgfmathsetmacro{\endangle}{90-\startangle}% + + % Axes + \pgfmathsetmacro{\tmp}{\r*cos(90-\startangle)};% + \draw[fleche] (\tmp,0) -- (\R,0) node[below right] {$P$};% + \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% + \draw[fleche] (0,\tmp) -- (0,\R) node[above right] {$Q$};% + + % Cercle + \draw (\startangle:\r) arc[start angle=\startangle, end angle=\endangle, radius=\r];% + \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% + \pgfmathsetmacro{\tmpdeux}{\r*cos(\endangle)};% + \pgfmathsetmacro{\tmptrois}{\r*sin(\endangle)};% + \fill[interdit] (0,\tmp) -- (\tmpdeux,\tmp) -- (\tmpdeux,\tmptrois) arc[start angle=\endangle, + end angle=90, radius=\r];% + \draw[fleche] (0,0) -- (20:\r) node[above, midway, sloped] {$\smax$};% + + % Rectangle + \draw (0,\r) -- (\r,\r) -- (\r,0);% + + % Puissance théorique + \draw (\pth,0) -- (\pth,\r) node[below left] at (\pth,0) {$P^{\theo}$};% + \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% + \fill[interdit] (\pth,\qth) arc[start angle=\angth, end angle=\startangle, radius=\r] -- + (\pth,\tmp);% + + % Point noir sur P^{\theo} + \path (\pth,0) pic[pic type=point];% + + % Point en dehors du cercle + \pgfmathsetmacro{\rayon}{1.15*\r}% + \pgfmathsetmacro{\anglevaleur}{55}% + \coordinate (S) at (\anglevaleur:\rayon);% + + \node[right] at (S) {$\underline{S}$};% + \path (S) pic[pic type=point];% + + % Projection + \pgfmathsetmacro{\tmp}{\rayon*sin(\anglevaleur)};% + \pgfmathsetmacro{\tmpdeux}{sqrt(pow(\r,2)-pow(\tmp,2))};% + \coordinate (S correct) at (\tmpdeux,\tmp);% + \draw[fleche, blue] (S) -- (S correct);% + \path (S correct) pic {point=blue};% + \node[below left] at (S correct) {$\underline{S^{\text{proj.}}}$};% + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Control_PU_Cons.tex b/doc/images/Load/FlexibleLoad/Control_PU_Cons.tex new file mode 100644 index 00000000..702afdd4 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Control_PU_Cons.tex @@ -0,0 +1,108 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% +\usepgfplotslibrary{groupplots, colorbrewer}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + % + % Common parameters + % + \pgfmathsetmacro{\uminvaleur}{210.0}% + \pgfmathsetmacro{\uminnormvaleur}{1.0}% + \pgfmathsetmacro{\udownvaleur}{220.0}% + \pgfmathsetmacro{\udownnormvaleur}{\udownvaleur/\uminvaleur}% + \pgfmathsetmacro{\uupvaleur}{240.0}% + \pgfmathsetmacro{\uupnormvaleur}{\uupvaleur/\uminvaleur}% + \pgfmathsetmacro{\umaxvaleur}{250.0}% + \pgfmathsetmacro{\umaxnormvaleur}{\umaxvaleur/\uminvaleur}% + \pgfmathsetmacro{\unomvaleur}{(\udownvaleur+\uupvaleur)/2.0}% + \pgfmathsetmacro{\unomnormvaleur}{\unomvaleur/\uminvaleur}% + \pgfmathsetmacro{\umidminvaleur}{(\udownvaleur+\uminvaleur)/2.0}% + \pgfmathsetmacro{\umidminnormvaleur}{\umidminvaleur/\uminvaleur}% + \pgfmathsetmacro{\umidmaxvaleur}{(\uupvaleur+\umaxvaleur)/2.0}% + \pgfmathsetmacro{\umidmaxnormvaleur}{\umidmaxvaleur/\uminvaleur}% + + \pgfmathsetmacro{\xminvaleur}{\uminvaleur - 2.5}% + \pgfmathsetmacro{\xminnormvaleur}{\xminvaleur/\uminvaleur}% + \pgfmathsetmacro{\xmaxvaleur}{\umaxvaleur + 2.5}% + \pgfmathsetmacro{\xmaxnormvaleur}{\xmaxvaleur/\uminvaleur}% + + \pgfmathsetmacro{\yminnormvaleur}{0}% + \pgfmathsetmacro{\ymaxnormvaleur}{1}% + + % + % Style + % + \tikzset{lisse/.style={line width=0.3mm, + domain=\xminnormvaleur:\xmaxnormvaleur, samples=75, mark=none}}% + \tikzset{non lisse/.style={line width=0.3mm, mark=*}}% + + \begin{axis} + [% + height=7cm,% + width=0.9\textwidth,% + enlarge y limits,% + grid=major,% + xlabel={$|V_{p_1}-V_{p_2}|$},% + xtick={\uminnormvaleur,\umidminnormvaleur,\udownnormvaleur,\unomnormvaleur,\uupnormvaleur,\umidmaxnormvaleur,\umaxnormvaleur},% + xticklabels={% + $\uminnorm$,,$\udownnorm$,$\unomnorm$,$\uupnorm$,,$\umaxnorm$% + },% + y tick label style={/pgf/number format/.cd,% + set thousands separator={},% + fixed,% + % fixed zerofill,% + precision=1,% + use comma% + },% + xmin=\xminnormvaleur,% + xmax=\xmaxnormvaleur,% + ymin=\yminnormvaleur,% + ymax=\ymaxnormvaleur,% + legend columns=2,% + legend style={% + at={(0.5,-0.25)},% + anchor=north,% + nodes={text width=4cm}% + },% + cycle list/YlOrRd-5, % initialize YlOrRd-5 + cycle list name=YlOrRd-5% + ] + + % Piecewise linear function + \addplot[non lisse, red] coordinates {% + (\xminnormvaleur,0)% + (\uminnormvaleur,0)% + (\udownnormvaleur,1)% + (\xmaxnormvaleur,1)% + };% + \addlegendentry{Non-smooth control};% + + + % Soft clipping functions + \pgfplotsset{cycle list shift=-1}% Reset cycle to 0 + \foreach \alphavaleur in {50,100,200,300,400} {% + \addplot+[lisse] expression {% + 1.0/(\alphavaleur*(\udownnormvaleur-\uminnormvaleur)) * + ln((1+exp(\alphavaleur*(x-\uminnormvaleur)))/(1+exp(\alphavaleur*(x-\udownnormvaleur)))) + };% + \addlegendentryexpanded{Soft clipping ($\alpha=\num{\alphavaleur}$)};% + };% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Control_PU_Prod.tex b/doc/images/Load/FlexibleLoad/Control_PU_Prod.tex new file mode 100644 index 00000000..cdc28a40 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Control_PU_Prod.tex @@ -0,0 +1,108 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% +\usepgfplotslibrary{groupplots, colorbrewer}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + % + % Common parameters + % + \pgfmathsetmacro{\umaxvaleur}{250.0}% + \pgfmathsetmacro{\umaxnormvaleur}{1.0}% + \pgfmathsetmacro{\uupvaleur}{240.0}% + \pgfmathsetmacro{\uupnormvaleur}{\uupvaleur/\umaxvaleur}% + \pgfmathsetmacro{\udownvaleur}{220.0}% + \pgfmathsetmacro{\udownnormvaleur}{\udownvaleur/\umaxvaleur}% + \pgfmathsetmacro{\uminvaleur}{210.0}% + \pgfmathsetmacro{\uminnormvaleur}{\uminvaleur/\umaxvaleur}% + \pgfmathsetmacro{\unomvaleur}{(\udownvaleur+\uupvaleur)/2.0}% + \pgfmathsetmacro{\unomnormvaleur}{\unomvaleur/\umaxvaleur}% + \pgfmathsetmacro{\umidminvaleur}{(\udownvaleur+\uminvaleur)/2.0}% + \pgfmathsetmacro{\umidminnormvaleur}{\umidminvaleur/\umaxvaleur}% + \pgfmathsetmacro{\umidmaxvaleur}{(\uupvaleur+\umaxvaleur)/2.0}% + \pgfmathsetmacro{\umidmaxnormvaleur}{\umidmaxvaleur/\umaxvaleur}% + + \pgfmathsetmacro{\xminvaleur}{\uminvaleur - 2.5}% + \pgfmathsetmacro{\xminnormvaleur}{\xminvaleur/\umaxvaleur}% + \pgfmathsetmacro{\xmaxvaleur}{\umaxvaleur + 2.5}% + \pgfmathsetmacro{\xmaxnormvaleur}{\xmaxvaleur/\umaxvaleur}% + + \pgfmathsetmacro{\yminnormvaleur}{0}% + \pgfmathsetmacro{\ymaxnormvaleur}{1}% + + % + % Style + % + \tikzset{lisse/.style={line width=0.3mm, domain=\xminnormvaleur:\xmaxnormvaleur, samples=75, + mark=none}}% + \tikzset{non lisse/.style={line width=0.3mm, mark=*}}% + + \begin{axis} + [% + height=7cm,% + width=0.9\textwidth,% + enlarge y limits,% + grid=major,% + xlabel={$|V_{p_1}-V_{p_2}|$},% + xtick={\uminnormvaleur,\umidminnormvaleur,\udownnormvaleur,\unomnormvaleur,\uupnormvaleur,\umidmaxnormvaleur,\umaxnormvaleur},% + xticklabels={% + $\uminnorm$,,$\udownnorm$,$\unomnorm$,$\uupnorm$,,$\umaxnorm$% + },% + y tick label style={/pgf/number format/.cd,% + set thousands separator={},% + fixed,% + % fixed zerofill,% + precision=1,% + use comma% + },% + xmin=\xminnormvaleur,% + xmax=\xmaxnormvaleur,% + ymin=\yminnormvaleur,% + ymax=\ymaxnormvaleur,% + legend columns=2,% + legend style={% + at={(0.5,-0.25)},% + anchor=north,% + nodes={text width=4cm}% + },% + cycle list/YlOrRd-5, % initialize YlOrRd-5 + cycle list name=YlOrRd-5% + ] + + % Piecewise linear function + \addplot[non lisse, red] coordinates {% + (\xminnormvaleur,1)% + (\uupnormvaleur,1)% + (\umaxnormvaleur,0)% + (\xmaxnormvaleur,0)% + };% + \addlegendentry{Non-smooth control};% + + + % Soft clipping functions + \pgfplotsset{cycle list shift=-1}% Reset cycle to 0 + \foreach \alphavaleur in {50,100,200,300,400} {% + \addplot+[lisse] expression {% + 1.0 + 1.0/(\alphavaleur*(\umaxnormvaleur-\uupnormvaleur)) * + ln((1+exp(\alphavaleur*(x-\umaxnormvaleur)))/(1+exp(\alphavaleur*(x-\uupnormvaleur)))) + };% + \addlegendentryexpanded{Soft clipping ($\alpha=\num{\alphavaleur}$)};% + };% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Control_QU.tex b/doc/images/Load/FlexibleLoad/Control_QU.tex new file mode 100644 index 00000000..1c944ad1 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Control_QU.tex @@ -0,0 +1,117 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% +\usepgfplotslibrary{groupplots, colorbrewer}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + % + % Common parameters + % + \pgfmathsetmacro{\umaxvaleur}{250.0}% + \pgfmathsetmacro{\umaxnormvaleur}{1.0}% + \pgfmathsetmacro{\uupvaleur}{240.0}% + \pgfmathsetmacro{\uupnormvaleur}{\uupvaleur/\umaxvaleur}% + \pgfmathsetmacro{\udownvaleur}{220.0}% + \pgfmathsetmacro{\udownnormvaleur}{\udownvaleur/\umaxvaleur}% + \pgfmathsetmacro{\uminvaleur}{210.0}% + \pgfmathsetmacro{\uminnormvaleur}{\uminvaleur/\umaxvaleur}% + \pgfmathsetmacro{\unomvaleur}{(\udownvaleur+\uupvaleur)/2.0}% + \pgfmathsetmacro{\unomnormvaleur}{\unomvaleur/\umaxvaleur}% + \pgfmathsetmacro{\umidminvaleur}{(\udownvaleur+\uminvaleur)/2.0}% + \pgfmathsetmacro{\umidminnormvaleur}{\umidminvaleur/\umaxvaleur}% + \pgfmathsetmacro{\umidmaxvaleur}{(\uupvaleur+\umaxvaleur)/2.0}% + \pgfmathsetmacro{\umidmaxnormvaleur}{\umidmaxvaleur/\umaxvaleur}% + + \pgfmathsetmacro{\xminvaleur}{\uminvaleur - 2.5}% + \pgfmathsetmacro{\xminnormvaleur}{\xminvaleur/\umaxvaleur}% + \pgfmathsetmacro{\xmaxvaleur}{\umaxvaleur + 2.5}% + \pgfmathsetmacro{\xmaxnormvaleur}{\xmaxvaleur/\umaxvaleur}% + + \pgfmathsetmacro{\yminnormvaleur}{-1}% + \pgfmathsetmacro{\ymaxnormvaleur}{1}% + + \pgfmathsetmacro{\qthnormvaleur}{0.30}% + + % + % Style + % + \tikzset{lisse/.style={line width=0.3mm, domain=\xminnormvaleur:\xmaxnormvaleur, samples=75, + mark=none}}% + \tikzset{non lisse/.style={line width=0.3mm, mark=*}}% + + \begin{axis} + [% + height=7cm,% + width=0.9\textwidth,% + enlarge y limits,% + grid=major,% + xlabel={$|V_{p_1}-V_{p_2}|$},% + xtick={\uminnormvaleur,\umidminnormvaleur,\udownnormvaleur,\unomnormvaleur,\uupnormvaleur,\umidmaxnormvaleur,\umaxnormvaleur},% + xticklabels={% + $\uminnorm$,,$\udownnorm$,$\unomnorm$,$\uupnorm$,,$\umaxnorm$% + },% + y tick label style={/pgf/number format/.cd,% + set thousands separator={},% + fixed,% + fixed zerofill,% + precision=1,% + use comma% + },% + ytick={\yminnormvaleur,0,\qthnormvaleur,\ymaxnormvaleur},% + yticklabels={-1,0,$\dfrac{Q^{\theo}_{p_1p_2}}{\smax_{p_1p_2}}$,1},% + xmin=\xminnormvaleur,% + xmax=\xmaxnormvaleur,% + ymin=\yminnormvaleur,% + ymax=\ymaxnormvaleur,% + % ylabel={$\alpha\left(|V_{p}|\right)$},% + % legend entries={Contrôles non lisses,Contrôles lisses},% + legend columns=2,% + legend style={% + at={(0.5,-0.25)},% + anchor=north,% + nodes={text width=4cm}% + },% + cycle list/YlOrRd-5, % initialize YlOrRd-5 + cycle list name=YlOrRd-5% + ] + + % Fonction linéaire par morceaux + \addplot[non lisse, red] coordinates {% + (\xminnormvaleur,-1)% + (\uminnormvaleur,-1)% + (\udownnormvaleur,\qthnormvaleur)% + (\uupnormvaleur,\qthnormvaleur)% + (\umaxnormvaleur,1)% + (\xmaxnormvaleur,1)% + };% + \addlegendentry{Non-smooth control};% + + % Soft clipping functions + \pgfplotsset{cycle list shift=-1}% Reset cycle to 0 + \foreach \alphavaleur in {50,100,200,300,400} {% + \addplot+[lisse] expression {% + \qthnormvaleur + ( 1.0/(\alphavaleur*(\uminnormvaleur-\udownnormvaleur)) * + ln((1+exp(\alphavaleur*(x-\udownnormvaleur)))/(1+exp(\alphavaleur*(x-\uminnormvaleur))))-1.0 + )*(1+\qthnormvaleur) + ( 1.0/(\alphavaleur*(\umaxnormvaleur-\uupnormvaleur)) * + ln((1+exp(\alphavaleur*(x-\uupnormvaleur)))/(1+exp(\alphavaleur*(x-\umaxnormvaleur)))) + )*(1-\qthnormvaleur) };% + \addlegendentryexpanded{Soft clipping ($\alpha=\num{\alphavaleur}$)};% + };% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Domain_Common.tikz b/doc/images/Load/FlexibleLoad/Domain_Common.tikz similarity index 95% rename from doc/images/Domain_Common.tikz rename to doc/images/Load/FlexibleLoad/Domain_Common.tikz index d6164e22..63f2cf06 100644 --- a/doc/images/Domain_Common.tikz +++ b/doc/images/Load/FlexibleLoad/Domain_Common.tikz @@ -10,8 +10,8 @@ % % Macros % -\pgfmathsetmacro{\r}{1.2};% -\pgfmathsetmacro{\R}{1.5};% +\pgfmathsetmacro{\r}{1.6};% +\pgfmathsetmacro{\R}{2};% \pgfmathsetmacro{\pthvaleur}{-0.55*\r};% \pgfmathsetmacro{\qthvaleur}{0.6*\r};% \pgfmathsetmacro{\angthvaleur}{acos(\pthvaleur/\r)}% diff --git a/doc/images/Load/FlexibleLoad/Domain_Pconst_QU_Eucl.tex b/doc/images/Load/FlexibleLoad/Domain_Pconst_QU_Eucl.tex new file mode 100644 index 00000000..b2aff101 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Domain_Pconst_QU_Eucl.tex @@ -0,0 +1,30 @@ +\input{Preambule}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \input{Load/FlexibleLoad/Domain_Common.tikz}% + + % The domain is a segment with two arc circle + \draw[domaine] (-\angeuclthvaleur:\r) arc[start angle=-\angeuclthvaleur, end + angle=-\angthvaleur, radius=\r] -- (\pthvaleur,\qthmaxvaleur) arc [start angle=\angthvaleur, end + angle=\angeuclthvaleur, radius=\r];% + + \pic[domaine] at (\pthvaleur,\qthvaleur) {point};% + + \draw[dashed] (0,0) -- (\pthvaleur,\r);% + \draw[dashed] (0,0) -- (\pthvaleur,-\r);% + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Domain_Pconst_QU_P.tex b/doc/images/Load/FlexibleLoad/Domain_Pconst_QU_P.tex new file mode 100644 index 00000000..67cf5b91 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Domain_Pconst_QU_P.tex @@ -0,0 +1,25 @@ +\input{Preambule}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \input{Load/FlexibleLoad/Domain_Common.tikz}% + + % The domain is a segment + \draw[domaine] (\pthvaleur,-\qthmaxvaleur) -- (\pthvaleur,\qthmaxvaleur);% + + \pic[domaine] at (\pthvaleur,\qthvaleur) {point};% + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Domain_Pconst_QU_Q.tex b/doc/images/Load/FlexibleLoad/Domain_Pconst_QU_Q.tex new file mode 100644 index 00000000..9b58de00 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Domain_Pconst_QU_Q.tex @@ -0,0 +1,26 @@ +\input{Preambule}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \input{Load/FlexibleLoad/Domain_Common.tikz}% + + % The domain is a segment with two portions of circle arc + \draw[domaine] (0,-\r) arc[start angle=-90, end angle=-\angthvaleur, radius=\r] -- + (\pthvaleur,\qthmaxvaleur) arc [start angle=\angthvaleur, end angle=90, radius=\r];% + + \pic[domaine] at (\pthvaleur,\qthvaleur) {point};% + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Domain_Pconst_QU_Qmin_Qmax.tex b/doc/images/Load/FlexibleLoad/Domain_Pconst_QU_Qmin_Qmax.tex new file mode 100644 index 00000000..42fc9995 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Domain_Pconst_QU_Qmin_Qmax.tex @@ -0,0 +1,35 @@ +\input{Preambule}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \input{Load/FlexibleLoad/Domain_Common.tikz}% + \pgfmathsetmacro{\qmaxvaleur}{0.75*\r};% + \pgfmathsetmacro{\qminvaleur}{-0.5*\r};% + \pgfmathsetmacro{\qmaxx}{\r*cos(asin(\qmaxvaleur/\r))};% + \pgfmathsetmacro{\qminx}{\r*cos(asin(\qminvaleur/\r))};% + + % The domain is a segment + \draw[domaine] (\pthvaleur,\qminvaleur) -- (\pthvaleur,\qmaxvaleur);% + + \draw[dashed] (-\qmaxx,\qmaxvaleur) -- (\qmaxx,\qmaxvaleur);% + \draw[dashed] (-\qminx,\qminvaleur) -- (\qminx,\qminvaleur);% + + \node[below right] at (0,\qmaxvaleur) {$Q^{\max}$};% + \node[below right] at (0,\qminvaleur) {$Q^{\min}$};% + + \pic[domaine] at (\pthvaleur,\qthvaleur) {point};% + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Domain_Pconst_Qconst.tex b/doc/images/Load/FlexibleLoad/Domain_Pconst_Qconst.tex new file mode 100644 index 00000000..93829aeb --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Domain_Pconst_Qconst.tex @@ -0,0 +1,22 @@ +\input{Preambule}% +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \input{Load/FlexibleLoad/Domain_Common.tikz}% + + % The domain is limited to a point + \pic[domaine] at (\pthvaleur,\qthvaleur) {point};% + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Domain_PmaxU_QU.tex b/doc/images/Load/FlexibleLoad/Domain_PmaxU_QU.tex new file mode 100644 index 00000000..8b8cbe7c --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Domain_PmaxU_QU.tex @@ -0,0 +1,26 @@ +\input{Preambule}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \input{Load/FlexibleLoad/Domain_Common.tikz}% + + % The domain is a prtion of the circle + \filldraw[domaine hache] (0,-\r) arc[start angle=-90, end angle=-\angthvaleur, radius=\r] -- + (\pthvaleur, \qthmaxvaleur) arc[start angle=\angthvaleur, end angle=90, radius=\r] --cycle;% + + \pic[domaine] at (\pthvaleur,\qthvaleur) {point};% + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Domain_PmaxU_Qconst.tex b/doc/images/Load/FlexibleLoad/Domain_PmaxU_Qconst.tex new file mode 100644 index 00000000..cf85c1fe --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Domain_PmaxU_Qconst.tex @@ -0,0 +1,24 @@ +\input{Preambule}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \input{Load/FlexibleLoad/Domain_Common.tikz}% + + % The domain is a segment + \draw[domaine] (0,\qthvaleur) -- (\pthvaleur,\qthvaleur);% + \pic[domaine] at (\pthvaleur,\qthvaleur) {point};% + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Euclidean_Projection.tex b/doc/images/Load/FlexibleLoad/Euclidean_Projection.tex new file mode 100644 index 00000000..bc08f74b --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Euclidean_Projection.tex @@ -0,0 +1,73 @@ +\input{Preambule}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + % Styles + \tikzset{fleche/.style={->, -{Latex}}}% + \tikzset{interdit/.style={pattern=north east lines, pattern color=red}}% + \tikzset{point/.pic={\filldraw[#1] (0,0) circle[radius=0.05];}, point/.default=black}% + + % Paramètres + \pgfmathsetmacro{\r}{3.5}% + \pgfmathsetmacro{\R}{1.1 * \r}% + \pgfmathsetmacro{\pth}{0.8 * \r}% + \pgfmathsetmacro{\angth}{acos(\pth/\r)}% + \pgfmathsetmacro{\qth}{\r * sin(\angth)}% + \pgfmathsetmacro{\startangle}{-10}% + \pgfmathsetmacro{\endangle}{90-\startangle}% + + % Axes + \pgfmathsetmacro{\tmp}{\r*cos(90-\startangle)};% + \draw[fleche] (\tmp,0) -- (\R,0) node[below right] {$P$};% + \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% + \draw[fleche] (0,\tmp) -- (0,\R) node[above right] {$Q$};% + + % Circle + \draw (\startangle:\r) arc[start angle=\startangle, end angle=\endangle, radius=\r];% + \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% + \pgfmathsetmacro{\tmpdeux}{\r*cos(\endangle)};% + \pgfmathsetmacro{\tmptrois}{\r*sin(\endangle)};% + \fill[interdit] (0,\tmp) -- (\tmpdeux,\tmp) -- (\tmpdeux,\tmptrois) arc[start angle=\endangle, + end angle=90, radius=\r];% + \draw[fleche] (0,0) -- (20:\r) node[above, midway, sloped] {$\smax$};% + + % Rectangle + \draw (0,\r) -- (\r,\r) -- (\r,0);% + + % Theoretical power + \draw (\pth,0) -- (\pth,\r) node[below left] at (\pth,0) {$P^{\theo}$};% + \pgfmathsetmacro{\tmp}{\r*sin(\startangle)};% + \fill[interdit] (\pth,\qth) arc[start angle=\angth, end angle=\startangle, radius=\r] -- + (\pth,\tmp);% + + % Point P^{\theo} + \path (\pth,0) pic[pic type=point];% + + % Point outside of the circle + \pgfmathsetmacro{\rayon}{1.15*\r}% + \pgfmathsetmacro{\anglevaleur}{55}% + \coordinate (S) at (\anglevaleur:\rayon);% + + \node[right] at (S) {$\underline{S}$};% + \path (S) pic[pic type=point];% + + % Projection + \coordinate (S correct) at (\anglevaleur:\r);% + \draw[fleche, blue] (S) -- (S correct);% + \path (S correct) pic {point=blue};% + \node[below left] at (S correct) {$\underline{S^{\text{proj.}}}$};% + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Pconst_QU_Eucl_Control_Curve_Example.tex b/doc/images/Load/FlexibleLoad/Pconst_QU_Eucl_Control_Curve_Example.tex new file mode 100644 index 00000000..de060c90 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Pconst_QU_Eucl_Control_Curve_Example.tex @@ -0,0 +1,81 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \begin{axis} + [% + xlabel={Voltages (V)},% + x label style={at={(axis description cs:0.5,-0.1)},anchor=north},% + ylabel={Reactive power (VAr)},% + y label style={at={(axis description cs:-0.1,0.5)},anchor=south},% + grid=both,% + legend entries={Actual power,Non-smooth theoretical control},% + legend style={% + legend cell align=left,% + legend pos=north west,% + },% + sharp plot,% + mark size=0.5mm,% + height=9cm,% + width=16cm,% + ytick={-5000,-2500,0,2500,5000},% + minor tick num=1, + scaled y ticks=base 10:-3,% + xticklabel style={align=center},% + xticklabel={% + $\pgfmathprintnumber{\tick}$ + \pgfmathifthenelse{\tick==210}{% + "\\$\umin$"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==220}{% + "\\$\udown$"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==240}{% + "\\$\uup$"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==250}{% + "\\$\umax$"% + }{""}% + \pgfmathresult% + },% + yticklabel style={align=center},% + yticklabel={% + \pgfmathifthenelse{\tick==0}{% + "$Q^{\theo}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==5}{% + "$\smax$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==-5}{% + "$-\smax$\hspace{3mm}"% + }{""}% + \pgfmathresult% + $\pgfmathprintnumber{\tick}$ + },% + ] + \addplot+[only marks] table[x=v, y=q, col sep=comma] {Load/FlexibleLoad/Pconst_QU_Eucl_Example.csv};% + \addplot+ coordinates {(205,-5000) (210,-5000) (220,0) (240,0) (250,5000) (255,5000)};% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Pconst_QU_Eucl_Example.csv b/doc/images/Load/FlexibleLoad/Pconst_QU_Eucl_Example.csv new file mode 100644 index 00000000..81db670f --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Pconst_QU_Eucl_Example.csv @@ -0,0 +1,52 @@ +v,p,q +205.0,-2236.0679775919675,-4472.135954953491 +206.0,-2236.067982532515,-4472.135952483217 +207.0,-2236.068252276505,-4472.135817611212 +208.0,-2236.082977416831,-4472.128455009614 +209.0,-2236.879924830704,-4471.729889191584 +210.0,-2267.4462214926693,-4456.308745210392 +211.0,-2429.150449642832,-4370.266364078971 +212.0,-2500.0,-3999.9580742033895 +213.0,-2500.0,-3499.999231975902 +214.0,-2500.0,-2999.999985937823 +215.0,-2500.0,-2500.0 +216.0,-2500.0,-2000.0000140621771 +217.0,-2500.0,-1500.0007680240985 +218.0,-2500.0,-1000.0419257966104 +219.0,-2500.0,-502.26874098972695 +220.0,-2500.0,-86.64339756999372 +221.0,-2500.0,-2.268740989725404 +222.0,-2500.0,-0.041925796612218846 +223.0,-2500.0,-0.0007680241859153725 +224.0,-2500.0,-1.4066895981379446e-05 +225.0,-2500.0,-2.5764390620963695e-07 +226.0,-2500.0,-4.719002966169228e-09 +227.0,-2500.0,-8.715250743307479e-11 +228.0,-2500.0,-2.7755575615628914e-12 +229.0,-2500.0,1.1102230246251565e-12 +230.0,-2500.0,0.0 +231.0,-2500.0,-1.0824674490095276e-12 +232.0,-2500.0,2.692290834715993e-12 +233.0,-2500.0,8.754108549166365e-11 +234.0,-2500.0,4.7189196993533036e-09 +235.0,-2500.0,2.576431010324217e-07 +236.0,-2500.0,1.4066897160515013e-05 +237.0,-2500.0,0.0007680241858286514 +238.0,-2500.0,0.041925796610847534 +239.0,-2500.0,2.26874098972511 +240.0,-2500.0,86.64339756999419 +241.0,-2500.0,502.26874098972735 +242.0,-2500.0,1000.0419257966093 +243.0,-2500.0,1500.000768024097 +244.0,-2500.0,2000.000014062176 +245.0,-2500.0,2499.999999999999 +246.0,-2500.0,2999.999985937824 +247.0,-2500.0,3499.9992319759026 +248.0,-2500.0,3999.9580742033904 +249.0,-2429.150449642832,4370.266364078971 +250.0,-2267.44622149267,4456.308745210391 +251.0,-2236.879924830704,4471.729889191584 +252.0,-2236.0829774168315,4472.128455009614 +253.0,-2236.0682522765055,4472.135817611211 +254.0,-2236.067982532515,4472.135952483217 +255.0,-2236.0679775919666,4472.135954953491 diff --git a/doc/images/Load/FlexibleLoad/Pconst_QU_Eucl_Trajectory_Example.tex b/doc/images/Load/FlexibleLoad/Pconst_QU_Eucl_Trajectory_Example.tex new file mode 100644 index 00000000..98a6791d --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Pconst_QU_Eucl_Trajectory_Example.tex @@ -0,0 +1,94 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \tikzset{% + pin style/.style={pin distance=1mm},% + }% + + \begin{axis} + [% + xlabel={Active power (W)},% + x label style={at={(axis description cs:0.5,-0.1)},anchor=north},% + ylabel={Reactive power (VAr)},% + y label style={at={(axis description cs:-0.2,0.5)},anchor=south},% + grid=both,% + scatter,% + only marks,% + mark size=0.5mm,% + height=9cm,% + width=9cm,% + axis equal=true,% + enlarge y limits,% + enlarge x limits,% + xmin=-5000,% + xmax=5000,% + ymin=-5000,% + ymax=5000,% + xtick={-5000,-2500,0,2500,5000},% + ytick={-5000,-2500,0,2500,5000},% + minor tick num=1, + scaled x ticks=base 10:-3,% + every x tick scale label/.style={at={(axis description cs:1,-0.05)}},% + scaled y ticks=base 10:-3,% + xticklabel style={align=center},% + xticklabel={% + $\pgfmathprintnumber{\tick}$ + \pgfmathifthenelse{\tick==-2.5}{% + "\\$P^{\theo}$"% + }{""}% + \pgfmathresult% + },% + yticklabel style={align=center},% + yticklabel={% + \pgfmathifthenelse{\tick==0}{% + "$Q^{\theo}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + $\pgfmathprintnumber{\tick}$ + },% + colorbar,% + colorbar style={% + ylabel=Voltage (V),% + },% + nodes near coords*={},% By default, nothing + nodes near coords style={% + anchor=center,% + },% + coordinate style/.condition={\thisrow{v}==205}{pin={[pin style] below:{\mylabel}}},% + coordinate style/.condition={\thisrow{v}==210}{pin={[pin style] above right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==215}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==220}{pin={[pin style] below left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==225}{pin={[pin style] below right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==230}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==235}{pin={[pin style] above right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==240}{pin={[pin style] above left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==245}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==250}{pin={[pin style] below right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==255}{pin={[pin style] above:\mylabel}},% + visualization depends on={\thisrow{v} \as \myvalue},% Required to use the "v" column for two usages + visualization depends on={% + value $\qty[parse-numbers=false]{\pgfmathprintnumber{\thisrow{v}}}{V}$\as\mylabel% + },% + ] + \addplot[point meta={\thisrow{v}}] table[x=p, y=q, col sep=comma] {Load/FlexibleLoad/Pconst_QU_Eucl_Example.csv};% + \draw[black] (axis cs:0,0) circle[radius=5000];% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Pconst_QU_P_Control_Curve_Example.tex b/doc/images/Load/FlexibleLoad/Pconst_QU_P_Control_Curve_Example.tex new file mode 100644 index 00000000..598814a0 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Pconst_QU_P_Control_Curve_Example.tex @@ -0,0 +1,81 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \begin{axis} + [% + xlabel={Voltages (V)},% + x label style={at={(axis description cs:0.5,-0.1)},anchor=north},% + ylabel={Reactive power (VAr)},% + y label style={at={(axis description cs:-0.1,0.5)},anchor=south},% + grid=both,% + legend entries={Actual power,Non-smooth theoretical control},% + legend style={% + legend cell align=left,% + legend pos=north west,% + },% + sharp plot,% + mark size=0.5mm,% + height=9cm,% + width=16cm,% + ytick={-5000,-2500,0,2500,5000},% + minor tick num=1, + scaled y ticks=base 10:-3,% + xticklabel style={align=center},% + xticklabel={% + $\pgfmathprintnumber{\tick}$ + \pgfmathifthenelse{\tick==210}{% + "\\$\umin$"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==220}{% + "\\$\udown$"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==240}{% + "\\$\uup$"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==250}{% + "\\$\umax$"% + }{""}% + \pgfmathresult% + },% + yticklabel style={align=center},% + yticklabel={% + \pgfmathifthenelse{\tick==0}{% + "$Q^{\theo}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==5}{% + "$\smax$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==-5}{% + "$-\smax$\hspace{3mm}"% + }{""}% + \pgfmathresult% + $\pgfmathprintnumber{\tick}$ + },% + ] + \addplot+[only marks] table[x=v, y=q, col sep=comma] {Load/FlexibleLoad/Pconst_QU_P_Example.csv};% + \addplot+ coordinates {(205,-5000) (210,-5000) (220,0) (240,0) (250,5000) (255,5000)};% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Pconst_QU_P_Example.csv b/doc/images/Load/FlexibleLoad/Pconst_QU_P_Example.csv new file mode 100644 index 00000000..5094743d --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Pconst_QU_P_Example.csv @@ -0,0 +1,52 @@ +v,p,q +205.0,-2500.0,-4330.127018922193 +206.0,-2500.0,-4330.127018922193 +207.0,-2500.0,-4330.127018922193 +208.0,-2500.0,-4330.127018922193 +209.0,-2500.0,-4330.127018922193 +210.0,-2500.0,-4330.127018922199 +211.0,-2500.0,-4330.150847081422 +212.0,-2500.0,-3999.958097622331 +213.0,-2500.0,-3499.999231975902 +214.0,-2500.0,-2999.999985937823 +215.0,-2500.0,-2500.0 +216.0,-2500.0,-2000.0000140621771 +217.0,-2500.0,-1500.0007680240985 +218.0,-2500.0,-1000.0419257966104 +219.0,-2500.0,-502.26874098972695 +220.0,-2500.0,-86.64339756999357 +221.0,-2500.0,-0.9641585416943067 +222.0,-2500.0,-0.004192533096206056 +223.0,-2500.0,-7.680241798760731e-05 +224.0,-2500.0,-1.4066895981342335e-06 +225.0,-2500.0,-2.5764390620963675e-08 +226.0,-2500.0,-4.719002966169228e-10 +227.0,-2500.0,-8.715250743307479e-12 +228.0,-2500.0,-2.7755575615628914e-13 +229.0,-2500.0,1.1102230246251565e-13 +230.0,-2500.0,0.0 +231.0,-2500.0,-1.0824674490095276e-13 +232.0,-2500.0,2.692290834715993e-13 +233.0,-2500.0,8.754108549166364e-12 +234.0,-2500.0,4.718919699353304e-10 +235.0,-2500.0,2.5764310103242154e-08 +236.0,-2500.0,1.4066897160477902e-06 +237.0,-2500.0,7.680241797893518e-05 +238.0,-2500.0,0.004192533096068925 +239.0,-2500.0,0.9641585416940721 +240.0,-2500.0,86.64339756999404 +241.0,-2500.0,502.26874098972735 +242.0,-2500.0,1000.0419257966093 +243.0,-2500.0,1500.000768024097 +244.0,-2500.0,2000.000014062176 +245.0,-2500.0,2499.999999999999 +246.0,-2500.0,2999.999985937824 +247.0,-2500.0,3499.9992319759026 +248.0,-2500.0,3999.9580976223315 +249.0,-2500.0,4330.150847081422 +250.0,-2500.0,4330.127018922199 +251.0,-2500.0,4330.127018922193 +252.0,-2500.0,4330.127018922193 +253.0,-2500.0,4330.127018922193 +254.0,-2500.0,4330.127018922193 +255.0,-2500.0,4330.127018922193 diff --git a/doc/images/Load/FlexibleLoad/Pconst_QU_P_Trajectory_Example.tex b/doc/images/Load/FlexibleLoad/Pconst_QU_P_Trajectory_Example.tex new file mode 100644 index 00000000..072bbab7 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Pconst_QU_P_Trajectory_Example.tex @@ -0,0 +1,94 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \tikzset{% + pin style/.style={pin distance=1mm},% + }% + + \begin{axis} + [% + xlabel={Active power (W)},% + x label style={at={(axis description cs:0.5,-0.1)},anchor=north},% + ylabel={Reactive power (VAr)},% + y label style={at={(axis description cs:-0.2,0.5)},anchor=south},% + grid=both,% + scatter,% + only marks,% + mark size=0.5mm,% + height=9cm,% + width=9cm,% + axis equal=true,% + enlarge y limits,% + enlarge x limits,% + xmin=-5000,% + xmax=5000,% + ymin=-5000,% + ymax=5000,% + xtick={-5000,-2500,0,2500,5000},% + ytick={-5000,-2500,0,2500,5000},% + minor tick num=1, + scaled x ticks=base 10:-3,% + every x tick scale label/.style={at={(axis description cs:1,-0.05)}},% + scaled y ticks=base 10:-3,% + xticklabel style={align=center},% + xticklabel={% + $\pgfmathprintnumber{\tick}$ + \pgfmathifthenelse{\tick==-2.5}{% + "\\$P^{\theo}$"% + }{""}% + \pgfmathresult% + },% + yticklabel style={align=center},% + yticklabel={% + \pgfmathifthenelse{\tick==0}{% + "$Q^{\theo}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + $\pgfmathprintnumber{\tick}$ + },% + colorbar,% + colorbar style={% + ylabel=Voltage (V),% + },% + nodes near coords*={},% By default, nothing + nodes near coords style={% + anchor=center,% + },% + coordinate style/.condition={\thisrow{v}==205}{pin={[pin style] below left:{\mylabel}}},% + coordinate style/.condition={\thisrow{v}==210}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==215}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==220}{pin={[pin style] below left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==225}{pin={[pin style] below right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==230}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==235}{pin={[pin style] above right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==240}{pin={[pin style] above left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==245}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==250}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==255}{pin={[pin style] above left:\mylabel}},% + visualization depends on={\thisrow{v} \as \myvalue},% Required to use the "v" column for two usages + visualization depends on={% + value $\qty[parse-numbers=false]{\pgfmathprintnumber{\thisrow{v}}}{V}$\as\mylabel% + },% + ] + \addplot[point meta={\thisrow{v}}] table[x=p, y=q, col sep=comma] {Load/FlexibleLoad/Pconst_QU_P_Example.csv};% + \draw[black] (axis cs:0,0) circle[radius=5000];% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Pconst_QU_Q_Control_Curve_Example.tex b/doc/images/Load/FlexibleLoad/Pconst_QU_Q_Control_Curve_Example.tex new file mode 100644 index 00000000..ebd3d188 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Pconst_QU_Q_Control_Curve_Example.tex @@ -0,0 +1,81 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \begin{axis} + [% + xlabel={Voltages (V)},% + x label style={at={(axis description cs:0.5,-0.1)},anchor=north},% + ylabel={Reactive power (VAr)},% + y label style={at={(axis description cs:-0.1,0.5)},anchor=south},% + grid=both,% + legend entries={Actual power,Non-smooth theoretical control},% + legend style={% + legend cell align=left,% + legend pos=north west,% + },% + sharp plot,% + mark size=0.5mm,% + height=9cm,% + width=16cm,% + ytick={-5000,-2500,0,2500,5000},% + minor tick num=1, + scaled y ticks=base 10:-3,% + xticklabel style={align=center},% + xticklabel={% + $\pgfmathprintnumber{\tick}$ + \pgfmathifthenelse{\tick==210}{% + "\\$\umin$"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==220}{% + "\\$\udown$"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==240}{% + "\\$\uup$"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==250}{% + "\\$\umax$"% + }{""}% + \pgfmathresult% + },% + yticklabel style={align=center},% + yticklabel={% + \pgfmathifthenelse{\tick==0}{% + "$Q^{\theo}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==5}{% + "$\smax$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==-5}{% + "$-\smax$\hspace{3mm}"% + }{""}% + \pgfmathresult% + $\pgfmathprintnumber{\tick}$ + },% + ] + \addplot+[only marks] table[x=v, y=q, col sep=comma] {Load/FlexibleLoad/Pconst_QU_Q_Example.csv};% + \addplot+ coordinates {(205,-5000) (210,-5000) (220,0) (240,0) (250,5000) (255,5000)};% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Pconst_QU_Q_Example.csv b/doc/images/Load/FlexibleLoad/Pconst_QU_Q_Example.csv new file mode 100644 index 00000000..95733389 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Pconst_QU_Q_Example.csv @@ -0,0 +1,52 @@ +v,p,q +205.0,-0.5000132304722589,-4999.999999742356 +206.0,-0.532061670101206,-4999.999985933104 +207.0,-2.771324820863182,-4999.999231975815 +208.0,-20.475746832553064,-4999.958074203388 +209.0,-150.60631697104301,-4997.731259010274 +210.0,-926.7830907809562,-4913.356602430007 +211.0,-2184.1748739974582,-4497.731259010274 +212.0,-2500.0000374699125,-3999.9580742033895 +213.0,-2500.0,-3499.999231975902 +214.0,-2500.0,-2999.999985937823 +215.0,-2500.0,-2500.0 +216.0,-2500.0,-2000.0000140621771 +217.0,-2500.0,-1500.0007680240985 +218.0,-2500.0,-1000.0419257966104 +219.0,-2500.0,-502.26874098972695 +220.0,-2500.0,-86.64339756999372 +221.0,-2500.0,-2.268740989725404 +222.0,-2500.0,-0.041925796612218846 +223.0,-2500.0,-0.0007680241859153725 +224.0,-2500.0,-1.4066895981379446e-05 +225.0,-2500.0,-2.5764390620963695e-07 +226.0,-2500.0,-4.719002966169228e-09 +227.0,-2500.0,-8.715250743307479e-11 +228.0,-2500.0,-2.7755575615628914e-12 +229.0,-2500.0,1.1102230246251565e-12 +230.0,-2500.0,0.0 +231.0,-2500.0,-1.0824674490095276e-12 +232.0,-2500.0,2.692290834715993e-12 +233.0,-2500.0,8.754108549166365e-11 +234.0,-2500.0,4.7189196993533036e-09 +235.0,-2500.0,2.576431010324217e-07 +236.0,-2500.0,1.4066897160515013e-05 +237.0,-2500.0,0.0007680241858286514 +238.0,-2500.0,0.041925796610847534 +239.0,-2500.0,2.26874098972511 +240.0,-2500.0,86.64339756999419 +241.0,-2500.0,502.26874098972735 +242.0,-2500.0,1000.0419257966093 +243.0,-2500.0,1500.000768024097 +244.0,-2500.0,2000.000014062176 +245.0,-2500.0,2499.999999999999 +246.0,-2500.0,2999.999985937824 +247.0,-2500.0,3499.9992319759026 +248.0,-2500.0000374699125,3999.9580742033904 +249.0,-2184.1748739974582,4497.731259010274 +250.0,-926.7830907809681,4913.356602430005 +251.0,-150.60631697104301,4997.731259010274 +252.0,-20.475746832824168,4999.958074203387 +253.0,-2.7713248238677637,4999.999231975813 +254.0,-0.5320616633668144,4999.999985933106 +255.0,-0.500013230187694,4999.999999742358 diff --git a/doc/images/Load/FlexibleLoad/Pconst_QU_Q_Trajectory_Example.tex b/doc/images/Load/FlexibleLoad/Pconst_QU_Q_Trajectory_Example.tex new file mode 100644 index 00000000..aa1d2038 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Pconst_QU_Q_Trajectory_Example.tex @@ -0,0 +1,94 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \tikzset{% + pin style/.style={pin distance=1mm},% + }% + + \begin{axis} + [% + xlabel={Active power (W)},% + x label style={at={(axis description cs:0.5,-0.1)},anchor=north},% + ylabel={Reactive power (VAr)},% + y label style={at={(axis description cs:-0.2,0.5)},anchor=south},% + grid=both,% + scatter,% + only marks,% + mark size=0.5mm,% + height=9cm,% + width=9cm,% + axis equal=true,% + enlarge y limits,% + enlarge x limits,% + xmin=-5000,% + xmax=5000,% + ymin=-5000,% + ymax=5000,% + xtick={-5000,-2500,0,2500,5000},% + ytick={-5000,-2500,0,2500,5000},% + minor tick num=1, + scaled x ticks=base 10:-3,% + every x tick scale label/.style={at={(axis description cs:1,-0.05)}},% + scaled y ticks=base 10:-3,% + xticklabel style={align=center},% + xticklabel={% + $\pgfmathprintnumber{\tick}$ + \pgfmathifthenelse{\tick==-2.5}{% + "\\$P^{\theo}$"% + }{""}% + \pgfmathresult% + },% + yticklabel style={align=center},% + yticklabel={% + \pgfmathifthenelse{\tick==0}{% + "$Q^{\theo}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + $\pgfmathprintnumber{\tick}$ + },% + colorbar,% + colorbar style={% + ylabel=Voltage (V),% + },% + nodes near coords*={},% By default, nothing + nodes near coords style={% + anchor=center,% + },% + coordinate style/.condition={\thisrow{v}==205}{pin={[pin style] below right:{\mylabel}}},% + coordinate style/.condition={\thisrow{v}==210}{pin={[pin style] below:\mylabel}},% + coordinate style/.condition={\thisrow{v}==215}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==220}{pin={[pin style] below left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==225}{pin={[pin style] below right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==230}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==235}{pin={[pin style] above right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==240}{pin={[pin style] above left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==245}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==250}{pin={[pin style] above:\mylabel}},% + coordinate style/.condition={\thisrow{v}==255}{pin={[pin style] above right:\mylabel}},% + visualization depends on={\thisrow{v} \as \myvalue},% Required to use the "v" column for two usages + visualization depends on={% + value $\qty[parse-numbers=false]{\pgfmathprintnumber{\thisrow{v}}}{V}$\as\mylabel% + },% + ] + \addplot[point meta={\thisrow{v}}] table[x=p, y=q, col sep=comma] {Load/FlexibleLoad/Pconst_QU_Q_Example.csv};% + \draw[black] (axis cs:0,0) circle[radius=5000];% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Control_Curve_Example.tex b/doc/images/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Control_Curve_Example.tex new file mode 100644 index 00000000..a57290e0 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Control_Curve_Example.tex @@ -0,0 +1,83 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \begin{axis} + [% + xlabel={Voltages (V)},% + x label style={at={(axis description cs:0.5,-0.1)},anchor=north},% + ylabel={Reactive power (VAr)},% + y label style={at={(axis description cs:-0.1,0.5)},anchor=south},% + grid=both,% + legend entries={Actual power,Non-smooth theoretical control},% + legend style={% + legend cell align=left,% + legend pos=north west,% + },% + sharp plot,% + mark size=0.5mm,% + height=9cm,% + width=16cm,% + ymin=-6000, + ymax=6000, + ytick={-5000,-2500,0,2500,5000},% + minor tick num=1, + scaled y ticks=base 10:-3,% + xticklabel style={align=center},% + xticklabel={% + $\pgfmathprintnumber{\tick}$ + \pgfmathifthenelse{\tick==210}{% + "\\$\umin$"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==220}{% + "\\$\udown$"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==240}{% + "\\$\uup$"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==250}{% + "\\$\umax$"% + }{""}% + \pgfmathresult% + },% + yticklabel style={align=center},% + yticklabel={% + \pgfmathifthenelse{\tick==0}{% + "$Q^{\theo}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==5}{% + "$\smax$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==-5}{% + "$-\smax$\hspace{3mm}"% + }{""}% + \pgfmathresult% + $\pgfmathprintnumber{\tick}$ + },% + ] + \addplot+[only marks] table[x=v, y=q, col sep=comma] {Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Example.csv};% + \addplot+ coordinates {(205,-3000) (210,-3000) (220,0) (240,0) (250,4000) (255,4000)};% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Example.csv b/doc/images/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Example.csv new file mode 100644 index 00000000..aaa5bf2e --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Example.csv @@ -0,0 +1,52 @@ +v,p,q +205.0,-2500.0,-2999.999999845413 +206.0,-2500.0,-2999.9999915598623 +207.0,-2500.0,-2999.999539185489 +208.0,-2500.0,-2999.9748445220325 +209.0,-2500.0,-2998.6387554061644 +210.0,-2500.0,-2948.0139614580035 +211.0,-2500.0,-2698.6387554061644 +212.0,-2500.0,-2399.974844522034 +213.0,-2500.0,-2099.9995391855414 +214.0,-2500.0,-1799.9999915626938 +215.0,-2500.0,-1500.0 +216.0,-2500.0,-1200.0000084373062 +217.0,-2500.0,-900.0004608144591 +218.0,-2500.0,-600.0251554779662 +219.0,-2500.0,-301.36124459383615 +220.0,-2500.0,-51.98603854199624 +221.0,-2500.0,-1.3612445938352424 +222.0,-2500.0,-0.025155477967331308 +223.0,-2500.0,-0.0004608145115492235 +224.0,-2500.0,-8.440137588827668e-06 +225.0,-2500.0,-1.5458634372578217e-07 +226.0,-2500.0,-2.8314017797015367e-09 +227.0,-2500.0,-5.229150445984487e-11 +228.0,-2500.0,-1.6653345369377348e-12 +229.0,-2500.0,6.661338147750939e-13 +230.0,-2500.0,0.0 +231.0,-2500.0,-6.439293542825908e-13 +232.0,-2500.0,1.931788062847763e-12 +233.0,-2500.0,6.981082378840588e-11 +234.0,-2500.0,3.775135759482643e-09 +235.0,-2500.0,2.0611470287054231e-07 +236.0,-2500.0,1.1253517506367405e-05 +237.0,-2500.0,0.0006144193484408765 +238.0,-2500.0,0.03354063728890007 +239.0,-2500.0,1.8149927917803101 +240.0,-2500.0,69.31471805599514 +241.0,-2500.0,401.8149927917817 +242.0,-2500.0,800.0335406372877 +243.0,-2500.0,1200.0006144192778 +244.0,-2500.0,1600.000011249741 +245.0,-2500.0,1999.9999999999993 +246.0,-2500.0,2399.9999887502586 +247.0,-2500.0,2799.9993855807215 +248.0,-2500.0,3199.966459362712 +249.0,-2500.0,3598.185007208219 +250.0,-2500.0,3930.6852819440046 +251.0,-2500.0,3998.1850072082193 +252.0,-2500.0,3999.96645936271 +253.0,-2500.0,3999.9993855806506 +254.0,-2500.0,3999.999988746485 +255.0,-2500.0,3999.999999793886 diff --git a/doc/images/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Trajectory_Example.tex b/doc/images/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Trajectory_Example.tex new file mode 100644 index 00000000..0e286b92 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Trajectory_Example.tex @@ -0,0 +1,94 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \tikzset{% + pin style/.style={pin distance=1mm},% + }% + + \begin{axis} + [% + xlabel={Active power (W)},% + x label style={at={(axis description cs:0.5,-0.1)},anchor=north},% + ylabel={Reactive power (VAr)},% + y label style={at={(axis description cs:-0.2,0.5)},anchor=south},% + grid=both,% + scatter,% + only marks,% + mark size=0.5mm,% + height=9cm,% + width=9cm,% + axis equal=true,% + enlarge y limits,% + enlarge x limits,% + xmin=-5000,% + xmax=5000,% + ymin=-5000,% + ymax=5000,% + xtick={-5000,-2500,0,2500,5000},% + ytick={-5000,-2500,0,2500,5000},% + minor tick num=1, + scaled x ticks=base 10:-3,% + every x tick scale label/.style={at={(axis description cs:1,-0.05)}},% + scaled y ticks=base 10:-3,% + xticklabel style={align=center},% + xticklabel={% + $\pgfmathprintnumber{\tick}$ + \pgfmathifthenelse{\tick==-2.5}{% + "\\$P^{\theo}$"% + }{""}% + \pgfmathresult% + },% + yticklabel style={align=center},% + yticklabel={% + \pgfmathifthenelse{\tick==0}{% + "$Q^{\theo}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + $\pgfmathprintnumber{\tick}$ + },% + colorbar,% + colorbar style={% + ylabel=Voltage (V),% + },% + nodes near coords*={},% By default, nothing + nodes near coords style={% + anchor=center,% + },% + coordinate style/.condition={\thisrow{v}==205}{pin={[pin style] below left:{\mylabel}}},% + coordinate style/.condition={\thisrow{v}==210}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==215}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==220}{pin={[pin style] below left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==225}{pin={[pin style] below right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==230}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==235}{pin={[pin style] above right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==240}{pin={[pin style] above left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==245}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==250}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==255}{pin={[pin style] above left:\mylabel}},% + visualization depends on={\thisrow{v} \as \myvalue},% Required to use the "v" column for two usages + visualization depends on={% + value $\qty[parse-numbers=false]{\pgfmathprintnumber{\thisrow{v}}}{V}$\as\mylabel% + },% + ] + \addplot[point meta={\thisrow{v}}] table[x=p, y=q, col sep=comma] {Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Example.csv}; % + \draw[black] (axis cs:0,0) circle[radius=5000];% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/PmaxU_QU_Sequential_1_Example.csv b/doc/images/Load/FlexibleLoad/PmaxU_QU_Sequential_1_Example.csv new file mode 100644 index 00000000..69b85579 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/PmaxU_QU_Sequential_1_Example.csv @@ -0,0 +1,52 @@ +v,p,q +205.0,-2236.0679775382478,-4472.135954980351 +206.0,-2236.067979980322,-4472.135953759313 +207.0,-2236.068137494058,-4472.135875002441 +208.0,-2236.0782959651738,-4472.1307957520075 +209.0,-2236.7286604177757,-4471.805552533081 +210.0,-2266.1763700890097,-4456.954639622239 +211.0,-2428.9761116929394,-4370.36326279922 +212.0,-2500.0,-3999.9711591289265 +213.0,-2500.0,-3499.9995528024824 +214.0,-2500.0,-2999.9999930683607 +215.0,-2500.0,-2499.999999999993 +216.0,-2500.0,-2000.0000069316266 +217.0,-2500.0,-1500.0004471975176 +218.0,-2500.0,-1000.0288408710728 +219.0,-2500.0,-501.84618761732133 +220.0,-2500.0,-83.17766166719365 +221.0,-2500.0,-1.8461876173236913 +222.0,-2500.0,-0.028840871076507568 +223.0,-2500.0,-0.00044719752148036923 +224.0,-2500.0,-6.931630709416948e-06 +225.0,-2500.0,-6.222063389793264e-14 +226.0,-2500.0,6.93163158381857e-06 +227.0,-2500.0,0.000447197520726527 +228.0,-2500.0,0.0288408710766433 +229.0,-2500.0,1.846187617322751 +230.0,-2500.0,83.1776616671935 +231.0,-2500.0,501.8461876173226 +232.0,-2500.0,1000.0288408710736 +233.0,-2500.0,1500.0004471975176 +234.0,-2500.0,2000.0000069316252 +235.0,-2500.0,2499.999999999993 +236.0,-2500.0,2999.9999930683603 +237.0,-2500.0,3499.9995528024824 +238.0,-2500.0,3999.9711591289274 +239.0,-2428.9761116929385,4370.36326279922 +240.0,-2266.176370088993,4456.954639622248 +241.0,-2236.728660416862,4471.805552533539 +242.0,-2236.0782959152757,4472.130795776957 +243.0,-2236.0681347697605,4472.13587636459 +244.0,-2236.0678312387536,4472.136028130094 +245.0,-2236.059856693918,4472.140015393299 +246.0,-2235.625100721815,4472.357365978548 +247.0,-2213.2770547707137,4483.459008268669 +248.0,-1831.53905946031,4652.4686644480735 +249.0,-984.2745173321633,4902.16316277061 +250.0,-173.0958262224784,4994.816946155494 +251.0,-4.534130344163855,4996.535636675593 +252.0,-0.08378970712469609,4996.536664347264 +253.0,-0.0015349147105347424,4996.536664698182 +254.0,-2.8113029017036173e-05,4996.536664698299 +255.0,-5.149078683543149e-07,4996.536664698299 diff --git a/doc/images/Load/FlexibleLoad/PmaxU_QU_Sequential_1_Trajectory_Example.tex b/doc/images/Load/FlexibleLoad/PmaxU_QU_Sequential_1_Trajectory_Example.tex new file mode 100644 index 00000000..f233c667 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/PmaxU_QU_Sequential_1_Trajectory_Example.tex @@ -0,0 +1,102 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \tikzset{% + pin style/.style={pin distance=1mm},% + }% + + \begin{axis} + [% + xlabel={Active power (W)},% + x label style={at={(axis description cs:0.5,-0.1)},anchor=north},% + ylabel={Reactive power (VAr)},% + y label style={at={(axis description cs:-0.2,0.5)},anchor=south},% + grid=both,% + scatter,% + only marks,% + mark size=0.5mm,% + height=9cm,% + width=9cm,% + axis equal=true,% + enlarge y limits,% + enlarge x limits,% + xmin=-5000,% + xmax=5000,% + ymin=-5000,% + ymax=5000,% + xtick={-5000,-2500,0,2500,5000},% + ytick={-5000,-2500,0,2500,5000},% + minor tick num=1,% + scaled x ticks=base 10:-3,% + every x tick scale label/.style={at={(axis description cs:1,-0.05)}},% + scaled y ticks=base 10:-3,% + xticklabel style={align=center},% + xticklabel={% + $\pgfmathprintnumber{\tick}$ + \pgfmathifthenelse{\tick==-2.5}{% + "\\$P^{\theo}$"% + }{""}% + \pgfmathresult% + },% + yticklabel style={align=center},% + yticklabel={% + \pgfmathifthenelse{\tick==0}{% + "$Q^{\theo}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==-5}{% + "$-S^{\max}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==5}{% + "$S^{\max}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + $\pgfmathprintnumber{\tick}$ + },% + colorbar,% + colorbar style={% + ylabel=Voltage (V),% + },% + nodes near coords*={},% By default, nothing + nodes near coords style={% + anchor=center,% + },% + coordinate style/.condition={\thisrow{v}==205}{pin={[pin style] right:{\mylabel}}},% + coordinate style/.condition={\thisrow{v}==210}{pin={[pin style] below:\mylabel}},% + coordinate style/.condition={\thisrow{v}==215}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==220}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==225}{pin={[pin style] left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==230}{pin={[pin style] above left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==235}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==240}{pin={[pin style] left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==245}{pin={[pin style] above:\mylabel}},% + coordinate style/.condition={\thisrow{v}==250}{pin={[pin style] above:\mylabel}},% + coordinate style/.condition={\thisrow{v}==255}{pin={[pin style] below:\mylabel}},% + visualization depends on={\thisrow{v} \as \myvalue},% Required to use the "v" column for two usages + visualization depends on={% + value $\qty[parse-numbers=false]{\pgfmathprintnumber{\thisrow{v}}}{V}$\as\mylabel% + },% + ] + \addplot[point meta={\thisrow{v}}] table[x=p, y=q, col sep=comma] {Load/FlexibleLoad/PmaxU_QU_Sequential_1_Example.csv};% + \draw[black] (axis cs:0,0) circle[radius=5000];% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/PmaxU_QU_Sequential_2_Example.csv b/doc/images/Load/FlexibleLoad/PmaxU_QU_Sequential_2_Example.csv new file mode 100644 index 00000000..b85b7aad --- /dev/null +++ b/doc/images/Load/FlexibleLoad/PmaxU_QU_Sequential_2_Example.csv @@ -0,0 +1,52 @@ +v,p,q +205.0,-2236.0679775919675,-4472.135954953491 +206.0,-2236.067982532515,-4472.135952483217 +207.0,-2236.068252276505,-4472.135817611212 +208.0,-2236.082977416831,-4472.128455009614 +209.0,-2236.879924830704,-4471.729889191584 +210.0,-2267.4462214926693,-4456.308745210392 +211.0,-2429.150449642832,-4370.266364078971 +212.0,-2500.0,-3999.9580742033895 +213.0,-2500.0,-3499.999231975902 +214.0,-2500.0,-2999.999985937823 +215.0,-2500.0,-2500.0 +216.0,-2500.0,-2000.0000140621771 +217.0,-2500.0,-1500.0007680240985 +218.0,-2500.0,-1000.0419257966104 +219.0,-2500.0,-502.26874098972695 +220.0,-2500.0,-86.64339756999372 +221.0,-2500.0,-2.268740989725404 +222.0,-2500.0,-0.041925796612218846 +223.0,-2500.0,-0.0007680241859153725 +224.0,-2500.0,-1.4066895981379446e-05 +225.0,-2500.0,-2.5764390620963695e-07 +226.0,-2500.0,-4.719002966169228e-09 +227.0,-2499.9999999999995,-8.715250743307479e-11 +228.0,-2499.999999999974,-2.7755575615628914e-12 +229.0,-2499.9999999983334,1.1102230246251565e-12 +230.0,-2499.9999998925073,0.0 +231.0,-2499.999993066702,-1.1102230246251565e-12 +232.0,-2499.999552802453,1.1102230246251565e-12 +233.0,-2499.9711591289492,1.1102230246251565e-12 +234.0,-2498.153812384344,0.0 +235.0,-2416.8223384402995,-1.1102230246251565e-12 +236.0,-1998.1538193159781,1.1657341758564144e-12 +237.0,-1499.971606326463,4.27435864480683e-12 +238.0,-1000.0283936735241,1.7175150190945182e-10 +239.0,-501.84618068402244,9.436729175681982e-09 +240.0,-83.17766155970074,5.152895327339173e-07 +241.0,-1.8461876156564139,2.813379315529586e-05 +242.0,-0.028840871050817007,0.0015360483651625176 +243.0,-0.00044719754704880543,0.08385159304994605 +244.0,-6.9332981023073614e-06,4.53748197001349 +245.0,-1.0749290346723228e-07,173.28679462469663 +246.0,-1.6669998714746725e-09,1004.5374538456617 +247.0,-2.609024107869118e-11,2000.0823155448559 +248.0,-5.551115123125783e-13,2999.917684455146 +249.0,-0.0,3995.46254615434 +250.0,-0.0,4826.713205375302 +251.0,-8.323850959092303e-13,4993.769666036564 +252.0,-0.0,4996.494563038826 +253.0,-0.0,4996.535896615463 +254.0,-8.320905096191371e-13,4996.536650631391 +255.0,-0.0,4996.536664440657 diff --git a/doc/images/Load/FlexibleLoad/PmaxU_QU_Sequential_2_Trajectory_Example.tex b/doc/images/Load/FlexibleLoad/PmaxU_QU_Sequential_2_Trajectory_Example.tex new file mode 100644 index 00000000..6ed3e43a --- /dev/null +++ b/doc/images/Load/FlexibleLoad/PmaxU_QU_Sequential_2_Trajectory_Example.tex @@ -0,0 +1,104 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \tikzset{% + pin style/.style={pin distance=1mm},% + }% + + \begin{axis} + [% + xlabel={Active power (W)},% + x label style={at={(axis description cs:0.5,-0.1)},anchor=north},% + ylabel={Reactive power (VAr)},% + y label style={at={(axis description cs:-0.2,0.5)},anchor=south},% + grid=both,% + scatter,% + only marks,% + mark size=0.5mm,% + height=9cm,% + width=9cm,% + axis equal=true,% + enlarge y limits,% + enlarge x limits,% + xmin=-5000,% + xmax=5000,% + ymin=-5000,% + ymax=5000,% + xtick={-5000,-2500,0,2500,5000},% + ytick={-5000,-2500,0,2500,5000},% + minor tick num=1,% + scaled x ticks=base 10:-3,% + every x tick scale label/.style={at={(axis description cs:1,-0.05)}},% + scaled y ticks=base 10:-3,% + xticklabel style={align=center},% + xticklabel={% + $\pgfmathprintnumber{\tick}$ + \pgfmathifthenelse{\tick==-2.5}{% + "\\$P^{\theo}$"% + }{""}% + \pgfmathresult% + },% + yticklabel style={align=center},% + yticklabel={% + \pgfmathifthenelse{\tick==0}{% + "$Q^{\theo}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==-5}{% + "$-S^{\max}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==5}{% + "$S^{\max}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + $\pgfmathprintnumber{\tick}$ + },% + colorbar,% + colorbar style={% + ylabel=Voltage (V),% + },% + nodes near coords*={},% By default, nothing + nodes near coords style={% + anchor=center,% + },% + coordinate style/.condition={\thisrow{v}==205}{pin={[pin style] below:{\mylabel}}},% + coordinate style/.condition={\thisrow{v}==210}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==215}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==220}{pin={[pin style] below left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==225}{pin={[pin style] left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==230}{pin={[pin style] above left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==235}{pin={[pin style] above right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==240}{pin={[pin style] below:\mylabel}},% + coordinate style/.condition={\thisrow{v}==245}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==250}{pin={[pin style] below right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==255}{pin={[pin style] below left:\mylabel}},% + visualization depends on={\thisrow{v} \as \myvalue},% Required to use the "v" column for two usages + visualization depends on={% + value $\qty[parse-numbers=false]{\pgfmathprintnumber{\thisrow{v}}}{V}$\as\mylabel% + },% + ] + \addplot[point meta={\thisrow{v}}] table[x=p, y=q, col sep=comma] {% + Load/FlexibleLoad/PmaxU_QU_Sequential_2_Example.csv% + };% + \draw[black] (axis cs:0,0) circle[radius=5000];% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/PmaxU_QU_Simultaneous_2_Example.csv b/doc/images/Load/FlexibleLoad/PmaxU_QU_Simultaneous_2_Example.csv new file mode 100644 index 00000000..d8630953 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/PmaxU_QU_Simultaneous_2_Example.csv @@ -0,0 +1,52 @@ +v,p,q +205.0,-3123.4752378702615,-3904.34404713664 +206.0,-3123.4752431303655,-3904.344042928556 +207.0,-3123.4755303214724,-3904.3438131756525 +208.0,-3123.4912078505167,-3904.3312710988703 +209.0,-3124.3395901559534,-3903.6524083714385 +210.0,-3156.7159632002063,-3877.517804946483 +211.0,-3322.7549000972035,-3736.2146450491878 +212.0,-3535.552434740721,-3535.5153770276493 +213.0,-3762.883831585663,-3292.52263014109 +214.0,-3997.2293351288868,-2997.921987294228 +215.0,-4000.0,-2500.0 +216.0,-4000.0,-2000.0000140621771 +217.0,-4000.0,-1500.0007680240985 +218.0,-4000.0,-1000.0419257966104 +219.0,-4000.0,-502.26874098972695 +220.0,-4000.0,-86.64339756999372 +221.0,-4000.0,-2.268740989725404 +222.0,-4000.0,-0.041925796612218846 +223.0,-4000.0,-0.0007680241859153725 +224.0,-4000.0,-1.4066895981379446e-05 +225.0,-4000.0,-2.5764390620963695e-07 +226.0,-4000.0,-4.719002966169228e-09 +227.0,-4000.0,-8.715250743307479e-11 +228.0,-4000.0,-2.7755575615628914e-12 +229.0,-4000.0,1.1102230246251565e-12 +230.0,-4000.0,0.0 +231.0,-4000.0,-1.0824674490095276e-12 +232.0,-4000.0,2.692290834715993e-12 +233.0,-4000.0,8.754108549166365e-11 +234.0,-3999.999999999998,4.7189196993533036e-09 +235.0,-3999.9999999999136,2.576431010324217e-07 +236.0,-3999.999999995281,1.4066897160515013e-05 +237.0,-3999.9999997423565,0.0007680241858286514 +238.0,-3999.9999859331037,0.041925796610847534 +239.0,-3999.9992319758153,2.26874098972511 +240.0,-3999.958074203388,86.64339756999419 +241.0,-3997.731259010274,502.26874098972735 +242.0,-3913.356602430009,1000.0419257966093 +243.0,-3497.73125901036,1500.000768024097 +244.0,-2999.9580742081066,2000.000014062176 +245.0,-2499.999232233459,2499.999999999999 +246.0,-2000.0,2999.999985937824 +247.0,-1500.0007677665405,3499.9992319759026 +248.0,-1000.041925791893,3999.9580742033904 +249.0,-502.2687409896398,4497.731259010274 +250.0,-86.64339756698323,4913.356602259406 +251.0,-2.2676257006015934,4995.274427072589 +252.0,-0.04189693115933036,4996.515657751498 +253.0,-0.0007674922580982291,4996.536280671605 +254.0,-1.4057152436597654e-05,4996.536657664851 +255.0,-2.5746611044154283e-07,4996.536664569479 diff --git a/doc/images/Load/FlexibleLoad/PmaxU_QU_Simultaneous_2_Trajectory_Example.tex b/doc/images/Load/FlexibleLoad/PmaxU_QU_Simultaneous_2_Trajectory_Example.tex new file mode 100644 index 00000000..daf3208a --- /dev/null +++ b/doc/images/Load/FlexibleLoad/PmaxU_QU_Simultaneous_2_Trajectory_Example.tex @@ -0,0 +1,113 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \tikzset{% + pin style/.style={pin distance=1mm},% + }% + + \begin{axis} + [% + xlabel={Active power (W)},% + x label style={at={(axis description cs:0.5,-0.1)},anchor=north},% + ylabel={Reactive power (VAr)},% + y label style={at={(axis description cs:-0.2,0.5)},anchor=south},% + grid=both,% + scatter,% + only marks,% + mark size=0.5mm,% + height=9cm,% + width=9cm,% + axis equal=true,% + enlarge y limits,% + enlarge x limits,% + xmin=-5000,% + xmax=5000,% + ymin=-5000,% + ymax=5000,% + xtick={-5000,-2500,0,2500,5000},% + ytick={-5000,-2500,0,2500,5000},% + extra x ticks={-4000},% + minor tick num=1,% + scaled x ticks=base 10:-3,% + every x tick scale label/.style={at={(axis description cs:1,-0.05)}},% + scaled y ticks=base 10:-3,% + xticklabel style={align=center},% + xticklabel={% + $\pgfmathprintnumber{\tick}$ + \pgfmathifthenelse{\tick==-4}{% + "\\$P^{\theo}$"% + }{""}% + \pgfmathresult% + },% + extra x tick label={% + \pgfmathparse{\tick/1000}% + $\pgfmathprintnumber{\pgfmathresult}$ + \pgfmathifthenelse{\tick==-4000}{% + "\\$P^{\theo}$"% + }{""}% + \pgfmathresult% + },% + yticklabel style={align=center},% + yticklabel={% + \pgfmathifthenelse{\tick==0}{% + "$Q^{\theo}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==-5}{% + "$-S^{\max}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==5}{% + "$S^{\max}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + $\pgfmathprintnumber{\tick}$ + },% + colorbar,% + colorbar style={% + ylabel=Voltage (V),% + },% + nodes near coords*={},% By default, nothing + nodes near coords style={% + anchor=center,% + },% + coordinate style/.condition={\thisrow{v}==205}{pin={[pin style] right:{\mylabel}}},% + coordinate style/.condition={\thisrow{v}==210}{pin={[pin style] below:\mylabel}},% + coordinate style/.condition={\thisrow{v}==215}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==220}{pin={[pin style] below right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==225}{pin={[pin style] below left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==230}{pin={[pin style] left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==235}{pin={[pin style] above left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==240}{pin={[pin style] above right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==245}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==250}{pin={[pin style] above:\mylabel}},% + coordinate style/.condition={\thisrow{v}==255}{pin={[pin style] below right:\mylabel}},% + visualization depends on={\thisrow{v} \as \myvalue},% Required to use the "v" column for two usages + visualization depends on={% + value $\qty[parse-numbers=false]{\pgfmathprintnumber{\thisrow{v}}}{V}$\as\mylabel% + },% + ] + \addplot[point meta={\thisrow{v}}] table[x=p, y=q, col sep=comma] {% + Load/FlexibleLoad/PmaxU_QU_Simultaneous_2_Example.csv% + };% + \draw[black] (axis cs:0,0) circle[radius=5000];% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/PmaxU_QU_Simultaneous_Example.csv b/doc/images/Load/FlexibleLoad/PmaxU_QU_Simultaneous_Example.csv new file mode 100644 index 00000000..14dacb5f --- /dev/null +++ b/doc/images/Load/FlexibleLoad/PmaxU_QU_Simultaneous_Example.csv @@ -0,0 +1,52 @@ +v,p,q +205.0,-2236.0679775919675,-4472.135954953491 +206.0,-2236.067982532515,-4472.135952483217 +207.0,-2236.068252276505,-4472.135817611212 +208.0,-2236.082977416831,-4472.128455009614 +209.0,-2236.879924830704,-4471.729889191584 +210.0,-2267.4462214926693,-4456.308745210392 +211.0,-2429.150449642832,-4370.266364078971 +212.0,-2500.0,-3999.9580742033895 +213.0,-2500.0,-3499.999231975902 +214.0,-2500.0,-2999.999985937823 +215.0,-2500.0,-2500.0 +216.0,-2500.0,-2000.0000140621771 +217.0,-2500.0,-1500.0007680240985 +218.0,-2500.0,-1000.0419257966104 +219.0,-2500.0,-502.26874098972695 +220.0,-2500.0,-86.64339756999372 +221.0,-2500.0,-2.268740989725404 +222.0,-2500.0,-0.041925796612218846 +223.0,-2500.0,-0.0007680241859153725 +224.0,-2500.0,-1.4066895981379446e-05 +225.0,-2500.0,-2.5764390620963695e-07 +226.0,-2500.0,-4.719002966169228e-09 +227.0,-2500.0,-8.715250743307479e-11 +228.0,-2500.0,-2.7755575615628914e-12 +229.0,-2500.0,1.1102230246251565e-12 +230.0,-2500.0,0.0 +231.0,-2500.0,-1.0824674490095276e-12 +232.0,-2500.0,2.692290834715993e-12 +233.0,-2500.0,8.754108549166365e-11 +234.0,-2500.0,4.7189196993533036e-09 +235.0,-2500.0,2.576431010324217e-07 +236.0,-2500.0,1.4066897160515013e-05 +237.0,-2499.999999999998,0.0007680241858286514 +238.0,-2499.9999999999136,0.041925796610847534 +239.0,-2499.999999995281,2.26874098972511 +240.0,-2499.9999997423556,86.64339756999419 +241.0,-2499.999985933104,502.26874098972735 +242.0,-2499.9992319758167,1000.0419257966093 +243.0,-2499.9580742034746,1500.000768024097 +244.0,-2497.7312590149927,2000.000014062176 +245.0,-2413.356602687651,2499.999999999999 +246.0,-1997.7312730771698,2999.999985937824 +247.0,-1499.9588422275729,3499.9992319759026 +248.0,-1000.0411577724275,3999.9580742033904 +249.0,-502.2687269228301,4497.731259010274 +250.0,-86.64339730934032,4913.356602259406 +251.0,-2.2676256958850214,4995.274427072589 +252.0,-0.041896931073236374,4996.515657751498 +253.0,-0.0007674922572661384,4996.536280671605 +254.0,-1.4057152880379259e-05,4996.536657664851 +255.0,-2.5746572213263946e-07,4996.536664569479 diff --git a/doc/images/Load/FlexibleLoad/PmaxU_QU_Simultaneous_Trajectory_Example.tex b/doc/images/Load/FlexibleLoad/PmaxU_QU_Simultaneous_Trajectory_Example.tex new file mode 100644 index 00000000..9d346a6c --- /dev/null +++ b/doc/images/Load/FlexibleLoad/PmaxU_QU_Simultaneous_Trajectory_Example.tex @@ -0,0 +1,102 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \tikzset{% + pin style/.style={pin distance=1mm},% + }% + + \begin{axis} + [% + xlabel={Active power (W)},% + x label style={at={(axis description cs:0.5,-0.1)},anchor=north},% + ylabel={Reactive power (VAr)},% + y label style={at={(axis description cs:-0.2,0.5)},anchor=south},% + grid=both,% + scatter,% + only marks,% + mark size=0.5mm,% + height=9cm,% + width=9cm,% + axis equal=true,% + enlarge y limits,% + enlarge x limits,% + xmin=-5000,% + xmax=5000,% + ymin=-5000,% + ymax=5000,% + xtick={-5000,-2500,0,2500,5000},% + ytick={-5000,-2500,0,2500,5000},% + minor tick num=1,% + scaled x ticks=base 10:-3,% + every x tick scale label/.style={at={(axis description cs:1,-0.05)}},% + scaled y ticks=base 10:-3,% + xticklabel style={align=center},% + xticklabel={% + $\pgfmathprintnumber{\tick}$ + \pgfmathifthenelse{\tick==-2.5}{% + "\\$P^{\theo}$"% + }{""}% + \pgfmathresult% + },% + yticklabel style={align=center},% + yticklabel={% + \pgfmathifthenelse{\tick==0}{% + "$Q^{\theo}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==-5}{% + "$-S^{\max}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==5}{% + "$S^{\max}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + $\pgfmathprintnumber{\tick}$ + },% + colorbar,% + colorbar style={% + ylabel=Voltage (V),% + },% + nodes near coords*={},% By default, nothing + nodes near coords style={% + anchor=center,% + },% + coordinate style/.condition={\thisrow{v}==205}{pin={[pin style] right:{\mylabel}}},% + coordinate style/.condition={\thisrow{v}==210}{pin={[pin style] below:\mylabel}},% + coordinate style/.condition={\thisrow{v}==215}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==220}{pin={[pin style] below right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==225}{pin={[pin style] below left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==230}{pin={[pin style] left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==235}{pin={[pin style] above left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==240}{pin={[pin style] above right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==245}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==250}{pin={[pin style] above:\mylabel}},% + coordinate style/.condition={\thisrow{v}==255}{pin={[pin style] below right:\mylabel}},% + visualization depends on={\thisrow{v} \as \myvalue},% Required to use the "v" column for two usages + visualization depends on={% + value $\qty[parse-numbers=false]{\pgfmathprintnumber{\thisrow{v}}}{V}$\as\mylabel% + },% + ] + \addplot[point meta={\thisrow{v}}] table[x=p, y=q, col sep=comma] {Load/FlexibleLoad/PmaxU_QU_Simultaneous_Example.csv};% + \draw[black] (axis cs:0,0) circle[radius=5000];% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/PmaxU_Qconst_Control_Curve_Example.tex b/doc/images/Load/FlexibleLoad/PmaxU_Qconst_Control_Curve_Example.tex new file mode 100644 index 00000000..21e5b922 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/PmaxU_Qconst_Control_Curve_Example.tex @@ -0,0 +1,78 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \begin{axis} + [% + xlabel={Voltages (V)},% + x label style={at={(axis description cs:0.5,-0.15)},anchor=north},% + ylabel={Active power (W)},% + y label style={at={(axis description cs:-0.15,0.5)},anchor=south},% + grid=both,% + legend entries={Actual power,Non-smooth theoretical control},% + legend style={% + legend cell align=left,% + legend pos=north west,% + },% + sharp plot,% + mark size=0.5mm,% + height=9cm,% + width=16cm,% + ytick={-5000,-4000,-3000,-2000,-1000,0},% + extra y ticks={-2500},% + minor tick num=1,% + scaled y ticks=base 10:-3,% + xticklabel style={align=center},% + xticklabel={% + $\pgfmathprintnumber{\tick}$ + \pgfmathifthenelse{\tick==240}{% + "\\$\uup$"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==250}{% + "\\$\umax$"% + }{""}% + \pgfmathresult% + },% + yticklabel style={align=center},% + yticklabel={% + \pgfmathifthenelse{\tick==-5}{% + "$-\smax$\hspace{3mm}"% + }{""}% + \pgfmathresult% + $\pgfmathprintnumber{\tick}$ + },% + extra y tick label={% + \pgfmathifthenelse{\tick==-2500}{% + "$P^{\theo}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathifthenelse{\tick==-5000}{% + "$-\smax$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathparse{\tick/1000}% + $\pgfmathprintnumber{\pgfmathresult}$ + },% + ] + \addplot+[only marks] table[x=v, y=p, col sep=comma] {Load/FlexibleLoad/PmaxU_Qconst_Example.csv};% + \addplot+ coordinates {(205,-5000) (240,-5000) (250,0) (255,0)};% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Load/FlexibleLoad/PmaxU_Qconst_Example.csv b/doc/images/Load/FlexibleLoad/PmaxU_Qconst_Example.csv new file mode 100644 index 00000000..d4a5f236 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/PmaxU_Qconst_Example.csv @@ -0,0 +1,52 @@ +v,p,q +205.0,-2500.0,1000.0 +206.0,-2500.0,1000.0 +207.0,-2500.0,1000.0 +208.0,-2500.0,1000.0 +209.0,-2500.0,1000.0 +210.0,-2500.0,1000.0 +211.0,-2500.0,1000.0 +212.0,-2500.0,1000.0 +213.0,-2500.0,1000.0 +214.0,-2500.0,1000.0 +215.0,-2500.0,1000.0 +216.0,-2500.0,1000.0 +217.0,-2500.0,1000.0 +218.0,-2500.0,1000.0 +219.0,-2500.0,1000.0 +220.0,-2500.0,1000.0 +221.0,-2500.0,1000.0 +222.0,-2500.0,1000.0 +223.0,-2500.0,1000.0 +224.0,-2500.0,1000.0 +225.0,-2500.0,1000.0 +226.0,-2500.0,1000.0 +227.0,-2500.0,1000.0 +228.0,-2500.0,1000.0 +229.0,-2500.0,1000.0 +230.0,-2500.0,1000.0 +231.0,-2500.0,1000.0 +232.0,-2500.0,1000.0 +233.0,-2500.0,1000.0 +234.0,-2500.0,1000.0 +235.0,-2500.0,1000.0 +236.0,-2500.0,1000.0 +237.0,-2499.999999999998,1000.0 +238.0,-2499.9999999999136,1000.0 +239.0,-2499.999999995281,1000.0 +240.0,-2499.9999997423556,1000.0 +241.0,-2499.999985933104,1000.0 +242.0,-2499.9992319758167,1000.0 +243.0,-2499.9580742034746,1000.0 +244.0,-2497.7312590149927,1000.0 +245.0,-2413.356602687651,1000.0 +246.0,-1997.7312730771698,1000.0 +247.0,-1499.9588422275729,1000.0 +248.0,-1000.0411577724275,1000.0 +249.0,-502.2687269228301,1000.0 +250.0,-86.64339731234871,1000.0 +251.0,-2.2687409850075113,1000.0 +252.0,-0.04192579652562145,1000.0 +253.0,-0.0007680241834173707,1000.0 +254.0,-1.4066896536490958e-05,1000.0 +255.0,-2.576441837653931e-07,1000.0 diff --git a/doc/images/Load/FlexibleLoad/PmaxU_Qconst_Trajectory_Example.tex b/doc/images/Load/FlexibleLoad/PmaxU_Qconst_Trajectory_Example.tex new file mode 100644 index 00000000..e00fbb87 --- /dev/null +++ b/doc/images/Load/FlexibleLoad/PmaxU_Qconst_Trajectory_Example.tex @@ -0,0 +1,96 @@ +\input{Preambule}% + +\usepackage{pgfplots}% +\pgfplotsset{compat=newest}% + +\begin{document} + \begin{tikzpicture}[% + show background rectangle,% + tight background,% + background rectangle/.style={fill=white}% + ] + \tikzset{% + pin style/.style={pin distance=1mm},% + }% + + \begin{axis} + [% + xlabel={Active power (W)},% + x label style={at={(axis description cs:0.5,-0.1)},anchor=north},% + ylabel={Reactive power (VAr)},% + y label style={at={(axis description cs:-0.2,0.5)},anchor=south},% + grid=both,% + scatter,% + only marks,% + mark size=0.5mm,% + height=9cm,% + width=9cm,% + axis equal=true,% + enlarge y limits,% + enlarge x limits,% + xmin=-5000,% + xmax=5000,% + ymin=-5000,% + ymax=5000,% + xtick={-5000,-2500,0,2500,5000},% + ytick={-5000,-2500,0,2500,5000},% + extra y ticks={1000},% + minor tick num=1,% + scaled x ticks=base 10:-3,% + every x tick scale label/.style={at={(axis description cs:1,-0.05)}},% + scaled y ticks=base 10:-3,% + xticklabel style={align=center},% + xticklabel={% + $\pgfmathprintnumber{\tick}$ + \pgfmathifthenelse{\tick==-2.5}{% + "\\$P^{\theo}$"% + }{""}% + \pgfmathresult% + },% + yticklabel style={align=center},% + extra y tick label={% + \pgfmathifthenelse{\tick==1000}{% + "$Q^{\theo}$\hspace{3mm}"% + }{""}% + \pgfmathresult% + \pgfmathparse{\tick/1000}% + $\pgfmathprintnumber{\pgfmathresult}$ + },% + colorbar,% + colorbar style={% + ylabel=Voltage (V),% + },% + nodes near coords*={},% By default, nothing + nodes near coords style={% + anchor=center,% + },% +% coordinate style/.condition={\thisrow{v}==205}{pin={[pin style] below right:{\mylabel}}},% +% coordinate style/.condition={\thisrow{v}==210}{pin={[pin style] below:\mylabel}},% +% coordinate style/.condition={\thisrow{v}==215}{pin={[pin style] right:\mylabel}},% +% coordinate style/.condition={\thisrow{v}==220}{pin={[pin style] below left:\mylabel}},% +% coordinate style/.condition={\thisrow{v}==225}{pin={[pin style] below right:\mylabel}},% +% coordinate style/.condition={\thisrow{v}==230}{pin={[pin style] right:\mylabel}},% + coordinate style/.condition={\thisrow{v}==235}{pin={[pin style] below:\mylabel}},% + coordinate style/.condition={\thisrow{v}==240}{pin={[pin style] left:\mylabel}},% + coordinate style/.condition={\thisrow{v}==245}{pin={[pin style] above:\mylabel}},% + coordinate style/.condition={\thisrow{v}==250}{pin={[pin style] above:\mylabel}},% + coordinate style/.condition={\thisrow{v}==255}{pin={[pin style] below:\mylabel}},% + visualization depends on={\thisrow{v} \as \myvalue},% Required to use the "v" column for two usages + visualization depends on={% + value $\qty[parse-numbers=false]{\pgfmathprintnumber{\thisrow{v}}}{V}$\as\mylabel% + },% + ] + \addplot[point meta={\thisrow{v}}] table[x=p, y=q, col sep=comma] {Load/FlexibleLoad/PmaxU_Qconst_Example.csv};% + \draw[black] (axis cs:0,0) circle[radius=5000];% + \end{axis} + \end{tikzpicture} +\end{document} +% Local Variables: +% mode: latex +% TeX-engine: luatex +% TeX-source-correlate-method-active: synctex +% ispell-local-dictionary: "british" +% coding: utf-8 +% LaTeX-indent-level: 4 +% fill-column: 100 +% End: diff --git a/doc/images/Makefile b/doc/images/Makefile index 382627c3..3a293459 100644 --- a/doc/images/Makefile +++ b/doc/images/Makefile @@ -6,43 +6,42 @@ LUALATEX:=$(shell which lualatex) PDF2SVG:=$(shell which pdf2svg) # Folders +INPUT_FOLDER:=$(shell realpath .) OUTPUT_FOLDER:=$(shell realpath ../_static/) # Files -TEX_FILES:=$(filter-out Preambule.tex, $(wildcard *.tex) $(wildcard **/*.tex)) +TEX_FILES:=$(filter-out Preambule.tex, $(shell find . -name '*.tex' -type f | sed -r 's/^\.\///')) PDF_FILES:=$(TEX_FILES:%.tex=%.pdf) SVG_FILES:=$(TEX_FILES:%.tex=$(OUTPUT_FOLDER)/%.svg) -AUX_FILES:=$(TEX_FILES:%.tex=%.aux) $(wildcard $(OUTPUT_FOLDER)/*.aux) $(wildcard $(OUTPUT_FOLDER)/**/*.aux) -LOG_FILES:=$(TEX_FILES:%.tex=%.log) $(wildcard $(OUTPUT_FOLDER)/*.log) $(wildcard $(OUTPUT_FOLDER)/**/*.log) +AUX_FILES:=$(shell find $(OUTPUT_FOLDER) -name '*.aux' -type f | sed -r 's/^\.\///') +LOG_FILES:=$(shell find $(OUTPUT_FOLDER) -name '*.log' -type f | sed -r 's/^\.\///') # Rules -all: | checks $(SVG_FILES) .PHONY: clean cleanall checks -$(OUTPUT_FOLDER)/Domain_%.svg: Domain_%.tex Domain_Common.tikz Preambule.tex - @$(LUALATEX) --jobname=$(basename $<) --file-line-error --interaction=nonstopmode \ - --shell-escape --output-directory=$(OUTPUT_FOLDER) $< - -$(OUTPUT_FOLDER)/Transformer/Winding%.svg: Transformer/Winding%.tex Transformer/Windings_Common.tikz Preambule.tex - @$(LUALATEX) --jobname=$(basename $<) --file-line-error --interaction=nonstopmode \ - --shell-escape --output-directory=$(OUTPUT_FOLDER) $< +all: | checks $(SVG_FILES) +$(OUTPUT_FOLDER)/Domain_%.svg: $(INPUT_FOLDER)/Load/FlexibleLoad/Domain_%.tex $(INPUT_FOLDER)/Load/FlexibleLoad/Domain_Common.tikz Preambule.tex +$(OUTPUT_FOLDER)/Transformer/Winding%.svg: $(INPUT_FOLDER)/Transformer/Winding%.tex $(INPUT_FOLDER)/Transformer/Windings_Common.tikz Preambule.tex +$(OUTPUT_FOLDER)/Load/FlexibleLoad/%_Control_Curve_Example.svg: $(INPUT_FOLDER)/Load/FlexibleLoad/%_Control_Curve_Example.tex $(INPUT_FOLDER)/Load/FlexibleLoad/%_Example.csv Preambule.tex +$(OUTPUT_FOLDER)/Load/FlexibleLoad/%_Trajectory_Example.svg: $(INPUT_FOLDER)/Load/FlexibleLoad/%_Trajectory_Example.tex $(INPUT_FOLDER)/Load/FlexibleLoad/%_Example.csv Preambule.tex $(OUTPUT_FOLDER)/%.svg: %.tex Preambule.tex @$(LUALATEX) --jobname=$(basename $<) --file-line-error --interaction=nonstopmode \ --shell-escape --output-directory=$(OUTPUT_FOLDER) $< - clean: @rm -f $(AUX_FILES) $(LOG_FILES) $(PDF_FILES) *~ - @$(LATEXMK) -c -output-directory=$(OUTPUT_FOLDER) - @$(LATEXMK) -c + @for dir in $$(find $(INPUT_FOLDER) -type d); do \ + cd $$dir && $(LATEXMK) -c -output-directory=$(OUTPUT_FOLDER) && $(LATEXMK) -c && cd -; \ + done; cleanall: clean @rm -f $(SVG_FILES) - @$(LATEXMK) -C -output-directory=$(OUTPUT_FOLDER) - @$(LATEXMK) -C + @for dir in $$(find $(INPUT_FOLDER) -type d); do \ + cd $$dir && $(LATEXMK) -C -output-directory=$(OUTPUT_FOLDER) && $(LATEXMK) -C && cd -; \ + done; checks: @if [ -z "$(PDF2SVG)" ]; then \ diff --git a/doc/index.md b/doc/index.md index 046142d1..49e1c4c7 100644 --- a/doc/index.md +++ b/doc/index.md @@ -42,7 +42,8 @@ usage/index ## Models -A description of the electrical models used for each component is available: +A description of the electrical models used for each component, an example usage, and a reference +to the API of the classes are available here: ```{toctree} --- diff --git a/doc/models/Bus.md b/doc/models/Bus.md index de3be57a..e55951cc 100644 --- a/doc/models/Bus.md +++ b/doc/models/Bus.md @@ -101,3 +101,12 @@ en.res_branches[["current1"]].transform([np.abs, ft.partial(np.angle, deg=True)] # | ('line', 'c') | 0 | 0 | # | ('line', 'n') | 0 | 0 | ``` + +## API Reference + +```{eval-rst} +.. autoclass:: roseau.load_flow.models.Bus + :members: + :show-inheritance: + :no-index: +``` diff --git a/doc/models/Ground.md b/doc/models/Ground.md index c4f6a10c..7f222bca 100644 --- a/doc/models/Ground.md +++ b/doc/models/Ground.md @@ -147,3 +147,12 @@ en.res_buses_voltages.transform([np.abs, ft.partial(np.angle, deg=True)]) # | ('bus3', 'bc') | 385.429 | -121.026 | # | ('bus3', 'ca') | 399.18 | 118.807 | ``` + +## API Reference + +```{eval-rst} +.. autoclass:: roseau.load_flow.models.Ground + :members: + :show-inheritance: + :no-index: +``` diff --git a/doc/models/Line/ShuntLine.md b/doc/models/Line/ShuntLine.md index d0342b5a..363a5645 100644 --- a/doc/models/Line/ShuntLine.md +++ b/doc/models/Line/ShuntLine.md @@ -128,6 +128,28 @@ load = PowerLoad( id="load", bus=bus2, powers=Q_(np.array([5.0, 2.5, 0]) * (1 - 0.3j), "kVA") ) +# The impedance matrix (in Ohm) can be accessed from the line instance +line.z_line +# array( +# [[0.3+0.35j, 0. +0.25j, 0. +0.25j, 0. +0.25j], +# [0. +0.25j, 0.3+0.35j, 0. +0.25j, 0. +0.25j], +# [0. +0.25j, 0. +0.25j, 0.3+0.35j, 0. +0.25j], +# [0. +0.25j, 0. +0.25j, 0. +0.25j, 0.3+0.35j]] +# ) + +# The shunt admittance matrix (in Siemens) can be accessed from the line instance +line.y_shunt +# array( +# [[2.e-05+4.75e-04j, 0.e+00-6.80e-05j, 0.e+00-1.00e-05j, 0.e+00-6.80e-05j], +# [0.e+00-6.80e-05j, 2.e-05+4.75e-04j, 0.e+00-6.80e-05j, 0.e+00-1.00e-05j], +# [0.e+00-1.00e-05j, 0.e+00-6.80e-05j, 2.e-05+4.75e-04j, 0.e+00-6.80e-05j], +# [0.e+00-6.80e-05j, 0.e+00-1.00e-05j, 0.e+00-6.80e-05j, 2.e-05+4.75e-04j]] +# ) + +# For a shunt line, the property `with_shunt` is True +line.with_shunt +# True + # Create a network and solve a load flow en = ElectricalNetwork.from_element(bus1) auth = ("username", "password") diff --git a/doc/models/Line/SimplifiedLine.md b/doc/models/Line/SimplifiedLine.md index 45869da1..fa83c1bf 100644 --- a/doc/models/Line/SimplifiedLine.md +++ b/doc/models/Line/SimplifiedLine.md @@ -74,6 +74,27 @@ load = PowerLoad( id="load", bus=bus2, powers=Q_(np.array([5.0, 2.5, 0]) * (1 - 0.3j), "kVA") ) +# The impedance matrix (in Ohm) can be accessed from the line instance +line.z_line +# array( +# [[0.35+0.j, 0. +0.j, 0. +0.j, 0. +0.j], +# [0. +0.j, 0.35+0.j, 0. +0.j, 0. +0.j], +# [0. +0.j, 0. +0.j, 0.35+0.j, 0. +0.j], +# [0. +0.j, 0. +0.j, 0. +0.j, 0.35+0.j]] +# ) + +# For a simplified line, the property `with_shunt` is False and the `y_shunt` matrix is zero +line.with_shunt +# False + +line.y_shunt +# array( +# [[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], +# [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], +# [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], +# [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]] +# ) + # Create a network and solve a load flow en = ElectricalNetwork.from_element(bus1) auth = ("username", "password") diff --git a/doc/models/Line/index.md b/doc/models/Line/index.md index dc2cad5e..874893d3 100644 --- a/doc/models/Line/index.md +++ b/doc/models/Line/index.md @@ -8,8 +8,8 @@ $\underline{Y}$. ## Matrices definition -Before diving into the different line models, lets define the series impedance matrix $Z$, and the -shunt admittance matrix $Y$ used to model the lines. +Before diving into the different line models, lets define the series impedance matrix $\underline{Z}$, and the +shunt admittance matrix $\underline{Y}$ used to model the lines. ### Series impedance matrix @@ -158,6 +158,11 @@ shunt_line_parameters = LineParameters( ) ``` +```{tip} +The `Line` instance itself has the `z_line` and `y_shunt` properties. They retrieve the line impedance in $\Omega$ +and the line shunt admittance in Siemens (taking into account the length of the line). +``` + There are several alternative constructors for `LineParameters` objects. The description of them can be found in the dedicated [Line parameters page](Parameters.md). @@ -175,3 +180,16 @@ Parameters ShuntLine SimplifiedLine ``` + +## API Reference + +```{eval-rst} +.. autoclass:: roseau.load_flow.models.LineParameters + :members: + :show-inheritance: + :no-index: +.. autoclass:: roseau.load_flow.models.Line + :members: + :show-inheritance: + :no-index: +``` diff --git a/doc/models/Load/FlexibleLoad.md b/doc/models/Load/FlexibleLoad.md deleted file mode 100644 index c25ceaf7..00000000 --- a/doc/models/Load/FlexibleLoad.md +++ /dev/null @@ -1,430 +0,0 @@ -# Flexible loads - -They are a special case of power loads: instead of being constant, the power will depend on the -voltage measured at the load and the control applied to the load. - -## Equations - -The equations are the following (star loads): - -```{math} -\left\{ - \begin{aligned} - \underline{I_{\mathrm{abc}}} &= \left(\frac{ - \underline{S_{\mathrm{abc}}}(\underline{V_{\mathrm{abc}}}-\underline{V_{\mathrm{n}}}) - }{\underline{V_{\mathrm{abc}}}-\underline{V_{\mathrm{n}}}}\right)^{\star} \\ - \underline{I_{\mathrm{n}}} &= -\sum_{p\in\{\mathrm{a},\mathrm{b},\mathrm{c}\}}\underline{I_{p}} - \end{aligned} -\right. -``` - -And the following (delta loads): - -```{math} -\left\{ - \begin{aligned} - \underline{I_{\mathrm{ab}}} &= \left(\frac{\underline{S_{\mathrm{ab}}}(\underline{V_{\mathrm{a}}}-\underline - {V_{\mathrm{b}}})}{\underline{V_{\mathrm{a}}}-\underline{V_{\mathrm{b}}}}\right)^{\star} \\ - \underline{I_{\mathrm{bc}}} &= \left(\frac{\underline{S_{\mathrm{bc}}}(\underline{V_{\mathrm{b}}}-\underline - {V_{\mathrm{c}}})}{\underline{V_{\mathrm{b}}}-\underline{V_{\mathrm{c}}}}\right)^{\star} \\ - \underline{I_{\mathrm{ca}}} &= \left(\frac{\underline{S_{\mathrm{ca}}}(\underline{V_{\mathrm{c}}}-\underline - {V_{\mathrm{a}}})}{\underline{V_{\mathrm{c}}}-\underline{V_{\mathrm{a}}}}\right)^{\star} - \end{aligned} -\right. -``` - -The expression $\underline{S}(U)$ depends on four parameters: - -- The theoretical power $\underline{S^{\mathrm{th.}}}$ that the load would have if no control is applied. -- The maximal power $S^{\max}$ that can be injected/consumed by the load. For a PV installation, this is - usually the rated power of the inverter. -- The type of control (see below). -- The type of projection (see below). - -(models-flexible_load-controls)= - -## Controls - -There are four available types of control. - -### Constant control - -No control is applied, this is equivalent to a classical power load. The constant control can be -built like this: - -```python -from roseau.load_flow import Control - -# Use the constructor. Note that the voltages are not important in this case. -control = Control(type="constant", u_min=0.0, u_down=0.0, u_up=0.0, u_max=0.0) - -# Or prefer using the shortcut -control = Control.constant() -``` - -(models-flexible_load-p_u_control)= - -### P(U) control - -Control the maximum active power of a load (often a PV inverter) based on the voltage $P^{\max}(U)$. - -```{note} -The functions $s_{\alpha}$ used for the P(U) controls are derived from the *soft clipping function* of -{cite:p}`Klimek_2020`. -``` - -#### Production - -With this control, the following soft clipping family of functions $s_{\alpha}(U)$ is used. The -default value of `alpha` is 1000. - -```{image} /_static/Control_PU_Prod.svg -:alt: P(U) production control -:width: 600 -:align: center -``` - -The final $P$ is then $P(U) = \max(s_{\alpha}(U) \times S^{\max}, P^{\mathrm{th.}})$ - -```python -from roseau.load_flow import Control, Q_ - -# Use the constructor. Note that u_min and u_down are useless with the production control -production_control = Control( - type="p_max_u_production", u_min=0, u_down=0, u_up=Q_(240, "V"), u_max=Q_(250, "V") -) - -# Or prefer the shortcut -production_control = Control.p_max_u_production(u_up=Q_(240, "V"), u_max=Q_(250, "V")) -``` - -#### Consumption - -With this control, the following soft clipping family of functions $s_{\alpha}(U)$ is used. The -default value of `alpha` is 1000. - -```{image} /_static/Control_PU_Cons.svg -:alt: P(U) consumption control -:width: 600 -:align: center -``` - -The final $P$ is then $P(U) = \min(s_{\alpha}(U) \times S^{\max}, P^{\mathrm{th.}})$ - -```python -from roseau.load_flow import Control, Q_ - -# Use the constructor. Note that u_max and u_up are useless with the consumption control -consumption_control = Control( - type="p_max_u_consumption", u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=0, u_max=0 -) - -# Or prefer the shortcut -consumption_control = Control.p_max_u_consumption(u_min=Q_(210, "V"), u_down=Q_(220, "V")) -``` - -(models-flexible_load-q_u_control)= - -### Q(U) control - -Control the reactive power based on the voltage $Q(U)$. With this control, the following soft -clipping family of functions $s_{\alpha}(U)$ is used. The default value of `alpha` is 1000. - -```{image} /_static/Control_QU.svg -:alt: Q(U) control -:width: 600 -:align: center -``` - -The final $Q$ is then $Q(U) = s_{\alpha}(U) \times S^{\max}$ - -```{note} -The function $s_{\alpha}$ used for the Q(U) control is derived from the *soft clipping function* of -{cite:p}`Klimek_2020`. -``` - -```python -from roseau.load_flow import Control, Q_ - -# Use the constructor. Note that all the voltages are important. -control = Control( - type="q_u", - u_min=Q_(210, "V"), - u_down=Q_(220, "V"), - u_up=Q_(240, "V"), - u_max=Q_(250, "V"), -) - -# Or prefer the shortcut -control = Control.q_u( - u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=Q_(240, "V"), u_max=Q_(250, "V") -) -``` - -(models-flexible_load-projection)= - -## Projection - -The different controls may produce values for $P$ and $Q$ that are not feasible. The feasibility -domain in the $(P, Q)$ space is a part of the circle of radius $S^{\max}$. In these cases, the -solution found by the control algorithm has to be projected on the feasible domain. That's why we -need to define how the projection is done. There are three available projection types: the -_Euclidean_ projection, the projection at _Constant $P$_ and the projection at _Constant $Q$_. - -The projection accepts two approximation parameters: `alpha` and `epsilon`. - -- `alpha` is used to compute soft sign function and soft projection function. The higher `alpha` - is, the better the approximations are. -- `epsilon` is used to approximate a smooth square root function: - ```{math} - \sqrt{S} = \sqrt{\varepsilon \times \exp\left(\frac{-{|S|}^2}{\varepsilon}\right) + {|S|}^2} - ``` - The lower `epsilon` is, the better the approximations are. - -### Euclidean projection - -A Euclidean projection on the feasible domain. This is the default value for projections when it is -not specified. - -```{image} /_static/Euclidean_Projection.svg -:width: 300 -:align: center -``` - -```python -from roseau.load_flow import Projection - -projection = Projection(type="euclidean") # alpha and epsilon can be provided -``` - -### Constant $P$ - -Keep the value of $P$ computed by the control and project $Q$ on the feasible domain. - -```{image} /_static/Constant_P_Projection.svg -:width: 300 -:align: center -``` - -```python -from roseau.load_flow import Projection - -projection = Projection(type="keep_p") # alpha and epsilon can be provided -``` - -### Constant $Q$ - -Keep the value of $Q$ computed by the control and project $P$ on the feasible domain. - -```{image} /_static/Constant_Q_Projection.svg -:width: 300 -:align: center -``` - -```python -from roseau.load_flow import Projection - -projection = Projection(type="keep_q") # alpha and epsilon can be provided -``` - -(models-flexible_load-flexible_parameters)= - -## Flexible parameters - -A flexible parameter is a combination of a control on the active power, a control on the reactive -power, a projection and a maximal apparent power for one phase. - -### Example - -Here, we define a flexible parameter with: - -- a constant control on $P$ (meaning, no control), -- a control $Q(U)$ on $Q$, -- a projection which keeps $P$ constant, -- an $S^{\max}$ of 5 kVA. - -```python -from roseau.load_flow import FlexibleParameter, Control, Projection, Q_ - -fp = FlexibleParameter( - control_p=Control.constant(), - control_q=Control.q_u( - u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=Q_(240, "V"), u_max=Q_(250, "V") - ), - projection=Projection(type="keep_p"), - s_max=Q_(5, "kVA"), -) -``` - -### Usage - -To create a flexible load, create a `PowerLoad` passing it a list of `FlexibleParameter` objects -using the `flexible_params` parameter, one for each phase of the load. - -#### Scenario 1: Same $Q(U)$ control on all phases - -In this scenario, we apply the same $Q(U)$ control on the three phases of a load. We define a -flexible parameter with constant $P$ control and use it three times in the load constructor. - -```python -import numpy as np - -from roseau.load_flow import FlexibleParameter, Control, Projection, Q_, PowerLoad, Bus - -bus = Bus(id="bus", phases="abcn") - -# Create a flexible parameter object -fp = FlexibleParameter( - control_p=Control.constant(), - control_q=Control.q_u( - u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=Q_(240, "V"), u_max=Q_(250, "V") - ), - projection=Projection(type="keep_p"), - s_max=Q_(5, "kVA"), -) - -# Use it for the three phases of the load -load = PowerLoad( - id="load", - bus=bus, - powers=Q_(np.array([1000, 1000, 1000]) * (1 - 0.3j), "VA"), - flexible_params=[fp, fp, fp], # <- this makes the load "flexible" -) -``` - -The created load is a three-phase star-connected load as the phases inherited from the bus include -`"n"`. The `powers` parameter of the `PowerLoad` constructor represents the theoretical powers of -the three phases of the load. The load is flexible on its three phases with the same flexible -parameters. - -#### Scenario 2: Different controls on different phases - -In this scenario, we create a load with only two phases and a neutral connected to a three-phase -bus with a neutral. Two different controls are applied by the load on the two phases. - -```python -import numpy as np - -from roseau.load_flow import FlexibleParameter, Control, Projection, Q_, PowerLoad, Bus - -bus = Bus(id="bus", phases="abcn") - -# Create a first flexible parameter (Q(U) control) -fp1 = FlexibleParameter( - control_p=Control.constant(), - control_q=Control.q_u( - u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=Q_(240, "V"), u_max=Q_(250, "V") - ), - projection=Projection(type="keep_p"), - s_max=Q_(5, "kVA"), -) - -# Create a second flexible parameter (P(U) control) -fp2 = FlexibleParameter( - control_p=Control.p_max_u_consumption(u_min=Q_(210, "V"), u_down=Q_(220, "V")), - control_q=Control.constant(), - projection=Projection(type="euclidean"), - s_max=Q_(3, "kVA"), -) - -# Use them in a load -load = PowerLoad( - id="load", - bus=bus, - phases="abn", - powers=Q_(np.array([1000, 1000]) * (1 - 0.3j), "VA"), - flexible_params=[fp1, fp2], -) -``` - -The first element of the load is connected between phase "a" and "n" of the bus. Its control is a -$Q(U)$ control with a projection at constant $P$ and an $S^{\max}$ of 5 kVA. - -The second element of the load is connected between phase "b" and "n" of the bus. Its control is a -$P(U)$ control with an Euclidean projection and an $S^{\max}$ of 3 kVA. - -#### Scenario 3: PQ(U) control - -Finally, it is possible to combine $P(U)$ and $Q(U)$ controls, for example by first using all -available reactive power before reducing the active power in order to limit the impact for the -client. - -```python -import numpy as np - -from roseau.load_flow import FlexibleParameter, Control, Projection, Q_, PowerLoad, Bus - -bus = Bus(id="bus", phases="abc") - -# Create a flexible parameter -fp = FlexibleParameter( - control_p=Control.p_max_u_production(u_up=Q_(245, "V"), u_max=Q_(250, "V")), - control_q=Control.q_u( - u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=Q_(240, "V"), u_max=Q_(245, "V") - ), - projection=Projection(type="euclidean"), - s_max=Q_(5, "kVA"), -) - -# Or using the shortcut -fp = FlexibleParameter.pq_u_production( - up_up=Q_(245, "V"), - up_max=Q_(250, "V"), - uq_min=Q_(210, "V"), - uq_down=Q_(220, "V"), - uq_up=Q_(240, "V"), - uq_max=Q_(245, "V"), - s_max=Q_(5, "kVA"), -) - -# Use it in a load -load = PowerLoad( - id="load", - bus=bus, - powers=Q_(-np.array([1000, 1000, 1000]), "VA"), - flexible_params=[fp, fp, fp], -) -``` - -In this example, the same flexible parameter is used to control all phases of the three-phase -delta-connected load. In the flexible parameter, one can remark that the $Q(U)$ control on high -voltages triggers at 240 V (production) and reaches its maximum at 245 V. The $P(U)$ control -however triggers at 245 V and is maxed out at 250 V. - -Using this configuration, a _sequential PQ(U) control_ has been created for this load. A -_simultaneous PQ(U) control_ could have been defined by using the same voltage thresholds for both -controls. - -## Feasible domains - -Depending on the mix of controls and projection used through this class, the feasible domains in -the $(P, Q)$ space changes. Here is an illustration with a theoretical production power -($P^{\mathrm{th.}} < 0$). - -```{list-table} -:class: borderless -:header-rows: 1 -:widths: 20 20 20 20 20 - -* - - - $Q^{\mathrm{const.}}$ - - $Q(U)$ with an Euclidean projection - - $Q(U)$ with a constant P projection - - $Q(U)$ with a constant Q projection -* - $P^{\mathrm{const.}}$ - - ![image](/_static/Domain_Pconst_Qconst.svg) - - ![image](/_static/Domain_Pconst_QU_Eucl.svg) - - ![image](/_static/Domain_Pconst_QU_P.svg) - - ![image](/_static/Domain_Pconst_QU_Q.svg) -* - $P^{\max}(U)$ - - ![image](/_static/Domain_PmaxU_Qconst.svg) - - ![image](/_static/Domain_PmaxU_QU.svg) - - ![image](/_static/Domain_PmaxU_QU.svg) - - ![image](/_static/Domain_PmaxU_QU.svg) -``` - -## Bibliography - -```{bibliography} -:filter: docname in docnames -``` diff --git a/doc/models/Load/FlexibleLoad/Control.md b/doc/models/Load/FlexibleLoad/Control.md new file mode 100644 index 00000000..e8d2d99d --- /dev/null +++ b/doc/models/Load/FlexibleLoad/Control.md @@ -0,0 +1,131 @@ +(models-flexible_load-controls)= + +# Controls + +There are four available types of control. + +## Constant control + +No control is applied, this is equivalent to a classical power load. The constant control can be +built like this: + +```python +from roseau.load_flow import Control + +# Use the constructor. Note that the voltages are not important in this case. +control = Control(type="constant", u_min=0.0, u_down=0.0, u_up=0.0, u_max=0.0) + +# Or prefer using the shortcut +control = Control.constant() +``` + +(models-flexible_load-p_u_control)= + +## $P(U)$ control + +Control the maximum active power of a load (often a PV inverter) based on the voltage $P^{\max}(U)$. + +```{note} +The functions $s_{\alpha}$ used for the $P(U)$ controls are derived from the *soft clipping function* of +{cite:p}`Klimek_2020`. +``` + +### Production + +With this control, the following soft clipping family of functions $s_{\alpha}(U)$ is used. The default value of +`alpha` is 1000. + +```{image} /_static/Load/FlexibleLoad/Control_PU_Prod.svg +:alt: P(U) production control +:width: 600 +:align: center +``` + +The final $P$ is then $P(U) = \max(s_{\alpha}(U) \times S^{\max}, P^{\mathrm{th.}})$. Note that this final +$\underline{S(U)}$ point may lie outside the disc of radius $S^{\max}$ in the $(P, Q)$ plane. See the +[Projection page](models-flexible_load-projections) for more details about this case. + +```python +from roseau.load_flow import Control, Q_ + +# Use the constructor. Note that u_min and u_down are useless with the production control +production_control = Control( + type="p_max_u_production", u_min=0, u_down=0, u_up=Q_(240, "V"), u_max=Q_(250, "V") +) + +# Or prefer the shortcut +production_control = Control.p_max_u_production(u_up=Q_(240, "V"), u_max=Q_(250, "V")) +``` + +### Consumption + +With this control, the following soft clipping family of functions $s_{\alpha}(U)$ is used. The default value of +`alpha` is 1000. + +```{image} /_static/Load/FlexibleLoad/Control_PU_Cons.svg +:alt: P(U) consumption control +:width: 600 +:align: center +``` + +The final $P$ is then $P(U) = \min(s_{\alpha}(U) \times S^{\max}, P^{\mathrm{th.}})$. Note that this final +$\underline{S(U)}$ point may lie outside the disc of radius $S^{\max}$ in the $(P, Q)$ plane. See the +[Projection page](models-flexible_load-projections) for more details about this case. + +```python +from roseau.load_flow import Control, Q_ + +# Use the constructor. Note that u_max and u_up are useless with the consumption control +consumption_control = Control( + type="p_max_u_consumption", u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=0, u_max=0 +) + +# Or prefer the shortcut +consumption_control = Control.p_max_u_consumption(u_min=Q_(210, "V"), u_down=Q_(220, "V")) +``` + +(models-flexible_load-q_u_control)= + +## $Q(U)$ control + +Control the reactive power based on the voltage $Q(U)$. With this control, the following soft clipping family of +functions $s_{\alpha}(U)$ is used. The default value of `alpha` is 1000. + +```{image} /_static/Load/FlexibleLoad/Control_QU.svg +:alt: Q(U) control +:width: 600 +:align: center +``` + +The final $Q$ is then $Q(U) = s_{\alpha}(U) \times S^{\max}$. Note that this final $\underline{S(U)}$ point +may lie outside the disc of radius $S^{\max}$ in the $(P, Q)$ plane. See the +[Projection page](models-flexible_load-projections) for more details about this case. + +```{note} +The function $s_{\alpha}$ used for the $Q(U)$ control is derived from the *soft clipping function* of +{cite:p}`Klimek_2020`. +``` + +```python +from roseau.load_flow import Control, Q_ + +# Use the constructor. Note that all the voltages are important. +control = Control( + type="q_u", + u_min=Q_(210, "V"), + u_down=Q_(220, "V"), + u_up=Q_(240, "V"), + u_max=Q_(250, "V"), +) + +# Or prefer the shortcut +control = Control.q_u( + u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=Q_(240, "V"), u_max=Q_(250, "V") +) +``` + +## Bibliography + +```{bibliography} +:filter: docname in docnames +``` diff --git a/doc/models/Load/FlexibleLoad/FeasibleDomain.md b/doc/models/Load/FlexibleLoad/FeasibleDomain.md new file mode 100644 index 00000000..103ca4ad --- /dev/null +++ b/doc/models/Load/FlexibleLoad/FeasibleDomain.md @@ -0,0 +1,922 @@ +(models-flexible_load-feasible_domains)= + +# Feasible domains + +Depending on the mix of controls and projection used through the class `FlexibleParameter`, the feasible domain in +the $(P, Q)$ space changes. + +```{note} +On this page, all the images are drawn for a producer so $P^{\text{th.}}\leqslant0$. +``` + +## Everything constant + +If there is no control at all, i.e. the `flexible_params` argument is **not** given to the `PowerLoad` constructor or +`constant` control used for both active and reactive powers, the consumed (or produced) power will be the one +provided to the load, noted $\underline{S^{\mathrm{th.}}}=P^{\mathrm{th.}}+jQ^{\mathrm{th.}}$. The feasible domain +is reduced to a single point as depicted in the figure below. + +```{image} /_static/Load/FlexibleLoad/Domain_Pconst_Qconst.svg +:width: 300 +:align: center +``` + +Here is a small example of usage of the constant control for active and reactive powers. + +```python +import numpy as np + +from roseau.load_flow import ( + PowerLoad, + Bus, + Q_, + FlexibleParameter, + Control, + Projection, + VoltageSource, + ElectricalNetwork, + PotentialRef, +) + +# A voltage source +bus = Bus(id="bus", phases="abcn") +un = 400 / np.sqrt(3) +voltages = Q_(un * np.exp([0, -2j * np.pi / 3, 2j * np.pi / 3]), "V") +vs = VoltageSource(id="source", bus=bus, voltages=voltages) + +# A potential ref +pref = PotentialRef("pref", element=bus, phase="n") + +# No flexible params +load = PowerLoad( + id="load", + bus=bus, + powers=Q_(np.array([1000, 1000, 1000]), "VA"), +) + +# Build a network and solve a load flow +en = ElectricalNetwork.from_element(bus) +auth = ("username", "password") +en.solve_load_flow(auth=auth) + +# The voltage source provided 1 kVA per phase for the load +vs.res_powers +# array( +# [-1000.-0.00000000e+00j, -1000.+1.93045819e-14j, -1000.-1.93045819e-14j, 0.+0.00000000e+00j] +# ) + +# Disconnect the load +load.disconnect() + +# Constant flexible params +# The projection is useless as there are only constant controls +# The s_max is useless as there are only constant controls +fp = FlexibleParameter( + control_p=Control.constant(), + control_q=Control.constant(), + projection=Projection(type="euclidean"), + s_max=Q_(5, "kVA"), +) + +# For each phase, the provided `powers` are lower than 5 kVA. +load = PowerLoad( + id="load", + bus=bus, + powers=Q_(np.array([1000, 1000, 1000]), "VA"), + flexible_params=[fp, fp, fp], +) +en.solve_load_flow(auth=auth) + +# Again the voltage source provided 1 kVA per phase +vs.res_powers +# array( +# [-1000.-0.00000000e+00j, -1000.+1.93045819e-14j, -1000.-1.93045819e-14j, 0.+0.00000000e+00j] +# ) + +# Disconnect the load +load.disconnect() + +# For some phases, the provided `powers` are greater than 5 kVA. The projection is still useless. +load = PowerLoad( + id="load", + bus=bus, + powers=Q_(np.array([6, 4.5, 6]), "kVA"), # Above 5 kVA -> also OK! + flexible_params=[fp, fp, fp], +) +en.solve_load_flow(auth=auth) + +# The load provides exactly the power consumed by the load even if it is greater than s_max +vs.res_powers +# array( +# [-6000.-0.00000000e+00j, -4500.-3.01980663e-14j, -6000.-2.18385501e-13j, 0.+0.00000000e+00j] +# ) +``` + +## Active power control only + +When the reactive power is constant, only the active power changes as a function of the local +voltage. Thus, the active power may vary between 0 and $P^{\mathrm{th.}}$. + +When a control is activated, the load's "theoretical" power **must** always be inside the disc of +radius $S^{\max}$, otherwise an error is thrown: + +```python +import numpy as np + +from roseau.load_flow import PowerLoad, Bus, Q_, FlexibleParameter, Control, Projection + +bus = Bus(id="bus", phases="an") + +# Flexible load +fp = FlexibleParameter( + control_p=Control.p_max_u_production(u_up=Q_(240, "V"), u_max=Q_(250, "V")), + control_q=Control.constant(), + projection=Projection(type="keep_p"), + s_max=Q_(5, "kVA"), +) + +# Raises an error! +load = PowerLoad( + id="load", + bus=bus, + powers=Q_(np.array([-5 + 5j], dtype=complex), "kVA"), # > s_max + flexible_params=[fp], +) +# RoseauLoadFlowException: The power is greater than the parameter s_max +# for flexible load "load" [bad_s_value] +``` + +The active power control algorithm produces a factor between 0 and 1 that gets multiplied by $S^{\max}$. +The resulting flexible power is the minimum absolute value between this result and $P^{\mathrm{th.}}$. +As a consequence, the resulting power lies on the horizontal segment between the points +$(0, Q^{\text{th.}})$ and $(P^{\text{th.}}, Q^{\text{th.}})$. + +```{important} +The projection is useless when only active power control is applied as no point can lie outside the disc of radius +$S^{\max}$. +``` + +This domain of feasible points is depicted in the figure below: + +```{image} /_static/Load/FlexibleLoad/Domain_PmaxU_Qconst.svg +:width: 300 +:align: center +``` + +The `FlexibleParameter` class has a method {meth}`~roseau.load_flow.FlexibleParameter.compute_powers` +that allows to compute the resulting powers of the control at different voltage levels for a given +theoretical power. + +In the following example, we define a flexible parameter with a $P(U)$ control, a constant $P$ +projection, and a 5 kVA maximum power. We want to know what would the control produce for all +voltages between 205 V and 255 V if given a theoretical power of $-2.5 + 1j$ kVA. + +```python +import numpy as np + +from roseau.load_flow import Q_, FlexibleParameter, Control, Projection + +# A flexible parameter +fp = FlexibleParameter( + control_p=Control.p_max_u_production(u_up=Q_(240, "V"), u_max=Q_(250, "V")), + control_q=Control.constant(), + projection=Projection(type="keep_p"), # <----- No consequence + s_max=Q_(5, "kVA"), +) + +# We want to get the res_flexible_powers for a set of voltages norms +voltages = np.arange(205, 256, dtype=float) + +# and when the theoretical power is the following +power = Q_(-2.5 + 1j, "kVA") + +# Get the resulting flexible powers for the given theoretical power and voltages list. +auth = ("username", "password") +res_flexible_powers = fp.compute_powers(auth=auth, voltages=voltages, power=power) +``` + +Plotting the control curve $P(U)$ using the variables `voltages` and `res_flexible_powers` of the +example above produces the following plot: + +```{image} /_static/Load/FlexibleLoad/PmaxU_Qconst_Control_Curve_Example.svg +:width: 700 +:align: center +``` + +```{note} +Using `compute_powers` actually requests the solver to solve a load flow for each voltage in the list. +It needs an internet connection to access the server and may take some time (similar to the +{meth}`roseau.load_flow.ElectricalNetwork.solve_load_flow` method). +``` + +The non-smooth theoretical control function is the control function applied to $S^{\max}$. The +"Actual power" plotted is the power actually produced by the load for each voltage. Below 240 V, +there is no variation in the produced power which is expected. Between 240 V and approximately +245 V, there is no reduction of the produced power because the curtailment +factor (computed from the voltage) times $S^{\max}$ is lower than $P^{\mathrm{th.}}$. As a consequence, +$P^{\mathrm{th.}}$ is produced. Starting at approximately 245 V, the comparison changes and the +actually produced power starts to decrease until it reaches 0 W at 250 V. + +The same plot can be obtained with: + +```python +from matplotlib import pyplot as plt + +ax, res_flexible_powers = fp.plot_control_p( + auth=auth, voltages=voltages, power=power, res_flexible_powers=res_flexible_powers +) +plt.show() +``` + +Note that in this example, `res_flexible_powers` is provided as input to the plotting function. If +it was not provided, the powers would have been computed by requesting the server (using the +`compute_powers()` method above). The method returns a 2-tuple with the _matplotlib axis_ of the +plot and the computed powers. + +`````{tip} +To install _matplotlib_ along side _roseau-load-flow_, you can use the `plot` extra: + +````{tab} Linux/MacOS +```console +$ python -m pip install "roseau-load-flow[plot]" +``` +```` + +````{tab} Windows +```doscon +C:> py -m pip install "roseau-load-flow[plot]" +``` +```` + +Matplotlib is always installed when `conda` is used. +````` + +If we plot the trajectory of the control in the $(P, Q)$ space, we get: + +```{image} /_static/Load/FlexibleLoad/PmaxU_Qconst_Trajectory_Example.svg +:width: 700 +:align: center +``` + +All the points have been plotted (1 per volt between 205 V and 255 V). Many points overlap. + +The same plot can be obtained with: + +```python +from matplotlib import pyplot as plt + +ax = plt.subplot() # New axes +ax, res_flexible_powers = fp.plot_pq( + auth=auth, + voltages=voltages, + power=power, + res_flexible_powers=res_flexible_powers, + voltages_labels_mask=np.isin(voltages, [240, 250]), + ax=ax, +) +plt.show() +``` + +## Reactive power control only + +When the active power is constant (no $P$ control), only the reactive power changes as a function +of the local voltage. Thus, the reactive power may vary between $-S^{\max}$ and $+S^{\max}$. When +$P^{\mathrm{th.}}\neq 0$, the point $(P, Q)$ produced by the control might lie outside the disc of +radius $S^{\max}$ (when $P^{\mathrm{th.}}\neq 0$). Those points are projected on the circle of +radius $S^{\max}$ and depending on the projection, the feasible domains change. + +### Constant $P$ + +If the _constant $P$_ (`keep_p`) projection is chosen, the feasible domain is limited to a vertical +segment as shown below. + +```{image} /_static/Load/FlexibleLoad/Domain_Pconst_QU_P.svg +:width: 300 +:align: center +``` + +Here is an example of a flexible parameter with a reactive power control and without active power +control: + +```python +import numpy as np + +from roseau.load_flow import Q_, FlexibleParameter, Control, Projection + +# Flexible parameter +fp = FlexibleParameter( + control_p=Control.constant(), + control_q=Control.q_u( + u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=Q_(240, "V"), u_max=Q_(250, "V") + ), + projection=Projection(type="keep_p"), # <---- Keep P + s_max=Q_(5, "kVA"), +) + +# We want to get the res_flexible_powers for a set of voltages norms +voltages = np.arange(205, 256, dtype=float) + +# and when the theoretical power is the following +power = Q_(-2.5, "kVA") + +# Get the resulting flexible powers for the given theoretical power and voltages list. +auth = ("username", "password") +res_flexible_powers = fp.compute_powers(auth=auth, voltages=voltages, power=power) +``` + +The variable `res_flexible_powers` contains the powers that have been actually produced by +the flexible load for the voltages stored in the variable named `voltages`. + +Plotting the control curve $Q(U)$ gives: + +```{image} /_static/Load/FlexibleLoad/Pconst_QU_P_Control_Curve_Example.svg +:width: 700 +:align: center +``` + +Notice that, even with a voltage lower than $U^{\min}$ or greater than $U^{\max}$, the available reactive +power (by default taken in the interval $[-S^{\max}, S^{\max}]$) was never fully used because of the choice of the +projection. + +The same plot can be obtained with: + +```python +from matplotlib import pyplot as plt + +ax = plt.subplot() # New axes +ax, res_flexible_powers = fp.plot_control_q( + auth=auth, + voltages=voltages, + power=power, + res_flexible_powers=res_flexible_powers, + ax=ax, +) +plt.show() +``` + +If we plot the trajectory of the control in the $(P, Q)$ space, we get: + +```{image} /_static/Load/FlexibleLoad/Pconst_QU_P_Trajectory_Example.svg +:width: 700 +:align: center +``` + +As in the previous plot, there is one point per volt from 205 V to 255 V. Several remarks on this plot: + +1. All the points are aligned on the straight line $P=-2.5$ kVA because it was the power provided to the flexible + load and because the projection used was at _Constant P_. +2. Several points are overlapping for low and high voltages. For these extremities, the + theoretical control curves would have forced the point of operation to be outside the disc of radius $S^{\max}$ (5 + kVA in this example). + +The same plot can be obtained with: + +```python +from matplotlib import pyplot as plt + +ax = plt.subplot() # New axes +ax, res_flexible_powers = fp.plot_pq( + auth=auth, + voltages=voltages, + power=power, + res_flexible_powers=res_flexible_powers, + voltages_labels_mask=np.isin(voltages, [210, 215, 230, 245, 250]), + ax=ax, +) +plt.show() +``` + +### Constant $Q$ + +If the _constant $Q$_ (`keep_q`) projection is chosen, the feasible domain is limited to a segment with two arcs as +defined below. + +```{image} /_static/Load/FlexibleLoad/Domain_Pconst_QU_Q.svg +:width: 300 +:align: center +``` + +```{warning} +Note that using this projection with a constant active power may result in a final active power +lower than the one provided (could reach 0 W in the worst-case)! +``` + +Here is an example the creation of such control with a constant $Q$ projection: + +```python +import numpy as np + +from roseau.load_flow import Q_, FlexibleParameter, Control, Projection + +# Flexible parameter +fp = FlexibleParameter( + control_p=Control.constant(), + control_q=Control.q_u( + u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=Q_(240, "V"), u_max=Q_(250, "V") + ), + projection=Projection(type="keep_q"), # <---- Keep Q + s_max=Q_(5, "kVA"), +) + +# We want to get the res_flexible_powers for a set of voltages norms +voltages = np.arange(205, 256, dtype=float) + +# and when the theoretical power is the following +power = Q_(-2.5, "kVA") + +# Get the resulting flexible powers for the given theoretical power and voltages list. +auth = ("username", "password") +res_flexible_powers = fp.compute_powers(auth=auth, voltages=voltages, power=power) +``` + +The variable `res_flexible_powers` contains the powers that have been actually produced by +the flexible load for the voltages stored in the variable named `voltages`. + +Plotting the control curve $Q(U)$ gives: + +```{image} /_static/Load/FlexibleLoad/Pconst_QU_Q_Control_Curve_Example.svg +:width: 700 +:align: center +``` + +Here, the complete possible range of reactive power is used. When the control finds an infeasible +solution, it reduces the active power because the projection type is _Constant Q_. + +The same plot can be obtained with: + +```python +from matplotlib import pyplot as plt + +ax = plt.subplot() # New axes +ax, res_flexible_powers = fp.plot_control_q( + auth=auth, + voltages=voltages, + power=power, + res_flexible_powers=res_flexible_powers, + ax=ax, +) +plt.show() +``` + +If we plot the trajectory of the control in the $(P, Q)$ space, we get: + +```{image} /_static/Load/FlexibleLoad/Pconst_QU_Q_Trajectory_Example.svg +:width: 700 +:align: center +``` + +The same plot can be obtained with: + +```python +from matplotlib import pyplot as plt + +ax = plt.subplot() # New axes +ax, res_flexible_powers = fp.plot_pq( + auth=auth, + voltages=voltages, + power=power, + res_flexible_powers=res_flexible_powers, + voltages_labels_mask=np.isin(voltages, [210, 215, 230, 245, 250]), + ax=ax, +) +plt.show() +``` + +Notice that when the voltages were too low or too high, the projection at constant $Q$ forces the +reduction of the produced active power to ensure a feasible solution. Like before, there is one +point per volt. Several points overlap at the extremities near the coordinates (0,5 kVA) and +(0, -5 kVA). + +### Euclidean projection + +If the _Euclidean_ (`euclidean`) projection is chosen, the feasible domain is limited to a segment with two +small arcs as defined below. + +```{image} /_static/Load/FlexibleLoad/Domain_Pconst_QU_Eucl.svg +:width: 300 +:align: center +``` + +```{warning} +Note that using this projection with a constant active power may result in a final active power lower than the one +provided! +``` + +```python +import numpy as np + +from roseau.load_flow import Q_, FlexibleParameter, Control, Projection + +# Flexible parameter +fp = FlexibleParameter( + control_p=Control.constant(), + control_q=Control.q_u( + u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=Q_(240, "V"), u_max=Q_(250, "V") + ), + projection=Projection(type="euclidean"), # <---- Euclidean + s_max=Q_(5, "kVA"), +) + +# We want to get the res_flexible_powers for a set of voltages norms +voltages = np.arange(205, 256, dtype=float) + +# and when the theoretical power is the following +power = Q_(-2.5, "kVA") + +# Get the resulting flexible powers for the given theoretical power and voltages list. +auth = ("username", "password") +res_flexible_powers = fp.compute_powers(auth=auth, voltages=voltages, power=power) +``` + +The variable `res_flexible_powers` contains the powers that have been actually produced by +the flexible load for the voltages stored in the variable named `voltages`. + +Plotting the control curve $Q(U)$ gives: + +```{image} /_static/Load/FlexibleLoad/Pconst_QU_Eucl_Control_Curve_Example.svg +:width: 700 +:align: center +``` + +Here, again the complete reactive power range is not fully used. + +The same plot can be obtained with: + +```python +from matplotlib import pyplot as plt + +ax = plt.subplot() # New axes +ax, res_flexible_powers = fp.plot_control_q( + auth=auth, + voltages=voltages, + power=power, + res_flexible_powers=res_flexible_powers, + ax=ax, +) +plt.show() +``` + +If we plot the trajectory of the control in the $(P, Q)$ space, we get: + +```{image} /_static/Load/FlexibleLoad/Pconst_QU_Eucl_Trajectory_Example.svg +:width: 700 +:align: center +``` + +The same plot can be obtained with: + +```python +from matplotlib import pyplot as plt + +ax = plt.subplot() # New axes +ax, res_flexible_powers = fp.plot_pq( + auth=auth, + voltages=voltages, + power=power, + res_flexible_powers=res_flexible_powers, + voltages_labels_mask=np.isin(voltages, [210, 215, 230, 245, 250]), + ax=ax, +) +plt.show() +``` + +Notice that when the voltages were too low or too high, the Euclidean projection forces the +reduction of the produced active power and the (produced and consumed) reactive power to ensure a +feasible solution. Like before, there is one point per volt and several points overlap. + +### $Q^{\min}$ and $Q^{\max}$ limits + +It is also possible to define a minimum and maximum reactive power values. In that case, the feasible domain is +constrained between those two values. + +```{image} /_static/Load/FlexibleLoad/Domain_Pconst_QU_Qmin_Qmax.svg +:width: 300 +:align: center +``` + +```python +import numpy as np + +from roseau.load_flow import Q_, FlexibleParameter, Control, Projection + +# Flexible parameter +fp = FlexibleParameter( + control_p=Control.constant(), + control_q=Control.q_u( + u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=Q_(240, "V"), u_max=Q_(250, "V") + ), + projection=Projection(type="euclidean"), + s_max=Q_(5, "kVA"), + q_min=Q_(-3, "kVAr"), # <---- set Q_min >= -S_max + q_max=Q_(4, "kVAr"), # <---- set Q_max <= S_max +) + +# We want to get the res_flexible_powers for a set of voltages norms +voltages = np.arange(205, 256, dtype=float) + +# and when the theoretical power is the following +power = Q_(-2.5, "kVA") + +# Get the resulting flexible powers for the given theoretical power and voltages list. +auth = ("username", "password") +res_flexible_powers = fp.compute_powers(auth=auth, voltages=voltages, power=power) +``` + +The variable `res_flexible_powers` contains the powers that have been actually produced by +the flexible load for the voltages stored in the variable named `voltages`. + +Plotting the control curve $Q(U)$ gives: + +```{image} /_static/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Control_Curve_Example.svg +:width: 700 +:align: center +``` + +Here, again the complete reactive power range is not fully used as it is constrained between the $Q^{\min}$ and +$Q^{\max}$ values defined. + +The same plot can be obtained with: + +```python +from matplotlib import pyplot as plt + +ax = plt.subplot() # New axes +ax, res_flexible_powers = fp.plot_control_q( + auth=auth, + voltages=voltages, + power=power, + res_flexible_powers=res_flexible_powers, + ax=ax, +) +plt.show() +``` + +If we plot the trajectory of the control in the $(P, Q)$ space, we get: + +```{image} /_static/Load/FlexibleLoad/Pconst_QU_Qmin_Qmax_Trajectory_Example.svg +:width: 700 +:align: center +``` + +The same plot can be obtained with: + +```python +from matplotlib import pyplot as plt + +ax = plt.subplot() # New axes +ax, res_flexible_powers = fp.plot_pq( + auth=auth, + voltages=voltages, + power=power, + res_flexible_powers=res_flexible_powers, + voltages_labels_mask=np.isin(voltages, [210, 215, 230, 245, 250]), + ax=ax, +) +plt.show() +``` + +## Both active and reactive powers control + +When both active and reactive power controls are activated, the feasible domain is the following: + +```{image} /_static/Load/FlexibleLoad/Domain_PmaxU_QU.svg +:width: 300 +:align: center +``` + +Every point whose abscissa is between $P^{\mathrm{th.}}$ and 0, whose ordinate is between $-S^{\max}$ +and $+S^{\max}$ and which lies in the disc of radius $S^{\max}$ (blue shaded area) is reachable. +Let's look at two examples: in the first one, the controls are activated sequentially (reactive power +first and then active power) and, in the other example, they are used together. + +### Sequentially activated controls + +Let's define different voltage thresholds for each control so that one triggers before the other. + +#### Reactive power control first + +Here, the reactive power control is activated at 230 V and fully used above 240 V. Then, at 245 V, the +active power control starts and is fully used at 250 V: + +```python +import numpy as np + +from roseau.load_flow import Q_, FlexibleParameter, Control, Projection + +# Flexible parameter +fp = FlexibleParameter( + control_p=Control.p_max_u_production(u_up=Q_(245, "V"), u_max=Q_(250, "V")), # <---- + control_q=Control.q_u( + u_min=Q_(210, "V"), + u_down=Q_(220, "V"), + u_up=Q_(230, "V"), # <---- lower than U_up of the P(U) control + u_max=Q_(240, "V"), # <---- lower than U_up of the P(U) control + ), + projection=Projection(type="euclidean"), # <---- Euclidean + s_max=Q_(5, "kVA"), +) + +# We want to get the res_flexible_powers for a set of voltages norms +voltages = np.arange(205, 256, dtype=float) + +# and when the theoretical power is the following +power = Q_(-2.5, "kVA") + +# Get the resulting flexible powers for the given theoretical power and voltages list. +auth = ("username", "password") +res_flexible_powers = fp.compute_powers(auth=auth, voltages=voltages, power=power) +``` + +If we plot the trajectory of the control in the $(P, Q)$ space, we get: + +```{image} /_static/Load/FlexibleLoad/PmaxU_QU_Sequential_1_Trajectory_Example.svg +:width: 700 +:align: center +``` + +The same plot can be obtained with: + +```python +from matplotlib import pyplot as plt + +ax = plt.subplot() # New axes +ax, res_flexible_powers = fp.plot_pq( + auth=auth, + voltages=voltages, + power=power, + res_flexible_powers=res_flexible_powers, + voltages_labels_mask=np.isin(voltages, [210, 215, 230, 245, 250]), + ax=ax, +) +plt.show() +``` + +When the voltage is low, only the reactive power control is activated (vertical segment at +$P^{\mathrm{th.}}$); similar to what we saw in the $Q(U)$ control section. + +When the voltage is high, there are two stages: + +1. Between 230 V and 240 V, only the reactive power changes: It increases on the vertical segment + to reach the perimeter of the disk. +2. Between 245 V and 250 V, the active power control starts reducing the active power until it + reaches 0 W at 250 V. + +#### Active power control first + +Here, the active power control is activated at 240 V and fully used above 245 V. Then, at 245 V, the +reactive power control starts and is fully activated at 250 V. + +```python +import numpy as np + +from roseau.load_flow import Q_, FlexibleParameter, Control, Projection + +# Flexible parameter +fp = FlexibleParameter( + control_p=Control.p_max_u_production(u_up=Q_(230, "V"), u_max=Q_(240, "V")), # <---- + control_q=Control.q_u( + u_min=Q_(210, "V"), + u_down=Q_(220, "V"), + u_up=Q_(245, "V"), # <---- higher than U_max of the P(U) control + u_max=Q_(250, "V"), # <---- higher than U_max of the P(U) control + ), + projection=Projection(type="euclidean"), # <---- Euclidean + s_max=Q_(5, "kVA"), +) + +# We want to get the res_flexible_powers for a set of voltages norms +voltages = np.arange(205, 256, dtype=float) + +# and when the theoretical power is the following +power = Q_(-2.5, "kVA") + +# Get the resulting flexible powers for the given theoretical power and voltages list. +auth = ("username", "password") +res_flexible_powers = fp.compute_powers(auth=auth, voltages=voltages, power=power) +``` + +If we plot the trajectory of the control in the $(P, Q)$ space, we get: + +```{image} /_static/Load/FlexibleLoad/PmaxU_QU_Sequential_2_Trajectory_Example.svg +:width: 700 +:align: center +``` + +The same plot can be obtained with: + +```python +from matplotlib import pyplot as plt + +ax = plt.subplot() # New axes +ax, res_flexible_powers = fp.plot_pq( + auth=auth, + voltages=voltages, + power=power, + res_flexible_powers=res_flexible_powers, + voltages_labels_mask=np.isin(voltages, [210, 215, 230, 245, 250]), + ax=ax, +) +plt.show() +``` + +When the voltage is low, only the reactive power control is activated (vertical segment at +$P^{\mathrm{th.}}$); similar to what we saw in the $Q(U)$ control section. + +When the voltage is high, there are two stages: + +1. Between 230 V and 240 V, only the active power changes. It decreases to about 0 W. +2. Between 245 V and 250 V, the reactive power control increases the reactive power until it + reaches $S^{max}$ at 250 V. + +### Simultaneously activated controls + +Here, the active and the reactive powers controls are both activated at 240 V and reach their full +effect at 250 V. + +```python +import numpy as np + +from roseau.load_flow import Q_, FlexibleParameter, Control, Projection + +# Flexible parameter +fp = FlexibleParameter( + control_p=Control.p_max_u_production(u_up=Q_(240, "V"), u_max=Q_(250, "V")), # <---- + control_q=Control.q_u( + u_min=Q_(210, "V"), + u_down=Q_(220, "V"), + u_up=Q_(240, "V"), # <---- same as U_up of the P(U) control + u_max=Q_(250, "V"), # <---- same as U_max of the P(U) control + ), + projection=Projection(type="euclidean"), # <---- Euclidean + s_max=Q_(5, "kVA"), +) + +# We want to get the res_flexible_powers for a set of voltages norms +voltages = np.arange(205, 256, dtype=float) + +# and when the theoretical power is the following +power = Q_(-2.5, "kVA") + +# Get the resulting flexible powers for the given theoretical power and voltages list. +auth = ("username", "password") +res_flexible_powers = fp.compute_powers(auth=auth, voltages=voltages, power=power) +``` + +If we plot the trajectory of the control in the $(P, Q)$ space, we get: + +```{image} /_static/Load/FlexibleLoad/PmaxU_QU_Simultaneous_Trajectory_Example.svg +:width: 700 +:align: center +``` + +The same plot can be obtained with: + +```python +from matplotlib import pyplot as plt + +ax = plt.subplot() # New axes +ax, res_flexible_powers = fp.plot_pq( + auth=auth, + voltages=voltages, + power=power, + res_flexible_powers=res_flexible_powers, + voltages_labels_mask=np.isin(voltages, [210, 215, 230, 245, 250]), + ax=ax, +) +plt.show() +``` + +When the voltage is low, only the reactive power control is activated (vertical segment at +$P^{\mathrm{th.}}$); similar to what we saw in the $Q(U)$ control section. + +When the voltage is high, there are two stages: + +1. Between 240 V and 245 V, the active power control does not modify the produced active power + while the reactive power control starts to increase the reactive power. +2. Between 245 V and 250 V, the active power control starts decreasing the active power while the + reactive power continues to increase. +3. Above 250 V, active power is reduced to 0 W and the reactive power is set to $S^{max}$. + +If we change the theoretical power to 4 kVA. + +```python +from matplotlib import pyplot as plt + +ax = plt.subplot() # New axes +ax, res_flexible_powers = fp.plot_pq( + auth=auth, + voltages=voltages, + power=Q_(-4, "kVA"), # <------ New power + # res_flexible_powers=res_flexible_powers, # Must be computed again! + voltages_labels_mask=np.isin(voltages, [210, 215, 230, 245, 250]), + ax=ax, +) +plt.show() +``` + +Now we get a different result: + +```{image} /_static/Load/FlexibleLoad/PmaxU_QU_Simultaneous_2_Trajectory_Example.svg +:width: 700 +:align: center +``` diff --git a/doc/models/Load/FlexibleLoad/FlexibleParameter.md b/doc/models/Load/FlexibleLoad/FlexibleParameter.md new file mode 100644 index 00000000..2a6f1823 --- /dev/null +++ b/doc/models/Load/FlexibleLoad/FlexibleParameter.md @@ -0,0 +1,167 @@ +(models-flexible_load-flexible_parameters)= + +# Flexible parameters + +A flexible parameter is a combination of a control on the active power, a control on the reactive +power, a projection and a maximal apparent power for one phase. + +## Example + +Here, we define a flexible parameter with: + +- a constant control on $P$ (meaning, no control), +- a control $Q(U)$ on $Q$, +- a projection which keeps $P$ constant, +- an $S^{\max}$ of 5 kVA. + +```python +from roseau.load_flow import FlexibleParameter, Control, Projection, Q_ + +fp = FlexibleParameter( + control_p=Control.constant(), + control_q=Control.q_u( + u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=Q_(240, "V"), u_max=Q_(250, "V") + ), + projection=Projection(type="keep_p"), + s_max=Q_(5, "kVA"), +) +``` + +## Usage + +To create a flexible load, create a `PowerLoad` passing it a list of `FlexibleParameter` objects +using the `flexible_params` parameter, one for each phase of the load. + +### Scenario 1: Same $Q(U)$ control on all phases + +In this scenario, we apply the same $Q(U)$ control on the three phases of a load. We define a +flexible parameter with constant $P$ control and use it three times in the load constructor. + +```python +import numpy as np + +from roseau.load_flow import FlexibleParameter, Control, Projection, Q_, PowerLoad, Bus + +bus = Bus(id="bus", phases="abcn") + +# Create a flexible parameter object +fp = FlexibleParameter( + control_p=Control.constant(), + control_q=Control.q_u( + u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=Q_(240, "V"), u_max=Q_(250, "V") + ), + projection=Projection(type="keep_p"), + s_max=Q_(5, "kVA"), +) + +# Use it for the three phases of the load +load = PowerLoad( + id="load", + bus=bus, + powers=Q_(np.array([1000, 1000, 1000]) * (1 - 0.3j), "VA"), + flexible_params=[fp, fp, fp], # <- this makes the load "flexible" +) +``` + +The created load is a three-phase star-connected load as the phases inherited from the bus include +`"n"`. The `powers` parameter of the `PowerLoad` constructor represents the theoretical powers of +the three phases of the load. The load is flexible on its three phases with the same flexible +parameters. + +### Scenario 2: Different controls on different phases + +In this scenario, we create a load with only two phases and a neutral connected to a three-phase +bus with a neutral. Two different controls are applied by the load on the two phases. + +```python +import numpy as np + +from roseau.load_flow import FlexibleParameter, Control, Projection, Q_, PowerLoad, Bus + +bus = Bus(id="bus", phases="abcn") + +# Create a first flexible parameter (Q(U) control) +fp1 = FlexibleParameter( + control_p=Control.constant(), + control_q=Control.q_u( + u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=Q_(240, "V"), u_max=Q_(250, "V") + ), + projection=Projection(type="keep_p"), + s_max=Q_(5, "kVA"), +) + +# Create a second flexible parameter (P(U) control) +fp2 = FlexibleParameter( + control_p=Control.p_max_u_consumption(u_min=Q_(210, "V"), u_down=Q_(220, "V")), + control_q=Control.constant(), + projection=Projection(type="euclidean"), + s_max=Q_(3, "kVA"), +) + +# Use them in a load +load = PowerLoad( + id="load", + bus=bus, + phases="abn", + powers=Q_(np.array([1000, 1000]) * (1 - 0.3j), "VA"), + flexible_params=[fp1, fp2], +) +``` + +The first element of the load is connected between phase "a" and "n" of the bus. Its control is a +$Q(U)$ control with a projection at constant $P$ and an $S^{\max}$ of 5 kVA. + +The second element of the load is connected between phase "b" and "n" of the bus. Its control is a +$P(U)$ control with a Euclidean projection and an $S^{\max}$ of 3 kVA. + +### Scenario 3: $PQ(U)$ control + +Finally, it is possible to combine $P(U)$ and $Q(U)$ controls, for example by first using all +available reactive power before reducing the active power in order to limit the impact for the +client. + +```python +import numpy as np + +from roseau.load_flow import FlexibleParameter, Control, Projection, Q_, PowerLoad, Bus + +bus = Bus(id="bus", phases="abc") + +# Create a flexible parameter +fp = FlexibleParameter( + control_p=Control.p_max_u_production(u_up=Q_(245, "V"), u_max=Q_(250, "V")), + control_q=Control.q_u( + u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=Q_(240, "V"), u_max=Q_(245, "V") + ), + projection=Projection(type="euclidean"), + s_max=Q_(5, "kVA"), +) + +# Or using the shortcut +fp = FlexibleParameter.pq_u_production( + up_up=Q_(245, "V"), + up_max=Q_(250, "V"), + uq_min=Q_(210, "V"), + uq_down=Q_(220, "V"), + uq_up=Q_(240, "V"), + uq_max=Q_(245, "V"), + s_max=Q_(5, "kVA"), +) + +# Use it in a load +load = PowerLoad( + id="load", + bus=bus, + powers=Q_(-np.array([1000, 1000, 1000]), "VA"), # <- negative powers (generator) + flexible_params=[fp, fp, fp], +) +``` + +In this example, the same flexible parameter is used to control all phases of the three-phase +delta-connected load. In the flexible parameter, one can remark that the $Q(U)$ control on high +voltages triggers at 240 V (production) and reaches its maximum at 245 V. The $P(U)$ control +however triggers at 245 V and is maxed out at 250 V. + +Using this configuration, a _sequential $PQ(U)$ control_ has been created for this load. A +_simultaneous $PQ(U)$ control_ could have been defined by using the same voltage thresholds for both +controls. diff --git a/doc/models/Load/FlexibleLoad/Projection.md b/doc/models/Load/FlexibleLoad/Projection.md new file mode 100644 index 00000000..beacd1f2 --- /dev/null +++ b/doc/models/Load/FlexibleLoad/Projection.md @@ -0,0 +1,86 @@ +(models-flexible_load-projections)= + +# Projections + +When the control algorithm is trying to find the best control for given voltage constraints, it +could find a solution that is not "feasible" by the load. This means that the active and reactive +powers $P$ and $Q$ that constitute the solution lie outside the feasible domain defined by a part +of the disc of radius $S^{\max}$ in the $(P, Q)$ space. In these cases, the solution has to be +projected into the feasible domain. We can choose how the projection is performed using three +available projection types: +the _Euclidean_ projection, the projection at _Constant $P$_ and the projection at _Constant $Q$_. + +The projection accepts two approximation parameters: `alpha` and `epsilon`. + +- `alpha` is used to compute soft sign function and soft projection function. The higher `alpha` + is, the better the approximations are. +- `epsilon` is used to approximate a smooth square root function: + ```{math} + \sqrt{S} \approx \sqrt{\varepsilon \times \exp\left(\frac{-{|S|}^2}{\varepsilon}\right) + {|S|}^2} + ``` + The lower `epsilon` is, the better the approximations are. + +```{important} +Please note that no projection is performed if the final $\underline{S(U)}$ point lies in the disc of radius $S^{\max}$. +``` + +## Euclidean projection + +A Euclidean projection on the feasible domain. This is the default value for projections when it is +not specified. + +```{image} /_static/Load/FlexibleLoad/Euclidean_Projection.svg +:width: 300 +:align: center +``` + +```python +from roseau.load_flow import Projection + +projection = Projection(type="euclidean") # alpha and epsilon can be provided +``` + +```{important} +Please note that using the Euclidean projection may reduce the provided $P^{\mathrm{th.}}$ and $Q^{\mathrm{th.}}$ of +the load. See the [Feasible Domain page](models-flexible_load-feasible_domains) for more details. +``` + +## Constant $P$ + +Keep the value of $P$ computed by the control and project $Q$ on the feasible domain. + +```{image} /_static/Load/FlexibleLoad/Constant_P_Projection.svg +:width: 300 +:align: center +``` + +```python +from roseau.load_flow import Projection + +projection = Projection(type="keep_p") # alpha and epsilon can be provided +``` + +```{important} +Please note that using the _Constant $P$_ projection may reduce the provided $Q^{\mathrm{th.}}$ of the load. See the +[Feasible Domain page](models-flexible_load-feasible_domains) for more details. +``` + +## Constant $Q$ + +Keep the value of $Q$ computed by the control and project $P$ on the feasible domain. + +```{image} /_static/Load/FlexibleLoad/Constant_Q_Projection.svg +:width: 300 +:align: center +``` + +```python +from roseau.load_flow import Projection + +projection = Projection(type="keep_q") # alpha and epsilon can be provided +``` + +```{important} +Please note that using the _Constant $Q$_ projection may reduce the provided $P^{\mathrm{th.}}$ of +the load. See the [Feasible Domain page](models-flexible_load-feasible_domains) for more details. +``` diff --git a/doc/models/Load/FlexibleLoad/index.md b/doc/models/Load/FlexibleLoad/index.md new file mode 100644 index 00000000..6de965c3 --- /dev/null +++ b/doc/models/Load/FlexibleLoad/index.md @@ -0,0 +1,74 @@ +# Flexible loads + +They are a special type of power loads: instead of being constant, the power will depend on the +voltage measured at the load and the control applied to the load. + +## Equations + +The equations are the following (star loads): + +```{math} +\left\{ + \begin{aligned} + \underline{I_{\mathrm{abc}}} &= \left(\frac{ + \underline{S_{\mathrm{abc}}}(\underline{V_{\mathrm{abc}}}-\underline{V_{\mathrm{n}}}) + }{\underline{V_{\mathrm{abc}}}-\underline{V_{\mathrm{n}}}}\right)^{\star} \\ + \underline{I_{\mathrm{n}}} &= -\sum_{p\in\{\mathrm{a},\mathrm{b},\mathrm{c}\}}\underline{I_{p}} + \end{aligned} +\right. +``` + +And the following (delta loads): + +```{math} +\left\{ + \begin{aligned} + \underline{I_{\mathrm{ab}}} &= \left(\frac{\underline{S_{\mathrm{ab}}}(\underline{V_{\mathrm{a}}}-\underline + {V_{\mathrm{b}}})}{\underline{V_{\mathrm{a}}}-\underline{V_{\mathrm{b}}}}\right)^{\star} \\ + \underline{I_{\mathrm{bc}}} &= \left(\frac{\underline{S_{\mathrm{bc}}}(\underline{V_{\mathrm{b}}}-\underline + {V_{\mathrm{c}}})}{\underline{V_{\mathrm{b}}}-\underline{V_{\mathrm{c}}}}\right)^{\star} \\ + \underline{I_{\mathrm{ca}}} &= \left(\frac{\underline{S_{\mathrm{ca}}}(\underline{V_{\mathrm{c}}}-\underline + {V_{\mathrm{a}}})}{\underline{V_{\mathrm{c}}}-\underline{V_{\mathrm{a}}}}\right)^{\star} + \end{aligned} +\right. +``` + +The expression $\underline{S}(U)$ depends on four parameters: + +- The theoretical power $\underline{S^{\mathrm{th.}}}$ that the load would have if no control is applied. +- The maximal power $S^{\max}$ that can be injected/consumed by the load. For a PV installation, this is + usually the rated power of the inverter. +- The type of control (see [here](models-flexible_load-controls)). +- The type of projection (see [here](models-flexible_load-projections)). + +## Detailed pages + +All these elements are detailed in the following sections: + +```{toctree} +--- +maxdepth: 2 +caption: Flexible loads +--- +Control +Projection +FlexibleParameter +FeasibleDomain +``` + +## API Reference + +```{eval-rst} +.. autoclass:: roseau.load_flow.models.Control + :members: + :show-inheritance: + :no-index: +.. autoclass:: roseau.load_flow.models.Projection + :members: + :show-inheritance: + :no-index: +.. autoclass:: roseau.load_flow.models.FlexibleParameter + :members: + :show-inheritance: + :no-index: +``` diff --git a/doc/models/Load/index.md b/doc/models/Load/index.md index fe8ce74b..a150851e 100644 --- a/doc/models/Load/index.md +++ b/doc/models/Load/index.md @@ -68,11 +68,32 @@ The following load models are available in _Roseau Load Flow_: ```{toctree} --- -maxdepth: 2 +maxdepth: 3 caption: Loads --- ImpedanceLoad CurrentLoad PowerLoad -FlexibleLoad +FlexibleLoad/index +``` + +## API Reference + +```{eval-rst} +.. autoclass:: roseau.load_flow.models.AbstractLoad + :members: + :show-inheritance: + :no-index: +.. autoclass:: roseau.load_flow.models.ImpedanceLoad + :members: + :show-inheritance: + :no-index: +.. autoclass:: roseau.load_flow.models.CurrentLoad + :members: + :show-inheritance: + :no-index: +.. autoclass:: roseau.load_flow.models.PowerLoad + :members: + :show-inheritance: + :no-index: ``` diff --git a/doc/models/PotentialRef.md b/doc/models/PotentialRef.md index 44b9965e..fb2886f6 100644 --- a/doc/models/PotentialRef.md +++ b/doc/models/PotentialRef.md @@ -47,3 +47,12 @@ from roseau.load_flow.models import Bus, PotentialRef bus = Bus(id="bus", phases="abcn") p_ref = PotentialRef(id="pref", element=bus, phase="a") ``` + +## API Reference + +```{eval-rst} +.. autoclass:: roseau.load_flow.models.PotentialRef + :members: + :show-inheritance: + :no-index: +``` diff --git a/doc/models/Switch.md b/doc/models/Switch.md index 476474a1..efc0f6ba 100644 --- a/doc/models/Switch.md +++ b/doc/models/Switch.md @@ -84,7 +84,7 @@ en.res_branches[["current2"]].transform([np.abs, ft.partial(np.angle, deg=True)] # The two currents are equal in magnitude and opposite in phase, as expected # The two buses have the same voltages -en.res_buses_voltages.transform([np.abs, ft.partial(np.angle, deg=True)]) +en.res_buses_voltages[["voltage"]].transform([np.abs, ft.partial(np.angle, deg=True)]) # | | ('voltage', 'absolute') | ('voltage', 'angle') | # |:---------------|--------------------------:|-----------------------:| # | ('bus1', 'an') | 230.94 | 0 | @@ -94,3 +94,12 @@ en.res_buses_voltages.transform([np.abs, ft.partial(np.angle, deg=True)]) # | ('bus2', 'bn') | 230.94 | -120 | # | ('bus2', 'cn') | 230.94 | 120 | ``` + +## API Reference + +```{eval-rst} +.. autoclass:: roseau.load_flow.models.Switch + :members: + :show-inheritance: + :no-index: +``` diff --git a/doc/models/Transformer/index.md b/doc/models/Transformer/index.md index e8d8d955..16521015 100644 --- a/doc/models/Transformer/index.md +++ b/doc/models/Transformer/index.md @@ -154,3 +154,16 @@ Single_Phase_Transformer Three_Phase_Transformer Center_Tapped_Transformer ``` + +## API Reference + +```{eval-rst} +.. autoclass:: roseau.load_flow.models.TransformerParameters + :members: + :show-inheritance: + :no-index: +.. autoclass:: roseau.load_flow.models.Transformer + :members: + :show-inheritance: + :no-index: +``` diff --git a/doc/models/VoltageSource.md b/doc/models/VoltageSource.md index 7647d4d5..f37d47ea 100644 --- a/doc/models/VoltageSource.md +++ b/doc/models/VoltageSource.md @@ -111,3 +111,12 @@ un = 400 voltages = un * np.exp([0, -2j * np.pi / 3]) # Only two elements!! VoltageSource(id="vs", bus=bus, phases="abc", voltages=voltages) # Error ``` + +## API Reference + +```{eval-rst} +.. autoclass:: roseau.load_flow.models.VoltageSource + :members: + :show-inheritance: + :no-index: +``` diff --git a/doc/usage/Connecting_Elements.md b/doc/usage/Connecting_Elements.md index 36fb6430..4c27df69 100644 --- a/doc/usage/Connecting_Elements.md +++ b/doc/usage/Connecting_Elements.md @@ -118,7 +118,16 @@ Creating a line connecting the `load_bus` (belonging to the network `en`) and ou belong to a network) will propagate the network to the new elements. ```pycon ->>> lp_u_al_240 = LineParameters.from_name_lv("U_AL_240") +>>> lp_u_al_240 = LineParameters.from_geometry( +... "U_AL_240", +... line_type=LineType.UNDERGROUND, +... conductor_type=ConductorType.AL, +... insulator_type=InsulatorType.PVC, +... section=240, +... section_neutral=240, +... height=Q_(-1.5, "m"), +... external_diameter=Q_(40, "mm"), +... ) >>> new_line = Line( ... id="new_line", ... bus1=load_bus, diff --git a/doc/usage/Extras.md b/doc/usage/Extras.md new file mode 100644 index 00000000..434d3fa9 --- /dev/null +++ b/doc/usage/Extras.md @@ -0,0 +1,161 @@ +# Extras + +`roseau-load-flow` comes with some extra features that can be useful for some users. + +## Graph theory + +{meth}`ElectricalNetwork.to_graph() ` can be used to +get a {class}`networkx.Graph` object from the electrical network. + +The graph contains the geometries of the buses in the nodes data and the geometries and branch +types in the edges data. + +```{note} +This method requires *networkx* which is not installed by default in pip managed installs. You can +install it with the `"graph"` extra if you are using pip: `pip install "roseau-load-flow[graph]"`. +``` + +In addition, you can use the property +{meth}`ElectricalNetwork.buses_clusters ` to +get a list of sets of IDs of buses in galvanically isolated sections of the network. In other terms, +to get groups of buses connected by one or more lines or a switches, stopping at transformers. For +example, for a network with a MV feeder, this property returns a list containing a set of MV buses +IDs and all sets of LV subnetworks buses IDs. If you want to get the cluster of only one bus, you +can use {meth}`Bus.get_connected_buses ` + +If we take the example network from the [Getting Started page](gs-creating-network): + +```pycon +>>> set(source_bus.get_connected_buses()) +{'sb', 'lb'} +>>> set(load_bus.get_connected_buses()) +{'sb', 'lb'} +>>> en.buses_clusters +[{'sb', 'lb'}] +``` + +As there are no transformers between the two buses, they all belong to the same cluster. + +## Conversion to symmetrical components + +{mod}`roseau.load_flow.converters` contains helpers to convert between phasor and symmetrical +components. For example, to convert a phasor voltage to symmetrical components: + +```pycon +>>> import numpy as np +>>> from roseau.load_flow.converters import phasor_to_sym, sym_to_phasor +>>> v = 230 * np.exp([0, -2j * np.pi / 3, 2j * np.pi / 3]) +>>> v +array([ 230. +0.j , -115.-199.18584287j, -115.+199.18584287j]) +>>> v_sym = phasor_to_sym(v) +>>> v_sym +array([[ 8.52651283e-14-1.42108547e-14j], + [ 2.30000000e+02+4.19109192e-14j], + [-7.10542736e-14-2.84217094e-14j]]) +``` + +As you can see, for this positive-sequence balanced voltage, only the positive-sequence component +is non-zero. Converting back to phasor, you get the original voltage values back: + +```pycon +>>> sym_to_phasor(v_sym) +array([[ 230.-7.21644966e-16j], + [-115.-1.99185843e+02j], + [-115.+1.99185843e+02j]]) +``` + +You can also convert pandas Series to symmetrical components. If we take the example network of the +[Getting Started](Getting_Started.md) page: + +```pycon +>>> from roseau.load_flow.converters import series_phasor_to_sym +>>> series_phasor_to_sym(en.res_buses_voltages["voltage"]) +bus_id sequence +lb zero 8.526513e-14-1.421085e-14j + pos 2.219282e+02+4.167975e-14j + neg -5.684342e-14-2.842171e-14j +sb zero 9.947598e-14-1.421085e-14j + pos 2.309401e+02+3.483159e-14j + neg -4.263256e-14-2.842171e-14j +Name: voltage, dtype: complex128 +``` + +## Potentials to voltages conversion + +{mod}`roseau.load_flow.converters` also contains helpers to convert a vector of potentials to a +vector of voltages. Example: + +```pycon +>>> import numpy as np +>>> from roseau.load_flow.converters import calculate_voltages, calculate_voltage_phases +>>> potentials = 230 * np.array([1, np.exp(-2j * np.pi / 3), np.exp(2j * np.pi / 3), 0]) +>>> potentials +array([ 230. +0.j , -115.-199.18584287j, -115.+199.18584287j, + 0. +0.j ]) +>>> phases = "abcn" +>>> calculate_voltages(potentials, phases) +array([ 230. +0.j , -115.-199.18584287j, -115.+199.18584287j]) +``` + +Because the phases include the neutral, the voltages calculated are phase-to-neutral voltages. +You can also calculate phase-to-phase voltages by omitting the neutral: + +```pycon +>>> calculate_voltages(potentials[:-1], phases[:-1]) +array([ 345.+199.18584287j, 0.-398.37168574j, -345.+199.18584287j]) +``` + +To get the phases of the voltage, you can use `calculate_voltage_phases`: + +```pycon +>>> calculate_voltage_phases(phases) +['an', 'bn', 'cn'] +``` + +Of course these functions work with arbitrary phases: + +```pycon +>>> calculate_voltages(potentials[:2], phases[:2]) +array([345.+199.18584287j]) +>>> calculate_voltage_phases(phases[:2]) +['ab'] +>>> calculate_voltage_phases("abc") +['ab', 'bc', 'ca'] +>>> calculate_voltage_phases("bc") +['bc'] +>>> calculate_voltage_phases("bcn") +['bn', 'cn'] +``` + +## Constants + +{mod}`roseau.load_flow.utils.constants` contains some common constants like the resistivity +and permeability of common conductor types in addition to other useful constants. Please refer to +the module documentation for more details. + +An enumeration of available conductor types can be found in the {mod}`roseau.load_flow.utils.types` +module. + +## Voltage unbalance + +It is possible to calculate the voltage unbalance due to asymmetric operation. There are many +definitions of voltage unbalance (see {cite:p}`Girigoudar_2019`). In `roseau-load-flow`, you can +use the {meth}`~roseau.load_flow.models.Bus.res_voltage_unbalance` method on a 3-phase bus to get +the Voltage Unbalance Factor (VUF) as per the IEC definition: + +```{math} +VUF = \frac{|V_n|}{|V_p|} * 100 (\%) +``` + +Where $V_n$ is the negative-sequence voltage and $V_p$ is the positive-sequence voltage. + +```{note} +Other definitions of voltage unbalance could be added in the future. If you need a specific +definition, please open an issue on the GitHub repository. +``` + +## Bibliography + +```{bibliography} +:filter: docname in docnames +``` diff --git a/doc/usage/Flexible_Loads.md b/doc/usage/Flexible_Loads.md index 5891c0f7..1b4a11ef 100644 --- a/doc/usage/Flexible_Loads.md +++ b/doc/usage/Flexible_Loads.md @@ -56,7 +56,16 @@ a Delta-Wye transformer and a small LV network. ... ) >>> # Add the LV network elements -... lp = LineParameters.from_name_lv("U_AL_150") +... lp = LineParameters.from_geometry( +... "U_AL_150", +... line_type=LineType.UNDERGROUND, +... conductor_type=ConductorType.AL, +... insulator_type=InsulatorType.PVC, +... section=150, +... section_neutral=150, +... height=Q_(-1.5, "m"), +... external_diameter=Q_(40, "mm"), +... ) ... bus1 = Bus(id="bus1", phases="abcn") ... bus2 = Bus(id="bus2", phases="abcn") ... load_bus1 = Bus(id="load_bus1", phases="abcn") @@ -202,12 +211,12 @@ flexible load implementing a $PQ(U)$ control instead. As before, we first create a `FlexibleParameter` but this time, we will use the `pq_u_production` class method. It requires several arguments: -- `up_up` and `up_max`: the voltages defining the interval of the `P(U)` control activation. +- `up_up` and `up_max`: the voltages defining the interval of the $P(U)$ control activation. Below `up_up`, no control is applied and above `u_max`, the production is totally shut down. -- `uq_min`, `uq_down`, `uq_up` and `uq_max` which are the voltages defining the `Q(U)` control +- `uq_min`, `uq_down`, `uq_up` and `uq_max` which are the voltages defining the $Q(U)$ control activation. - Below `uq_min`, the power plant produces the maximum possible reactive power. - - Between `uq_down` and `uq_up`, there is no `Q(U)` control. + - Between `uq_down` and `uq_up`, there is no $Q(U)$ control. - Above `uq_max`, the power plant consumes the maximum possible reactive power. In the example below, as the new load is a production load, only the `up_up`, `up_max`, `uq_up` @@ -216,7 +225,7 @@ exhausted at 240 V. After that, the $P(U)$ is activated and is exhausted at 250 production is totally shut down. ```pycon ->>> # Let's try with pq(u) control, by injecting reactive power before reducing active power +>>> # Let's try with PQ(u) control, by injecting reactive power before reducing active power ... en.loads["load3"].disconnect() ... fp = FlexibleParameter.pq_u_production( ... up_up=240, up_max=250, uq_min=200, uq_down=210, uq_up=235, uq_max=240, s_max=4000 # V and VA @@ -244,4 +253,4 @@ array([-2566.23768012+3068.29336425j, 0.+0.j, 0.+0.j]) One can note that this time, the phase `'a'` consumes reactive power to limit the voltage rise in the network. Moreover, the magnitude of the power on phase `'a'` is approximately $4 kVA$ which is the maximum allowed apparent power for `load3`. In order to maintain this maximum, a -[Euclidean projection](models-flexible_load-projection) has been used. +[Euclidean projection](models-flexible_load-projections) has been used. diff --git a/doc/usage/Getting_Started.md b/doc/usage/Getting_Started.md index be252290..6d8de653 100644 --- a/doc/usage/Getting_Started.md +++ b/doc/usage/Getting_Started.md @@ -9,9 +9,10 @@ In this tutorial you will learn how to: 1. [Create a simple electrical network with one source and one load](gs-creating-network); 2. [Solve a load flow](gs-solving-load-flow); 3. [Get the results of the load flow](gs-getting-results); -4. [Update the elements of the network](gs-updating-elements); -5. [Save the network and the results to the disk for later analysis](gs-saving-network); -6. [Load the saved network and the results from the disk](gs-loading-network). +4. [Analyze the results](gs-analysis-and-violations); +5. [Update the elements of the network](gs-updating-elements); +6. [Save the network and the results to the disk for later analysis](gs-saving-network); +7. [Load the saved network and the results from the disk](gs-loading-network). (gs-creating-network)= @@ -57,8 +58,6 @@ The following is a summary of the available elements: - `PotentialRef`: A potential reference sets the reference of potentials in the network. It can be connected to buses or grounds. -For a more detailed description of the elements, please refer to the [API reference](../autoapi/roseau/load_flow/models/index). - Let's use some of these elements to build the following network with a voltage source, a simple line and a constant power load. This network is a low voltage network (three-phase + neutral wire). @@ -70,9 +69,17 @@ It leads to the following code >>> import numpy as np ... from roseau.load_flow import * +>>> # Nominal phase-to-neutral voltage +... un = 400 / np.sqrt(3) # In Volts + +>>> # Optional network limits (for results analysis only) +... u_min = 0.9 * un # V +... u_max = 1.1 * un # V +... i_max = 500.0 # A + >>> # Create two buses -... source_bus = Bus(id="sb", phases="abcn") -... load_bus = Bus(id="lb", phases="abcn") +... source_bus = Bus(id="sb", phases="abcn", min_voltage=u_min, max_voltage=u_max) +... load_bus = Bus(id="lb", phases="abcn", min_voltage=u_min, max_voltage=u_max) >>> # Define the reference of potentials to be the neutral of the source bus ... ground = Ground(id="gnd") @@ -81,17 +88,20 @@ It leads to the following code ... ground.connect(source_bus, phase="n") >>> # Create a LV source at the first bus -... # Volts (phase-to-neutral because the source is connected to the neutral) -... un = 400 / np.sqrt(3) -... source_voltages = [un, un * np.exp(-2j * np.pi / 3), un * np.exp(2j * np.pi / 3)] -... vs = VoltageSource(id="vs", bus=source_bus, voltages=source_voltages) +... # (phase-to-neutral voltage because the source is connected to the neutral) +... source_voltages = un * np.exp([0, -2j * np.pi / 3, 2j * np.pi / 3]) +... vs = VoltageSource( +... id="vs", bus=source_bus, voltages=source_voltages +... ) # phases="abcn" inferred from the bus >>> # Add a load at the second bus ... load = PowerLoad(id="load", bus=load_bus, powers=[10e3 + 0j, 10e3, 10e3]) # VA >>> # Add a LV line between the source bus and the load bus ... # R = 0.1 Ohm/km, X = 0 -... lp = LineParameters("lp", z_line=(0.1 + 0.0j) * np.eye(4, dtype=complex)) +... lp = LineParameters( +... "lp", z_line=(0.1 + 0.0j) * np.eye(4, dtype=complex), max_current=i_max +... ) ... line = Line(id="line", bus1=source_bus, bus2=load_bus, parameters=lp, length=2.0) ``` @@ -208,15 +218,16 @@ The results returned by the `res_` properties are also `Quantity` objects. The available results depend on the type of element. The following table summarizes the available results for each element type: -| Element type | Available results | -| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| `Bus` | `res_potentials`, `res_voltages` | -| `Line` | `res_currents`, `res_powers`, `res_potentials`, `res_voltages`, `res_series_power_losses`, `res_shunt_power_losses`, `res_power_losses` | -| `Transformer`, `Switch` | `res_currents`, `res_powers`, `res_potentials`, `res_voltages` | -| `ImpedanceLoad`, `CurrentLoad`, `PowerLoad` | `res_currents`, `res_powers`, `res_potentials`, `res_voltages`, `res_flexible_powers`⁎ | -| `VoltageSource` | `res_currents`, `res_powers`, `res_potentials`, `res_voltages` | -| `Ground` | `res_potential` | -| `PotentialRef` | `res_current` _(Always zero for a successful load flow)_ | +| Element type | Available results | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Bus` | `res_potentials`, `res_voltages`, `res_violated` | +| `Line` | `res_currents`, `res_powers`, `res_potentials`, `res_voltages`, `res_series_power_losses`, `res_shunt_power_losses`, `res_power_losses`, `res_violated` | +| `Transformer` | `res_currents`, `res_powers`, `res_potentials`, `res_voltages`, `res_violated` | +| `Switch` | `res_currents`, `res_powers`, `res_potentials`, `res_voltages` | +| `ImpedanceLoad`, `CurrentLoad`, `PowerLoad` | `res_currents`, `res_powers`, `res_potentials`, `res_voltages`, `res_flexible_powers`⁎ | +| `VoltageSource` | `res_currents`, `res_powers`, `res_potentials`, `res_voltages` | +| `Ground` | `res_potential` | +| `PotentialRef` | `res_current` _(Always zero for a successful load flow)_ | ⁎: `res_flexible_powers` is only available for flexible loads (`PowerLoad`s with `flexible_params`). You'll see an example on the usage of flexible loads in the _Flexible Loads_ section. @@ -242,7 +253,8 @@ array([0.22192818, 0.22192818, 0.22192818]) ```{important} Everywhere in `roseau-load-flow`, the `voltages` of an element depend on the element's `phases`. -Voltages of elements connected in a *Star (wye)* configuration (elements that have a neutral connection indicated by the presence of the `'n'` char in their `phases` attribute) are the +Voltages of elements connected in a *Star (wye)* configuration (elements that have a neutral +connection indicated by the presence of the `'n'` char in their `phases` attribute) are the **phase-to-neutral** voltages. Voltages of elements connected in a *Delta* configuration (elements that do not have a neutral connection indicated by the absence of the `'n'` char from their `phases` attribute) are the **phase-to-phase** voltages. This is true for *input* voltages, such @@ -276,9 +288,13 @@ The results can also be retrieved for the entire network using `res_` properties Available results for the network are: - `res_buses`: Buses potentials indexed by _(bus id, phase)_ -- `res_buses_voltages`: Buses voltages indexed by _(bus id, voltage phase)_ +- `res_buses_voltages`: Buses voltages and voltage limits indexed by _(bus id, voltage phase)_ - `res_branches`: Branches currents, powers, and potentials indexed by _(branch id, phase)_ -- `res_lines`: Lines currents, powers, potentials, series losses, series currents indexed by _(line id, phase)_ +- `res_transformers`: Transformers currents, powers, potentials, and power limits indexed by + _(transformer id, phase)_ +- `res_lines`: Lines currents, powers, potentials, series losses, series currents, and current + limits indexed by _(line id, phase)_ +- `res_switches`: Switches currents, powers, and potentials indexed by _(switch id, phase)_ - `res_loads`: Loads currents, powers, and potentials indexed by _(load id, phase)_ - `res_loads_voltages`: Loads voltages indexed by _(load id, voltage phase)_ - `res_loads_flexible_powers`: Loads flexible powers (only for flexible loads) indexed by @@ -317,14 +333,14 @@ All the following tables are rounded to 2 decimals to be properly displayed. >>> en.res_buses_voltages ``` -| bus_id | phase | voltage | -| :----- | :---- | -------------: | -| sb | an | 230.94+0j | -| sb | bn | -115.47-200j | -| sb | cn | -115.47+200j | -| lb | an | 221.93+0j | -| lb | bn | -110.96-192.2j | -| lb | cn | -110.96+192.2j | +| bus_id | phase | voltage | min_voltage | max_voltage | violated | +| :----- | :---- | -------------: | ----------: | ----------: | :------- | +| sb | an | 230.94+0j | 207.846 | 254.034 | False | +| sb | bn | -115.47-200j | 207.846 | 254.034 | False | +| sb | cn | -115.47+200j | 207.846 | 254.034 | False | +| lb | an | 221.93-0j | 207.846 | 254.034 | False | +| lb | bn | -110.96-192.2j | 207.846 | 254.034 | False | +| lb | cn | -110.96+192.2j | 207.846 | 254.034 | False | ```pycon >>> en.res_branches @@ -341,12 +357,26 @@ All the following tables are rounded to 2 decimals to be properly displayed. >>> en.res_lines ``` -| branch_id | phase | current1 | current2 | power1 | power2 | potential1 | potential2 | series_losses | series_current | -| :-------- | :---- | ------------: | -----------: | ----------: | --------: | -----------: | -------------: | ------------: | -------------: | -| line | a | 45.06+0j | -45.06-0j | 10406.07-0j | -10000+0j | 230.94+0j | 221.93-0j | 406.07-0j | 45.06+0j | -| line | b | -22.53-39.02j | 22.53+39.02j | 10406.07+0j | -10000-0j | -115.47-200j | -110.96-192.2j | 406.07-0j | -22.53-39.02j | -| line | c | -22.53+39.02j | 22.53-39.02j | 10406.07-0j | -10000+0j | -115.47+200j | -110.96+192.2j | 406.07+0j | -22.53+39.02j | -| line | n | 0j | -0j | -0j | -0j | 0j | -0j | -0j | -0+0j | +| line_id | phase | current1 | current2 | power1 | power2 | potential1 | potential2 | series_losses | series_current | max_current | violated | +| :------ | :---- | ------------: | -----------: | ----------: | --------: | -----------: | -------------: | ------------: | -------------: | ----------: | :------- | +| line | a | 45.06-0j | -45.06+0j | 10406.07+0j | -10000-0j | 230.94+0j | 221.93+0j | 406.07-0j | 45.06-0j | 500 | False | +| line | b | -22.53-39.02j | 22.53+39.02j | 10406.07+0j | -10000-0j | -115.47-200j | -110.96-192.2j | 406.07-0j | -22.53-39.02j | 500 | False | +| line | c | -22.53+39.02j | 22.53-39.02j | 10406.07-0j | -10000+0j | -115.47+200j | -110.96+192.2j | 406.07+0j | -22.53+39.02j | 500 | False | +| line | n | -0-0j | 0j | -0+0j | -0j | 0j | 0j | -0j | -0-0j | 500 | False | + +```pycon +>>> en.res_transformers +``` + +| transformer_id | phase | current1 | current2 | power1 | power2 | potential1 | potential2 | max_power | violated | +| -------------- | ----- | -------- | -------- | ------ | ------ | ---------- | ---------- | --------- | -------- | + +```pycon +>>> en.res_switches +``` + +| switch_id | phase | current1 | current2 | power1 | power2 | potential1 | potential2 | +| --------- | ----- | -------- | -------- | ------ | ------ | ---------- | ---------- | ```pycon >>> en.res_loads @@ -373,12 +403,12 @@ All the following tables are rounded to 2 decimals to be properly displayed. >>> en.res_sources ``` -| source_id | phase | current | power | potential | -| :-------- | :---- | -----------: | ------------: | -----------: | -| vs | a | -45.06-0j | -10406.07+0j) | 230.94+0j | -| vs | b | 22.53+39.02j | -10406.07-0j) | -115.47-200j | -| vs | c | 22.53-39.02j | -10406.07+0j) | -115.47+200j | -| vs | n | 0j | 0j | 0j | +| source_id | phase | current | power | potential | +| :-------- | :---- | -----------: | -----------: | -----------: | +| vs | a | -45.06-0j | -10406.07+0j | 230.94+0j | +| vs | b | 22.53+39.02j | -10406.07-0j | -115.47-200j | +| vs | c | 22.53-39.02j | -10406.07+0j | -115.47+200j | +| vs | n | 0j | 0j | 0j | ```pycon >>> en.res_grounds @@ -400,33 +430,70 @@ Using the `transform` method of data frames, the results can easily be converted to magnitude and angle values. ```pycon ->>> en.res_buses_voltages.transform([np.abs, np.angle]) +>>> en.res_buses_voltages["voltage"].transform([np.abs, np.angle]) ``` -| bus_id | phase | ('voltage', 'absolute') | ('voltage', 'angle') | -| :----- | :---- | ----------------------: | -------------------: | -| sb | an | 230.94 | 0 | -| sb | bn | 230.94 | -2.0944 | -| sb | cn | 230.94 | 2.0944 | -| lb | an | 221.928 | 2.89102e-19 | -| lb | bn | 221.928 | -2.0944 | -| lb | cn | 221.928 | 2.0944 | +| bus_id | phase | absolute | angle | +| :----- | :---- | -------: | ----------: | +| sb | an | 230.94 | 0 | +| sb | bn | 230.94 | -2.0944 | +| sb | cn | 230.94 | 2.0944 | +| lb | an | 221.928 | 2.89102e-19 | +| lb | bn | 221.928 | -2.0944 | +| lb | cn | 221.928 | 2.0944 | Or, if you prefer degrees: ```pycon >>> import functools as ft -... en.res_buses_voltages.transform([np.abs, ft.partial(np.angle, deg=True)]) +... en.res_buses_voltages["voltage"].transform([np.abs, ft.partial(np.angle, deg=True)]) +``` + +| bus_id | phase | absolute | angle | +| :----- | :---- | -------: | ----------: | +| sb | an | 230.94 | 0 | +| sb | bn | 230.94 | -120 | +| sb | cn | 230.94 | 120 | +| lb | an | 221.928 | 1.65643e-17 | +| lb | bn | 221.928 | -120 | +| lb | cn | 221.928 | 120 | + +(gs-analysis-and-violations)= + +## Analyzing the results and detecting violations + +In the example network above, `min_voltage` and `max_voltage` arguments were passed to the `Bus` +constructor and `max_current` was passed to the `LineParameters` constructor. These arguments +define the limits of the network that can be used to check if the network is in a valid state +or not. Note that these limits have no effect on the load flow calculation. + +If you set `min_voltage` or `max_voltage` on a bus, the `res_violated` property will tell you if +the voltage limits are violated or not at this bus. Here, the voltage limits are not violated. + +```pycon +>>> load_bus.res_violated +False +``` + +Similarly, if you set `max_current` on a line, the `res_violated` property will tell you if the +current loading of the line in any phase exceeds the limit. Here, the current limit is not violated. + +```pycon +>>> line.res_violated +False ``` -| bus_id | phase | ('voltage', 'absolute') | ('voltage', 'angle') | -| :----- | :---- | ----------------------: | -------------------: | -| sb | an | 230.94 | 0 | -| sb | bn | 230.94 | -120 | -| sb | cn | 230.94 | 120 | -| lb | an | 221.928 | 1.65643e-17 | -| lb | bn | 221.928 | -120 | -| lb | cn | 221.928 | 120 | +The power limit of the transformer can be defined using the `max_power` argument of the +`TransformerParameters`. Transformers also have a `res_violated` property that indicates if the +power loading of the transformer exceeds the limit. + +The data frame results on the electrical network also include a `violated` column that indicates if +the limits are violated or not for the corresponding element. + +```{tip} +You can use the {meth}`Bus.propagate_limits() ` method to +propagate the limits from a bus to buses connected to it galvanically (i.e. via lines or switches). +``` (gs-updating-elements)= diff --git a/doc/usage/Short_Circuit.md b/doc/usage/Short_Circuit.md index 196794ec..8c8a8e5b 100644 --- a/doc/usage/Short_Circuit.md +++ b/doc/usage/Short_Circuit.md @@ -31,11 +31,29 @@ is impossible. ... vs = VoltageSource(id="vs", bus=source_bus, phases="abcn", voltages=source_voltages) >>> # Add LV lines -... lp1 = LineParameters.from_name_lv("U_AL_240") +... lp1 = LineParameters.from_geometry( +... "U_AL_240", +... line_type=LineType.UNDERGROUND, +... conductor_type=ConductorType.AL, +... insulator_type=InsulatorType.PVC, +... section=240, +... section_neutral=240, +... height=Q_(-1.5, "m"), +... external_diameter=Q_(40, "mm"), +... ) ... line1 = Line( ... id="line1", bus1=source_bus, bus2=bus1, parameters=lp1, length=1.0, ground=ground ... ) -... lp2 = LineParameters.from_name_lv("U_AL_150") +... lp2 = LineParameters.from_geometry( +... "U_AL_150", +... line_type=LineType.UNDERGROUND, +... conductor_type=ConductorType.AL, +... insulator_type=InsulatorType.PVC, +... section=150, +... section_neutral=150, +... height=Q_(-1.5, "m"), +... external_diameter=Q_(40, "mm"), +... ) ... line2 = Line(id="line2", bus1=bus1, bus2=bus2, parameters=lp2, length=2.0, ground=ground) >>> # Create network diff --git a/doc/usage/index.md b/doc/usage/index.md index 05695cd9..664f16cc 100644 --- a/doc/usage/index.md +++ b/doc/usage/index.md @@ -13,4 +13,5 @@ Flexible_Loads Short_Circuit Catalogues Plotting +Extras ``` diff --git a/poetry.lock b/poetry.lock index e6021b73..2d058cdc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. [[package]] name = "alabaster" @@ -22,52 +22,19 @@ files = [ {file = "anyascii-0.3.2.tar.gz", hash = "sha256:9d5d32ef844fe225b8bc7cba7f950534fae4da27a9bf3a6bea2cb0ea46ce4730"}, ] -[[package]] -name = "appnope" -version = "0.1.3" -description = "Disable App Nap on macOS >= 10.9" -optional = false -python-versions = "*" -files = [ - {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, - {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, -] - [[package]] name = "astroid" -version = "2.15.6" +version = "3.0.1" description = "An abstract syntax tree for Python with inference support." optional = false -python-versions = ">=3.7.2" +python-versions = ">=3.8.0" files = [ - {file = "astroid-2.15.6-py3-none-any.whl", hash = "sha256:389656ca57b6108f939cf5d2f9a2a825a3be50ba9d589670f393236e0a03b91c"}, - {file = "astroid-2.15.6.tar.gz", hash = "sha256:903f024859b7c7687d7a7f3a3f73b17301f8e42dfd9cc9df9d4418172d3e2dbd"}, + {file = "astroid-3.0.1-py3-none-any.whl", hash = "sha256:7d5895c9825e18079c5aeac0572bc2e4c83205c95d416e0b4fee8bc361d2d9ca"}, + {file = "astroid-3.0.1.tar.gz", hash = "sha256:86b0bb7d7da0be1a7c4aedb7974e391b32d4ed89e33de6ed6902b4b15c97577e"}, ] [package.dependencies] -lazy-object-proxy = ">=1.4.0" typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} -wrapt = [ - {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, - {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, -] - -[[package]] -name = "asttokens" -version = "2.2.1" -description = "Annotate AST trees with source code positions" -optional = false -python-versions = "*" -files = [ - {file = "asttokens-2.2.1-py2.py3-none-any.whl", hash = "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"}, - {file = "asttokens-2.2.1.tar.gz", hash = "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3"}, -] - -[package.dependencies] -six = "*" - -[package.extras] -test = ["astroid", "pytest"] [[package]] name = "attrs" @@ -89,25 +56,20 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte [[package]] name = "babel" -version = "2.12.1" +version = "2.13.1" description = "Internationalization utilities" optional = false python-versions = ">=3.7" files = [ - {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, - {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, + {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, + {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, ] -[[package]] -name = "backcall" -version = "0.2.0" -description = "Specifications for callback functions passed in to an API" -optional = false -python-versions = "*" -files = [ - {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, - {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, -] +[package.dependencies] +setuptools = {version = "*", markers = "python_version >= \"3.12\""} + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "beautifulsoup4" @@ -127,63 +89,15 @@ soupsieve = ">1.2" html5lib = ["html5lib"] lxml = ["lxml"] -[[package]] -name = "black" -version = "23.7.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, - {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, - {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, - {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, - {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, - {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, - {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, - {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, - {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, - {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, - {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, -] - -[package.dependencies] -click = ">=8.0.0" -ipython = {version = ">=7.8.0", optional = true, markers = "extra == \"jupyter\""} -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tokenize-rt = {version = ">=3.2.0", optional = true, markers = "extra == \"jupyter\""} -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "certifi" -version = "2023.7.22" +version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] [[package]] @@ -199,86 +113,101 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.2.0" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, - {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] @@ -340,65 +269,128 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "contourpy" +version = "1.2.0" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.9" +files = [ + {file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"}, + {file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa"}, + {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9"}, + {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab"}, + {file = "contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488"}, + {file = "contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41"}, + {file = "contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727"}, + {file = "contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686"}, + {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286"}, + {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95"}, + {file = "contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6"}, + {file = "contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de"}, + {file = "contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0"}, + {file = "contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0"}, + {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0"}, + {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431"}, + {file = "contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f"}, + {file = "contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9"}, + {file = "contourpy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc"}, + {file = "contourpy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5"}, + {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e"}, + {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808"}, + {file = "contourpy-1.2.0-cp39-cp39-win32.whl", hash = "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4"}, + {file = "contourpy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956"}, + {file = "contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a"}, +] + +[package.dependencies] +numpy = ">=1.20,<2.0" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.6.1)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] + [[package]] name = "coverage" -version = "7.3.0" +version = "7.3.2" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db76a1bcb51f02b2007adacbed4c88b6dee75342c37b05d1822815eed19edee5"}, - {file = "coverage-7.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c02cfa6c36144ab334d556989406837336c1d05215a9bdf44c0bc1d1ac1cb637"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:477c9430ad5d1b80b07f3c12f7120eef40bfbf849e9e7859e53b9c93b922d2af"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2ee86ca75f9f96072295c5ebb4ef2a43cecf2870b0ca5e7a1cbdd929cf67e1"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68d8a0426b49c053013e631c0cdc09b952d857efa8f68121746b339912d27a12"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3eb0c93e2ea6445b2173da48cb548364f8f65bf68f3d090404080d338e3a689"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:90b6e2f0f66750c5a1178ffa9370dec6c508a8ca5265c42fbad3ccac210a7977"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96d7d761aea65b291a98c84e1250cd57b5b51726821a6f2f8df65db89363be51"}, - {file = "coverage-7.3.0-cp310-cp310-win32.whl", hash = "sha256:63c5b8ecbc3b3d5eb3a9d873dec60afc0cd5ff9d9f1c75981d8c31cfe4df8527"}, - {file = "coverage-7.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:97c44f4ee13bce914272589b6b41165bbb650e48fdb7bd5493a38bde8de730a1"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74c160285f2dfe0acf0f72d425f3e970b21b6de04157fc65adc9fd07ee44177f"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b543302a3707245d454fc49b8ecd2c2d5982b50eb63f3535244fd79a4be0c99d"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad0f87826c4ebd3ef484502e79b39614e9c03a5d1510cfb623f4a4a051edc6fd"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13c6cbbd5f31211d8fdb477f0f7b03438591bdd077054076eec362cf2207b4a7"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac440c43e9b479d1241fe9d768645e7ccec3fb65dc3a5f6e90675e75c3f3e3a"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c9834d5e3df9d2aba0275c9f67989c590e05732439b3318fa37a725dff51e74"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4c8e31cf29b60859876474034a83f59a14381af50cbe8a9dbaadbf70adc4b214"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7a9baf8e230f9621f8e1d00c580394a0aa328fdac0df2b3f8384387c44083c0f"}, - {file = "coverage-7.3.0-cp311-cp311-win32.whl", hash = "sha256:ccc51713b5581e12f93ccb9c5e39e8b5d4b16776d584c0f5e9e4e63381356482"}, - {file = "coverage-7.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:887665f00ea4e488501ba755a0e3c2cfd6278e846ada3185f42d391ef95e7e70"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d000a739f9feed900381605a12a61f7aaced6beae832719ae0d15058a1e81c1b"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59777652e245bb1e300e620ce2bef0d341945842e4eb888c23a7f1d9e143c446"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9737bc49a9255d78da085fa04f628a310c2332b187cd49b958b0e494c125071"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5247bab12f84a1d608213b96b8af0cbb30d090d705b6663ad794c2f2a5e5b9fe"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ac9a1de294773b9fa77447ab7e529cf4fe3910f6a0832816e5f3d538cfea9a"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:85b7335c22455ec12444cec0d600533a238d6439d8d709d545158c1208483873"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:36ce5d43a072a036f287029a55b5c6a0e9bd73db58961a273b6dc11a2c6eb9c2"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:211a4576e984f96d9fce61766ffaed0115d5dab1419e4f63d6992b480c2bd60b"}, - {file = "coverage-7.3.0-cp312-cp312-win32.whl", hash = "sha256:56afbf41fa4a7b27f6635bc4289050ac3ab7951b8a821bca46f5b024500e6321"}, - {file = "coverage-7.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f297e0c1ae55300ff688568b04ff26b01c13dfbf4c9d2b7d0cb688ac60df479"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac0dec90e7de0087d3d95fa0533e1d2d722dcc008bc7b60e1143402a04c117c1"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:438856d3f8f1e27f8e79b5410ae56650732a0dcfa94e756df88c7e2d24851fcd"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1084393c6bda8875c05e04fce5cfe1301a425f758eb012f010eab586f1f3905e"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49ab200acf891e3dde19e5aa4b0f35d12d8b4bd805dc0be8792270c71bd56c54"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67e6bbe756ed458646e1ef2b0778591ed4d1fcd4b146fc3ba2feb1a7afd4254"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f39c49faf5344af36042b293ce05c0d9004270d811c7080610b3e713251c9b0"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7df91fb24c2edaabec4e0eee512ff3bc6ec20eb8dccac2e77001c1fe516c0c84"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:34f9f0763d5fa3035a315b69b428fe9c34d4fc2f615262d6be3d3bf3882fb985"}, - {file = "coverage-7.3.0-cp38-cp38-win32.whl", hash = "sha256:bac329371d4c0d456e8d5f38a9b0816b446581b5f278474e416ea0c68c47dcd9"}, - {file = "coverage-7.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b859128a093f135b556b4765658d5d2e758e1fae3e7cc2f8c10f26fe7005e543"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61260ec93f99f2c2d93d264b564ba912bec502f679793c56f678ba5251f0393"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97af9554a799bd7c58c0179cc8dbf14aa7ab50e1fd5fa73f90b9b7215874ba28"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3558e5b574d62f9c46b76120a5c7c16c4612dc2644c3d48a9f4064a705eaee95"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37d5576d35fcb765fca05654f66aa71e2808d4237d026e64ac8b397ffa66a56a"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07ea61bcb179f8f05ffd804d2732b09d23a1238642bf7e51dad62082b5019b34"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80501d1b2270d7e8daf1b64b895745c3e234289e00d5f0e30923e706f110334e"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4eddd3153d02204f22aef0825409091a91bf2a20bce06fe0f638f5c19a85de54"}, - {file = "coverage-7.3.0-cp39-cp39-win32.whl", hash = "sha256:2d22172f938455c156e9af2612650f26cceea47dc86ca048fa4e0b2d21646ad3"}, - {file = "coverage-7.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:60f64e2007c9144375dd0f480a54d6070f00bb1a28f65c408370544091c9bc9e"}, - {file = "coverage-7.3.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:5492a6ce3bdb15c6ad66cb68a0244854d9917478877a25671d70378bdc8562d0"}, - {file = "coverage-7.3.0.tar.gz", hash = "sha256:49dbb19cdcafc130f597d9e04a29d0a032ceedf729e41b181f51cd170e6ee865"}, + {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, + {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, + {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, + {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, + {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, + {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, + {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, + {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, + {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, + {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, + {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, + {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, + {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, + {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, ] [package.dependencies] @@ -408,16 +400,20 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1 toml = ["tomli"] [[package]] -name = "decorator" -version = "5.1.1" -description = "Decorators for Humans" +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" files = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, ] +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + [[package]] name = "distlib" version = "0.3.7" @@ -442,13 +438,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.1.3" +version = "1.2.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] [package.extras] @@ -468,65 +464,53 @@ files = [ [package.extras] testing = ["hatch", "pre-commit", "pytest", "tox"] -[[package]] -name = "executing" -version = "1.2.0" -description = "Get the currently executing AST node of a frame, and other information" -optional = false -python-versions = "*" -files = [ - {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"}, - {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"}, -] - -[package.extras] -tests = ["asttokens", "littleutils", "pytest", "rich"] - [[package]] name = "filelock" -version = "3.12.3" +version = "3.13.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.12.3-py3-none-any.whl", hash = "sha256:f067e40ccc40f2b48395a80fcbd4728262fab54e232e090a4063ab804179efeb"}, - {file = "filelock-3.12.3.tar.gz", hash = "sha256:0ecc1dd2ec4672a10c8550a8182f1bd0c0a5088470ecd5a125e45f49472fac3d"}, + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.7.1", markers = "python_version < \"3.11\""} - [package.extras] -docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] [[package]] name = "fiona" -version = "1.9.4.post1" +version = "1.9.5" description = "Fiona reads and writes spatial data files" optional = false python-versions = ">=3.7" files = [ - {file = "Fiona-1.9.4.post1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:d6483a20037db2209c8e9a0c6f1e552f807d03c8f42ed0c865ab500945a37c4d"}, - {file = "Fiona-1.9.4.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dbe158947099a83ad16f9acd3a21f50ff01114c64e2de67805e382e6b6e0083a"}, - {file = "Fiona-1.9.4.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2c7b09eecee3bb074ef8aa518cd6ab30eb663c6fdd0eff3c88d454a9746eaa"}, - {file = "Fiona-1.9.4.post1-cp310-cp310-win_amd64.whl", hash = "sha256:1da8b954f6f222c3c782bc285586ea8dd9d7e55e1bc7861da9cd772bca671660"}, - {file = "Fiona-1.9.4.post1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c671d8832287cda397621d79c5a635d52e4631f33a8f0e6fdc732a79a93cb96c"}, - {file = "Fiona-1.9.4.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b633a2e550e083805c638d2ab8059c283ca112aaea8241e170c012d2ee0aa905"}, - {file = "Fiona-1.9.4.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1faa625d5202b8403471bbc9f9c96b1bf9099cfcb0ee02a80a3641d3d02383e"}, - {file = "Fiona-1.9.4.post1-cp311-cp311-win_amd64.whl", hash = "sha256:39baf11ff0e4318397e2b2197de427b4eebdc49d4a9a7c1366f8a7ed682978a4"}, - {file = "Fiona-1.9.4.post1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d93c993265f6378b23f47708c83bddb3377ca6814a1f0b5a0ae0bee9c8d72cf8"}, - {file = "Fiona-1.9.4.post1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:b0387cae39e27f338fd948b3b50b6e6ce198cc4cec257fc91660849697c69dc3"}, - {file = "Fiona-1.9.4.post1-cp37-cp37m-win_amd64.whl", hash = "sha256:450561d308d3ce7c7e30294822b1de3f4f942033b703ddd4a91a7f7f5f506ca0"}, - {file = "Fiona-1.9.4.post1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:71b023ef5248ebfa5524e7a875033f7db3bbfaf634b1b5c1ae36958d1eb82083"}, - {file = "Fiona-1.9.4.post1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74511d3755695d75cea0f4ff6f5e0c6c5d5be8e0d46dafff124c6a219e99b1eb"}, - {file = "Fiona-1.9.4.post1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:285f3dd4f96aa0a3955ed469f0543375b20989731b2dddc85124453f11ac62bc"}, - {file = "Fiona-1.9.4.post1-cp38-cp38-win_amd64.whl", hash = "sha256:a670ea4262cb9140445bcfc97cbfd2f508a058be342f4a97e966b8ce7696601f"}, - {file = "Fiona-1.9.4.post1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:ea7c44c15b3a653452b9b3173181490b7afc5f153b0473c145c43c0fbf90448b"}, - {file = "Fiona-1.9.4.post1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7bfb1f49e0e53f6cd7ad64ae809d72646266b37a7b9881205977408b443a8d79"}, - {file = "Fiona-1.9.4.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a585002a6385cc8ab0f66ddf3caf18711f531901906abd011a67a0cc89ab7b0"}, - {file = "Fiona-1.9.4.post1-cp39-cp39-win_amd64.whl", hash = "sha256:f5da66b723a876142937e683431bbaa5c3d81bb2ed3ec98941271bc99b7f8cd0"}, - {file = "Fiona-1.9.4.post1.tar.gz", hash = "sha256:5679d3f7e0d513035eb72e59527bb90486859af4405755dfc739138633106120"}, + {file = "fiona-1.9.5-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:5f40a40529ecfca5294260316cf987a0420c77a2f0cf0849f529d1afbccd093e"}, + {file = "fiona-1.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:374efe749143ecb5cfdd79b585d83917d2bf8ecfbfc6953c819586b336ce9c63"}, + {file = "fiona-1.9.5-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:35dae4b0308eb44617cdc4461ceb91f891d944fdebbcba5479efe524ec5db8de"}, + {file = "fiona-1.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:5b4c6a3df53bee8f85bb46685562b21b43346be1fe96419f18f70fa1ab8c561c"}, + {file = "fiona-1.9.5-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:6ad04c1877b9fd742871b11965606c6a52f40706f56a48d66a87cc3073943828"}, + {file = "fiona-1.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fb9a24a8046c724787719e20557141b33049466145fc3e665764ac7caf5748c"}, + {file = "fiona-1.9.5-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:d722d7f01a66f4ab6cd08d156df3fdb92f0669cf5f8708ddcb209352f416f241"}, + {file = "fiona-1.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:7ede8ddc798f3d447536080c6db9a5fb73733ad8bdb190cb65eed4e289dd4c50"}, + {file = "fiona-1.9.5-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:8b098054a27c12afac4f819f98cb4d4bf2db9853f70b0c588d7d97d26e128c39"}, + {file = "fiona-1.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d9f29e9bcbb33232ff7fa98b4a3c2234db910c1dc6c4147fc36c0b8b930f2e0"}, + {file = "fiona-1.9.5-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:f1af08da4ecea5036cb81c9131946be4404245d1b434b5b24fd3871a1d4030d9"}, + {file = "fiona-1.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:c521e1135c78dec0d7774303e5a1b4c62e0efb0e602bb8f167550ef95e0a2691"}, + {file = "fiona-1.9.5-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:fce4b1dd98810cabccdaa1828430c7402d283295c2ae31bea4f34188ea9e88d7"}, + {file = "fiona-1.9.5-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:3ea04ec2d8c57b5f81a31200fb352cb3242aa106fc3e328963f30ffbdf0ff7c8"}, + {file = "fiona-1.9.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4877cc745d9e82b12b3eafce3719db75759c27bd8a695521202135b36b58c2e7"}, + {file = "fiona-1.9.5-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:ac2c250f509ec19fad7959d75b531984776517ef3c1222d1cc5b4f962825880b"}, + {file = "fiona-1.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4df21906235928faad856c288cfea0298e9647f09c9a69a230535cbc8eadfa21"}, + {file = "fiona-1.9.5-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:81d502369493687746cb8d3cd77e5ada4447fb71d513721c9a1826e4fb32b23a"}, + {file = "fiona-1.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:ce3b29230ef70947ead4e701f3f82be81082b7f37fd4899009b1445cc8fc276a"}, + {file = "fiona-1.9.5-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:8b53ce8de773fcd5e2e102e833c8c58479edd8796a522f3d83ef9e08b62bfeea"}, + {file = "fiona-1.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bd2355e859a1cd24a3e485c6dc5003129f27a2051629def70036535ffa7e16a4"}, + {file = "fiona-1.9.5-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:9a2da52f865db1aff0eaf41cdd4c87a7c079b3996514e8e7a1ca38457309e825"}, + {file = "fiona-1.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:cfef6db5b779d463298b1113b50daa6c5b55f26f834dc9e37752116fa17277c1"}, + {file = "fiona-1.9.5.tar.gz", hash = "sha256:99e2604332caa7692855c2ae6ed91e1fffdf9b59449aa8032dd18e070e59a2f7"}, ] [package.dependencies] @@ -536,6 +520,7 @@ click = ">=8.0,<9.0" click-plugins = ">=1.0" cligj = ">=0.5" importlib-metadata = {version = "*", markers = "python_version < \"3.10\""} +setuptools = "*" six = "*" [package.extras] @@ -544,15 +529,80 @@ calc = ["shapely"] s3 = ["boto3 (>=1.3.1)"] test = ["Fiona[s3]", "pytest (>=7)", "pytest-cov", "pytz"] +[[package]] +name = "fonttools" +version = "4.45.1" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.45.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:45fa321c458ea29224067700954ec44493ae869b47e7c5485a350a149a19fb53"}, + {file = "fonttools-4.45.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0dc7617d96b1e668eea9250e1c1fe62d0c78c3f69573ce7e3332cc40e6d84356"}, + {file = "fonttools-4.45.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ed3bda541e86725f6b4e1b94213f13ed1ae51a5a1f167028534cedea38c010"}, + {file = "fonttools-4.45.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f4a5870e3b56788fb196da8cf30d0dfd51a76dc3b907861d018165f76ae4c2"}, + {file = "fonttools-4.45.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a3c11d9687479f01eddef729aa737abcdea0a44fdaffb62a930a18892f186c9b"}, + {file = "fonttools-4.45.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:316cec50581e844c3ab69d7c82455b54c7cf18236b2f09e722faf665fbfcac58"}, + {file = "fonttools-4.45.1-cp310-cp310-win32.whl", hash = "sha256:e2277cba9f0b525e30de2a9ad3cb4219aa4bc697230c1645666b0deee9f914f0"}, + {file = "fonttools-4.45.1-cp310-cp310-win_amd64.whl", hash = "sha256:1b9e9ad2bcded9a1431afaa57c8d3c39143ac1f050862d66bddd863c515464a2"}, + {file = "fonttools-4.45.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff6a698bdd435d24c379f6e8a54908cd9bb7dda23719084d56bf8c87709bf3bd"}, + {file = "fonttools-4.45.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c980d60cd6ec1376206fe55013d166e5627ad0b149b5c81e74eaa913ab6134f"}, + {file = "fonttools-4.45.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a12dee6523c02ca78aeedd0a5e12bfa9b7b29896350edd5241542897b072ae23"}, + {file = "fonttools-4.45.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37cd1ced6efb3dd6fe82e9f9bf92fd74ac58a5aefc284045f59ecd517a5fb9ab"}, + {file = "fonttools-4.45.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3d24248221bd7151dfff0d88b1b5da02dccd7134bd576ce8888199827bbaa19"}, + {file = "fonttools-4.45.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba6c23591427844dfb0a13658f1718489de75de6a46b64234584c0d17573162d"}, + {file = "fonttools-4.45.1-cp311-cp311-win32.whl", hash = "sha256:cebcddbe9351b67166292b4f71ffdbfcce01ba4b07d4267824eb46b277aeb19a"}, + {file = "fonttools-4.45.1-cp311-cp311-win_amd64.whl", hash = "sha256:f22eb69996a0bd49f76bdefb30be54ce8dbb89a0d1246874d610f05c2aa2e69e"}, + {file = "fonttools-4.45.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:794de93e83297db7b4943f2431e206d8b1ea69cb3ae14638a49cc50332bf0db8"}, + {file = "fonttools-4.45.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4ba17822a6681d06849078daaf6e03eccc9f467efe7c4c60280e28a78e8e5df9"}, + {file = "fonttools-4.45.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e50f794d09df0675da8d9dbd7c66bfcab2f74a708343aabcad41936d26556891"}, + {file = "fonttools-4.45.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b07b857d4f9de3199a8c3d1b1bf2078c0f37447891ca1a8d9234106b9a27aff"}, + {file = "fonttools-4.45.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:777ba42b94a27bb7fb2b4082522fccfd345667c32a56011e1c3e105979af5b79"}, + {file = "fonttools-4.45.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:21e96b99878348c74aa58059b8578d7586f9519cbcdadacf56486737038aa043"}, + {file = "fonttools-4.45.1-cp312-cp312-win32.whl", hash = "sha256:5cbf02cda8465b69769d07385f5d11e7bba19954e7787792f46fe679ec755ebb"}, + {file = "fonttools-4.45.1-cp312-cp312-win_amd64.whl", hash = "sha256:800e354e0c3afaeb8d9552769773d02f228e98c37b8cb03041157c3d0687cffc"}, + {file = "fonttools-4.45.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6eb2c54f7a07c92108daabcf02caf31df97825738db02a28270633946bcda4d0"}, + {file = "fonttools-4.45.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:43a3d267334109ff849c37cf3629476b5feb392ef1d2e464a167b83de8cd599c"}, + {file = "fonttools-4.45.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e1aefc2bf3c43e0f33f995f828a7bbeff4adc9393a7760b11456dbcf14388f6"}, + {file = "fonttools-4.45.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f53a19dcdd5737440839b8394eeebb35da9ec8109f7926cb6456639b5b58e47"}, + {file = "fonttools-4.45.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a17706b9cc24b27721613fe5773d93331ab7f0ecaca9955aead89c6b843d3a7"}, + {file = "fonttools-4.45.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fb36e5f40191274a95938b40c0a1fa7f895e36935aea8709e1d6deff0b2d0d4f"}, + {file = "fonttools-4.45.1-cp38-cp38-win32.whl", hash = "sha256:46eabddec12066829b8a1efe45ae552ba2f1796981ecf538d5f68284c354c589"}, + {file = "fonttools-4.45.1-cp38-cp38-win_amd64.whl", hash = "sha256:b6de2f0fcd3302fb82f94801002cb473959e998c14c24ec28234adb674aed345"}, + {file = "fonttools-4.45.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:392d0e3cc23daee910193625f7cf1b387aff9dd5b6f1a5f4a925680acb6dcbc2"}, + {file = "fonttools-4.45.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b9544b1346d99848ac0e9b05b5d45ee703d7562fc4c9c48cf4b781de9632e57"}, + {file = "fonttools-4.45.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8717db3e4895e4820ade64ea379187738827ee60748223cb0438ef044ee208c6"}, + {file = "fonttools-4.45.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e29d5f298d616a93a4c5963682dc6cc8cc09f6d89cad2c29019fc5fb3b4d9472"}, + {file = "fonttools-4.45.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cb472905da3049960e80fc1cf808231880d79727a8410e156bf3e5063a1c574f"}, + {file = "fonttools-4.45.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ba299f1fbaa2a1e33210aaaf6fa816d4059e4d3cfe2ae9871368d4ab548c1c6a"}, + {file = "fonttools-4.45.1-cp39-cp39-win32.whl", hash = "sha256:105099968b58a5b4cef6f3eb409db8ea8578b302a9d05e23fecba1b8b0177b5f"}, + {file = "fonttools-4.45.1-cp39-cp39-win_amd64.whl", hash = "sha256:847f3f49dd3423e5a678c098e2ba92c7f4955d4aab3044f6a507b0bb0ecb07e0"}, + {file = "fonttools-4.45.1-py3-none-any.whl", hash = "sha256:3bdd7dfca8f6c9f4779384064027e8477ad6a037d6a327b09381f43e0247c6f3"}, + {file = "fonttools-4.45.1.tar.gz", hash = "sha256:6e441286d55fe7ec7c4fb36812bf914924813776ff514b744b510680fc2733f2"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "scipy"] +lxml = ["lxml (>=4.0,<5)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.1.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + [[package]] name = "furo" -version = "2023.8.19" +version = "2023.9.10" description = "A clean customisable Sphinx documentation theme." optional = false python-versions = ">=3.8" files = [ - {file = "furo-2023.8.19-py3-none-any.whl", hash = "sha256:12f99f87a1873b6746228cfde18f77244e6c1ffb85d7fed95e638aae70d80590"}, - {file = "furo-2023.8.19.tar.gz", hash = "sha256:e671ee638ab3f1b472f4033b0167f502ab407830e0db0f843b1c1028119c9cd1"}, + {file = "furo-2023.9.10-py3-none-any.whl", hash = "sha256:513092538537dc5c596691da06e3c370714ec99bc438680edc1debffb73e5bfc"}, + {file = "furo-2023.9.10.tar.gz", hash = "sha256:5707530a476d2a63b8cad83b4f961f3739a69f4b058bcf38a03a39fa537195b2"}, ] [package.dependencies] @@ -563,31 +613,31 @@ sphinx-basic-ng = "*" [[package]] name = "geopandas" -version = "0.13.2" +version = "0.14.1" description = "Geographic pandas extensions" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "geopandas-0.13.2-py3-none-any.whl", hash = "sha256:101cfd0de54bcf9e287a55b5ea17ebe0db53a5e25a28bacf100143d0507cabd9"}, - {file = "geopandas-0.13.2.tar.gz", hash = "sha256:e5b56d9c20800c77bcc0c914db3f27447a37b23b2cd892be543f5001a694a968"}, + {file = "geopandas-0.14.1-py3-none-any.whl", hash = "sha256:ed5a7cae7874bfc3238fb05e0501cc1760e1b7b11e5b76ecad29da644ca305da"}, + {file = "geopandas-0.14.1.tar.gz", hash = "sha256:4853ff89ecb6d1cfc43e7b3671092c8160e8a46a3dd7368f25906283314e42bb"}, ] [package.dependencies] -fiona = ">=1.8.19" +fiona = ">=1.8.21" packaging = "*" -pandas = ">=1.1.0" -pyproj = ">=3.0.1" -shapely = ">=1.7.1" +pandas = ">=1.4.0" +pyproj = ">=3.3.0" +shapely = ">=1.8.0" [[package]] name = "identify" -version = "2.5.27" +version = "2.5.32" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.27-py2.py3-none-any.whl", hash = "sha256:fdb527b2dfe24602809b2201e033c2a113d7bdf716db3ca8e3243f735dcecaba"}, - {file = "identify-2.5.27.tar.gz", hash = "sha256:287b75b04a0e22d727bc9a41f0d4f3c1bcada97490fa6eabb5b28f0e9097e733"}, + {file = "identify-2.5.32-py2.py3-none-any.whl", hash = "sha256:0b7656ef6cba81664b783352c73f8c24b39cf82f926f78f4550eda928e5e0545"}, + {file = "identify-2.5.32.tar.gz", hash = "sha256:5d9979348ec1a21c768ae07e0a652924538e8bce67313a73cb0f681cf08ba407"}, ] [package.extras] @@ -595,13 +645,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] [[package]] @@ -635,74 +685,34 @@ perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "ipython" -version = "8.14.0" -description = "IPython: Productive Interactive Computing" +name = "importlib-resources" +version = "6.1.1" +description = "Read resources from Python packages" optional = false -python-versions = ">=3.9" +python-versions = ">=3.8" files = [ - {file = "ipython-8.14.0-py3-none-any.whl", hash = "sha256:248aca623f5c99a6635bc3857677b7320b9b8039f99f070ee0d20a5ca5a8e6bf"}, - {file = "ipython-8.14.0.tar.gz", hash = "sha256:1d197b907b6ba441b692c48cf2a3a2de280dc0ac91a3405b39349a50272ca0a1"}, + {file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"}, + {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"}, ] [package.dependencies] -appnope = {version = "*", markers = "sys_platform == \"darwin\""} -backcall = "*" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -decorator = "*" -jedi = ">=0.16" -matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} -pickleshare = "*" -prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" -pygments = ">=2.4.0" -stack-data = "*" -traitlets = ">=5" -typing-extensions = {version = "*", markers = "python_version < \"3.10\""} +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] -black = ["black"] -doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] -kernel = ["ipykernel"] -nbconvert = ["nbconvert"] -nbformat = ["nbformat"] -notebook = ["ipywidgets", "notebook"] -parallel = ["ipyparallel"] -qtconsole = ["qtconsole"] -test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] -test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] - -[[package]] -name = "jedi" -version = "0.19.0" -description = "An autocompletion tool for Python that can be used for text editors." +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "jedi-0.19.0-py2.py3-none-any.whl", hash = "sha256:cb8ce23fbccff0025e9386b5cf85e892f94c9b822378f8da49970471335ac64e"}, - {file = "jedi-0.19.0.tar.gz", hash = "sha256:bcf9894f1753969cbac8022a8c2eaee06bfa3724e4192470aaffe7eb6272b0c4"}, + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[package.dependencies] -parso = ">=0.8.3,<0.9.0" - -[package.extras] -docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] -qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] - [[package]] name = "jinja2" version = "3.1.2" @@ -720,6 +730,119 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "kiwisolver" +version = "1.4.5" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.7" +files = [ + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, + {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, +] + [[package]] name = "latexcodec" version = "2.0.1" @@ -734,51 +857,6 @@ files = [ [package.dependencies] six = ">=1.4.1" -[[package]] -name = "lazy-object-proxy" -version = "1.9.0" -description = "A fast and thorough lazy object proxy." -optional = false -python-versions = ">=3.7" -files = [ - {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, -] - [[package]] name = "markdown-it-py" version = "3.0.0" @@ -830,6 +908,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -863,18 +951,53 @@ files = [ ] [[package]] -name = "matplotlib-inline" -version = "0.1.6" -description = "Inline Matplotlib backend for Jupyter" +name = "matplotlib" +version = "3.8.2" +description = "Python plotting package" optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" files = [ - {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, - {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, + {file = "matplotlib-3.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:09796f89fb71a0c0e1e2f4bdaf63fb2cefc84446bb963ecdeb40dfee7dfa98c7"}, + {file = "matplotlib-3.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9c6976748a25e8b9be51ea028df49b8e561eed7809146da7a47dbecebab367"}, + {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78e4f2cedf303869b782071b55fdde5987fda3038e9d09e58c91cc261b5ad18"}, + {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e208f46cf6576a7624195aa047cb344a7f802e113bb1a06cfd4bee431de5e31"}, + {file = "matplotlib-3.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a569130ff53798ea5f50afce7406e91fdc471ca1e0e26ba976a8c734c9427a"}, + {file = "matplotlib-3.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:830f00640c965c5b7f6bc32f0d4ce0c36dfe0379f7dd65b07a00c801713ec40a"}, + {file = "matplotlib-3.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d86593ccf546223eb75a39b44c32788e6f6440d13cfc4750c1c15d0fcb850b63"}, + {file = "matplotlib-3.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a5430836811b7652991939012f43d2808a2db9b64ee240387e8c43e2e5578c8"}, + {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9576723858a78751d5aacd2497b8aef29ffea6d1c95981505877f7ac28215c6"}, + {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ba9cbd8ac6cf422f3102622b20f8552d601bf8837e49a3afed188d560152788"}, + {file = "matplotlib-3.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:03f9d160a29e0b65c0790bb07f4f45d6a181b1ac33eb1bb0dd225986450148f0"}, + {file = "matplotlib-3.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:3773002da767f0a9323ba1a9b9b5d00d6257dbd2a93107233167cfb581f64717"}, + {file = "matplotlib-3.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c318c1e95e2f5926fba326f68177dee364aa791d6df022ceb91b8221bd0a627"}, + {file = "matplotlib-3.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:091275d18d942cf1ee9609c830a1bc36610607d8223b1b981c37d5c9fc3e46a4"}, + {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b0f3b8ea0e99e233a4bcc44590f01604840d833c280ebb8fe5554fd3e6cfe8d"}, + {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b1704a530395aaf73912be741c04d181f82ca78084fbd80bc737be04848331"}, + {file = "matplotlib-3.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533b0e3b0c6768eef8cbe4b583731ce25a91ab54a22f830db2b031e83cca9213"}, + {file = "matplotlib-3.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:0f4fc5d72b75e2c18e55eb32292659cf731d9d5b312a6eb036506304f4675630"}, + {file = "matplotlib-3.8.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:deaed9ad4da0b1aea77fe0aa0cebb9ef611c70b3177be936a95e5d01fa05094f"}, + {file = "matplotlib-3.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:172f4d0fbac3383d39164c6caafd3255ce6fa58f08fc392513a0b1d3b89c4f89"}, + {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7d36c2209d9136cd8e02fab1c0ddc185ce79bc914c45054a9f514e44c787917"}, + {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5864bdd7da445e4e5e011b199bb67168cdad10b501750367c496420f2ad00843"}, + {file = "matplotlib-3.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ef8345b48e95cee45ff25192ed1f4857273117917a4dcd48e3905619bcd9c9b8"}, + {file = "matplotlib-3.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:7c48d9e221b637c017232e3760ed30b4e8d5dfd081daf327e829bf2a72c731b4"}, + {file = "matplotlib-3.8.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa11b3c6928a1e496c1a79917d51d4cd5d04f8a2e75f21df4949eeefdf697f4b"}, + {file = "matplotlib-3.8.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1095fecf99eeb7384dabad4bf44b965f929a5f6079654b681193edf7169ec20"}, + {file = "matplotlib-3.8.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:bddfb1db89bfaa855912261c805bd0e10218923cc262b9159a49c29a7a1c1afa"}, + {file = "matplotlib-3.8.2.tar.gz", hash = "sha256:01a978b871b881ee76017152f1f1a0cbf6bd5f7b8ff8c96df0df1bd57d8755a1"}, ] [package.dependencies] -traitlets = "*" +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} +kiwisolver = ">=1.3.1" +numpy = ">=1.21,<2" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" [[package]] name = "mdit-py-plugins" @@ -906,17 +1029,6 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - [[package]] name = "myst-parser" version = "2.0.0" @@ -943,6 +1055,24 @@ rtd = ["ipython", "pydata-sphinx-theme (==v0.13.0rc4)", "sphinx-autodoc2 (>=0.4. testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=7,<8)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx-pytest"] testing-docutils = ["pygments", "pytest (>=7,<8)", "pytest-param-files (>=0.3.4,<0.4.0)"] +[[package]] +name = "networkx" +version = "3.2.1" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.9" +files = [ + {file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"}, + {file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"}, +] + +[package.extras] +default = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.4)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] + [[package]] name = "nodeenv" version = "1.8.0" @@ -959,166 +1089,194 @@ setuptools = "*" [[package]] name = "numpy" -version = "1.25.2" +version = "1.26.2" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"}, - {file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"}, - {file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"}, - {file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"}, - {file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"}, - {file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"}, - {file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"}, - {file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"}, - {file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"}, - {file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"}, - {file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"}, - {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, + {file = "numpy-1.26.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3703fc9258a4a122d17043e57b35e5ef1c5a5837c3db8be396c82e04c1cf9b0f"}, + {file = "numpy-1.26.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cc392fdcbd21d4be6ae1bb4475a03ce3b025cd49a9be5345d76d7585aea69440"}, + {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36340109af8da8805d8851ef1d74761b3b88e81a9bd80b290bbfed61bd2b4f75"}, + {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc008217145b3d77abd3e4d5ef586e3bdfba8fe17940769f8aa09b99e856c00"}, + {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ced40d4e9e18242f70dd02d739e44698df3dcb010d31f495ff00a31ef6014fe"}, + {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b272d4cecc32c9e19911891446b72e986157e6a1809b7b56518b4f3755267523"}, + {file = "numpy-1.26.2-cp310-cp310-win32.whl", hash = "sha256:22f8fc02fdbc829e7a8c578dd8d2e15a9074b630d4da29cda483337e300e3ee9"}, + {file = "numpy-1.26.2-cp310-cp310-win_amd64.whl", hash = "sha256:26c9d33f8e8b846d5a65dd068c14e04018d05533b348d9eaeef6c1bd787f9919"}, + {file = "numpy-1.26.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b96e7b9c624ef3ae2ae0e04fa9b460f6b9f17ad8b4bec6d7756510f1f6c0c841"}, + {file = "numpy-1.26.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa18428111fb9a591d7a9cc1b48150097ba6a7e8299fb56bdf574df650e7d1f1"}, + {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06fa1ed84aa60ea6ef9f91ba57b5ed963c3729534e6e54055fc151fad0423f0a"}, + {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca5482c3dbdd051bcd1fce8034603d6ebfc125a7bd59f55b40d8f5d246832b"}, + {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:854ab91a2906ef29dc3925a064fcd365c7b4da743f84b123002f6139bcb3f8a7"}, + {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f43740ab089277d403aa07567be138fc2a89d4d9892d113b76153e0e412409f8"}, + {file = "numpy-1.26.2-cp311-cp311-win32.whl", hash = "sha256:a2bbc29fcb1771cd7b7425f98b05307776a6baf43035d3b80c4b0f29e9545186"}, + {file = "numpy-1.26.2-cp311-cp311-win_amd64.whl", hash = "sha256:2b3fca8a5b00184828d12b073af4d0fc5fdd94b1632c2477526f6bd7842d700d"}, + {file = "numpy-1.26.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a4cd6ed4a339c21f1d1b0fdf13426cb3b284555c27ac2f156dfdaaa7e16bfab0"}, + {file = "numpy-1.26.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d5244aabd6ed7f312268b9247be47343a654ebea52a60f002dc70c769048e75"}, + {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a3cdb4d9c70e6b8c0814239ead47da00934666f668426fc6e94cce869e13fd7"}, + {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa317b2325f7aa0a9471663e6093c210cb2ae9c0ad824732b307d2c51983d5b6"}, + {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:174a8880739c16c925799c018f3f55b8130c1f7c8e75ab0a6fa9d41cab092fd6"}, + {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f79b231bf5c16b1f39c7f4875e1ded36abee1591e98742b05d8a0fb55d8a3eec"}, + {file = "numpy-1.26.2-cp312-cp312-win32.whl", hash = "sha256:4a06263321dfd3598cacb252f51e521a8cb4b6df471bb12a7ee5cbab20ea9167"}, + {file = "numpy-1.26.2-cp312-cp312-win_amd64.whl", hash = "sha256:b04f5dc6b3efdaab541f7857351aac359e6ae3c126e2edb376929bd3b7f92d7e"}, + {file = "numpy-1.26.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4eb8df4bf8d3d90d091e0146f6c28492b0be84da3e409ebef54349f71ed271ef"}, + {file = "numpy-1.26.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a13860fdcd95de7cf58bd6f8bc5a5ef81c0b0625eb2c9a783948847abbef2c2"}, + {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64308ebc366a8ed63fd0bf426b6a9468060962f1a4339ab1074c228fa6ade8e3"}, + {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf8aab04a2c0e859da118f0b38617e5ee65d75b83795055fb66c0d5e9e9b818"}, + {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d73a3abcac238250091b11caef9ad12413dab01669511779bc9b29261dd50210"}, + {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b361d369fc7e5e1714cf827b731ca32bff8d411212fccd29ad98ad622449cc36"}, + {file = "numpy-1.26.2-cp39-cp39-win32.whl", hash = "sha256:bd3f0091e845164a20bd5a326860c840fe2af79fa12e0469a12768a3ec578d80"}, + {file = "numpy-1.26.2-cp39-cp39-win_amd64.whl", hash = "sha256:2beef57fb031dcc0dc8fa4fe297a742027b954949cabb52a2a376c144e5e6060"}, + {file = "numpy-1.26.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1cc3d5029a30fb5f06704ad6b23b35e11309491c999838c31f124fee32107c79"}, + {file = "numpy-1.26.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94cc3c222bb9fb5a12e334d0479b97bb2df446fbe622b470928f5284ffca3f8d"}, + {file = "numpy-1.26.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe6b44fb8fcdf7eda4ef4461b97b3f63c466b27ab151bec2366db8b197387841"}, + {file = "numpy-1.26.2.tar.gz", hash = "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea"}, ] [[package]] name = "packaging" -version = "23.1" +version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] name = "pandas" -version = "2.0.3" +version = "2.1.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"}, - {file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"}, - {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"}, - {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"}, - {file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"}, - {file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"}, - {file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"}, - {file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"}, - {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"}, - {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"}, - {file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"}, - {file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"}, - {file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"}, - {file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"}, - {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"}, - {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"}, - {file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"}, - {file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"}, - {file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"}, - {file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"}, - {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"}, - {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"}, - {file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"}, - {file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"}, - {file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"}, + {file = "pandas-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acf08a73b5022b479c1be155d4988b72f3020f308f7a87c527702c5f8966d34f"}, + {file = "pandas-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3cc4469ff0cf9aa3a005870cb49ab8969942b7156e0a46cc3f5abd6b11051dfb"}, + {file = "pandas-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35172bff95f598cc5866c047f43c7f4df2c893acd8e10e6653a4b792ed7f19bb"}, + {file = "pandas-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59dfe0e65a2f3988e940224e2a70932edc964df79f3356e5f2997c7d63e758b4"}, + {file = "pandas-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0296a66200dee556850d99b24c54c7dfa53a3264b1ca6f440e42bad424caea03"}, + {file = "pandas-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:465571472267a2d6e00657900afadbe6097c8e1dc43746917db4dfc862e8863e"}, + {file = "pandas-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04d4c58e1f112a74689da707be31cf689db086949c71828ef5da86727cfe3f82"}, + {file = "pandas-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fa2ad4ff196768ae63a33f8062e6838efed3a319cf938fdf8b95e956c813042"}, + {file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4441ac94a2a2613e3982e502ccec3bdedefe871e8cea54b8775992485c5660ef"}, + {file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ded6ff28abbf0ea7689f251754d3789e1edb0c4d0d91028f0b980598418a58"}, + {file = "pandas-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca5680368a5139d4920ae3dc993eb5106d49f814ff24018b64d8850a52c6ed2"}, + {file = "pandas-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:de21e12bf1511190fc1e9ebc067f14ca09fccfb189a813b38d63211d54832f5f"}, + {file = "pandas-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a5d53c725832e5f1645e7674989f4c106e4b7249c1d57549023ed5462d73b140"}, + {file = "pandas-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7cf4cf26042476e39394f1f86868d25b265ff787c9b2f0d367280f11afbdee6d"}, + {file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72c84ec1b1d8e5efcbff5312abe92bfb9d5b558f11e0cf077f5496c4f4a3c99e"}, + {file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f539e113739a3e0cc15176bf1231a553db0239bfa47a2c870283fd93ba4f683"}, + {file = "pandas-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc77309da3b55732059e484a1efc0897f6149183c522390772d3561f9bf96c00"}, + {file = "pandas-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:08637041279b8981a062899da0ef47828df52a1838204d2b3761fbd3e9fcb549"}, + {file = "pandas-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b99c4e51ef2ed98f69099c72c75ec904dd610eb41a32847c4fcbc1a975f2d2b8"}, + {file = "pandas-2.1.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7ea8ae8004de0381a2376662c0505bb0a4f679f4c61fbfd122aa3d1b0e5f09d"}, + {file = "pandas-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd76d67ca2d48f56e2db45833cf9d58f548f97f61eecd3fdc74268417632b8a"}, + {file = "pandas-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1329dbe93a880a3d7893149979caa82d6ba64a25e471682637f846d9dbc10dd2"}, + {file = "pandas-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:321ecdb117bf0f16c339cc6d5c9a06063854f12d4d9bc422a84bb2ed3207380a"}, + {file = "pandas-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:11a771450f36cebf2a4c9dbd3a19dfa8c46c4b905a3ea09dc8e556626060fe71"}, + {file = "pandas-2.1.3.tar.gz", hash = "sha256:22929f84bca106921917eb73c1521317ddd0a4c71b395bcf767a106e3494209f"}, ] [package.dependencies] numpy = [ - {version = ">=1.20.3", markers = "python_version < \"3.10\""}, - {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, + {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" tzdata = ">=2022.1" [package.extras] -all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"] -aws = ["s3fs (>=2021.08.0)"] -clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"] -compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"] -computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"] +all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"] +aws = ["s3fs (>=2022.05.0)"] +clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"] +compression = ["zstandard (>=0.17.0)"] +computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"] feather = ["pyarrow (>=7.0.0)"] -fss = ["fsspec (>=2021.07.0)"] -gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"] -hdf5 = ["tables (>=3.6.1)"] -html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"] -mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"] +fss = ["fsspec (>=2022.05.0)"] +gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"] +hdf5 = ["tables (>=3.7.0)"] +html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"] +mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"] parquet = ["pyarrow (>=7.0.0)"] -performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"] +performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"] plot = ["matplotlib (>=3.6.1)"] -postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"] -spss = ["pyreadstat (>=1.1.2)"] -sql-other = ["SQLAlchemy (>=1.4.16)"] -test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.6.3)"] +postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"] +spss = ["pyreadstat (>=1.1.5)"] +sql-other = ["SQLAlchemy (>=1.4.36)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.8.0)"] [[package]] -name = "parso" -version = "0.8.3" -description = "A Python Parser" +name = "pillow" +version = "10.1.0" +description = "Python Imaging Library (Fork)" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, - {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, + {file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"}, + {file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"}, + {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"}, + {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"}, + {file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"}, + {file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"}, + {file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"}, + {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"}, + {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"}, + {file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"}, + {file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"}, + {file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"}, + {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"}, + {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"}, + {file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"}, + {file = "Pillow-10.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793"}, + {file = "Pillow-10.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d"}, + {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80"}, + {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212"}, + {file = "Pillow-10.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14"}, + {file = "Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099"}, + {file = "Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd"}, + {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28"}, + {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2"}, + {file = "Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"}, + {file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"}, ] [package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["docopt", "pytest (<6.0.0)"] - -[[package]] -name = "pathspec" -version = "0.11.2" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, -] - -[[package]] -name = "pexpect" -version = "4.8.0" -description = "Pexpect allows easy control of interactive console applications." -optional = false -python-versions = "*" -files = [ - {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, - {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, -] - -[package.dependencies] -ptyprocess = ">=0.5" - -[[package]] -name = "pickleshare" -version = "0.7.5" -description = "Tiny 'shelve'-like database with concurrency support" -optional = false -python-versions = "*" -files = [ - {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, - {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, -] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] [[package]] name = "pint" @@ -1146,13 +1304,13 @@ xarray = ["xarray"] [[package]] name = "platformdirs" -version = "3.10.0" +version = "4.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, - {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, + {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, + {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, ] [package.extras] @@ -1176,13 +1334,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.3.3" +version = "3.5.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.8" files = [ - {file = "pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"}, - {file = "pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"}, + {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, + {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, ] [package.dependencies] @@ -1192,45 +1350,6 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" -[[package]] -name = "prompt-toolkit" -version = "3.0.39" -description = "Library for building powerful interactive command lines in Python" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, - {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, -] - -[package.dependencies] -wcwidth = "*" - -[[package]] -name = "ptyprocess" -version = "0.7.0" -description = "Run a subprocess in a pseudo terminal" -optional = false -python-versions = "*" -files = [ - {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, - {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, -] - -[[package]] -name = "pure-eval" -version = "0.2.2" -description = "Safely evaluate AST nodes without side effects" -optional = false -python-versions = "*" -files = [ - {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, - {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, -] - -[package.extras] -tests = ["pytest"] - [[package]] name = "pybtex" version = "0.24.0" @@ -1267,50 +1386,67 @@ pybtex = ">=0.16" [[package]] name = "pygments" -version = "2.16.1" +version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, - {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, ] [package.extras] plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyparsing" +version = "3.1.1" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, + {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pyproj" -version = "3.6.0" +version = "3.6.1" description = "Python interface to PROJ (cartographic projections and coordinate transformations library)" optional = false python-versions = ">=3.9" files = [ - {file = "pyproj-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e600f6a2771d3b41aeb2cc1efd96771ae9a01451013da1dd48ff272e7c6e34ef"}, - {file = "pyproj-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d7f6cd045df29aae960391dfe06a575c110af598f1dea5add8be6ca42332b0f5"}, - {file = "pyproj-3.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:557e6592855111c84eda176ddf6b130f55d5e2b9cb1c017b8c91b69f37f474f5"}, - {file = "pyproj-3.6.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de6288b6ceabdeeac01abf627c74414822d322d8f55dc8efe4d29dedd27c5719"}, - {file = "pyproj-3.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e427ccdbb1763872416549bdfa9fa1f5f169054653c4daf674e71480cc39cf11"}, - {file = "pyproj-3.6.0-cp310-cp310-win32.whl", hash = "sha256:1283d3c1960edbb74828f5f3405b27578a9a27f7766ab6a3956f4bd851f08239"}, - {file = "pyproj-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:9de1aab71234bfd3fd648a1152519b5ee152c43113d7d8ea52590a0140129501"}, - {file = "pyproj-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:00fab048596c17572fa8980014ef117dbb2a445e6f7ba3b9ddfcc683efc598e7"}, - {file = "pyproj-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba5e7c8ddd6ed5a3f9fcf95ea80ba44c931913723de2ece841c94bb38b200c4a"}, - {file = "pyproj-3.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08dfc5c9533c78a97afae9d53b99b810a4a8f97c3be9eb2b8f323b726c736403"}, - {file = "pyproj-3.6.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18a8bdb87aeb41b60a2e91d32f623227de3569fb83b4c64b174c3a7c5b0ed3ae"}, - {file = "pyproj-3.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfe392dfc0eba2248dc08c976a72f52ff9da2bddfddfd9ff5dcf18e8e88200c7"}, - {file = "pyproj-3.6.0-cp311-cp311-win32.whl", hash = "sha256:78276c6b0c831255c97c56dff7313a3571f327a284d8ac63d6a56437a72ed0e0"}, - {file = "pyproj-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:8fbac2eb9a0e425d7d6b7c6f4ebacd675cf3bdef0c59887057b8b4b0374e7c12"}, - {file = "pyproj-3.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:95120d65cbc5983dfd877076f28dbc18b9b329cbee38ca6e217bb7a5a043c099"}, - {file = "pyproj-3.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:830e6de7cfe43853967afee5ef908dfd5aa72d1ec12af9b9e3fecc179886e346"}, - {file = "pyproj-3.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e342b3010b2b20134671564ff9a8c476e5e512bf589477480aded1a5813af7c8"}, - {file = "pyproj-3.6.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23787460fab85ba2f857ee60ffb2e8e21fd9bd5db9833c51c1c05b2a6d9f0be5"}, - {file = "pyproj-3.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595376e4d3bb72b7dceeccbce0f4c43053d47561f17a1ad0224407e9980ee849"}, - {file = "pyproj-3.6.0-cp39-cp39-win32.whl", hash = "sha256:4d8a9773503085eada59b6892c96ddf686ab8cf64cfdc18ad744d13ee76dfa6f"}, - {file = "pyproj-3.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:137a07404f937f264b11b7130cd4cfa00002dbe4333b222e8056db84849c2ea4"}, - {file = "pyproj-3.6.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2799499a4045e4fb73e44c31bdacab0593a253a7a4b6baae6fdd27d604cf9bc2"}, - {file = "pyproj-3.6.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f04f6297c615c3b17f835df2556ac8fb9b4f51f281e960437eaf0cd80e7ae26a"}, - {file = "pyproj-3.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a4d2d438b007cb1f8d5f6f308d53d7ff9a2508cff8f9da6e2a93b76ffd98aaf"}, - {file = "pyproj-3.6.0.tar.gz", hash = "sha256:a5b111865b3f0f8b77b3983f2fbe4dd6248fc09d3730295949977c8dcd988062"}, + {file = "pyproj-3.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab7aa4d9ff3c3acf60d4b285ccec134167a948df02347585fdd934ebad8811b4"}, + {file = "pyproj-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4bc0472302919e59114aa140fd7213c2370d848a7249d09704f10f5b062031fe"}, + {file = "pyproj-3.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5279586013b8d6582e22b6f9e30c49796966770389a9d5b85e25a4223286cd3f"}, + {file = "pyproj-3.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fafd1f3eb421694857f254a9bdbacd1eb22fc6c24ca74b136679f376f97d35"}, + {file = "pyproj-3.6.1-cp310-cp310-win32.whl", hash = "sha256:c41e80ddee130450dcb8829af7118f1ab69eaf8169c4bf0ee8d52b72f098dc2f"}, + {file = "pyproj-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:db3aedd458e7f7f21d8176f0a1d924f1ae06d725228302b872885a1c34f3119e"}, + {file = "pyproj-3.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ebfbdbd0936e178091309f6cd4fcb4decd9eab12aa513cdd9add89efa3ec2882"}, + {file = "pyproj-3.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:447db19c7efad70ff161e5e46a54ab9cc2399acebb656b6ccf63e4bc4a04b97a"}, + {file = "pyproj-3.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7e13c40183884ec7f94eb8e0f622f08f1d5716150b8d7a134de48c6110fee85"}, + {file = "pyproj-3.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65ad699e0c830e2b8565afe42bd58cc972b47d829b2e0e48ad9638386d994915"}, + {file = "pyproj-3.6.1-cp311-cp311-win32.whl", hash = "sha256:8b8acc31fb8702c54625f4d5a2a6543557bec3c28a0ef638778b7ab1d1772132"}, + {file = "pyproj-3.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:38a3361941eb72b82bd9a18f60c78b0df8408416f9340521df442cebfc4306e2"}, + {file = "pyproj-3.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1e9fbaf920f0f9b4ee62aab832be3ae3968f33f24e2e3f7fbb8c6728ef1d9746"}, + {file = "pyproj-3.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d227a865356f225591b6732430b1d1781e946893789a609bb34f59d09b8b0f8"}, + {file = "pyproj-3.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83039e5ae04e5afc974f7d25ee0870a80a6bd6b7957c3aca5613ccbe0d3e72bf"}, + {file = "pyproj-3.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb059ba3bced6f6725961ba758649261d85ed6ce670d3e3b0a26e81cf1aa8d"}, + {file = "pyproj-3.6.1-cp312-cp312-win32.whl", hash = "sha256:2d6ff73cc6dbbce3766b6c0bce70ce070193105d8de17aa2470009463682a8eb"}, + {file = "pyproj-3.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:7a27151ddad8e1439ba70c9b4b2b617b290c39395fa9ddb7411ebb0eb86d6fb0"}, + {file = "pyproj-3.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ba1f9b03d04d8cab24d6375609070580a26ce76eaed54631f03bab00a9c737b"}, + {file = "pyproj-3.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18faa54a3ca475bfe6255156f2f2874e9a1c8917b0004eee9f664b86ccc513d3"}, + {file = "pyproj-3.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd43bd9a9b9239805f406fd82ba6b106bf4838d9ef37c167d3ed70383943ade1"}, + {file = "pyproj-3.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50100b2726a3ca946906cbaa789dd0749f213abf0cbb877e6de72ca7aa50e1ae"}, + {file = "pyproj-3.6.1-cp39-cp39-win32.whl", hash = "sha256:9274880263256f6292ff644ca92c46d96aa7e57a75c6df3f11d636ce845a1877"}, + {file = "pyproj-3.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:36b64c2cb6ea1cc091f329c5bd34f9c01bb5da8c8e4492c709bda6a09f96808f"}, + {file = "pyproj-3.6.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd93c1a0c6c4aedc77c0fe275a9f2aba4d59b8acf88cebfc19fe3c430cfabf4f"}, + {file = "pyproj-3.6.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6420ea8e7d2a88cb148b124429fba8cd2e0fae700a2d96eab7083c0928a85110"}, + {file = "pyproj-3.6.1.tar.gz", hash = "sha256:44aa7c704c2b7d8fb3d483bbf75af6cb2350d30a63b144279a09b75fead501bf"}, ] [package.dependencies] @@ -1318,13 +1454,13 @@ certifi = "*" [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, ] [package.dependencies] @@ -1358,13 +1494,13 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "pytest-xdist" -version = "3.3.1" +version = "3.5.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-xdist-3.3.1.tar.gz", hash = "sha256:d5ee0520eb1b7bcca50a60a518ab7a7707992812c578198f8b44fdfac78e8c93"}, - {file = "pytest_xdist-3.3.1-py3-none-any.whl", hash = "sha256:ff9daa7793569e6a68544850fd3927cd257cc03a7ef76c95e86915355e82b5f2"}, + {file = "pytest-xdist-3.5.0.tar.gz", hash = "sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a"}, + {file = "pytest_xdist-3.5.0-py3-none-any.whl", hash = "sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24"}, ] [package.dependencies] @@ -1392,13 +1528,13 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2023.3" +version = "2023.3.post1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, - {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, ] [[package]] @@ -1413,6 +1549,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1420,8 +1557,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1438,6 +1582,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1445,6 +1590,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1452,99 +1598,99 @@ files = [ [[package]] name = "regex" -version = "2023.8.8" +version = "2023.10.3" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "regex-2023.8.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88900f521c645f784260a8d346e12a1590f79e96403971241e64c3a265c8ecdb"}, - {file = "regex-2023.8.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3611576aff55918af2697410ff0293d6071b7e00f4b09e005d614686ac4cd57c"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8a0ccc8f2698f120e9e5742f4b38dc944c38744d4bdfc427616f3a163dd9de5"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c662a4cbdd6280ee56f841f14620787215a171c4e2d1744c9528bed8f5816c96"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf0633e4a1b667bfe0bb10b5e53fe0d5f34a6243ea2530eb342491f1adf4f739"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551ad543fa19e94943c5b2cebc54c73353ffff08228ee5f3376bd27b3d5b9800"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54de2619f5ea58474f2ac211ceea6b615af2d7e4306220d4f3fe690c91988a61"}, - {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ec4b3f0aebbbe2fc0134ee30a791af522a92ad9f164858805a77442d7d18570"}, - {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ae646c35cb9f820491760ac62c25b6d6b496757fda2d51be429e0e7b67ae0ab"}, - {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca339088839582d01654e6f83a637a4b8194d0960477b9769d2ff2cfa0fa36d2"}, - {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d9b6627408021452dcd0d2cdf8da0534e19d93d070bfa8b6b4176f99711e7f90"}, - {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:bd3366aceedf274f765a3a4bc95d6cd97b130d1dda524d8f25225d14123c01db"}, - {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7aed90a72fc3654fba9bc4b7f851571dcc368120432ad68b226bd593f3f6c0b7"}, - {file = "regex-2023.8.8-cp310-cp310-win32.whl", hash = "sha256:80b80b889cb767cc47f31d2b2f3dec2db8126fbcd0cff31b3925b4dc6609dcdb"}, - {file = "regex-2023.8.8-cp310-cp310-win_amd64.whl", hash = "sha256:b82edc98d107cbc7357da7a5a695901b47d6eb0420e587256ba3ad24b80b7d0b"}, - {file = "regex-2023.8.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1e7d84d64c84ad97bf06f3c8cb5e48941f135ace28f450d86af6b6512f1c9a71"}, - {file = "regex-2023.8.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce0f9fbe7d295f9922c0424a3637b88c6c472b75eafeaff6f910494a1fa719ef"}, - {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06c57e14ac723b04458df5956cfb7e2d9caa6e9d353c0b4c7d5d54fcb1325c46"}, - {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7a9aaa5a1267125eef22cef3b63484c3241aaec6f48949b366d26c7250e0357"}, - {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b7408511fca48a82a119d78a77c2f5eb1b22fe88b0d2450ed0756d194fe7a9a"}, - {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14dc6f2d88192a67d708341f3085df6a4f5a0c7b03dec08d763ca2cd86e9f559"}, - {file = "regex-2023.8.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48c640b99213643d141550326f34f0502fedb1798adb3c9eb79650b1ecb2f177"}, - {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0085da0f6c6393428bf0d9c08d8b1874d805bb55e17cb1dfa5ddb7cfb11140bf"}, - {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:964b16dcc10c79a4a2be9f1273fcc2684a9eedb3906439720598029a797b46e6"}, - {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7ce606c14bb195b0e5108544b540e2c5faed6843367e4ab3deb5c6aa5e681208"}, - {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:40f029d73b10fac448c73d6eb33d57b34607f40116e9f6e9f0d32e9229b147d7"}, - {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3b8e6ea6be6d64104d8e9afc34c151926f8182f84e7ac290a93925c0db004bfd"}, - {file = "regex-2023.8.8-cp311-cp311-win32.whl", hash = "sha256:942f8b1f3b223638b02df7df79140646c03938d488fbfb771824f3d05fc083a8"}, - {file = "regex-2023.8.8-cp311-cp311-win_amd64.whl", hash = "sha256:51d8ea2a3a1a8fe4f67de21b8b93757005213e8ac3917567872f2865185fa7fb"}, - {file = "regex-2023.8.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e951d1a8e9963ea51efd7f150450803e3b95db5939f994ad3d5edac2b6f6e2b4"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704f63b774218207b8ccc6c47fcef5340741e5d839d11d606f70af93ee78e4d4"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22283c769a7b01c8ac355d5be0715bf6929b6267619505e289f792b01304d898"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91129ff1bb0619bc1f4ad19485718cc623a2dc433dff95baadbf89405c7f6b57"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de35342190deb7b866ad6ba5cbcccb2d22c0487ee0cbb251efef0843d705f0d4"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b993b6f524d1e274a5062488a43e3f9f8764ee9745ccd8e8193df743dbe5ee61"}, - {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3026cbcf11d79095a32d9a13bbc572a458727bd5b1ca332df4a79faecd45281c"}, - {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:293352710172239bf579c90a9864d0df57340b6fd21272345222fb6371bf82b3"}, - {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d909b5a3fff619dc7e48b6b1bedc2f30ec43033ba7af32f936c10839e81b9217"}, - {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3d370ff652323c5307d9c8e4c62efd1956fb08051b0e9210212bc51168b4ff56"}, - {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:b076da1ed19dc37788f6a934c60adf97bd02c7eea461b73730513921a85d4235"}, - {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e9941a4ada58f6218694f382e43fdd256e97615db9da135e77359da257a7168b"}, - {file = "regex-2023.8.8-cp36-cp36m-win32.whl", hash = "sha256:a8c65c17aed7e15a0c824cdc63a6b104dfc530f6fa8cb6ac51c437af52b481c7"}, - {file = "regex-2023.8.8-cp36-cp36m-win_amd64.whl", hash = "sha256:aadf28046e77a72f30dcc1ab185639e8de7f4104b8cb5c6dfa5d8ed860e57236"}, - {file = "regex-2023.8.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:423adfa872b4908843ac3e7a30f957f5d5282944b81ca0a3b8a7ccbbfaa06103"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ae594c66f4a7e1ea67232a0846649a7c94c188d6c071ac0210c3e86a5f92109"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e51c80c168074faa793685656c38eb7a06cbad7774c8cbc3ea05552d615393d8"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09b7f4c66aa9d1522b06e31a54f15581c37286237208df1345108fcf4e050c18"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e73e5243af12d9cd6a9d6a45a43570dbe2e5b1cdfc862f5ae2b031e44dd95a8"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:941460db8fe3bd613db52f05259c9336f5a47ccae7d7def44cc277184030a116"}, - {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f0ccf3e01afeb412a1a9993049cb160d0352dba635bbca7762b2dc722aa5742a"}, - {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e9216e0d2cdce7dbc9be48cb3eacb962740a09b011a116fd7af8c832ab116ca"}, - {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5cd9cd7170459b9223c5e592ac036e0704bee765706445c353d96f2890e816c8"}, - {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4873ef92e03a4309b3ccd8281454801b291b689f6ad45ef8c3658b6fa761d7ac"}, - {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:239c3c2a339d3b3ddd51c2daef10874410917cd2b998f043c13e2084cb191684"}, - {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1005c60ed7037be0d9dea1f9c53cc42f836188227366370867222bda4c3c6bd7"}, - {file = "regex-2023.8.8-cp37-cp37m-win32.whl", hash = "sha256:e6bd1e9b95bc5614a7a9c9c44fde9539cba1c823b43a9f7bc11266446dd568e3"}, - {file = "regex-2023.8.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9a96edd79661e93327cfeac4edec72a4046e14550a1d22aa0dd2e3ca52aec921"}, - {file = "regex-2023.8.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2181c20ef18747d5f4a7ea513e09ea03bdd50884a11ce46066bb90fe4213675"}, - {file = "regex-2023.8.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a2ad5add903eb7cdde2b7c64aaca405f3957ab34f16594d2b78d53b8b1a6a7d6"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9233ac249b354c54146e392e8a451e465dd2d967fc773690811d3a8c240ac601"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920974009fb37b20d32afcdf0227a2e707eb83fe418713f7a8b7de038b870d0b"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2b6c5dfe0929b6c23dde9624483380b170b6e34ed79054ad131b20203a1a63"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96979d753b1dc3b2169003e1854dc67bfc86edf93c01e84757927f810b8c3c93"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ae54a338191e1356253e7883d9d19f8679b6143703086245fb14d1f20196be9"}, - {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2162ae2eb8b079622176a81b65d486ba50b888271302190870b8cc488587d280"}, - {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c884d1a59e69e03b93cf0dfee8794c63d7de0ee8f7ffb76e5f75be8131b6400a"}, - {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf9273e96f3ee2ac89ffcb17627a78f78e7516b08f94dc435844ae72576a276e"}, - {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:83215147121e15d5f3a45d99abeed9cf1fe16869d5c233b08c56cdf75f43a504"}, - {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f7454aa427b8ab9101f3787eb178057c5250478e39b99540cfc2b889c7d0586"}, - {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0640913d2c1044d97e30d7c41728195fc37e54d190c5385eacb52115127b882"}, - {file = "regex-2023.8.8-cp38-cp38-win32.whl", hash = "sha256:0c59122ceccb905a941fb23b087b8eafc5290bf983ebcb14d2301febcbe199c7"}, - {file = "regex-2023.8.8-cp38-cp38-win_amd64.whl", hash = "sha256:c12f6f67495ea05c3d542d119d270007090bad5b843f642d418eb601ec0fa7be"}, - {file = "regex-2023.8.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82cd0a69cd28f6cc3789cc6adeb1027f79526b1ab50b1f6062bbc3a0ccb2dbc3"}, - {file = "regex-2023.8.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bb34d1605f96a245fc39790a117ac1bac8de84ab7691637b26ab2c5efb8f228c"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:987b9ac04d0b38ef4f89fbc035e84a7efad9cdd5f1e29024f9289182c8d99e09"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dd6082f4e2aec9b6a0927202c85bc1b09dcab113f97265127c1dc20e2e32495"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7eb95fe8222932c10d4436e7a6f7c99991e3fdd9f36c949eff16a69246dee2dc"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7098c524ba9f20717a56a8d551d2ed491ea89cbf37e540759ed3b776a4f8d6eb"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b694430b3f00eb02c594ff5a16db30e054c1b9589a043fe9174584c6efa8033"}, - {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2aeab3895d778155054abea5238d0eb9a72e9242bd4b43f42fd911ef9a13470"}, - {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:988631b9d78b546e284478c2ec15c8a85960e262e247b35ca5eaf7ee22f6050a"}, - {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:67ecd894e56a0c6108ec5ab1d8fa8418ec0cff45844a855966b875d1039a2e34"}, - {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:14898830f0a0eb67cae2bbbc787c1a7d6e34ecc06fbd39d3af5fe29a4468e2c9"}, - {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f2200e00b62568cfd920127782c61bc1c546062a879cdc741cfcc6976668dfcf"}, - {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9691a549c19c22d26a4f3b948071e93517bdf86e41b81d8c6ac8a964bb71e5a6"}, - {file = "regex-2023.8.8-cp39-cp39-win32.whl", hash = "sha256:6ab2ed84bf0137927846b37e882745a827458689eb969028af8032b1b3dac78e"}, - {file = "regex-2023.8.8-cp39-cp39-win_amd64.whl", hash = "sha256:5543c055d8ec7801901e1193a51570643d6a6ab8751b1f7dd9af71af467538bb"}, - {file = "regex-2023.8.8.tar.gz", hash = "sha256:fcbdc5f2b0f1cd0f6a56cdb46fe41d2cce1e644e3b68832f3eeebc5fb0f7712e"}, + {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, + {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, + {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, + {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, + {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, + {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, + {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, + {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, + {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, + {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, + {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, + {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, + {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, + {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, + {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, + {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, ] [[package]] @@ -1589,13 +1735,13 @@ test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "tes [[package]] name = "rich" -version = "13.5.2" +version = "13.7.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.5.2-py3-none-any.whl", hash = "sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808"}, - {file = "rich-13.5.2.tar.gz", hash = "sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39"}, + {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, + {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, ] [package.dependencies] @@ -1607,91 +1753,94 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ruff" -version = "0.0.286" -description = "An extremely fast Python linter, written in Rust." +version = "0.1.6" +description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.0.286-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:8e22cb557e7395893490e7f9cfea1073d19a5b1dd337f44fd81359b2767da4e9"}, - {file = "ruff-0.0.286-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:68ed8c99c883ae79a9133cb1a86d7130feee0397fdf5ba385abf2d53e178d3fa"}, - {file = "ruff-0.0.286-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8301f0bb4ec1a5b29cfaf15b83565136c47abefb771603241af9d6038f8981e8"}, - {file = "ruff-0.0.286-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acc4598f810bbc465ce0ed84417ac687e392c993a84c7eaf3abf97638701c1ec"}, - {file = "ruff-0.0.286-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88c8e358b445eb66d47164fa38541cfcc267847d1e7a92dd186dddb1a0a9a17f"}, - {file = "ruff-0.0.286-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:0433683d0c5dbcf6162a4beb2356e820a593243f1fa714072fec15e2e4f4c939"}, - {file = "ruff-0.0.286-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddb61a0c4454cbe4623f4a07fef03c5ae921fe04fede8d15c6e36703c0a73b07"}, - {file = "ruff-0.0.286-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:47549c7c0be24c8ae9f2bce6f1c49fbafea83bca80142d118306f08ec7414041"}, - {file = "ruff-0.0.286-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:559aa793149ac23dc4310f94f2c83209eedb16908a0343663be19bec42233d25"}, - {file = "ruff-0.0.286-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d73cfb1c3352e7aa0ce6fb2321f36fa1d4a2c48d2ceac694cb03611ddf0e4db6"}, - {file = "ruff-0.0.286-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3dad93b1f973c6d1db4b6a5da8690c5625a3fa32bdf38e543a6936e634b83dc3"}, - {file = "ruff-0.0.286-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26afc0851f4fc3738afcf30f5f8b8612a31ac3455cb76e611deea80f5c0bf3ce"}, - {file = "ruff-0.0.286-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9b6b116d1c4000de1b9bf027131dbc3b8a70507788f794c6b09509d28952c512"}, - {file = "ruff-0.0.286-py3-none-win32.whl", hash = "sha256:556e965ac07c1e8c1c2d759ac512e526ecff62c00fde1a046acb088d3cbc1a6c"}, - {file = "ruff-0.0.286-py3-none-win_amd64.whl", hash = "sha256:5d295c758961376c84aaa92d16e643d110be32add7465e197bfdaec5a431a107"}, - {file = "ruff-0.0.286-py3-none-win_arm64.whl", hash = "sha256:1d6142d53ab7f164204b3133d053c4958d4d11ec3a39abf23a40b13b0784e3f0"}, - {file = "ruff-0.0.286.tar.gz", hash = "sha256:f1e9d169cce81a384a26ee5bb8c919fe9ae88255f39a1a69fd1ebab233a85ed2"}, + {file = "ruff-0.1.6-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:88b8cdf6abf98130991cbc9f6438f35f6e8d41a02622cc5ee130a02a0ed28703"}, + {file = "ruff-0.1.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c549ed437680b6105a1299d2cd30e4964211606eeb48a0ff7a93ef70b902248"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cf5f701062e294f2167e66d11b092bba7af6a057668ed618a9253e1e90cfd76"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:05991ee20d4ac4bb78385360c684e4b417edd971030ab12a4fbd075ff535050e"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87455a0c1f739b3c069e2f4c43b66479a54dea0276dd5d4d67b091265f6fd1dc"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:683aa5bdda5a48cb8266fcde8eea2a6af4e5700a392c56ea5fb5f0d4bfdc0240"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:137852105586dcbf80c1717facb6781555c4e99f520c9c827bd414fac67ddfb6"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd98138a98d48a1c36c394fd6b84cd943ac92a08278aa8ac8c0fdefcf7138f35"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0cd909d25f227ac5c36d4e7e681577275fb74ba3b11d288aff7ec47e3ae745"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8fd1c62a47aa88a02707b5dd20c5ff20d035d634aa74826b42a1da77861b5ff"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fd89b45d374935829134a082617954120d7a1470a9f0ec0e7f3ead983edc48cc"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:491262006e92f825b145cd1e52948073c56560243b55fb3b4ecb142f6f0e9543"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ea284789861b8b5ca9d5443591a92a397ac183d4351882ab52f6296b4fdd5462"}, + {file = "ruff-0.1.6-py3-none-win32.whl", hash = "sha256:1610e14750826dfc207ccbcdd7331b6bd285607d4181df9c1c6ae26646d6848a"}, + {file = "ruff-0.1.6-py3-none-win_amd64.whl", hash = "sha256:4558b3e178145491e9bc3b2ee3c4b42f19d19384eaa5c59d10acf6e8f8b57e33"}, + {file = "ruff-0.1.6-py3-none-win_arm64.whl", hash = "sha256:03910e81df0d8db0e30050725a5802441c2022ea3ae4fe0609b76081731accbc"}, + {file = "ruff-0.1.6.tar.gz", hash = "sha256:1b09f29b16c6ead5ea6b097ef2764b42372aebe363722f1605ecbcd2b9207184"}, ] [[package]] name = "setuptools" -version = "68.1.2" +version = "69.0.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"}, - {file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"}, + {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, + {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "shapely" -version = "2.0.1" +version = "2.0.2" description = "Manipulation and analysis of geometric objects" optional = false python-versions = ">=3.7" files = [ - {file = "shapely-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b06d031bc64149e340448fea25eee01360a58936c89985cf584134171e05863f"}, - {file = "shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9a6ac34c16f4d5d3c174c76c9d7614ec8fe735f8f82b6cc97a46b54f386a86bf"}, - {file = "shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:865bc3d7cc0ea63189d11a0b1120d1307ed7a64720a8bfa5be2fde5fc6d0d33f"}, - {file = "shapely-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45b4833235b90bc87ee26c6537438fa77559d994d2d3be5190dd2e54d31b2820"}, - {file = "shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce88ec79df55430e37178a191ad8df45cae90b0f6972d46d867bf6ebbb58cc4d"}, - {file = "shapely-2.0.1-cp310-cp310-win32.whl", hash = "sha256:01224899ff692a62929ef1a3f5fe389043e262698a708ab7569f43a99a48ae82"}, - {file = "shapely-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:da71de5bf552d83dcc21b78cc0020e86f8d0feea43e202110973987ffa781c21"}, - {file = "shapely-2.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:502e0a607f1dcc6dee0125aeee886379be5242c854500ea5fd2e7ac076b9ce6d"}, - {file = "shapely-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7d3bbeefd8a6a1a1017265d2d36f8ff2d79d0162d8c141aa0d37a87063525656"}, - {file = "shapely-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f470a130d6ddb05b810fc1776d918659407f8d025b7f56d2742a596b6dffa6c7"}, - {file = "shapely-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4641325e065fd3e07d55677849c9ddfd0cf3ee98f96475126942e746d55b17c8"}, - {file = "shapely-2.0.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90cfa4144ff189a3c3de62e2f3669283c98fb760cfa2e82ff70df40f11cadb39"}, - {file = "shapely-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70a18fc7d6418e5aea76ac55dce33f98e75bd413c6eb39cfed6a1ba36469d7d4"}, - {file = "shapely-2.0.1-cp311-cp311-win32.whl", hash = "sha256:09d6c7763b1bee0d0a2b84bb32a4c25c6359ad1ac582a62d8b211e89de986154"}, - {file = "shapely-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:d8f55f355be7821dade839df785a49dc9f16d1af363134d07eb11e9207e0b189"}, - {file = "shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:83a8ec0ee0192b6e3feee9f6a499d1377e9c295af74d7f81ecba5a42a6b195b7"}, - {file = "shapely-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a529218e72a3dbdc83676198e610485fdfa31178f4be5b519a8ae12ea688db14"}, - {file = "shapely-2.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91575d97fd67391b85686573d758896ed2fc7476321c9d2e2b0c398b628b961c"}, - {file = "shapely-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8b0d834b11be97d5ab2b4dceada20ae8e07bcccbc0f55d71df6729965f406ad"}, - {file = "shapely-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:b4f0711cc83734c6fad94fc8d4ec30f3d52c1787b17d9dca261dc841d4731c64"}, - {file = "shapely-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:05c51a29336e604c084fb43ae5dbbfa2c0ef9bd6fedeae0a0d02c7b57a56ba46"}, - {file = "shapely-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b519cf3726ddb6c67f6a951d1bb1d29691111eaa67ea19ddca4d454fbe35949c"}, - {file = "shapely-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:193a398d81c97a62fc3634a1a33798a58fd1dcf4aead254d080b273efbb7e3ff"}, - {file = "shapely-2.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e55698e0ed95a70fe9ff9a23c763acfe0bf335b02df12142f74e4543095e9a9b"}, - {file = "shapely-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f32a748703e7bf6e92dfa3d2936b2fbfe76f8ce5f756e24f49ef72d17d26ad02"}, - {file = "shapely-2.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a34a23d6266ca162499e4a22b79159dc0052f4973d16f16f990baa4d29e58b6"}, - {file = "shapely-2.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d173d24e85e51510e658fb108513d5bc11e3fd2820db6b1bd0522266ddd11f51"}, - {file = "shapely-2.0.1-cp38-cp38-win32.whl", hash = "sha256:3cb256ae0c01b17f7bc68ee2ffdd45aebf42af8992484ea55c29a6151abe4386"}, - {file = "shapely-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:c7eed1fb3008a8a4a56425334b7eb82651a51f9e9a9c2f72844a2fb394f38a6c"}, - {file = "shapely-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ac1dfc397475d1de485e76de0c3c91cc9d79bd39012a84bb0f5e8a199fc17bef"}, - {file = "shapely-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33403b8896e1d98aaa3a52110d828b18985d740cc9f34f198922018b1e0f8afe"}, - {file = "shapely-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2569a4b91caeef54dd5ae9091ae6f63526d8ca0b376b5bb9fd1a3195d047d7d4"}, - {file = "shapely-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a70a614791ff65f5e283feed747e1cc3d9e6c6ba91556e640636bbb0a1e32a71"}, - {file = "shapely-2.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c43755d2c46b75a7b74ac6226d2cc9fa2a76c3263c5ae70c195c6fb4e7b08e79"}, - {file = "shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad81f292fffbd568ae71828e6c387da7eb5384a79db9b4fde14dd9fdeffca9a"}, - {file = "shapely-2.0.1-cp39-cp39-win32.whl", hash = "sha256:b50c401b64883e61556a90b89948297f1714dbac29243d17ed9284a47e6dd731"}, - {file = "shapely-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:bca57b683e3d94d0919e2f31e4d70fdfbb7059650ef1b431d9f4e045690edcd5"}, - {file = "shapely-2.0.1.tar.gz", hash = "sha256:66a6b1a3e72ece97fc85536a281476f9b7794de2e646ca8a4517e2e3c1446893"}, + {file = "shapely-2.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6ca8cffbe84ddde8f52b297b53f8e0687bd31141abb2c373fd8a9f032df415d6"}, + {file = "shapely-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:baa14fc27771e180c06b499a0a7ba697c7988c7b2b6cba9a929a19a4d2762de3"}, + {file = "shapely-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:36480e32c434d168cdf2f5e9862c84aaf4d714a43a8465ae3ce8ff327f0affb7"}, + {file = "shapely-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef753200cbffd4f652efb2c528c5474e5a14341a473994d90ad0606522a46a2"}, + {file = "shapely-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9a41ff4323fc9d6257759c26eb1cf3a61ebc7e611e024e6091f42977303fd3a"}, + {file = "shapely-2.0.2-cp310-cp310-win32.whl", hash = "sha256:72b5997272ae8c25f0fd5b3b967b3237e87fab7978b8d6cd5fa748770f0c5d68"}, + {file = "shapely-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:34eac2337cbd67650248761b140d2535855d21b969d76d76123317882d3a0c1a"}, + {file = "shapely-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b0c052709c8a257c93b0d4943b0b7a3035f87e2d6a8ac9407b6a992d206422f"}, + {file = "shapely-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2d217e56ae067e87b4e1731d0dc62eebe887ced729ba5c2d4590e9e3e9fdbd88"}, + {file = "shapely-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94ac128ae2ab4edd0bffcd4e566411ea7bdc738aeaf92c32a8a836abad725f9f"}, + {file = "shapely-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa3ee28f5e63a130ec5af4dc3c4cb9c21c5788bb13c15e89190d163b14f9fb89"}, + {file = "shapely-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:737dba15011e5a9b54a8302f1748b62daa207c9bc06f820cd0ad32a041f1c6f2"}, + {file = "shapely-2.0.2-cp311-cp311-win32.whl", hash = "sha256:45ac6906cff0765455a7b49c1670af6e230c419507c13e2f75db638c8fc6f3bd"}, + {file = "shapely-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:dc9342fc82e374130db86a955c3c4525bfbf315a248af8277a913f30911bed9e"}, + {file = "shapely-2.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:06f193091a7c6112fc08dfd195a1e3846a64306f890b151fa8c63b3e3624202c"}, + {file = "shapely-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:eebe544df5c018134f3c23b6515877f7e4cd72851f88a8d0c18464f414d141a2"}, + {file = "shapely-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7e92e7c255f89f5cdf777690313311f422aa8ada9a3205b187113274e0135cd8"}, + {file = "shapely-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be46d5509b9251dd9087768eaf35a71360de6afac82ce87c636990a0871aa18b"}, + {file = "shapely-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5533a925d8e211d07636ffc2fdd9a7f9f13d54686d00577eeb11d16f00be9c4"}, + {file = "shapely-2.0.2-cp312-cp312-win32.whl", hash = "sha256:084b023dae8ad3d5b98acee9d3bf098fdf688eb0bb9b1401e8b075f6a627b611"}, + {file = "shapely-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:ea84d1cdbcf31e619d672b53c4532f06253894185ee7acb8ceb78f5f33cbe033"}, + {file = "shapely-2.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ed1e99702125e7baccf401830a3b94d810d5c70b329b765fe93451fe14cf565b"}, + {file = "shapely-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7d897e6bdc6bc64f7f65155dbbb30e49acaabbd0d9266b9b4041f87d6e52b3a"}, + {file = "shapely-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0521d76d1e8af01e712db71da9096b484f081e539d4f4a8c97342e7971d5e1b4"}, + {file = "shapely-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:5324be299d4c533ecfcfd43424dfd12f9428fd6f12cda38a4316da001d6ef0ea"}, + {file = "shapely-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:78128357a0cee573257a0c2c388d4b7bf13cb7dbe5b3fe5d26d45ebbe2a39e25"}, + {file = "shapely-2.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87dc2be34ac3a3a4a319b963c507ac06682978a5e6c93d71917618b14f13066e"}, + {file = "shapely-2.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:42997ac806e4583dad51c80a32d38570fd9a3d4778f5e2c98f9090aa7db0fe91"}, + {file = "shapely-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ccfd5fa10a37e67dbafc601c1ddbcbbfef70d34c3f6b0efc866ddbdb55893a6c"}, + {file = "shapely-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7c95d3379ae3abb74058938a9fcbc478c6b2e28d20dace38f8b5c587dde90aa"}, + {file = "shapely-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a21353d28209fb0d8cc083e08ca53c52666e0d8a1f9bbe23b6063967d89ed24"}, + {file = "shapely-2.0.2-cp38-cp38-win32.whl", hash = "sha256:03e63a99dfe6bd3beb8d5f41ec2086585bb969991d603f9aeac335ad396a06d4"}, + {file = "shapely-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:c6fd29fbd9cd76350bd5cc14c49de394a31770aed02d74203e23b928f3d2f1aa"}, + {file = "shapely-2.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f217d28ecb48e593beae20a0082a95bd9898d82d14b8fcb497edf6bff9a44d7"}, + {file = "shapely-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:394e5085b49334fd5b94fa89c086edfb39c3ecab7f669e8b2a4298b9d523b3a5"}, + {file = "shapely-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fd3ad17b64466a033848c26cb5b509625c87d07dcf39a1541461cacdb8f7e91c"}, + {file = "shapely-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d41a116fcad58048d7143ddb01285e1a8780df6dc1f56c3b1e1b7f12ed296651"}, + {file = "shapely-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dea9a0651333cf96ef5bb2035044e3ad6a54f87d90e50fe4c2636debf1b77abc"}, + {file = "shapely-2.0.2-cp39-cp39-win32.whl", hash = "sha256:b8eb0a92f7b8c74f9d8fdd1b40d395113f59bd8132ca1348ebcc1f5aece94b96"}, + {file = "shapely-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:794affd80ca0f2c536fc948a3afa90bd8fb61ebe37fe873483ae818e7f21def4"}, + {file = "shapely-2.0.2.tar.gz", hash = "sha256:1713cc04c171baffc5b259ba8531c58acc2a301707b7f021d88a15ed090649e7"}, ] [package.dependencies] @@ -1725,24 +1874,24 @@ files = [ [[package]] name = "soupsieve" -version = "2.4.1" +version = "2.5" description = "A modern CSS selector implementation for Beautiful Soup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, - {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, ] [[package]] name = "sphinx" -version = "7.2.4" +version = "7.2.6" description = "Python documentation generator" optional = false python-versions = ">=3.9" files = [ - {file = "sphinx-7.2.4-py3-none-any.whl", hash = "sha256:9b3aa23254ffc5be468646810543e491653bf5a67f3f23e4ccd4e515b0bd0b9c"}, - {file = "sphinx-7.2.4.tar.gz", hash = "sha256:1aeec862bf1edff4374012ac38082e0d1daa066c9e327841a846401164797988"}, + {file = "sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560"}, + {file = "sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"}, ] [package.dependencies] @@ -1771,26 +1920,27 @@ test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools [[package]] name = "sphinx-autoapi" -version = "2.1.1" +version = "3.0.0" description = "Sphinx API documentation generator" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "sphinx-autoapi-2.1.1.tar.gz", hash = "sha256:fbadb96e79020d6b0ec45d888517bf816d6b587a2d340fbe1ec31135e300a6c8"}, - {file = "sphinx_autoapi-2.1.1-py2.py3-none-any.whl", hash = "sha256:d8da890477bd18e3327cafdead9d5a44a7d798476c6fa32492100e288250a5a3"}, + {file = "sphinx-autoapi-3.0.0.tar.gz", hash = "sha256:09ebd674a32b44467222b0fb8a917b97c89523f20dbf05b52cb8a3f0e15714de"}, + {file = "sphinx_autoapi-3.0.0-py2.py3-none-any.whl", hash = "sha256:ea207793cba1feff7b2ded0e29364f2995a4d157303a98603cee0ce94cea2688"}, ] [package.dependencies] anyascii = "*" -astroid = ">=2.7" +astroid = [ + {version = ">=2.7", markers = "python_version < \"3.12\""}, + {version = ">=3.0.0a1", markers = "python_version >= \"3.12\""}, +] Jinja2 = "*" PyYAML = "*" -sphinx = ">=5.2.0" +sphinx = ">=6.1.0" [package.extras] docs = ["furo", "sphinx", "sphinx-design"] -dotnet = ["sphinxcontrib-dotnetdomain"] -go = ["sphinxcontrib-golangdomain"] [[package]] name = "sphinx-basic-ng" @@ -1995,36 +2145,6 @@ Sphinx = ">=5" lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] -[[package]] -name = "stack-data" -version = "0.6.2" -description = "Extract data from python stack frames and tracebacks for informative displays" -optional = false -python-versions = "*" -files = [ - {file = "stack_data-0.6.2-py3-none-any.whl", hash = "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"}, - {file = "stack_data-0.6.2.tar.gz", hash = "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815"}, -] - -[package.dependencies] -asttokens = ">=2.1.0" -executing = ">=1.2.0" -pure-eval = "*" - -[package.extras] -tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] - -[[package]] -name = "tokenize-rt" -version = "5.2.0" -description = "A wrapper around the stdlib `tokenize` which roundtrips." -optional = false -python-versions = ">=3.8" -files = [ - {file = "tokenize_rt-5.2.0-py2.py3-none-any.whl", hash = "sha256:b79d41a65cfec71285433511b50271b05da3584a1da144a0752e9c621a285289"}, - {file = "tokenize_rt-5.2.0.tar.gz", hash = "sha256:9fe80f8a5c1edad2d3ede0f37481cc0cc1538a2f442c9c2f9e4feacd2792d054"}, -] - [[package]] name = "tomli" version = "2.0.1" @@ -2036,30 +2156,15 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -[[package]] -name = "traitlets" -version = "5.9.0" -description = "Traitlets Python configuration system" -optional = false -python-versions = ">=3.7" -files = [ - {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, - {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, -] - -[package.extras] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] - [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] @@ -2075,152 +2180,60 @@ files = [ [[package]] name = "urllib3" -version = "2.0.4" +version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, - {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, + {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, + {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.24.3" +version = "20.24.7" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.3-py3-none-any.whl", hash = "sha256:95a6e9398b4967fbcb5fef2acec5efaf9aa4972049d9ae41f95e0972a683fd02"}, - {file = "virtualenv-20.24.3.tar.gz", hash = "sha256:e5c3b4ce817b0b328af041506a2a299418c98747c4b1e68cb7527e74ced23efc"}, + {file = "virtualenv-20.24.7-py3-none-any.whl", hash = "sha256:a18b3fd0314ca59a2e9f4b556819ed07183b3e9a3702ecfe213f593d44f7b3fd"}, + {file = "virtualenv-20.24.7.tar.gz", hash = "sha256:69050ffb42419c91f6c1284a7b24e0475d793447e35929b488bf6a0aade39353"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<4" +platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] -[[package]] -name = "wcwidth" -version = "0.2.6" -description = "Measures the displayed width of unicode strings in a terminal" -optional = false -python-versions = "*" -files = [ - {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, - {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, -] - -[[package]] -name = "wrapt" -version = "1.15.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -files = [ - {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, - {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, - {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, - {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, - {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, - {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, - {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, - {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, - {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, - {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, - {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, - {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, - {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, - {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, - {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, - {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, - {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, - {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, - {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, -] - [[package]] name = "zipp" -version = "3.16.2" +version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, - {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, + {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, + {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +[extras] +graph = ["networkx"] +plot = ["matplotlib"] + [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "337b546c564d8a2ac0cf8b133bb9a74d4d0525a98d396ad0f293f2013635ee69" +content-hash = "ed57186be9cfa88d048a30d9d37120cd0ecbbebd74197bc207235f1cb88b05ab" diff --git a/pyproject.toml b/pyproject.toml index 7cd97a26..9004aaad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,13 @@ [tool.poetry] name = "roseau-load-flow" -version = "0.5.0" +version = "0.6.0" description = "Highly capable three-phase load flow solver" authors = [ "Ali Hamdan ", "Sébastien Vallet ", "Benoît Vinot ", - "Victor Gouin ", + "Florent Cadoux ", + "Victor Gouin", ] maintainers = ["Ali Hamdan "] license = "Proprietary" @@ -28,6 +29,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering :: Physics", ] @@ -37,7 +39,11 @@ build-backend = "poetry.core.masonry.api" [tool.poetry.dependencies] python = "^3.9" -numpy = ">=1.21.5" +numpy = [ + { version = ">=1.21.5", python = "<3.12" }, + { version = ">=1.26.0", python = ">=3.12,<3.13" }, + { version = "*", python = ">=3.13" }, +] pandas = ">=1.4.0" geopandas = ">=0.10.2" shapely = ">=2.0.0" @@ -47,39 +53,45 @@ pint = ">=0.21.0" typing-extensions = ">=4.6.2" rich = ">=13.5.1" +# Optional dependencies +matplotlib = { version = ">=3.7.2", optional = true } +networkx = { version = ">=3.0.0", optional = true } + +[tool.poetry.extras] +# DO NOT forget to update the installation page in the documentation when extras change +plot = ["matplotlib"] +graph = ["networkx"] + [tool.poetry.group.test.dependencies] pytest = "^7.1.2" pytest-cov = "^4.0.0" pytest-xdist = "^3.1.0" requests-mock = "^1.9.3" coverage = { version = "^7.0.5", extras = ["toml"] } +matplotlib = ">=3.7.2" +networkx = ">=3.0.0" [tool.poetry.group.dev.dependencies] pre-commit = "^3.0.0" -black = { version = "==23.7.0", extras = ["jupyter"] } # keep in sync with .pre-commit-config.yaml -ruff = "==0.0.286" # keep in sync with .pre-commit-config.yaml +ruff = "==0.1.6" # keep in sync with .pre-commit-config.yaml [tool.poetry.group.doc.dependencies] sphinx = "^7.0.1" myst-parser = ">=0.16.1" sphinx-math-dollar = "^1.2.1" -sphinx-autoapi = "^2.0.0" +sphinx-autoapi = "^3.0.0" sphinx-copybutton = ">=0.5.1" sphinx-inline-tabs = ">=2022.1.2b11" furo = ">=2022.9.29" sphinxcontrib-googleanalytics = ">=0.3" sphinxcontrib-bibtex = "^2.5.0" -[tool.black] -line-length = 120 -target-version = ["py39", "py310", "py311"] - [tool.ruff] line-length = 120 target-version = "py39" show-fixes = true namespace-packages = ["roseau"] -include = ["*.py", "*.pyi", "**/pyproject.toml", "*.ipynb"] +extend-include = ["*.ipynb"] select = ["E", "F", "C90", "W", "B", "UP", "I", "RUF100", "TID", "SIM", "PT", "PIE", "N", "C4", "NPY"] unfixable = ["B"] ignore = ["E501", "B024", "N818"] @@ -131,3 +143,6 @@ directory = "htmlcov" [tool.pytest.ini_options] addopts = "--color=yes -vv -n=0" testpaths = ["roseau/load_flow/"] +filterwarnings = [ + 'ignore:.*utcfromtimestamp:DeprecationWarning:dateutil.*', # dateutil is imported by pandas, not us +] diff --git a/roseau/load_flow/__about__.py b/roseau/load_flow/__about__.py index 230c8ffe..ce864993 100644 --- a/roseau/load_flow/__about__.py +++ b/roseau/load_flow/__about__.py @@ -1,4 +1,12 @@ -__author__ = "Benoît Vinot, Victor Gouin, Florent Cadoux, Sébastien Vallet, Ali Hamdan" +__authors__ = ", ".join( + ( + "Ali Hamdan ", + "Sébastien Vallet ", + "Benoît Vinot ", + "Florent Cadoux ", + "Victor Gouin", + ) +) __copyright__ = "Roseau Technologies 2018--2023" __credits__ = "Roseau Technologies" __license__ = "Proprietary" diff --git a/roseau/load_flow/__init__.py b/roseau/load_flow/__init__.py index e94310f2..c00522f4 100644 --- a/roseau/load_flow/__init__.py +++ b/roseau/load_flow/__init__.py @@ -8,7 +8,7 @@ import importlib.metadata from roseau.load_flow.__about__ import ( - __author__, + __authors__, __copyright__, __credits__, __email__, @@ -46,7 +46,7 @@ __version__ = importlib.metadata.version("roseau-load-flow") __all__ = [ - "__author__", + "__authors__", "__copyright__", "__credits__", "__email__", diff --git a/roseau/load_flow/_wrapper.py b/roseau/load_flow/_wrapper.py new file mode 100644 index 00000000..3f0153f7 --- /dev/null +++ b/roseau/load_flow/_wrapper.py @@ -0,0 +1,148 @@ +import functools +from collections.abc import Iterable, MutableSequence +from inspect import Parameter, Signature, signature +from itertools import zip_longest +from typing import Any, Callable, Optional, TypeVar, Union + +from pint import Quantity, Unit +from pint.registry import UnitRegistry +from pint.util import to_units_container + +T = TypeVar("T") +FuncT = TypeVar("FuncT", bound=Callable) + + +def _parse_wrap_args(args: Iterable[Optional[Union[str, Unit]]]) -> Callable: + """Create a converter function for the wrapper""" + # _to_units_container + args_as_uc = [to_units_container(arg) for arg in args] + + # Check for references in args, remove None values + unit_args_ndx = {ndx for ndx, arg in enumerate(args_as_uc) if arg is not None} + + def _converter(ureg: UnitRegistry, sig: Signature, values: list[Any], kw: dict[Any]): + len_initial_values = len(values) + + # pack kwargs + for i, param_name in enumerate(sig.parameters): + if i >= len_initial_values: + values.append(kw[param_name]) + + # convert arguments + for ndx in unit_args_ndx: + value = values[ndx] + if isinstance(value, ureg.Quantity): + values[ndx] = ureg.convert(value.magnitude, value.units, args_as_uc[ndx]) + elif isinstance(value, MutableSequence): + for i, val in enumerate(value): + if isinstance(val, ureg.Quantity): + value[i] = ureg.convert(val.magnitude, val.units, args_as_uc[ndx]) + + # unpack kwargs + for i, param_name in enumerate(sig.parameters): + if i >= len_initial_values: + kw[param_name] = values[i] + + return values[:len_initial_values], kw + + return _converter + + +def _apply_defaults(sig: Signature, args: tuple[Any], kwargs: dict[str, Any]) -> tuple[list[Any], dict[str, Any]]: + """Apply default keyword arguments. + + Named keywords may have been left blank. This function applies the default + values so that every argument is defined. + """ + n = len(args) + for i, param in enumerate(sig.parameters.values()): + if i >= n and param.default != Parameter.empty and param.name not in kwargs: + kwargs[param.name] = param.default + return list(args), kwargs + + +def wraps( + ureg: UnitRegistry, + ret: Optional[Union[str, Unit, Iterable[Optional[Union[str, Unit]]]]], + args: Optional[Union[str, Unit, Iterable[Optional[Union[str, Unit]]]]], +) -> Callable[[FuncT], FuncT]: + """Wraps a function to become pint-aware. + + Use it when a function requires a numerical value but in some specific + units. The wrapper function will take a pint quantity, convert to the units + specified in `args` and then call the wrapped function with the resulting + magnitude. + + The value returned by the wrapped function will be converted to the units + specified in `ret`. + + Args: + ureg: + A UnitRegistry instance. + + ret: + Units of each of the return values. Use `None` to skip argument conversion. + + args: + Units of each of the input arguments. Use `None` to skip argument conversion. + + Returns: + The wrapper function. + + Raises: + TypeError + if the number of given arguments does not match the number of function parameters. + if any of the provided arguments is not a unit a string or Quantity + """ + if not isinstance(args, (list, tuple)): + args = (args,) + + for arg in args: + if arg is not None and not isinstance(arg, (ureg.Unit, str)): + raise TypeError(f"wraps arguments must by of type str or Unit, not {type(arg)} ({arg})") + + converter = _parse_wrap_args(args) + + is_ret_container = isinstance(ret, (list, tuple)) + if is_ret_container: + for arg in ret: + if arg is not None and not isinstance(arg, (ureg.Unit, str)): + raise TypeError(f"wraps 'ret' argument must by of type str or Unit, not {type(arg)} ({arg})") + ret = ret.__class__([to_units_container(arg, ureg) for arg in ret]) + else: + if ret is not None and not isinstance(ret, (ureg.Unit, str)): + raise TypeError(f"wraps 'ret' argument must by of type str or Unit, not {type(ret)} ({ret})") + ret = to_units_container(ret, ureg) + + def decorator(func: Callable[..., Any]) -> Callable[..., Quantity]: + sig = signature(func) + count_params = len(sig.parameters) + if len(args) != count_params: + raise TypeError(f"{func.__name__} takes {count_params} parameters, but {len(args)} units were passed") + + assigned = tuple(attr for attr in functools.WRAPPER_ASSIGNMENTS if hasattr(func, attr)) + updated = tuple(attr for attr in functools.WRAPPER_UPDATES if hasattr(func, attr)) + + @functools.wraps(func, assigned=assigned, updated=updated) + def wrapper(*values, **kw) -> Quantity: + values, kw = _apply_defaults(sig, values, kw) + + # In principle, the values are used as is + # When then extract the magnitudes when needed. + new_values, new_kw = converter(ureg, sig, values, kw) + + result = func(*new_values, **new_kw) + + if is_ret_container: + return ret.__class__( + res if unit is None else ureg.Quantity(res, unit) for unit, res in zip_longest(ret, result) + ) + + if ret is None: + return result + + return ureg.Quantity(result, ret) + + return wrapper + + return decorator diff --git a/roseau/load_flow/conftest.py b/roseau/load_flow/conftest.py index d8079b39..e2181a46 100644 --- a/roseau/load_flow/conftest.py +++ b/roseau/load_flow/conftest.py @@ -4,6 +4,8 @@ import pytest from pandas.testing import assert_frame_equal +from roseau.load_flow.utils import console + # Variable to test the network HERE = Path(__file__).parent.expanduser().absolute() TEST_ALL_NETWORKS_DATA_FOLDER = HERE / "tests" / "data" / "networks" @@ -78,6 +80,11 @@ def dgs_network_path(request) -> Path: return request.param +@pytest.fixture(autouse=True, scope="session") +def _set_console_width() -> None: + console.width = 210 + + # # Utils # diff --git a/roseau/load_flow/converters.py b/roseau/load_flow/converters.py index 32417120..1ebeafe3 100644 --- a/roseau/load_flow/converters.py +++ b/roseau/load_flow/converters.py @@ -11,6 +11,8 @@ import numpy as np import pandas as pd +from roseau.load_flow.typing import ComplexArray + ALPHA = np.exp(2 / 3 * np.pi * 1j) """complex: Phasor rotation operator `alpha`, which rotates a phasor vector counterclockwise by 120 degrees when multiplied by it.""" @@ -21,23 +23,27 @@ [1, ALPHA**2, ALPHA], [1, ALPHA, ALPHA**2], ], - dtype=complex, + dtype=np.complex128, ) """numpy.ndarray[complex]: "A" matrix: transformation matrix from phasor to symmetrical components.""" _A_INV = np.linalg.inv(A) -def phasor_to_sym(v_abc: Sequence[complex]) -> np.ndarray[complex]: +def phasor_to_sym(v_abc: Sequence[complex]) -> ComplexArray: """Compute the symmetrical components `(0, +, -)` from the phasor components `(a, b, c)`.""" - v_012 = _A_INV @ np.asarray(v_abc).reshape((3, 1)) - return v_012 + v_abc_array = np.array(v_abc) + orig_shape = v_abc_array.shape + v_012 = _A_INV @ v_abc_array.reshape((3, 1)) + return v_012.reshape(orig_shape) -def sym_to_phasor(v_012: Sequence[complex]) -> np.ndarray[complex]: +def sym_to_phasor(v_012: Sequence[complex]) -> ComplexArray: """Compute the phasor components `(a, b, c)` from the symmetrical components `(0, +, -)`.""" - v_abc = A @ np.asarray(v_012).reshape((3, 1)) - return v_abc + v_012_array = np.array(v_012) + orig_shape = v_012_array.shape + v_abc = A @ v_012_array.reshape((3, 1)) + return v_abc.reshape(orig_shape) def series_phasor_to_sym(s_abc: pd.Series) -> pd.Series: @@ -103,7 +109,7 @@ def series_phasor_to_sym(s_abc: pd.Series) -> pd.Series: return s_012 -def calculate_voltages(potentials: np.ndarray, phases: str) -> np.ndarray: +def calculate_voltages(potentials: ComplexArray, phases: str) -> ComplexArray: """Calculate the voltages between phases given the potentials of each phase. Args: @@ -118,13 +124,13 @@ def calculate_voltages(potentials: np.ndarray, phases: str) -> np.ndarray: Otherwise, the voltages are Phase-Phase. Example: - >>> potentials = 230 * np.array([1, np.exp(-2j*np.pi/3), np.exp(2j*np.pi/3), 0], dtype=complex) + >>> potentials = 230 * np.array([1, np.exp(-2j*np.pi/3), np.exp(2j*np.pi/3), 0], dtype=np.complex128) >>> calculate_voltages(potentials, "abcn") array([ 230. +0.j , -115.-199.18584287j, -115.+199.18584287j]) - >>> potentials = np.array([230, 230 * np.exp(-2j*np.pi/3)], dtype=complex) + >>> potentials = np.array([230, 230 * np.exp(-2j*np.pi/3)], dtype=np.complex128) >>> calculate_voltages(potentials, "ab") array([345.+199.18584287j]) - >>> calculate_voltages(np.array([230, 0], dtype=complex), "an") + >>> calculate_voltages(np.array([230, 0], dtype=np.complex128), "an") array([230.+0.j]) """ assert len(potentials) == len(phases), "Number of potentials must match number of phases." diff --git a/roseau/load_flow/exceptions.py b/roseau/load_flow/exceptions.py index f843e31c..31d8ed0d 100644 --- a/roseau/load_flow/exceptions.py +++ b/roseau/load_flow/exceptions.py @@ -24,6 +24,7 @@ class RoseauLoadFlowExceptionCode(Enum): BAD_BUS_ID = auto() BAD_BUS_TYPE = auto() BAD_POTENTIALS_SIZE = auto() + BAD_VOLTAGES = auto() BAD_VOLTAGES_SIZE = auto() BAD_SHORT_CIRCUIT = auto() @@ -54,7 +55,7 @@ class RoseauLoadFlowExceptionCode(Enum): BAD_PROJECTION_VALUE = auto() # Flexible parameter - BAD_SMAX_VALUE = auto() + BAD_FLEXIBLE_PARAMETER_VALUE = auto() # Load BAD_LOAD_ID = auto() @@ -107,6 +108,9 @@ class RoseauLoadFlowExceptionCode(Enum): CATALOGUE_NOT_FOUND = auto() CATALOGUE_SEVERAL_FOUND = auto() + # Import Error + IMPORT_ERROR = auto() + @classmethod def package_name(cls) -> str: return "roseau.load_flow" diff --git a/roseau/load_flow/io/dict.py b/roseau/load_flow/io/dict.py index e1b481d3..8db7ac4f 100644 --- a/roseau/load_flow/io/dict.py +++ b/roseau/load_flow/io/dict.py @@ -132,15 +132,15 @@ def network_from_dict( return buses, branches_dict, loads, sources, grounds, potential_refs -def network_to_dict(en: "ElectricalNetwork", include_geometry: bool) -> JsonDict: +def network_to_dict(en: "ElectricalNetwork", *, _lf_only: bool) -> JsonDict: """Return a dictionary of the current network data. Args: en: The electrical network. - include_geometry: - If False, the geometry will not be added to the network dictionary. + _lf_only: + Internal argument, please do not use. Returns: The created dictionary. @@ -155,7 +155,7 @@ def network_to_dict(en: "ElectricalNetwork", include_geometry: bool) -> JsonDict sources: list[JsonDict] = [] short_circuits: list[JsonDict] = [] for bus in en.buses.values(): - buses.append(bus.to_dict(include_geometry=include_geometry)) + buses.append(bus.to_dict(_lf_only=_lf_only)) for element in bus._connected_elements: if isinstance(element, AbstractLoad): assert element.bus is bus @@ -171,7 +171,7 @@ def network_to_dict(en: "ElectricalNetwork", include_geometry: bool) -> JsonDict lines_params_dict: dict[Id, LineParameters] = {} transformers_params_dict: dict[Id, TransformerParameters] = {} for branch in en.branches.values(): - branches.append(branch.to_dict(include_geometry=include_geometry)) + branches.append(branch.to_dict(_lf_only=_lf_only)) if isinstance(branch, Line): params_id = branch.parameters.id if params_id in lines_params_dict and branch.parameters != lines_params_dict[params_id]: @@ -192,13 +192,13 @@ def network_to_dict(en: "ElectricalNetwork", include_geometry: bool) -> JsonDict # Line parameters line_params: list[JsonDict] = [] for lp in lines_params_dict.values(): - line_params.append(lp.to_dict()) + line_params.append(lp.to_dict(_lf_only=_lf_only)) line_params.sort(key=lambda x: x["id"]) # Always keep the same order # Transformer parameters transformer_params: list[JsonDict] = [] for tp in transformers_params_dict.values(): - transformer_params.append(tp.to_dict()) + transformer_params.append(tp.to_dict(_lf_only=_lf_only)) transformer_params.sort(key=lambda x: x["id"]) # Always keep the same order res = { diff --git a/roseau/load_flow/io/tests/test_dict.py b/roseau/load_flow/io/tests/test_dict.py index cc9a1057..2420586f 100644 --- a/roseau/load_flow/io/tests/test_dict.py +++ b/roseau/load_flow/io/tests/test_dict.py @@ -23,8 +23,8 @@ def test_to_dict(): ground = Ground("ground") vn = 400 / np.sqrt(3) voltages = [vn, vn * np.exp(-2 / 3 * np.pi * 1j), vn * np.exp(2 / 3 * np.pi * 1j)] - source_bus = Bus(id="source", phases="abcn", geometry=Point(0.0, 0.0)) - load_bus = Bus(id="load bus", phases="abcn", geometry=Point(0.0, 1.0)) + source_bus = Bus(id="source", phases="abcn", geometry=Point(0.0, 0.0), min_voltage=0.9 * vn) + load_bus = Bus(id="load bus", phases="abcn", geometry=Point(0.0, 1.0), max_voltage=1.1 * vn) ground.connect(load_bus) p_ref = PotentialRef("pref", element=ground) vs = VoltageSource("vs", source_bus, phases="abcn", voltages=voltages) @@ -44,6 +44,8 @@ def test_to_dict(): grounds=[ground], potential_refs=[p_ref], ) + + # Same id, different line parameters -> fail with pytest.raises(RoseauLoadFlowException) as e: en.to_dict() assert "There are multiple line parameters with id 'test'" in e.value.msg @@ -52,17 +54,28 @@ def test_to_dict(): # Same id, same line parameters -> ok lp2 = LineParameters("test", z_line=np.eye(4, dtype=complex), y_shunt=np.eye(4, dtype=complex)) line2.parameters = lp2 + en.to_dict() + + # Dict content + line2.parameters = lp1 + lp1.max_current = 1000 res = en.to_dict() assert "geometry" in res["buses"][0] assert "geometry" in res["buses"][1] assert "geometry" in res["branches"][0] assert "geometry" in res["branches"][1] + assert np.isclose(res["buses"][0]["min_voltage"], 0.9 * vn) + assert np.isclose(res["buses"][1]["max_voltage"], 1.1 * vn) + assert np.isclose(res["lines_params"][0]["max_current"], 1000) - res = en.to_dict(include_geometry=False) + res = en.to_dict(_lf_only=True) assert "geometry" not in res["buses"][0] assert "geometry" not in res["buses"][1] assert "geometry" not in res["branches"][0] assert "geometry" not in res["branches"][1] + assert "min_voltage" not in res["buses"][0] + assert "max_voltage" not in res["buses"][1] + assert "max_current" not in res["lines_params"][0] # Same id, different transformer parameters -> fail ground = Ground("ground") @@ -93,6 +106,8 @@ def test_to_dict(): grounds=[ground], potential_refs=[p_ref], ) + + # Same id, different transformer parameters -> fail with pytest.raises(RoseauLoadFlowException) as e: en.to_dict() assert "There are multiple transformer parameters with id 't'" in e.value.msg @@ -103,17 +118,24 @@ def test_to_dict(): "t", type="Dyn11", uhv=20000, ulv=400, sn=160 * 1e3, p0=460, i0=2.3 / 100, psc=2350, vsc=4 / 100 ) transformer2.parameters = tp2 + en.to_dict() + + # Dict content + transformer2.parameters = tp1 + tp1.max_power = 180_000 res = en.to_dict() assert "geometry" in res["buses"][0] assert "geometry" in res["buses"][1] assert "geometry" in res["branches"][0] assert "geometry" in res["branches"][1] + assert np.isclose(res["transformers_params"][0]["max_power"], 180_000) - res = en.to_dict(include_geometry=False) + res = en.to_dict(_lf_only=True) assert "geometry" not in res["buses"][0] assert "geometry" not in res["buses"][1] assert "geometry" not in res["branches"][0] assert "geometry" not in res["branches"][1] + assert "max_power" not in res["transformers_params"][0] def test_v0_to_v1_converter(monkeypatch): diff --git a/roseau/load_flow/models/branches.py b/roseau/load_flow/models/branches.py index c9206f6e..ea5fb888 100644 --- a/roseau/load_flow/models/branches.py +++ b/roseau/load_flow/models/branches.py @@ -8,7 +8,7 @@ from roseau.load_flow.converters import calculate_voltages from roseau.load_flow.models.buses import Bus from roseau.load_flow.models.core import Element -from roseau.load_flow.typing import Id, JsonDict +from roseau.load_flow.typing import ComplexArray, Id, JsonDict from roseau.load_flow.units import Q_, ureg_wraps logger = logging.getLogger(__name__) @@ -66,7 +66,7 @@ def __init__( self.bus2 = bus2 self.geometry = geometry self._connect(bus1, bus2) - self._res_currents: Optional[tuple[np.ndarray, np.ndarray]] = None + self._res_currents: Optional[tuple[ComplexArray, ComplexArray]] = None def __repr__(self) -> str: s = f"{type(self).__name__}(id={self.id!r}, phases1={self.phases1!r}, phases2={self.phases2!r}" @@ -76,16 +76,16 @@ def __repr__(self) -> str: s += ")" return s - def _res_currents_getter(self, warning: bool) -> tuple[np.ndarray, np.ndarray]: + def _res_currents_getter(self, warning: bool) -> tuple[ComplexArray, ComplexArray]: return self._res_getter(value=self._res_currents, warning=warning) @property - @ureg_wraps(("A", "A"), (None,), strict=False) - def res_currents(self) -> tuple[Q_[np.ndarray], Q_[np.ndarray]]: + @ureg_wraps(("A", "A"), (None,)) + def res_currents(self) -> tuple[Q_[ComplexArray], Q_[ComplexArray]]: """The load flow result of the branch currents (A).""" return self._res_currents_getter(warning=True) - def _res_powers_getter(self, warning: bool) -> tuple[np.ndarray, np.ndarray]: + def _res_powers_getter(self, warning: bool) -> tuple[ComplexArray, ComplexArray]: cur1, cur2 = self._res_currents_getter(warning) pot1, pot2 = self._res_potentials_getter(warning=False) # we warn on the previous line powers1 = pot1 * cur1.conj() @@ -93,29 +93,29 @@ def _res_powers_getter(self, warning: bool) -> tuple[np.ndarray, np.ndarray]: return powers1, powers2 @property - @ureg_wraps(("VA", "VA"), (None,), strict=False) - def res_powers(self) -> tuple[Q_[np.ndarray], Q_[np.ndarray]]: + @ureg_wraps(("VA", "VA"), (None,)) + def res_powers(self) -> tuple[Q_[ComplexArray], Q_[ComplexArray]]: """The load flow result of the branch powers (VA).""" return self._res_powers_getter(warning=True) - def _res_potentials_getter(self, warning: bool) -> tuple[np.ndarray, np.ndarray]: + def _res_potentials_getter(self, warning: bool) -> tuple[ComplexArray, ComplexArray]: pot1 = self.bus1._get_potentials_of(self.phases1, warning) pot2 = self.bus2._get_potentials_of(self.phases2, warning=False) # we warn on the previous line return pot1, pot2 @property - @ureg_wraps(("V", "V"), (None,), strict=False) - def res_potentials(self) -> tuple[Q_[np.ndarray], Q_[np.ndarray]]: + @ureg_wraps(("V", "V"), (None,)) + def res_potentials(self) -> tuple[Q_[ComplexArray], Q_[ComplexArray]]: """The load flow result of the branch potentials (V).""" return self._res_potentials_getter(warning=True) - def _res_voltages_getter(self, warning: bool) -> tuple[np.ndarray, np.ndarray]: + def _res_voltages_getter(self, warning: bool) -> tuple[ComplexArray, ComplexArray]: pot1, pot2 = self._res_potentials_getter(warning) return calculate_voltages(pot1, self.phases1), calculate_voltages(pot2, self.phases2) @property - @ureg_wraps(("V", "V"), (None,), strict=False) - def res_voltages(self) -> tuple[Q_[np.ndarray], Q_[np.ndarray]]: + @ureg_wraps(("V", "V"), (None,)) + def res_voltages(self) -> tuple[Q_[ComplexArray], Q_[ComplexArray]]: """The load flow result of the branch voltages (V).""" return self._res_voltages_getter(warning=True) @@ -126,7 +126,7 @@ def res_voltages(self) -> tuple[Q_[np.ndarray], Q_[np.ndarray]]: def from_dict(cls, data: JsonDict) -> Self: return cls(**data) # not used anymore - def to_dict(self, include_geometry: bool = True) -> JsonDict: + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: res = { "id": self.id, "type": self.branch_type, @@ -135,13 +135,13 @@ def to_dict(self, include_geometry: bool = True) -> JsonDict: "bus1": self.bus1.id, "bus2": self.bus2.id, } - if self.geometry is not None and include_geometry: + if not _lf_only and self.geometry is not None: res["geometry"] = self.geometry.__geo_interface__ return res def results_from_dict(self, data: JsonDict) -> None: - currents1 = np.array([complex(i[0], i[1]) for i in data["currents1"]], dtype=complex) - currents2 = np.array([complex(i[0], i[1]) for i in data["currents2"]], dtype=complex) + currents1 = np.array([complex(i[0], i[1]) for i in data["currents1"]], dtype=np.complex128) + currents2 = np.array([complex(i[0], i[1]) for i in data["currents2"]], dtype=np.complex128) self._res_currents = (currents1, currents2) def _results_to_dict(self, warning: bool) -> JsonDict: diff --git a/roseau/load_flow/models/buses.py b/roseau/load_flow/models/buses.py index 604ce662..b84b256b 100644 --- a/roseau/load_flow/models/buses.py +++ b/roseau/load_flow/models/buses.py @@ -1,15 +1,16 @@ import logging -from collections.abc import Sequence -from typing import TYPE_CHECKING, Any, Optional +from collections.abc import Iterator +from typing import TYPE_CHECKING, Any, Optional, Union import numpy as np +import pandas as pd from shapely import Point from typing_extensions import Self -from roseau.load_flow.converters import calculate_voltage_phases, calculate_voltages +from roseau.load_flow.converters import calculate_voltage_phases, calculate_voltages, phasor_to_sym from roseau.load_flow.exceptions import RoseauLoadFlowException, RoseauLoadFlowExceptionCode from roseau.load_flow.models.core import Element -from roseau.load_flow.typing import Id, JsonDict +from roseau.load_flow.typing import ComplexArray, ComplexArrayLike1D, Id, JsonDict from roseau.load_flow.units import Q_, ureg_wraps logger = logging.getLogger(__name__) @@ -19,11 +20,7 @@ class Bus(Element): - """An electrical bus. - - See Also: - :doc:`Bus model documentation ` - """ + """A multi-phase electrical bus.""" allowed_phases = frozenset({"ab", "bc", "ca", "an", "bn", "cn", "abn", "bcn", "can", "abc", "abcn"}) """The allowed phases for a bus are: @@ -39,7 +36,9 @@ def __init__( *, phases: str, geometry: Optional[Point] = None, - potentials: Optional[Sequence[complex]] = None, + potentials: Optional[ComplexArrayLike1D] = None, + min_voltage: Optional[float] = None, + max_voltage: Optional[float] = None, **kwargs: Any, ) -> None: """Bus constructor. @@ -51,17 +50,27 @@ def __init__( phases: The phases of the bus. A string like ``"abc"`` or ``"an"`` etc. The order of the phases is important. For a full list of supported phases, see the class attribute - :attr:`Bus.allowed_phases`. + :attr:`.allowed_phases`. geometry: An optional geometry of the bus; a :class:`~shapely.Point` that represents the x-y coordinates of the bus. potentials: - An optional list of initial potentials of each phase of the bus. - - ground: - The ground of the bus. + An optional array-like of initial potentials of each phase of the bus. If given, + these potentials are used as the starting point of the load flow computation. + Either complex values (V) or a :class:`Quantity ` of + complex values. + + min_voltage: + An optional minimum voltage of the bus (V). It is not used in the load flow. + It must be a phase-neutral voltage if the bus has a neutral, phase-phase otherwise. + Either a float (V) or a :class:`Quantity ` of float. + + max_voltage: + An optional maximum voltage of the bus (V). It is not used in the load flow. + It must be a phase-neutral voltage if the bus has a neutral, phase-phase otherwise. + Either a float (V) or a :class:`Quantity ` of float. """ super().__init__(id, **kwargs) self._check_phases(id, phases=phases) @@ -70,45 +79,49 @@ def __init__( potentials = [0] * len(phases) self.potentials = potentials self.geometry = geometry + self._min_voltage: Optional[float] = None + self._max_voltage: Optional[float] = None + self.min_voltage = min_voltage + self.max_voltage = max_voltage - self._res_potentials: Optional[np.ndarray] = None + self._res_potentials: Optional[ComplexArray] = None self._short_circuits: list[dict[str, Any]] = [] def __repr__(self) -> str: return f"{type(self).__name__}(id={self.id!r}, phases={self.phases!r})" @property - @ureg_wraps("V", (None,), strict=False) - def potentials(self) -> Q_[np.ndarray]: - """The potentials of the bus (V).""" + @ureg_wraps("V", (None,)) + def potentials(self) -> Q_[ComplexArray]: + """An array of initial potentials of the bus (V).""" return self._potentials @potentials.setter - @ureg_wraps(None, (None, "V"), strict=False) - def potentials(self, value: Sequence[complex]) -> None: + @ureg_wraps(None, (None, "V")) + def potentials(self, value: ComplexArrayLike1D) -> None: if len(value) != len(self.phases): msg = f"Incorrect number of potentials: {len(value)} instead of {len(self.phases)}" logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_POTENTIALS_SIZE) - self._potentials = np.asarray(value, dtype=complex) + self._potentials = np.array(value, dtype=np.complex128) self._invalidate_network_results() - def _res_potentials_getter(self, warning: bool) -> np.ndarray: + def _res_potentials_getter(self, warning: bool) -> ComplexArray: return self._res_getter(value=self._res_potentials, warning=warning) @property - @ureg_wraps("V", (None,), strict=False) - def res_potentials(self) -> Q_[np.ndarray]: + @ureg_wraps("V", (None,)) + def res_potentials(self) -> Q_[ComplexArray]: """The load flow result of the bus potentials (V).""" return self._res_potentials_getter(warning=True) - def _res_voltages_getter(self, warning: bool) -> np.ndarray: - potentials = np.asarray(self._res_potentials_getter(warning=warning)) + def _res_voltages_getter(self, warning: bool) -> ComplexArray: + potentials = np.array(self._res_potentials_getter(warning=warning)) return calculate_voltages(potentials, self.phases) @property - @ureg_wraps("V", (None,), strict=False) - def res_voltages(self) -> Q_[np.ndarray]: + @ureg_wraps("V", (None,)) + def res_voltages(self) -> Q_[ComplexArray]: """The load flow result of the bus voltages (V). If the bus has a neutral, the voltages are phase-neutral voltages for existing phases in @@ -122,11 +135,178 @@ def voltage_phases(self) -> list[str]: """The phases of the voltages.""" return calculate_voltage_phases(self.phases) - def _get_potentials_of(self, phases: str, warning: bool) -> np.ndarray: + def _get_potentials_of(self, phases: str, warning: bool) -> ComplexArray: """Get the potentials of the given phases.""" potentials = self._res_potentials_getter(warning) return np.array([potentials[self.phases.index(p)] for p in phases]) + @property + def min_voltage(self) -> Optional[Q_[float]]: + """The minimum voltage of the bus (V) if it is set.""" + return None if self._min_voltage is None else Q_(self._min_voltage, "V") + + @min_voltage.setter + @ureg_wraps(None, (None, "V")) + def min_voltage(self, value: Optional[Union[float, Q_[float]]]) -> None: + if value is not None and self._max_voltage is not None and value > self._max_voltage: + msg = ( + f"Cannot set min voltage of bus {self.id!r} to {value} V as it is higher than its " + f"max voltage ({self._max_voltage} V)." + ) + logger.error(msg) + raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_VOLTAGES) + if pd.isna(value): + value = None + self._min_voltage = value + + @property + def max_voltage(self) -> Optional[Q_[float]]: + """The maximum voltage of the bus (V) if it is set.""" + return None if self._max_voltage is None else Q_(self._max_voltage, "V") + + @max_voltage.setter + @ureg_wraps(None, (None, "V")) + def max_voltage(self, value: Optional[Union[float, Q_[float]]]) -> None: + if value is not None and self._min_voltage is not None and value < self._min_voltage: + msg = ( + f"Cannot set max voltage of bus {self.id!r} to {value} V as it is lower than its " + f"min voltage ({self._min_voltage} V)." + ) + logger.error(msg) + raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_VOLTAGES) + if pd.isna(value): + value = None + self._max_voltage = value + + @property + def res_violated(self) -> Optional[bool]: + """Whether the bus has voltage limits violations. + + Returns ``None`` if the bus has no voltage limits are not set. + """ + if self._min_voltage is None and self._max_voltage is None: + return None + voltages = abs(self._res_voltages_getter(warning=True)) + if self._min_voltage is None: + assert self._max_voltage is not None + return float(max(voltages)) > self._max_voltage + elif self._max_voltage is None: + return float(min(voltages)) < self._min_voltage + else: + return float(min(voltages)) < self._min_voltage or float(max(voltages)) > self._max_voltage + + def propagate_limits(self, force: bool = False) -> None: + """Propagate the voltage limits to galvanically connected buses. + + Galvanically connected buses are buses connected to this bus through lines or switches. This + ensures that these voltage limits are only applied to buses with the same voltage level. If + a bus is connected to this bus through a transformer, the voltage limits are not propagated + to that bus. + + If this bus does not define any voltage limits, calling this method will unset the limits + of the connected buses. + + Args: + force: + If ``False`` (default), an exception is raised if connected buses already have + limits different from this bus. If ``True``, the limits are propagated even if + connected buses have different limits. + """ + from roseau.load_flow.models.lines import Line, Switch + + buses: set[Bus] = set() + visited: set[Element] = set() + remaining = set(self._connected_elements) + + while remaining: + branch = remaining.pop() + visited.add(branch) + if not isinstance(branch, (Line, Switch)): + continue + for element in branch._connected_elements: + if not isinstance(element, Bus) or element is self or element in buses: + continue + buses.add(element) + to_add = set(element._connected_elements).difference(visited) + remaining.update(to_add) + if not ( + force + or self._min_voltage is None + or element._min_voltage is None + or np.isclose(element._min_voltage, self._min_voltage) + ): + msg = ( + f"Cannot propagate the minimum voltage ({self._min_voltage} V) of bus {self.id!r} " + f"to bus {element.id!r} with different minimum voltage ({element._min_voltage} V)." + ) + logger.error(msg) + raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_VOLTAGES) + if not ( + force + or self._max_voltage is None + or element._max_voltage is None + or np.isclose(element._max_voltage, self._max_voltage) + ): + msg = ( + f"Cannot propagate the maximum voltage ({self._max_voltage} V) of bus {self.id!r} " + f"to bus {element.id!r} with different maximum voltage ({element._max_voltage} V)." + ) + logger.error(msg) + raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_VOLTAGES) + + for bus in buses: + bus._min_voltage = self._min_voltage + bus._max_voltage = self._max_voltage + + def get_connected_buses(self) -> Iterator[Id]: + """Get IDs of all the buses galvanically connected to this bus. + + These are all the buses connected via one or more lines or switches to this bus. + """ + from roseau.load_flow.models.lines import Line, Switch + + visited_buses = {self.id} + yield self.id + + visited: set[Element] = set() + remaining = set(self._connected_elements) + + while remaining: + branch = remaining.pop() + visited.add(branch) + if not isinstance(branch, (Line, Switch)): + continue + for element in branch._connected_elements: + if not isinstance(element, Bus) or element.id in visited_buses: + continue + visited_buses.add(element.id) + yield element.id + to_add = set(element._connected_elements).difference(visited) + remaining.update(to_add) + + @ureg_wraps("percent", (None,)) + def res_voltage_unbalance(self) -> Q_[float]: + """Calculate the voltage unbalance on this bus according to the IEC definition. + + Voltage Unbalance Factor: + + :math:`VUF = \\frac{|V_n|}{|V_p|} * 100 (\\%)` + + Where :math:`V_n` is the negative-sequence voltage and :math:`V_p` is the positive-sequence + voltage. + """ + # https://std.iec.ch/terms/terms.nsf/3385f156e728849bc1256e8c00278ad2/771c5188e62fade5c125793a0043f2a5?OpenDocument + if self.phases not in {"abc", "abcn"}: + msg = f"Voltage unbalance is only available for 3-phases buses, bus {self.id!r} has phases {self.phases!r}" + logger.error(msg) + raise RoseauLoadFlowException(msg, code=RoseauLoadFlowExceptionCode.BAD_PHASE) + # We use the potentials here which is equivalent to using the "line to neutral" voltages as + # defined by the standard. The standard also has this note: + # NOTE 1 Phase-to-phase voltages may also be used instead of line to neutral voltages. + potentials = self._res_potentials_getter(warning=True) + _, vp, vn = phasor_to_sym(potentials[:3]) # (0, +, -) + return abs(vn) / abs(vp) * 100 + # # Json Mixin interface # @@ -136,18 +316,30 @@ def from_dict(cls, data: JsonDict) -> Self: potentials = data.get("potentials") if potentials is not None: potentials = [complex(v[0], v[1]) for v in potentials] - return cls(id=data["id"], phases=data["phases"], geometry=geometry, potentials=potentials) - - def to_dict(self, include_geometry: bool = True) -> JsonDict: + return cls( + id=data["id"], + phases=data["phases"], + geometry=geometry, + potentials=potentials, + min_voltage=data.get("min_voltage"), + max_voltage=data.get("max_voltage"), + ) + + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: res = {"id": self.id, "phases": self.phases} if not np.allclose(self.potentials, 0): res["potentials"] = [[v.real, v.imag] for v in self._potentials] - if self.geometry is not None and include_geometry: - res["geometry"] = self.geometry.__geo_interface__ + if not _lf_only: + if self.geometry is not None: + res["geometry"] = self.geometry.__geo_interface__ + if self.min_voltage is not None: + res["min_voltage"] = self.min_voltage.magnitude + if self.max_voltage is not None: + res["max_voltage"] = self.max_voltage.magnitude return res def results_from_dict(self, data: JsonDict) -> None: - self._res_potentials = np.array([complex(v[0], v[1]) for v in data["potentials"]], dtype=complex) + self._res_potentials = np.array([complex(v[0], v[1]) for v in data["potentials"]], dtype=np.complex128) def _results_to_dict(self, warning: bool) -> JsonDict: return { @@ -204,6 +396,6 @@ def short_circuits(self) -> list[dict[str, Any]]: """Return the list of short-circuits of this bus.""" return self._short_circuits[:] # return a copy as users should not modify the list directly - def clear_short_circuits(self): - """Remove the short-circuits.""" + def clear_short_circuits(self) -> None: + """Remove the short-circuits of this bus.""" self._short_circuits = [] diff --git a/roseau/load_flow/models/grounds.py b/roseau/load_flow/models/grounds.py index bbaf0cb1..083534af 100644 --- a/roseau/load_flow/models/grounds.py +++ b/roseau/load_flow/models/grounds.py @@ -1,14 +1,16 @@ import logging -from typing import Any, Optional +from typing import TYPE_CHECKING, Any, Optional from typing_extensions import Self from roseau.load_flow.exceptions import RoseauLoadFlowException, RoseauLoadFlowExceptionCode -from roseau.load_flow.models.buses import Bus from roseau.load_flow.models.core import Element from roseau.load_flow.typing import Id, JsonDict from roseau.load_flow.units import Q_, ureg_wraps +if TYPE_CHECKING: + from roseau.load_flow.models.buses import Bus + logger = logging.getLogger(__name__) @@ -28,10 +30,6 @@ class Ground(Element): To connect a ground to a line with shunt components, pass the ground object to the :class:`Line` constructor. Note that the ground connection is mandatory for shunt lines. - - - See Also: - :doc:`Ground model documentation ` """ allowed_phases = frozenset({"a", "b", "c", "n"}) @@ -55,7 +53,7 @@ def _res_potential_getter(self, warning: bool) -> complex: return self._res_getter(self._res_potential, warning) @property - @ureg_wraps("V", (None,), strict=False) + @ureg_wraps("V", (None,)) def res_potential(self) -> Q_[complex]: """The load flow result of the ground potential (V).""" return self._res_potential_getter(warning=True) @@ -65,7 +63,7 @@ def connected_buses(self) -> dict[Id, str]: """The bus ID and phase of the buses connected to this ground.""" return self._connected_buses.copy() # copy so that the user does not change it - def connect(self, bus: Bus, phase: str = "n") -> None: + def connect(self, bus: "Bus", phase: str = "n") -> None: """Connect the ground to a bus on the given phase. Args: @@ -97,7 +95,7 @@ def from_dict(cls, data: JsonDict) -> Self: self._connected_buses = data["buses"] return self - def to_dict(self, include_geometry: bool = True) -> JsonDict: + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: # Shunt lines and potential references will have the ground in their dict not here. return { "id": self.id, diff --git a/roseau/load_flow/models/lines/lines.py b/roseau/load_flow/models/lines/lines.py index f6862de8..6a0a4d3e 100644 --- a/roseau/load_flow/models/lines/lines.py +++ b/roseau/load_flow/models/lines/lines.py @@ -1,6 +1,6 @@ import logging import warnings -from typing import Any, Optional +from typing import Any, Optional, Union import numpy as np from shapely import LineString, Point @@ -12,18 +12,14 @@ from roseau.load_flow.models.grounds import Ground from roseau.load_flow.models.lines.parameters import LineParameters from roseau.load_flow.models.sources import VoltageSource -from roseau.load_flow.typing import Id, JsonDict +from roseau.load_flow.typing import ComplexArray, Id, JsonDict from roseau.load_flow.units import Q_, ureg_wraps logger = logging.getLogger(__name__) class Switch(AbstractBranch): - """A general purpose switch branch. - - See Also: - :doc:`Switch model documentation ` - """ + """A general purpose switch branch.""" branch_type = "switch" @@ -128,11 +124,7 @@ def _check_elements(self) -> None: class Line(AbstractBranch): - """An electrical line PI model with series impedance and optional shunt admittance. - - See Also: - :doc:`Line documentation ` - """ + """An electrical line PI model with series impedance and optional shunt admittance.""" branch_type = "line" @@ -152,7 +144,7 @@ def __init__( bus2: Bus, *, parameters: LineParameters, - length: float, + length: Union[float, Q_[float]], phases: Optional[str] = None, ground: Optional[Ground] = None, geometry: Optional[LineString] = None, @@ -171,7 +163,8 @@ def __init__( The second bus (aka `"to_bus"`) to connect to the line. parameters: - The parameters of the line, an instance of :class:`LineParameters`. + Parameters defining the electrical model of the line. This is an instance of the + :class:`LineParameters` class and can be used by multiple lines. length: The length of the line in km. @@ -217,7 +210,7 @@ def __init__( self._initialized = True # Handle the ground - if self.ground is not None and not self.parameters.with_shunt: + if self.ground is not None and not self.with_shunt: warnings.warn( message=( f"The ground element must not be provided for line {self.id!r} as it does not have a shunt " @@ -227,18 +220,18 @@ def __init__( stacklevel=2, ) self.ground = None - elif self.parameters.with_shunt: + elif self.with_shunt: # Connect the ground self._connect(self.ground) @property - @ureg_wraps("km", (None,), strict=False) + @ureg_wraps("km", (None,)) def length(self) -> Q_[float]: return self._length @length.setter - @ureg_wraps(None, (None, "km"), strict=False) - def length(self, value: float) -> None: + @ureg_wraps(None, (None, "km")) + def length(self, value: Union[float, Q_[float]]) -> None: if value <= 0: msg = f"A line length must be greater than 0. {value:.2f} km provided." logger.error(msg) @@ -260,7 +253,7 @@ def parameters(self, value: LineParameters) -> None: raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_Z_LINE_SHAPE) if value.with_shunt: - if self._initialized and not self.parameters.with_shunt: + if self._initialized and not self.with_shunt: msg = "Cannot set line parameters with a shunt to a line that does not have shunt components." logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_LINE_MODEL) @@ -273,94 +266,128 @@ def parameters(self, value: LineParameters) -> None: logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_LINE_TYPE) else: - if self._initialized and self.parameters.with_shunt: + if self._initialized and self.with_shunt: msg = "Cannot set line parameters without a shunt to a line that has shunt components." logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_LINE_MODEL) self._parameters = value self._invalidate_network_results() - def _res_series_values_getter(self, warning: bool) -> tuple[np.ndarray, np.ndarray]: + @property + @ureg_wraps("ohm", (None,)) + def z_line(self) -> Q_[ComplexArray]: + """Impedance of the line in Ohm""" + return self.parameters._z_line * self._length + + @property + @ureg_wraps("S", (None,)) + def y_shunt(self) -> Q_[ComplexArray]: + """Shunt admittance of the line in Siemens""" + return self.parameters._y_shunt * self._length + + @property + def max_current(self) -> Optional[Q_[float]]: + """The maximum current loading of the line in A.""" + # Do not add a setter. The user must know that if they change the max_current, it changes + # for all lines that share the parameters. It is better to set it on the parameters. + return self.parameters.max_current + + @property + def with_shunt(self) -> bool: + return self.parameters.with_shunt + + def _res_series_values_getter(self, warning: bool) -> tuple[ComplexArray, ComplexArray]: pot1, pot2 = self._res_potentials_getter(warning) # V du_line = pot1 - pot2 - z_line = self.parameters.z_line * self.length - i_line = np.linalg.inv(z_line.m_as("ohm")) @ du_line # Zₗ x Iₗ = ΔU -> I = Zₗ⁻¹ x ΔU + i_line = np.linalg.inv(self.z_line.m_as("ohm")) @ du_line # Zₗ x Iₗ = ΔU -> I = Zₗ⁻¹ x ΔU return du_line, i_line - def _res_series_currents_getter(self, warning: bool) -> np.ndarray: + def _res_series_currents_getter(self, warning: bool) -> ComplexArray: _, i_line = self._res_series_values_getter(warning) return i_line @property - @ureg_wraps("A", (None,), strict=False) - def res_series_currents(self) -> Q_[np.ndarray]: + @ureg_wraps("A", (None,)) + def res_series_currents(self) -> Q_[ComplexArray]: """Get the current in the series elements of the line (A).""" return self._res_series_currents_getter(warning=True) - def _res_series_power_losses_getter(self, warning: bool) -> np.ndarray: + def _res_series_power_losses_getter(self, warning: bool) -> ComplexArray: du_line, i_line = self._res_series_values_getter(warning) return du_line * i_line.conj() # Sₗ = ΔU.Iₗ* @property - @ureg_wraps("VA", (None,), strict=False) - def res_series_power_losses(self) -> Q_[np.ndarray]: + @ureg_wraps("VA", (None,)) + def res_series_power_losses(self) -> Q_[ComplexArray]: """Get the power losses in the series elements of the line (VA).""" return self._res_series_power_losses_getter(warning=True) - def _res_shunt_values_getter(self, warning: bool) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: - assert self.parameters.with_shunt, "this method only works when there is a shunt" - y_shunt = self.parameters.y_shunt + def _res_shunt_values_getter(self, warning: bool) -> tuple[ComplexArray, ComplexArray, ComplexArray, ComplexArray]: + assert self.with_shunt, "This method only works when there is a shunt" assert self.ground is not None pot1, pot2 = self._res_potentials_getter(warning) vg = self.ground.res_potential.m_as("V") - y_shunt = (y_shunt * self.length).m_as("S") + y_shunt = self.y_shunt.m_as("S") yg = y_shunt.sum(axis=1) # y_ig = Y_ia + Y_ib + Y_ic + Y_in for i in {a, b, c, n} i1_shunt = (y_shunt @ pot1 - yg * vg) / 2 i2_shunt = (y_shunt @ pot2 - yg * vg) / 2 return pot1, pot2, i1_shunt, i2_shunt - def _res_shunt_currents_getter(self, warning: bool) -> tuple[np.ndarray, np.ndarray]: - if not self.parameters.with_shunt: - zeros = np.zeros(len(self.phases), dtype=complex) + def _res_shunt_currents_getter(self, warning: bool) -> tuple[ComplexArray, ComplexArray]: + if not self.with_shunt: + zeros = np.zeros(len(self.phases), dtype=np.complex128) return zeros[:], zeros[:] _, _, cur1, cur2 = self._res_shunt_values_getter(warning) return cur1, cur2 @property - @ureg_wraps(("A", "A"), (None,), strict=False) - def res_shunt_currents(self) -> tuple[Q_[np.ndarray], Q_[np.ndarray]]: + @ureg_wraps(("A", "A"), (None,)) + def res_shunt_currents(self) -> tuple[Q_[ComplexArray], Q_[ComplexArray]]: """Get the currents in the shunt elements of the line (A).""" return self._res_shunt_currents_getter(warning=True) - def _res_shunt_power_losses_getter(self, warning: bool) -> np.ndarray: - if not self.parameters.with_shunt: - return np.zeros(len(self.phases), dtype=complex) + def _res_shunt_power_losses_getter(self, warning: bool) -> ComplexArray: + if not self.with_shunt: + return np.zeros(len(self.phases), dtype=np.complex128) pot1, pot2, cur1, cur2 = self._res_shunt_values_getter(warning) return pot1 * cur1.conj() + pot2 * cur2.conj() @property - @ureg_wraps("VA", (None,), strict=False) - def res_shunt_power_losses(self) -> Q_[np.ndarray]: + @ureg_wraps("VA", (None,)) + def res_shunt_power_losses(self) -> Q_[ComplexArray]: """Get the power losses in the shunt elements of the line (VA).""" return self._res_shunt_power_losses_getter(warning=True) - def _res_power_losses_getter(self, warning: bool) -> np.ndarray: + def _res_power_losses_getter(self, warning: bool) -> ComplexArray: series_losses = self._res_series_power_losses_getter(warning) shunt_losses = self._res_shunt_power_losses_getter(warning=False) # we warn on the previous line return series_losses + shunt_losses @property - @ureg_wraps("VA", (None,), strict=False) - def res_power_losses(self) -> Q_[np.ndarray]: + @ureg_wraps("VA", (None,)) + def res_power_losses(self) -> Q_[ComplexArray]: """Get the power losses in the line (VA).""" return self._res_power_losses_getter(warning=True) + @property + def res_violated(self) -> Optional[bool]: + """Whether the line current exceeds the maximum current (loading > 100%). + + Returns ``None`` if the maximum current is not set. + """ + i_max = self.parameters._max_current + if i_max is None: + return None + currents1, currents2 = self._res_currents_getter(warning=True) + # True if any phase is overloaded + return float(np.max([abs(currents1), abs(currents2)])) > i_max + # # Json Mixin interface # - def to_dict(self, include_geometry: bool = True) -> JsonDict: + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: res = { - **super().to_dict(include_geometry=include_geometry), + **super().to_dict(_lf_only=_lf_only), "length": self._length, "params_id": self.parameters.id, } diff --git a/roseau/load_flow/models/lines/parameters.py b/roseau/load_flow/models/lines/parameters.py index 9c21bd78..a2776fe9 100644 --- a/roseau/load_flow/models/lines/parameters.py +++ b/roseau/load_flow/models/lines/parameters.py @@ -1,14 +1,14 @@ import logging import re -from typing import NoReturn, Optional +from typing import NoReturn, Optional, Union import numpy as np import numpy.linalg as nplin import pandas as pd -from typing_extensions import Self +from typing_extensions import Self, deprecated from roseau.load_flow.exceptions import RoseauLoadFlowException, RoseauLoadFlowExceptionCode -from roseau.load_flow.typing import Id, JsonDict +from roseau.load_flow.typing import ComplexArray, ComplexArrayLike2D, Id, JsonDict from roseau.load_flow.units import Q_, ureg_wraps from roseau.load_flow.utils import ( CX, @@ -30,11 +30,7 @@ class LineParameters(Identifiable, JsonMixin): - """A class to store the line parameters of lines - - See Also: - :ref:`Line parameters documentation ` - """ + """Parameters that define electrical models of lines.""" _type_re = "|".join("|".join(x) for x in LineType.CODES.values()) _material_re = "|".join(x.code() for x in ConductorType) @@ -43,8 +39,14 @@ class LineParameters(Identifiable, JsonMixin): rf"^({_type_re})_({_material_re})_{_section_re}$", flags=re.IGNORECASE ) - @ureg_wraps(None, (None, None, "ohm/km", "S/km"), strict=False) - def __init__(self, id: Id, z_line: np.ndarray, y_shunt: Optional[np.ndarray] = None) -> None: + @ureg_wraps(None, (None, None, "ohm/km", "S/km", "A")) + def __init__( + self, + id: Id, + z_line: ComplexArrayLike2D, + y_shunt: Optional[ComplexArrayLike2D] = None, + max_current: Optional[float] = None, + ) -> None: """LineParameters constructor. Args: @@ -56,15 +58,19 @@ def __init__(self, id: Id, z_line: np.ndarray, y_shunt: Optional[np.ndarray] = N y_shunt: The Y matrix of the line (Siemens/km). This field is optional if the line has no shunt part. + + max_current: + An optional maximum current loading of the line (A). It is not used in the load flow. """ super().__init__(id) - self._z_line = np.asarray(z_line, dtype=complex) + self._z_line = np.array(z_line, dtype=np.complex128) if y_shunt is None: self._with_shunt = False - self._y_shunt = np.zeros_like(z_line, dtype=complex) + self._y_shunt = np.zeros_like(self._z_line, dtype=np.complex128) else: self._with_shunt = not np.allclose(y_shunt, 0) - self._y_shunt = np.asarray(y_shunt, dtype=complex) + self._y_shunt = np.array(y_shunt, dtype=np.complex128) + self.max_current = max_current self._check_matrix() def __eq__(self, other: object) -> bool: @@ -86,34 +92,43 @@ def __eq__(self, other: object) -> bool: ) @property - @ureg_wraps("ohm/km", (None,), strict=False) - def z_line(self) -> Q_[np.ndarray]: + @ureg_wraps("ohm/km", (None,)) + def z_line(self) -> Q_[ComplexArray]: return self._z_line @property - @ureg_wraps("S/km", (None,), strict=False) - def y_shunt(self) -> Q_[np.ndarray]: + @ureg_wraps("S/km", (None,)) + def y_shunt(self) -> Q_[ComplexArray]: return self._y_shunt @property def with_shunt(self) -> bool: return self._with_shunt + @property + def max_current(self) -> Optional[Q_[float]]: + """The maximum current loading of the line (A) if it is set.""" + return None if self._max_current is None else Q_(self._max_current, "A") + + @max_current.setter + @ureg_wraps(None, (None, "A")) + def max_current(self, value: Optional[Union[float, Q_[float]]]) -> None: + self._max_current = value + @classmethod - @ureg_wraps( - None, (None, None, "ohm/km", "ohm/km", "S/km", "S/km", "ohm/km", "ohm/km", "S/km", "S/km"), strict=False - ) + @ureg_wraps(None, (None, None, "ohm/km", "ohm/km", "S/km", "S/km", "ohm/km", "ohm/km", "S/km", "S/km", "A")) def from_sym( cls, id: Id, - z0: complex, - z1: complex, - y0: complex, - y1: complex, - zn: Optional[complex] = None, - xpn: Optional[float] = None, - bn: Optional[float] = None, - bpn: Optional[float] = None, + z0: Union[complex, Q_[complex]], + z1: Union[complex, Q_[complex]], + y0: Union[complex, Q_[complex]], + y1: Union[complex, Q_[complex]], + zn: Optional[Union[complex, Q_[complex]]] = None, + xpn: Optional[Union[float, Q_[float]]] = None, + bn: Optional[Union[float, Q_[float]]] = None, + bpn: Optional[Union[float, Q_[float]]] = None, + max_current: Optional[Union[float, Q_[float]]] = None, ) -> Self: """Create line parameters from a symmetric model. @@ -145,6 +160,9 @@ def from_sym( bpn: Phase to neutral susceptance (siemens/km) + max_current: + An optional maximum current loading of the line (A). It is not used in the load flow. + Returns: The created line parameters. @@ -154,7 +172,7 @@ def from_sym( impedance matrix is not invertible. """ z_line, y_shunt = cls._sym_to_zy(id=id, z0=z0, z1=z1, y0=y0, y1=y1, zn=zn, xpn=xpn, bn=bn, bpn=bpn) - return cls(id=id, z_line=z_line, y_shunt=y_shunt) + return cls(id=id, z_line=z_line, y_shunt=y_shunt, max_current=max_current) @staticmethod def _sym_to_zy( @@ -167,7 +185,7 @@ def _sym_to_zy( xpn: Optional[float] = None, bn: Optional[float] = None, bpn: Optional[float] = None, - ) -> tuple[np.ndarray, np.ndarray]: + ) -> tuple[ComplexArray, ComplexArray]: """Create impedance and admittance matrix from a symmetrical model. Args: @@ -225,8 +243,8 @@ def _sym_to_zy( # If all the neutral data have not been filled, the matrix is a 3x3 matrix if any_neutral_na: # No neutral data so retrieve a 3x3 matrix - z_line = np.array([[zs, zm, zm], [zm, zs, zm], [zm, zm, zs]], dtype=complex) - y_shunt = np.array([[ys, ym, ym], [ym, ys, ym], [ym, ym, ys]], dtype=complex) + z_line = np.array([[zs, zm, zm], [zm, zs, zm], [zm, zm, zs]], dtype=np.complex128) + y_shunt = np.array([[ys, ym, ym], [ym, ys, ym], [ym, ym, ys]], dtype=np.complex128) else: # Build the complex # zn: Neutral series impedance (ohm/km) @@ -239,16 +257,16 @@ def _sym_to_zy( f"The line model {id!r} does not have neutral elements. It will be modelled as a 3 wires line " f"instead." ) - z_line = np.array([[zs, zm, zm], [zm, zs, zm], [zm, zm, zs]], dtype=complex) - y_shunt = np.array([[ys, ym, ym], [ym, ys, ym], [ym, ym, ys]], dtype=complex) + z_line = np.array([[zs, zm, zm], [zm, zs, zm], [zm, zm, zs]], dtype=np.complex128) + y_shunt = np.array([[ys, ym, ym], [ym, ys, ym], [ym, ym, ys]], dtype=np.complex128) else: z_line = np.array( [[zs, zm, zm, zpn], [zm, zs, zm, zpn], [zm, zm, zs, zpn], [zpn, zpn, zpn, zn]], - dtype=complex, + dtype=np.complex128, ) y_shunt = np.array( [[ys, ym, ym, ypn], [ym, ys, ym, ypn], [ym, ym, ys, ypn], [ypn, ypn, ypn, yn]], - dtype=complex, + dtype=np.complex128, ) # Check the validity of the resulting matrices @@ -277,17 +295,18 @@ def _sym_to_zy( return z_line, y_shunt @classmethod - @ureg_wraps(None, (None, None, None, None, None, "mm**2", "mm**2", "m", "m"), strict=False) + @ureg_wraps(None, (None, None, None, None, None, "mm**2", "mm**2", "m", "m", "A")) def from_geometry( cls, id: Id, line_type: LineType, conductor_type: ConductorType, insulator_type: InsulatorType, - section: float, - section_neutral: float, - height: float, - external_diameter: float, + section: Union[float, Q_[float]], + section_neutral: Union[float, Q_[float]], + height: Union[float, Q_[float]], + external_diameter: Union[float, Q_[float]], + max_current: Optional[Union[float, Q_[float]]] = None, ) -> Self: """Create line parameters from its geometry. @@ -316,6 +335,9 @@ def from_geometry( external_diameter: External diameter of the wire (m). + max_current: + An optional maximum current loading of the line (A). It is not used in the load flow. + Returns: The created line parameters. @@ -332,7 +354,7 @@ def from_geometry( height=height, external_diameter=external_diameter, ) - return cls(id=id, z_line=z_line, y_shunt=y_shunt) + return cls(id=id, z_line=z_line, y_shunt=y_shunt, max_current=max_current) @staticmethod def _geometry_to_zy( @@ -344,7 +366,7 @@ def _geometry_to_zy( section_neutral: float, height: float, external_diameter: float, - ) -> tuple[np.ndarray, np.ndarray]: + ) -> tuple[ComplexArray, ComplexArray]: """Create impedance and admittance matrix using a geometric model. Args: @@ -424,7 +446,7 @@ def _geometry_to_zy( raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_LINE_TYPE) # Distance computation - sections = np.array([section, section, section, section_neutral], dtype=float) * 1e-6 # surfaces (m2) + sections = np.array([section, section, section, section_neutral], dtype=np.float64) * 1e-6 # surfaces (m2) radius = np.sqrt(sections / PI) # radius (m) gmr = radius * np.exp(-0.25) # geometric mean radius (m) # distance between two wires (m) @@ -436,13 +458,13 @@ def _geometry_to_zy( distance_prim = np.sqrt(np.einsum("ijk,ijk->ij", diff, diff)) # Useful matrices - mask_diagonal = np.eye(4, dtype=bool) + mask_diagonal = np.eye(4, dtype=np.bool_) mask_off_diagonal = ~mask_diagonal - minus = -np.ones((4, 4), dtype=float) + minus = -np.ones((4, 4), dtype=np.float64) np.fill_diagonal(minus, 1) # Electrical parameters - r = RHO[conductor_type].m_as("ohm*m") / sections * np.eye(4, dtype=float) * 1e3 # resistance (ohm/km) + r = RHO[conductor_type].m_as("ohm*m") / sections * np.eye(4, dtype=np.float64) * 1e3 # resistance (ohm/km) distance[mask_diagonal] = gmr inductance = MU_0.m_as("H/m") / (2 * PI) * np.log(1 / distance) * 1e3 # H/m->H/km distance[mask_diagonal] = radius @@ -450,32 +472,38 @@ def _geometry_to_zy( # Extract the conductivity and the capacities from the lambda (potential coefficients) lambda_inv = nplin.inv(lambdas) * 1e3 # capacities (F/km) - c = np.zeros((4, 4), dtype=float) # capacities (F/km) + c = np.zeros((4, 4), dtype=np.float64) # capacities (F/km) c[mask_diagonal] = np.einsum("ij,ij->i", lambda_inv, minus) c[mask_off_diagonal] = -lambda_inv[mask_off_diagonal] - g = np.zeros((4, 4), dtype=float) # conductance (S/km) + g = np.zeros((4, 4), dtype=np.float64) # conductance (S/km) omega = OMEGA.m_as("rad/s") - g[mask_diagonal] = TAN_D[insulator_type] * np.einsum("ii->i", c) * omega + g[mask_diagonal] = TAN_D[insulator_type].magnitude * np.einsum("ii->i", c) * omega # Build the impedance and admittance matrices z_line = r + inductance * omega * 1j y = g + c * omega * 1j # Compute the shunt admittance matrix from the admittance matrix - y_shunt = np.zeros((4, 4), dtype=complex) + y_shunt = np.zeros((4, 4), dtype=np.complex128) y_shunt[mask_diagonal] = np.einsum("ij->i", y) y_shunt[mask_off_diagonal] = -y[mask_off_diagonal] return z_line, y_shunt @classmethod - @ureg_wraps(None, (None, None, "mm²", "m", "mm"), strict=False) + @deprecated( + "The method LineParameters.from_name_lv() is deprecated and will be removed in a future " + "version. Use LineParameters.from_geometry() instead.", + category=FutureWarning, + ) + @ureg_wraps(None, (None, None, "mm²", "m", "mm", "A")) def from_name_lv( cls, name: str, - section_neutral: Optional[float] = None, - height: Optional[float] = None, - external_diameter: Optional[float] = None, + section_neutral: Optional[Union[float, Q_[float]]] = None, + height: Optional[Union[float, Q_[float]]] = None, + external_diameter: Optional[Union[float, Q_[float]]] = None, + max_current: Optional[Union[float, Q_[float]]] = None, ) -> Self: """Method to get the electrical parameters of a LV line from its canonical name. Some hypothesis will be made: the section of the neutral is the same as the other sections, the height and @@ -494,8 +522,14 @@ def from_name_lv( external_diameter: External diameter of the wire (mm). If None a default value will be used. + max_current: + An optional maximum current loading of the line (A). It is not used in the load flow. + Returns: The corresponding line parameters. + + .. deprecated:: 0.6.0 + Use :meth:`LineParameters.from_geometry` instead. """ match = cls._REGEXP_LINE_TYPE_NAME.fullmatch(string=name) if not match: @@ -527,16 +561,21 @@ def from_name_lv( section_neutral=section_neutral, height=height, external_diameter=external_diameter, + max_current=max_current, ) @classmethod - def from_name_mv(cls, name: str) -> Self: + @ureg_wraps(None, (None, None, "A")) + def from_name_mv(cls, name: str, max_current: Optional[Union[float, Q_[float]]] = None) -> Self: """Method to get the electrical parameters of a MV line from its canonical name. Args: name: The name of the line the parameters must be computed. E.g. "U_AL_150". + max_current: + An optional maximum current loading of the line (A). It is not used in the load flow. + Returns: The corresponding line parameters. """ @@ -571,9 +610,9 @@ def from_name_mv(cls, name: str) -> Self: b = (c_b1 + c_b2 * section) * 1e-4 * OMEGA b = b.to("S/km") - z_line = (r + x * 1j) * np.eye(3, dtype=float) # in ohms/km - y_shunt = b * 1j * np.eye(3, dtype=float) # in siemens/km - return cls(name, z_line=z_line, y_shunt=y_shunt) + z_line = (r + x * 1j) * np.eye(3, dtype=np.float64) # in ohms/km + y_shunt = b * 1j * np.eye(3, dtype=np.float64) # in siemens/km + return cls(name, z_line=z_line, y_shunt=y_shunt, max_current=max_current) # # Json Mixin interface @@ -589,15 +628,17 @@ def from_dict(cls, data: JsonDict) -> Self: Returns: The created line parameters. """ - z_line = np.asarray(data["z_line"][0]) + 1j * np.asarray(data["z_line"][1]) - y_shunt = np.asarray(data["y_shunt"][0]) + 1j * np.asarray(data["y_shunt"][1]) if "y_shunt" in data else None - return cls(id=data["id"], z_line=z_line, y_shunt=y_shunt) + z_line = np.array(data["z_line"][0]) + 1j * np.array(data["z_line"][1]) + y_shunt = np.array(data["y_shunt"][0]) + 1j * np.array(data["y_shunt"][1]) if "y_shunt" in data else None + return cls(id=data["id"], z_line=z_line, y_shunt=y_shunt, max_current=data.get("max_current")) - def to_dict(self, include_geometry: bool = True) -> JsonDict: + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: """Return the line parameters information as a dictionary format.""" res = {"id": self.id, "z_line": [self._z_line.real.tolist(), self._z_line.imag.tolist()]} if self.with_shunt: res["y_shunt"] = [self._y_shunt.real.tolist(), self._y_shunt.imag.tolist()] + if not _lf_only and self.max_current is not None: + res["max_current"] = self.max_current.magnitude return res def _results_to_dict(self, warning: bool) -> NoReturn: diff --git a/roseau/load_flow/models/loads/flexible_parameters.py b/roseau/load_flow/models/loads/flexible_parameters.py index 55cdc7f6..d687fbae 100644 --- a/roseau/load_flow/models/loads/flexible_parameters.py +++ b/roseau/load_flow/models/loads/flexible_parameters.py @@ -1,17 +1,28 @@ import logging import warnings -from typing import NoReturn +from typing import TYPE_CHECKING, NoReturn, Optional, Union import numpy as np +from numpy.typing import NDArray from typing_extensions import Self from roseau.load_flow.exceptions import RoseauLoadFlowException, RoseauLoadFlowExceptionCode -from roseau.load_flow.typing import ControlType, JsonDict, ProjectionType +from roseau.load_flow.typing import ( + Authentication, + ComplexArray, + ComplexArrayLike1D, + ControlType, + JsonDict, + ProjectionType, +) from roseau.load_flow.units import Q_, ureg_wraps -from roseau.load_flow.utils import JsonMixin +from roseau.load_flow.utils import JsonMixin, _optional_deps logger = logging.getLogger(__name__) +if TYPE_CHECKING: + from matplotlib.axes import Axes + class Control(JsonMixin): """Control class for flexible loads. @@ -29,22 +40,19 @@ class Control(JsonMixin): :math:`P^{\\max}_{\\mathrm{cons}}(U)`. * ``"q_u"``: control the reactive power based on the voltage :math:`Q(U)`. - - See Also: - :ref:`Control documentation ` """ - DEFAULT_ALPHA: float = 1000.0 + _DEFAULT_ALPHA: float = 1000.0 - @ureg_wraps(None, (None, None, "V", "V", "V", "V", None), strict=False) + @ureg_wraps(None, (None, None, "V", "V", "V", "V", None)) def __init__( self, type: ControlType, - u_min: float, - u_down: float, - u_up: float, - u_max: float, - alpha: float = DEFAULT_ALPHA, + u_min: Union[float, Q_[float]], + u_down: Union[float, Q_[float]], + u_up: Union[float, Q_[float]], + u_max: Union[float, Q_[float]], + alpha: Union[float, Q_[float]] = _DEFAULT_ALPHA, ) -> None: """Control constructor. @@ -144,25 +152,25 @@ def _check_values(self) -> None: raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_CONTROL_VALUE) @property - @ureg_wraps("V", (None,), strict=False) + @ureg_wraps("V", (None,)) def u_min(self) -> Q_[float]: """The minimum voltage i.e. the one the control reached the maximum action.""" return self._u_min @property - @ureg_wraps("V", (None,), strict=False) + @ureg_wraps("V", (None,)) def u_down(self) -> Q_[float]: """The voltage which starts to trigger the control (lower value).""" return self._u_down @property - @ureg_wraps("V", (None,), strict=False) + @ureg_wraps("V", (None,)) def u_up(self) -> Q_[float]: """TThe voltage which starts to trigger the control (upper value).""" return self._u_up @property - @ureg_wraps("V", (None,), strict=False) + @ureg_wraps("V", (None,)) def u_max(self) -> Q_[float]: """The maximum voltage i.e. the one the control reached its maximum action.""" return self._u_max @@ -179,8 +187,10 @@ def constant(cls) -> Self: return cls(type="constant", u_min=0.0, u_down=0.0, u_up=0.0, u_max=0.0) @classmethod - @ureg_wraps(None, (None, "V", "V", None), strict=False) - def p_max_u_production(cls, u_up: float, u_max: float, alpha: float = DEFAULT_ALPHA) -> Self: + @ureg_wraps(None, (None, "V", "V", None)) + def p_max_u_production( + cls, u_up: Union[float, Q_[float]], u_max: Union[float, Q_[float]], alpha: float = _DEFAULT_ALPHA + ) -> Self: """Create a control of the type ``"p_max_u_production"``. See Also: @@ -208,8 +218,10 @@ def p_max_u_production(cls, u_up: float, u_max: float, alpha: float = DEFAULT_AL return cls(type="p_max_u_production", u_min=0.0, u_down=0.0, u_up=u_up, u_max=u_max, alpha=alpha) @classmethod - @ureg_wraps(None, (None, "V", "V", None), strict=False) - def p_max_u_consumption(cls, u_min: float, u_down: float, alpha: float = DEFAULT_ALPHA) -> Self: + @ureg_wraps(None, (None, "V", "V", None)) + def p_max_u_consumption( + cls, u_min: Union[float, Q_[float]], u_down: Union[float, Q_[float]], alpha: float = _DEFAULT_ALPHA + ) -> Self: """Create a control of the type ``"p_max_u_consumption"``. See Also: @@ -237,8 +249,15 @@ def p_max_u_consumption(cls, u_min: float, u_down: float, alpha: float = DEFAULT return cls(type="p_max_u_consumption", u_min=u_min, u_down=u_down, u_up=0.0, u_max=0.0, alpha=alpha) @classmethod - @ureg_wraps(None, (None, "V", "V", "V", "V", None), strict=False) - def q_u(cls, u_min: float, u_down: float, u_up: float, u_max: float, alpha: float = DEFAULT_ALPHA) -> Self: + @ureg_wraps(None, (None, "V", "V", "V", "V", None)) + def q_u( + cls, + u_min: Union[float, Q_[float]], + u_down: Union[float, Q_[float]], + u_up: Union[float, Q_[float]], + u_max: Union[float, Q_[float]], + alpha: float = _DEFAULT_ALPHA, + ) -> Self: """Create a control of the type ``"q_u"``. See Also: @@ -280,7 +299,7 @@ def q_u(cls, u_min: float, u_down: float, u_up: float, u_max: float, alpha: floa # @classmethod def from_dict(cls, data: JsonDict) -> Self: - alpha = data["alpha"] if "alpha" in data else cls.DEFAULT_ALPHA + alpha = data["alpha"] if "alpha" in data else cls._DEFAULT_ALPHA if data["type"] == "constant": return cls.constant() elif data["type"] == "p_max_u_production": @@ -296,7 +315,7 @@ def from_dict(cls, data: JsonDict) -> Self: logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_CONTROL_TYPE) - def to_dict(self, include_geometry: bool = True) -> JsonDict: + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: if self.type == "constant": return {"type": "constant"} elif self.type == "p_max_u_production": @@ -332,18 +351,16 @@ class Projection(JsonMixin): """This class defines the projection on the feasible circle for a flexible load. The three possible projection types are: - * ``"euclidean"``: for an Euclidean projection on the feasible space; + * ``"euclidean"``: for a Euclidean projection on the feasible space; * ``"keep_p"``: for maintaining a constant P; * ``"keep_q"``: for maintaining a constant Q. - - See Also: - :ref:`Projection documentation ` """ - DEFAULT_ALPHA: float = 1000.0 - DEFAULT_EPSILON: float = 1e-8 + _DEFAULT_ALPHA: float = 1000.0 + _DEFAULT_EPSILON: float = 1e-8 + _DEFAULT_TYPE: ProjectionType = "euclidean" - def __init__(self, type: ProjectionType, alpha: float = DEFAULT_ALPHA, epsilon: float = DEFAULT_EPSILON) -> None: + def __init__(self, type: ProjectionType, alpha: float = _DEFAULT_ALPHA, epsilon: float = _DEFAULT_EPSILON) -> None: """Projection constructor. Args: @@ -406,11 +423,11 @@ def epsilon(self) -> float: # @classmethod def from_dict(cls, data: JsonDict) -> Self: - alpha = data["alpha"] if "alpha" in data else cls.DEFAULT_ALPHA - epsilon = data["epsilon"] if "epsilon" in data else cls.DEFAULT_EPSILON + alpha = data["alpha"] if "alpha" in data else cls._DEFAULT_ALPHA + epsilon = data["epsilon"] if "epsilon" in data else cls._DEFAULT_EPSILON return cls(type=data["type"], alpha=alpha, epsilon=epsilon) - def to_dict(self, include_geometry: bool = True) -> JsonDict: + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: return {"type": self.type, "alpha": self._alpha, "epsilon": self._epsilon} def _results_to_dict(self, warning: bool) -> NoReturn: @@ -436,16 +453,21 @@ class FlexibleParameter(JsonMixin): the radius of the feasible circle used by the projection For multi-phase loads, you need to use a `FlexibleParameter` instance per phase. - - See Also: - :ref:`Flexible Parameters documentation ` """ _control_class: type[Control] = Control _projection_class: type[Projection] = Projection - @ureg_wraps(None, (None, None, None, None, "VA"), strict=False) - def __init__(self, control_p: Control, control_q: Control, projection: Projection, s_max: float) -> None: + @ureg_wraps(None, (None, None, None, None, "VA", "VAr", "VAr")) + def __init__( + self, + control_p: Control, + control_q: Control, + projection: Projection, + s_max: Union[float, Q_[float]], + q_min: Optional[Union[float, Q_[float]]] = None, + q_max: Optional[Union[float, Q_[float]]] = None, + ) -> None: """FlexibleParameter constructor. Args: @@ -460,51 +482,113 @@ def __init__(self, control_p: Control, control_q: Control, projection: Projectio s_max: The apparent power of the flexible load (VA). It is the radius of the feasible circle. + + q_min: + The minimum reactive power of the flexible load (VAr). By default it is equal to -s_max, but it can + be further constrained. + + q_max: + The maximum reactive power of the flexible load (VAr). By default it is equal to s_max, but it can + be further constrained. """ self.control_p = control_p self.control_q = control_q self.projection = projection - self._s_max = s_max - self._check_values() - - def _check_values(self) -> None: - """Check the provided values.""" - if self._s_max <= 0: - msg = f"'s_max' must be greater than 0 but {self.s_max:P#~} was provided." - logger.error(msg) - raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_SMAX_VALUE) + self._q_min = None + self._q_max = None + self.s_max = s_max + self.q_min = q_min + self.q_max = q_max @property - @ureg_wraps("VA", (None,), strict=False) + @ureg_wraps("VA", (None,)) def s_max(self) -> Q_[float]: """The apparent power of the flexible load (VA). It is the radius of the feasible circle.""" return self._s_max + @s_max.setter + @ureg_wraps(None, (None, "VA")) + def s_max(self, value: Union[float, Q_[float]]) -> None: + if value <= 0: + s_max = Q_(value, "VA") + msg = f"'s_max' must be greater than 0 but {s_max:P#~} was provided." + logger.error(msg) + raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_FLEXIBLE_PARAMETER_VALUE) + self._s_max = value + if self._q_max is not None and self._q_max > self._s_max: + logger.warning("'s_max' has been updated but now 'q_max' is greater than s_max. 'q_max' is set to s_max") + self._q_max = self._s_max + if self._q_min is not None and self._q_min < -self._s_max: + logger.warning("'s_max' has been updated but now 'q_min' is less than -s_max. 'q_min' is set to -s_max") + self._q_min = -self._s_max + + @property + @ureg_wraps("VAr", (None,)) + def q_min(self) -> Q_[float]: + """The minimum reactive power of the flexible load (VAr).""" + return self._q_min if self._q_min is not None else -self._s_max + + @q_min.setter + @ureg_wraps(None, (None, "VAr")) + def q_min(self, value: Optional[Union[float, Q_[float]]]) -> None: + if value is not None and value < -self._s_max: + q_min = Q_(value, "VAr") + msg = f"'q_min' must be greater than -s_max ({-self.s_max:P#~}) but {q_min:P#~} was provided." + logger.error(msg) + raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_FLEXIBLE_PARAMETER_VALUE) + if value is not None and self._q_max is not None and value > self._q_max: + q_min = Q_(value, "VAr") + msg = f"'q_min' must be greater than q_max ({self.q_max:P#~}) but {q_min:P#~} was provided." + logger.error(msg) + raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_FLEXIBLE_PARAMETER_VALUE) + self._q_min = value + + @property + @ureg_wraps("VAr", (None,)) + def q_max(self) -> Q_[float]: + """The maximum reactive power of the flexible load (VAr).""" + return self._q_max if self._q_max is not None else self._s_max + + @q_max.setter + @ureg_wraps(None, (None, "VAr")) + def q_max(self, value: Optional[Union[float, Q_[float]]]) -> None: + if value is not None and value > self._s_max: + q_max = Q_(value, "VAr") + msg = f"'q_max' must be less than s_max ({self.s_max:P#~}) but {q_max:P#~} was provided." + logger.error(msg) + raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_FLEXIBLE_PARAMETER_VALUE) + if value is not None and self._q_min is not None and value < self._q_min: + q_max = Q_(value, "VAr") + msg = f"'q_max' must be greater than q_min ({self.q_min:P#~}) but {q_max:P#~} was provided." + logger.error(msg) + raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_FLEXIBLE_PARAMETER_VALUE) + self._q_max = value + @classmethod def constant(cls) -> Self: """Build flexible parameters for a constant control with a Euclidean projection. Returns: - A constant control i.e. no control at all. It is an equivalent of the constant power - load. + A constant control i.e. no control at all. It is an equivalent of the constant power load. """ return cls( control_p=cls._control_class.constant(), control_q=cls._control_class.constant(), - projection=cls._projection_class(type="euclidean"), + projection=cls._projection_class(type=cls._projection_class._DEFAULT_TYPE), s_max=1.0, ) @classmethod - @ureg_wraps(None, (None, "V", "V", "VA", None, None, None), strict=False) + @ureg_wraps(None, (None, "V", "V", "VA", None, None, None, None)) def p_max_u_production( cls, - u_up: float, - u_max: float, - s_max: float, - alpha_control: float = Control.DEFAULT_ALPHA, - alpha_proj: float = Projection.DEFAULT_ALPHA, - epsilon_proj: float = Projection.DEFAULT_EPSILON, + u_up: Union[float, Q_[float]], + u_max: Union[float, Q_[float]], + s_max: Union[float, Q_[float]], + alpha_control: float = Control._DEFAULT_ALPHA, + type_proj: ProjectionType = Projection._DEFAULT_TYPE, + alpha_proj: float = Projection._DEFAULT_ALPHA, + epsilon_proj: float = Projection._DEFAULT_EPSILON, ) -> Self: """Build flexible parameters for production ``P(U)`` control with a Euclidean projection. @@ -528,6 +612,9 @@ def p_max_u_production( An approximation factor used by the family function (soft clip). The greater, the closer the function are from the non-differentiable function. + type_proj: + The type of the projection to use. + alpha_proj: This value is used to make soft sign function and to build a soft projection function (see the diagram above). @@ -543,20 +630,21 @@ def p_max_u_production( return cls( control_p=control_p, control_q=cls._control_class.constant(), - projection=cls._projection_class(type="euclidean", alpha=alpha_proj, epsilon=epsilon_proj), + projection=cls._projection_class(type=type_proj, alpha=alpha_proj, epsilon=epsilon_proj), s_max=s_max, ) @classmethod - @ureg_wraps(None, (None, "V", "V", "VA", None, None, None), strict=False) + @ureg_wraps(None, (None, "V", "V", "VA", None, None, None, None)) def p_max_u_consumption( cls, - u_min: float, - u_down: float, - s_max: float, - alpha_control: float = Control.DEFAULT_ALPHA, - alpha_proj: float = Projection.DEFAULT_ALPHA, - epsilon_proj: float = Projection.DEFAULT_EPSILON, + u_min: Union[float, Q_[float]], + u_down: Union[float, Q_[float]], + s_max: Union[float, Q_[float]], + alpha_control: float = Control._DEFAULT_ALPHA, + type_proj: ProjectionType = Projection._DEFAULT_TYPE, + alpha_proj: float = Projection._DEFAULT_ALPHA, + epsilon_proj: float = Projection._DEFAULT_EPSILON, ) -> Self: """Build flexible parameters for consumption ``P(U)`` control with a Euclidean projection. @@ -577,6 +665,9 @@ def p_max_u_consumption( An approximation factor used by the family function (soft clip). The greater, the closer the function are from the non-differentiable function. + type_proj: + The type of the projection to use. + alpha_proj: This value is used to make soft sign function and to build a soft projection function. @@ -592,22 +683,25 @@ def p_max_u_consumption( return cls( control_p=control_p, control_q=cls._control_class.constant(), - projection=cls._projection_class("euclidean", alpha=alpha_proj, epsilon=epsilon_proj), + projection=cls._projection_class(type=type_proj, alpha=alpha_proj, epsilon=epsilon_proj), s_max=s_max, ) @classmethod - @ureg_wraps(None, (None, "V", "V", "V", "V", "VA", None, None, None), strict=False) + @ureg_wraps(None, (None, "V", "V", "V", "V", "VA", "Var", "Var", None, None, None, None)) def q_u( cls, - u_min: float, - u_down: float, - u_up: float, - u_max: float, - s_max: float, - alpha_control: float = Control.DEFAULT_ALPHA, - alpha_proj: float = Projection.DEFAULT_ALPHA, - epsilon_proj: float = Projection.DEFAULT_EPSILON, + u_min: Union[float, Q_[float]], + u_down: Union[float, Q_[float]], + u_up: Union[float, Q_[float]], + u_max: Union[float, Q_[float]], + s_max: Union[float, Q_[float]], + q_min: Optional[Union[float, Q_[float]]] = None, + q_max: Optional[Union[float, Q_[float]]] = None, + alpha_control: float = Control._DEFAULT_ALPHA, + type_proj: ProjectionType = Projection._DEFAULT_TYPE, + alpha_proj: float = Projection._DEFAULT_ALPHA, + epsilon_proj: float = Projection._DEFAULT_EPSILON, ) -> Self: """Build flexible parameters for ``Q(U)`` control with a Euclidean projection. @@ -631,10 +725,21 @@ def q_u( The apparent power of the flexible load (VA). It is the radius of the feasible circle. + q_min: + The minimum reactive power of the flexible load (VAr). By default it is equal to -s_max, but it can + be further constrained. + + q_max: + The maximum reactive power of the flexible load (VAr). By default it is equal to s_max, but it can + be further constrained. + alpha_control: An approximation factor used by the family function (soft clip). The greater, the closer the function are from the non-differentiable function. + type_proj: + The type of the projection to use. + alpha_proj: This value is used to make soft sign function and to build a soft projection function. @@ -650,24 +755,29 @@ def q_u( return cls( control_p=cls._control_class.constant(), control_q=control_q, - projection=cls._projection_class(type="euclidean", alpha=alpha_proj, epsilon=epsilon_proj), + projection=cls._projection_class(type=type_proj, alpha=alpha_proj, epsilon=epsilon_proj), s_max=s_max, + q_min=q_min, + q_max=q_max, ) @classmethod - @ureg_wraps(None, (None, "V", "V", "V", "V", "V", "V", "VA", None, None, None), strict=False) + @ureg_wraps(None, (None, "V", "V", "V", "V", "V", "V", "VA", "VAr", "VAr", None, None, None, None)) def pq_u_production( cls, - up_up: float, - up_max: float, - uq_min: float, - uq_down: float, - uq_up: float, - uq_max: float, - s_max: float, - alpha_control=Control.DEFAULT_ALPHA, - alpha_proj=Projection.DEFAULT_ALPHA, - epsilon_proj=Projection.DEFAULT_EPSILON, + up_up: Union[float, Q_[float]], + up_max: Union[float, Q_[float]], + uq_min: Union[float, Q_[float]], + uq_down: Union[float, Q_[float]], + uq_up: Union[float, Q_[float]], + uq_max: Union[float, Q_[float]], + s_max: Union[float, Q_[float]], + q_min: Optional[Union[float, Q_[float]]] = None, + q_max: Optional[Union[float, Q_[float]]] = None, + alpha_control=Control._DEFAULT_ALPHA, + type_proj: ProjectionType = Projection._DEFAULT_TYPE, + alpha_proj=Projection._DEFAULT_ALPHA, + epsilon_proj=Projection._DEFAULT_EPSILON, ) -> Self: """Build flexible parameters for production ``P(U)`` control and ``Q(U)`` control with a Euclidean projection. @@ -698,10 +808,21 @@ def pq_u_production( The apparent power of the flexible load (VA). It is the radius of the feasible circle. + q_min: + The minimum reactive power of the flexible load (VAr). By default it is equal to -s_max, but it can + be further constrained. + + q_max: + The maximum reactive power of the flexible load (VAr). By default it is equal to s_max, but it can + be further constrained. + alpha_control: An approximation factor used by the family function (soft clip). The greater, the closer the function are from the non-differentiable function. + type_proj: + The type of the projection to use. + alpha_proj: This value is used to make soft sign function and to build a soft projection function. @@ -713,7 +834,7 @@ def pq_u_production( Returns: A flexible parameter which performs "p_max_u_production" control and a "q_u" control. - .. seealso:: + See Also: :meth:`p_max_u_production` and :meth:`q_u` for more details. """ control_p = cls._control_class.p_max_u_production(u_up=up_up, u_max=up_max, alpha=alpha_control) @@ -721,24 +842,29 @@ def pq_u_production( return cls( control_p=control_p, control_q=control_q, - projection=cls._projection_class(type="euclidean", alpha=alpha_proj, epsilon=epsilon_proj), + projection=cls._projection_class(type=type_proj, alpha=alpha_proj, epsilon=epsilon_proj), s_max=s_max, + q_min=q_min, + q_max=q_max, ) @classmethod - @ureg_wraps(None, (None, "V", "V", "V", "V", "V", "V", "VA", None, None, None), strict=False) + @ureg_wraps(None, (None, "V", "V", "V", "V", "V", "V", "VA", "VAr", "VAr", None, None, None, None)) def pq_u_consumption( cls, - up_min: float, - up_down: float, - uq_min: float, - uq_down: float, - uq_up: float, - uq_max: float, - s_max: float, - alpha_control: float = Control.DEFAULT_ALPHA, - alpha_proj: float = Projection.DEFAULT_ALPHA, - epsilon_proj: float = Projection.DEFAULT_EPSILON, + up_min: Union[float, Q_[float]], + up_down: Union[float, Q_[float]], + uq_min: Union[float, Q_[float]], + uq_down: Union[float, Q_[float]], + uq_up: Union[float, Q_[float]], + uq_max: Union[float, Q_[float]], + s_max: Union[float, Q_[float]], + q_min: Optional[Union[float, Q_[float]]] = None, + q_max: Optional[Union[float, Q_[float]]] = None, + alpha_control: Union[float, Q_[float]] = Control._DEFAULT_ALPHA, + type_proj: ProjectionType = Projection._DEFAULT_TYPE, + alpha_proj: float = Projection._DEFAULT_ALPHA, + epsilon_proj: float = Projection._DEFAULT_EPSILON, ) -> Self: """Build flexible parameters for consumption ``P(U)`` control and ``Q(U)`` control with a Euclidean projection. @@ -769,10 +895,21 @@ def pq_u_consumption( The apparent power of the flexible load (VA). It is the radius of the feasible circle. + q_min: + The minimum reactive power of the flexible load (VAr). By default it is equal to -s_max, but it can + be further constrained. + + q_max: + The maximum reactive power of the flexible load (VAr). By default it is equal to s_max, but it can + be further constrained. + alpha_control: An approximation factor used by the family function (soft clip). The greater, the closer the function are from the non-differentiable function. + type_proj: + The type of the projection to use. + alpha_proj: This value is used to make soft sign function and to build a soft projection function. @@ -784,7 +921,7 @@ def pq_u_consumption( Returns: A flexible parameter which performs "p_max_u_consumption" control and "q_u" control. - .. seealso:: + See Also: :meth:`p_max_u_consumption` and :meth:`q_u` for more details. """ control_p = cls._control_class.p_max_u_consumption(u_min=up_min, u_down=up_down, alpha=alpha_control) @@ -792,8 +929,10 @@ def pq_u_consumption( return cls( control_p=control_p, control_q=control_q, - projection=cls._projection_class(type="euclidean", alpha=alpha_proj, epsilon=epsilon_proj), + projection=cls._projection_class(type=type_proj, alpha=alpha_proj, epsilon=epsilon_proj), s_max=s_max, + q_min=q_min, + q_max=q_max, ) # @@ -804,15 +943,29 @@ def from_dict(cls, data: JsonDict) -> Self: control_p = cls._control_class.from_dict(data["control_p"]) control_q = cls._control_class.from_dict(data["control_q"]) projection = cls._projection_class.from_dict(data["projection"]) - return cls(control_p=control_p, control_q=control_q, projection=projection, s_max=data["s_max"]) + q_min = data.get("q_min", None) + q_max = data.get("q_max", None) + return cls( + control_p=control_p, + control_q=control_q, + projection=projection, + s_max=data["s_max"], + q_min=q_min, + q_max=q_max, + ) - def to_dict(self, include_geometry: bool = True) -> JsonDict: - return { + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: + res = { "control_p": self.control_p.to_dict(), "control_q": self.control_q.to_dict(), "projection": self.projection.to_dict(), "s_max": self._s_max, } + if self._q_min is not None: + res["q_min"] = self._q_min + if self._q_max is not None: + res["q_max"] = self._q_max + return res def _results_to_dict(self, warning: bool) -> NoReturn: msg = f"The {type(self).__name__} has no results to export." @@ -823,3 +976,409 @@ def results_from_dict(self, data: JsonDict) -> NoReturn: msg = f"The {type(self).__name__} has no results to import." logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.JSON_NO_RESULTS) + + # + # Equivalent Python method + # + @ureg_wraps("VA", (None, None, "V", "VA", None)) + def compute_powers( + self, + auth: Authentication, + voltages: ComplexArrayLike1D, + power: Union[complex, Q_[complex]], + solve_kwargs: Optional[JsonDict] = None, + ) -> Q_[ComplexArray]: + """Compute the flexible powers for different voltages (norms) + + Args: + auth: + The login and password for the roseau load flow api. + + voltages: + The array of voltage norms to test with this flexible parameter. + + power: + The input theoretical power of the load. + + solve_kwargs: + Keywords arguments passed to the :meth:`~roseau.load_flow.ElectricalNetwork.solve_load_flow` method. + + Returns: + The flexible powers really consumed taking into account the control. One value per provided voltage norm. + """ + return self._compute_powers(auth=auth, voltages=voltages, power=power, solve_kwargs=solve_kwargs) + + def _compute_powers( + self, auth: Authentication, voltages: ComplexArrayLike1D, power: complex, solve_kwargs: Optional[JsonDict] + ) -> ComplexArray: + from roseau.load_flow import Bus, ElectricalNetwork, PotentialRef, PowerLoad, VoltageSource + + # Format the input + if solve_kwargs is None: + solve_kwargs = {} + voltages = np.array(np.abs(voltages), dtype=np.float64) + + # Simple network + bus = Bus(id="bus", phases="an") + vs = VoltageSource(id="source", bus=bus, voltages=[voltages[0]]) + PotentialRef(id="pref", element=bus, phase="n") + fp = FlexibleParameter.from_dict(data=self.to_dict(_lf_only=True)) + load = PowerLoad(id="load", bus=bus, powers=[power], flexible_params=[fp]) + en = ElectricalNetwork.from_element(bus) + + # Iterate over the provided voltages to get the associated flexible powers + res_flexible_powers = [] + for v in voltages: + vs.voltages = [v] + en.solve_load_flow(auth=auth, **solve_kwargs) + res_flexible_powers.append(load.res_flexible_powers.m_as("VA")[0]) + + return np.array(res_flexible_powers, dtype=np.complex128) + + @ureg_wraps((None, "VA"), (None, None, "V", "VA", None, None, None, "VA")) + def plot_pq( + self, + auth: Authentication, + voltages: Union[NDArray[np.float64], Q_[NDArray[np.float64]]], + power: Union[complex, Q_[complex]], + ax: Optional["Axes"] = None, + solve_kwargs: Optional[JsonDict] = None, + voltages_labels_mask: Optional[NDArray[np.bool_]] = None, + res_flexible_powers: Optional[ComplexArray] = None, + ) -> tuple["Axes", ComplexArray]: + """Plot the "trajectory" of the flexible powers (in the (P, Q) plane) for the provided voltages and theoretical + power. + + Args: + auth: + The login and password for the roseau load flow api. + + voltages: + The array of voltage norms to test with this flexible parameter. + + power: + The input theoretical power of the load. + + ax: + The optional axis to use for the plot. The current axis is used by default. + + solve_kwargs: + The keywords arguments of the :meth:`~roseau.load_flow.ElectricalNetwork.solve_load_flow` method. + + voltages_labels_mask: + A mask to activate the plot of voltages labels. By default, no voltages annotations. + + res_flexible_powers: + If None is provided, the `res_flexible_powers` are computed. Otherwise, the provided values are used. + + Returns: + The axis on which the plot has been drawn and the resulting flexible powers (the input if not `None` else + the computed values). + """ + plt = _optional_deps.pyplot # this line first for better error handling + from matplotlib import colormaps, patheffects + + # Get the axes + if ax is None: + ax = plt.gca() + + # Initialise some variables + if voltages_labels_mask is None: + voltages_labels_mask = np.zeros_like(voltages, dtype=np.bool_) + else: + voltages_labels_mask = np.array(voltages_labels_mask, dtype=np.bool_) + s_max = self._s_max + v_min = voltages.min() + v_max = voltages.max() + + # Compute the powers for the voltages norms + if res_flexible_powers is None: + res_flexible_powers = self._compute_powers( + auth=auth, voltages=voltages, power=power, solve_kwargs=solve_kwargs + ) + + # Draw a circle + circle = plt.Circle((0, 0), radius=s_max, color="black", fill=False) + ax.add_artist(circle) + + # Draw the powers + cm = colormaps.get_cmap("Spectral_r") + sc = ax.scatter( + x=res_flexible_powers.real, + y=res_flexible_powers.imag, + c=voltages, + cmap=cm, + vmin=v_min, + vmax=v_max, + marker=".", + s=50, + zorder=4, + ) + for m, v, x, y in zip(voltages_labels_mask, voltages, res_flexible_powers.real, res_flexible_powers.imag): + if not m: + continue + ax.annotate( + text=f"{v:.1f} V", + xy=(x, y), + xycoords="data", + path_effects=[patheffects.withStroke(linewidth=2, foreground="w")], + xytext=(4, 4), + textcoords="offset points", + ) + + # Draw the theoretical power + ax.axhline(y=power.imag, c="red", zorder=1.9) + ax.axvline(x=power.real, c="red", zorder=1.9) + ax.scatter(x=power.real, y=power.imag, marker=".", c="red", zorder=3) + ax.annotate( + xy=(power.real, power.imag), + text=r"$S^{\mathrm{th.}}$", + path_effects=[patheffects.withStroke(linewidth=2, foreground="w")], + ha="right", + ) + + # Refine the axes + ax.grid(visible=True) + plt.colorbar(sc, ax=ax) + ax.set_xlim(-s_max * 1.05, s_max * 1.05) + ax.set_ylim(-s_max * 1.05, s_max * 1.05) + ax.set_aspect("equal") + ax.set_xlabel("Active power (W)") + ax.set_ylabel("Reactive power (VAr)") + + return ax, res_flexible_powers + + @ureg_wraps((None, "VA"), (None, None, "V", "VA", None, None, "VA")) + def plot_control_p( + self, + auth: Authentication, + voltages: Union[NDArray[np.float64], Q_[NDArray[np.float64]]], + power: Union[complex, Q_[complex]], + ax: Optional["Axes"] = None, + solve_kwargs: Optional[JsonDict] = None, + res_flexible_powers: Optional[ComplexArray] = None, + ) -> tuple["Axes", ComplexArray]: + """Plot the flexible active power consumed (or produced) for the provided voltages and theoretical power. + + Args: + auth: + The login and password for the roseau load flow api. + + voltages: + The array of voltage norms to test with this flexible parameter. + + power: + The input theoretical power of the load. + + ax: + The optional axis to use for the plot. The current axis is used by default. + + solve_kwargs: + The keywords arguments of the :meth:`~roseau.load_flow.ElectricalNetwork.solve_load_flow` method. + + res_flexible_powers: + If None is provided, the `res_flexible_powers` are computed. Otherwise, the provided values are used. + + Returns: + The axis on which the plot has been drawn and the resulting flexible powers (the input if not `None` else + the computed values). + """ + plt = _optional_deps.pyplot + + # Get the axes + if ax is None: + ax = plt.gca() + + # Depending on the type of the control, several options + x, y, x_ticks = self._theoretical_control_data( + control=self.control_p, v_min=voltages.min(), v_max=voltages.max(), power=power.real, s_max=self._s_max + ) + + # Compute the powers for the voltages norms + if res_flexible_powers is None: + res_flexible_powers = self._compute_powers( + auth=auth, voltages=voltages, power=power, solve_kwargs=solve_kwargs + ) + ax.scatter(voltages, res_flexible_powers.real, marker=".", c="blue", zorder=2, label="Actual power") + + # Add the theoretical non-smooth curve + ax.plot(x, y, marker="s", c="red", zorder=1.9, label="Non-smooth theoretical control") + + # Refine the axis + ax.grid(visible=True) + ax.set_xticks(x, x_ticks) + ax.set_xlabel("Voltage (V)") + ax.set_ylabel("Active power (W)") + ax.legend() + ax.figure.tight_layout() + + return ax, res_flexible_powers + + @ureg_wraps((None, "VA"), (None, None, "V", "VA", None, None, "VA")) + def plot_control_q( + self, + auth: Authentication, + voltages: Union[NDArray[np.float64], Q_[NDArray[np.float64]]], + power: Union[complex, Q_[complex]], + ax: Optional["Axes"] = None, + solve_kwargs: Optional[JsonDict] = None, + res_flexible_powers: Optional[ComplexArray] = None, + ) -> tuple["Axes", ComplexArray]: + """Plot the flexible reactive power consumed (or produced) for the provided voltages and theoretical power. + + Args: + auth: + The login and password for the roseau load flow api. + + voltages: + The array of voltage norms to test with this flexible parameter. + + power: + The input theoretical power of the load. + + ax: + The optional axis to use for the plot. The current axis is used by default. + + solve_kwargs: + The keywords arguments of the :meth:`~roseau.load_flow.ElectricalNetwork.solve_load_flow` method + + res_flexible_powers: + If None is provided, the `res_flexible_powers` are computed. Otherwise, the provided values are used. + + Returns: + The axis on which the plot has been drawn and the resulting flexible powers (the input if not `None` else + the computed values). + """ + plt = _optional_deps.pyplot + + # Get the axes + if ax is None: + ax = plt.gca() + + # Depending on the type of the control, several options + x, y, x_ticks = self._theoretical_control_data( + control=self.control_q, v_min=voltages.min(), v_max=voltages.max(), power=power.imag, s_max=self._s_max + ) + + # Compute the powers for the voltages norms + if res_flexible_powers is None: + res_flexible_powers = self._compute_powers( + auth=auth, voltages=voltages, power=power, solve_kwargs=solve_kwargs + ) + ax.scatter(voltages, res_flexible_powers.imag, marker=".", c="blue", zorder=2, label="Actual power") + + # Add the theoretical non-smooth curve + ax.plot(x, y, marker="s", c="red", zorder=1.9, label="Non-smooth theoretical control") + + # Refine the axis + ax.grid(visible=True) + ax.set_xticks(x, x_ticks) + ax.set_xlabel("Voltage (V)") + ax.set_ylabel("Reactive power (VAr)") + ax.legend() + ax.figure.tight_layout() + + return ax, res_flexible_powers + + # + # Helpers + # + @staticmethod + def _theoretical_control_data( + control: Control, v_min: float, v_max: float, power: float, s_max: float + ) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.object_]]: + """Helper to get data for the different plots of the class. It provides the theoretical control curve + abscissas and ordinates values. It also provides ticks for the abscissa axis. + + Args: + control: + The control to extract the theoretical value. + + v_min: + The minimum voltage norm provided by the user in the `voltages` array. + + v_max: + The maximum voltage norm provided by the user in the `voltages` array. + + power: + The active (or reactive, depending on the provided control) to use in the constant control. + + s_max: + The `s_max` parameter to scale the control functions. + + Returns: + The x- and y-values of the theoretical control function with x-ticks to use for the plot. + """ + # Depending on the type of the control, several options + if control.type == "constant": + x = np.array([v_min, v_max], dtype=np.float64) + y = np.array([power, power], dtype=np.float64) + x_ticks = np.array([f"{v_min:.1f}", f"{v_max:.1f}"], dtype=np.object_) + elif control.type == "p_max_u_production": + u_up = control._u_up + u_max = control._u_max + x = np.array([u_up, u_max, v_min, v_max], dtype=np.float64) + y = np.zeros_like(x, dtype=np.float64) + y[x < u_up] = -s_max + mask = np.logical_and(u_up <= x, x < u_max) + y[mask] = -s_max * (x[mask] - u_max) / (u_up - u_max) + y[x >= u_max] = 0 + x_ticks = np.array( + [f"{u_up:.1f}\n$U^{{\\mathrm{{up}}}}$", f"{u_max:.1f}\n$U^{{\\max}}$", f"{v_min:.1f}", f"{v_max:.1f}"], + dtype=np.object_, + ) + elif control.type == "p_max_u_consumption": + u_min = control._u_min + u_down = control._u_down + x = np.array([u_min, u_down, v_min, v_max], dtype=np.float64) + y = np.zeros_like(x, dtype=np.float64) + y[x < u_min] = 0 + y[x >= u_down] = s_max + mask = np.logical_and(u_min <= x, x < u_down) + y[mask] = s_max * (x[mask] - u_min) / (u_down - u_min) + x_ticks = np.array( + [ + f"{u_min:.1f}\n$U^{{\\min}}$", + f"{u_down:.1f}\n$U^{{\\mathrm{{down}}}}$", + f"{v_min:.1f}", + f"{v_max:.1f}", + ], + dtype=np.object_, + ) + elif control.type == "q_u": + u_min = control._u_min + u_down = control._u_down + u_up = control._u_up + u_max = control._u_max + x = np.array([u_min, u_down, u_up, u_max, v_min, v_max], dtype=np.float64) + y = np.zeros_like(x, dtype=np.float64) + y[x < u_min] = -s_max + mask = np.logical_and(u_min <= x, x < u_down) + y[mask] = -s_max * (x[mask] - u_down) / (u_min - u_down) + y[np.logical_and(u_down <= x, x < u_up)] = 0 + mask = np.logical_and(u_up <= x, x < u_max) + y[mask] = s_max * (x[mask] - u_up) / (u_max - u_up) + y[x >= u_max] = s_max + x_ticks = np.array( + [ + f"{u_min:.1f}\n$U^{{\\min}}$", + f"{u_down:.1f}\n$U^{{\\mathrm{{down}}}}$", + f"{u_up:.1f}\n$U^{{\\mathrm{{up}}}}$", + f"{u_max:.1f}\n$U^{{\\max}}$", + f"{v_min:.1f}", + f"{v_max:.1f}", + ], + dtype=np.object_, + ) + else: # pragma: no-cover + msg = f"Unsupported control type {control.type!r}" + logger.error(msg) + raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_CONTROL_TYPE) + + # Sort everything according to the voltages + sort_index = np.argsort(x) + x = x[sort_index] + y = y[sort_index] + x_ticks = x_ticks[sort_index] + + return x, y, x_ticks diff --git a/roseau/load_flow/models/loads/loads.py b/roseau/load_flow/models/loads/loads.py index 685a68cd..dd92dc4d 100644 --- a/roseau/load_flow/models/loads/loads.py +++ b/roseau/load_flow/models/loads/loads.py @@ -1,6 +1,5 @@ import logging from abc import ABC -from collections.abc import Sequence from typing import Any, Literal, Optional import numpy as np @@ -10,7 +9,7 @@ from roseau.load_flow.models.buses import Bus from roseau.load_flow.models.core import Element from roseau.load_flow.models.loads.flexible_parameters import FlexibleParameter -from roseau.load_flow.typing import Id, JsonDict +from roseau.load_flow.typing import ComplexArray, ComplexArrayLike1D, Id, JsonDict from roseau.load_flow.units import Q_, ureg_wraps logger = logging.getLogger(__name__) @@ -20,11 +19,8 @@ class AbstractLoad(Element, ABC): """An abstract class of an electric load. The subclasses of this class can be used to depict: - * star-connected loads using a `phases` constructor argument containing a `"n"` - * delta-connected loads using a `phases` constructor argument which doesn't contain `"n"` - - See Also: - :doc:`Load documentation ` + * star-connected loads using a `phases` constructor argument containing `"n"` + * delta-connected loads using a `phases` constructor argument not containing `"n"` """ _power_load_class: type["PowerLoad"] @@ -82,7 +78,7 @@ def __init__(self, id: Id, bus: Bus, *, phases: Optional[str] = None, **kwargs: self._size = len(set(phases) - {"n"}) # Results - self._res_currents: Optional[np.ndarray] = None + self._res_currents: Optional[ComplexArray] = None def __repr__(self) -> str: bus_id = self.bus.id if self.bus is not None else None @@ -98,16 +94,16 @@ def voltage_phases(self) -> list[str]: """The phases of the load voltages.""" return calculate_voltage_phases(self.phases) - def _res_currents_getter(self, warning: bool) -> np.ndarray: + def _res_currents_getter(self, warning: bool) -> ComplexArray: return self._res_getter(value=self._res_currents, warning=warning) @property - @ureg_wraps("A", (None,), strict=False) - def res_currents(self) -> Q_[np.ndarray]: + @ureg_wraps("A", (None,)) + def res_currents(self) -> Q_[ComplexArray]: """The load flow result of the load currents (A).""" return self._res_currents_getter(warning=True) - def _validate_value(self, value: Sequence[complex]) -> np.ndarray: + def _validate_value(self, value: ComplexArrayLike1D) -> ComplexArray: if len(value) != self._size: msg = f"Incorrect number of {self._type}s: {len(value)} instead of {self._size}" logger.error(msg) @@ -119,36 +115,36 @@ def _validate_value(self, value: Sequence[complex]) -> np.ndarray: msg = f"An impedance of the load {self.id!r} is null" logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_Z_VALUE) - return np.asarray(value, dtype=complex) + return np.array(value, dtype=np.complex128) - def _res_potentials_getter(self, warning: bool) -> np.ndarray: + def _res_potentials_getter(self, warning: bool) -> ComplexArray: self._raise_disconnected_error() return self.bus._get_potentials_of(self.phases, warning) @property - @ureg_wraps("V", (None,), strict=False) - def res_potentials(self) -> Q_[np.ndarray]: + @ureg_wraps("V", (None,)) + def res_potentials(self) -> Q_[ComplexArray]: """The load flow result of the load potentials (V).""" return self._res_potentials_getter(warning=True) - def _res_voltages_getter(self, warning: bool) -> np.ndarray: + def _res_voltages_getter(self, warning: bool) -> ComplexArray: potentials = self._res_potentials_getter(warning) return calculate_voltages(potentials, self.phases) @property - @ureg_wraps("V", (None,), strict=False) - def res_voltages(self) -> Q_[np.ndarray]: + @ureg_wraps("V", (None,)) + def res_voltages(self) -> Q_[ComplexArray]: """The load flow result of the load voltages (V).""" return self._res_voltages_getter(warning=True) - def _res_powers_getter(self, warning: bool) -> np.ndarray: + def _res_powers_getter(self, warning: bool) -> ComplexArray: curs = self._res_currents_getter(warning) pots = self._res_potentials_getter(warning=False) # we warn on the previous line return pots * curs.conj() @property - @ureg_wraps("VA", (None,), strict=False) - def res_powers(self) -> Q_[np.ndarray]: + @ureg_wraps("VA", (None,)) + def res_powers(self) -> Q_[ComplexArray]: """The load flow result of the load powers (VA).""" return self._res_powers_getter(warning=True) @@ -193,7 +189,7 @@ def from_dict(cls, data: JsonDict) -> "AbstractLoad": raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_LOAD_TYPE) def results_from_dict(self, data: JsonDict) -> None: - self._res_currents = np.array([complex(i[0], i[1]) for i in data["currents"]], dtype=complex) + self._res_currents = np.array([complex(i[0], i[1]) for i in data["currents"]], dtype=np.complex128) def _results_to_dict(self, warning: bool) -> JsonDict: return { @@ -204,11 +200,7 @@ def _results_to_dict(self, warning: bool) -> JsonDict: class PowerLoad(AbstractLoad): - """A constant power load. - - See Also: - :doc:`Power Load documentation ` - """ + """A constant power load.""" _type = "power" @@ -217,7 +209,7 @@ def __init__( id: Id, bus: Bus, *, - powers: Sequence[complex], + powers: ComplexArrayLike1D, phases: Optional[str] = None, flexible_params: Optional[list[FlexibleParameter]] = None, **kwargs: Any, @@ -232,7 +224,8 @@ def __init__( The bus to connect the load to. powers: - List of power for each phase (VA). + An array-like of the powers for each phase component. Either complex values (VA) + or a :class:`Quantity ` of complex values. phases: The phases of the load. A string like ``"abc"`` or ``"an"`` etc. The order of the @@ -261,7 +254,7 @@ def __init__( self._flexible_params = flexible_params self.powers = powers - self._res_flexible_powers: Optional[np.ndarray] = None + self._res_flexible_powers: Optional[ComplexArray] = None @property def flexible_params(self) -> Optional[list[FlexibleParameter]]: @@ -272,51 +265,59 @@ def is_flexible(self) -> bool: return self._flexible_params is not None @property - @ureg_wraps("VA", (None,), strict=False) - def powers(self) -> Q_[np.ndarray]: + @ureg_wraps("VA", (None,)) + def powers(self) -> Q_[ComplexArray]: """The powers of the load (VA).""" return self._powers @powers.setter - @ureg_wraps(None, (None, "VA"), strict=False) - def powers(self, value: Sequence[complex]) -> None: + @ureg_wraps(None, (None, "VA")) + def powers(self, value: ComplexArrayLike1D) -> None: value = self._validate_value(value) if self.is_flexible: for power, fp in zip(value, self._flexible_params): if fp.control_p.type == "constant" and fp.control_q.type == "constant": continue # No checks for this case if abs(power) > fp.s_max.m_as("VA"): - msg = f"The power is greater than the parameter s_max for flexible load {id!r}" + msg = f"The power is greater than the parameter s_max for flexible load {self.id!r}" + logger.error(msg) + raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_S_VALUE) + if power.imag < fp.q_min.m_as("VAr"): + msg = f"The reactive power is lesser than the parameter q_min for flexible load {id!r}" + logger.error(msg) + raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_S_VALUE) + if power.imag > fp.q_max.m_as("VAr"): + msg = f"The reactive power is greater than the parameter q_max for flexible load {id!r}" logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_S_VALUE) if fp.control_p.type == "p_max_u_production" and power.real > 0: - msg = f"There is a production control but a positive power for flexible load {id!r}" + msg = f"There is a production control but a positive power for flexible load {self.id!r}" logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_S_VALUE) if fp.control_p.type == "p_max_u_consumption" and power.real < 0: - msg = f"There is a consumption control but a negative power for flexible load {id!r}" + msg = f"There is a consumption control but a negative power for flexible load {self.id!r}" logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_S_VALUE) if fp.control_p.type != "constant" and power.real == 0: - msg = f"There is a P control but a null active power for flexible load {id!r}" + msg = f"There is a P control but a null active power for flexible load {self.id!r}" logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_S_VALUE) self._powers = value self._invalidate_network_results() - def _res_flexible_powers_getter(self, warning: bool) -> np.ndarray: + def _res_flexible_powers_getter(self, warning: bool) -> ComplexArray: return self._res_getter(value=self._res_flexible_powers, warning=warning) @property - @ureg_wraps("VA", (None,), strict=False) - def res_flexible_powers(self) -> Q_[np.ndarray]: + @ureg_wraps("VA", (None,)) + def res_flexible_powers(self) -> Q_[ComplexArray]: """The load flow result of the load flexible powers (VA).""" return self._res_flexible_powers_getter(warning=True) # # Json Mixin interface # - def to_dict(self, include_geometry: bool = True) -> JsonDict: + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: self._raise_disconnected_error() res = { "id": self.id, @@ -331,7 +332,7 @@ def to_dict(self, include_geometry: bool = True) -> JsonDict: def results_from_dict(self, data: JsonDict) -> None: super().results_from_dict(data=data) if self.is_flexible: - self._res_flexible_powers = np.array([complex(p[0], p[1]) for p in data["powers"]], dtype=complex) + self._res_flexible_powers = np.array([complex(p[0], p[1]) for p in data["powers"]], dtype=np.complex128) def _results_to_dict(self, warning: bool) -> JsonDict: if self.is_flexible: @@ -344,16 +345,12 @@ def _results_to_dict(self, warning: bool) -> JsonDict: class CurrentLoad(AbstractLoad): - """A constant current load. - - See Also: - :doc:`Current Load documentation ` - """ + """A constant current load.""" _type = "current" def __init__( - self, id: Id, bus: Bus, *, currents: Sequence[complex], phases: Optional[str] = None, **kwargs: Any + self, id: Id, bus: Bus, *, currents: ComplexArrayLike1D, phases: Optional[str] = None, **kwargs: Any ) -> None: """CurrentLoad constructor. @@ -365,7 +362,8 @@ def __init__( The bus to connect the load to. currents: - List of currents for each phase (Amps). + An array-like of the currents for each phase component. Either complex values (A) + or a :class:`Quantity ` of complex values. phases: The phases of the load. A string like ``"abc"`` or ``"an"`` etc. The order of the @@ -377,18 +375,18 @@ def __init__( self.currents = currents # handles size checks and unit conversion @property - @ureg_wraps("A", (None,), strict=False) - def currents(self) -> Q_[np.ndarray]: + @ureg_wraps("A", (None,)) + def currents(self) -> Q_[ComplexArray]: """The currents of the load (Amps).""" return self._currents @currents.setter - @ureg_wraps(None, (None, "A"), strict=False) - def currents(self, value: Sequence[complex]) -> None: + @ureg_wraps(None, (None, "A")) + def currents(self, value: ComplexArrayLike1D) -> None: self._currents = self._validate_value(value) self._invalidate_network_results() - def to_dict(self, include_geometry: bool = True) -> JsonDict: + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: self._raise_disconnected_error() return { "id": self.id, @@ -399,16 +397,12 @@ def to_dict(self, include_geometry: bool = True) -> JsonDict: class ImpedanceLoad(AbstractLoad): - """A constant impedance load. - - See Also: - :doc:`Impedance Load documentation ` - """ + """A constant impedance load.""" _type = "impedance" def __init__( - self, id: Id, bus: Bus, *, impedances: Sequence[complex], phases: Optional[str] = None, **kwargs: Any + self, id: Id, bus: Bus, *, impedances: ComplexArrayLike1D, phases: Optional[str] = None, **kwargs: Any ) -> None: """ImpedanceLoad constructor. @@ -420,7 +414,8 @@ def __init__( The bus to connect the load to. impedances: - List of impedances for each phase (Ohms). + An array-like of the impedances for each phase component. Either complex values + (Ohms) or a :class:`Quantity ` of complex values. phases: The phases of the load. A string like ``"abc"`` or ``"an"`` etc. The order of the @@ -432,18 +427,18 @@ def __init__( self.impedances = impedances @property - @ureg_wraps("ohm", (None,), strict=False) - def impedances(self) -> Q_[np.ndarray]: + @ureg_wraps("ohm", (None,)) + def impedances(self) -> Q_[ComplexArray]: """The impedances of the load (Ohms).""" return self._impedances @impedances.setter - @ureg_wraps(None, (None, "ohm"), strict=False) - def impedances(self, impedances: Sequence[complex]) -> None: + @ureg_wraps(None, (None, "ohm")) + def impedances(self, impedances: ComplexArrayLike1D) -> None: self._impedances = self._validate_value(impedances) self._invalidate_network_results() - def to_dict(self, include_geometry: bool = True) -> JsonDict: + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: self._raise_disconnected_error() return { "id": self.id, diff --git a/roseau/load_flow/models/potential_refs.py b/roseau/load_flow/models/potential_refs.py index deb25c3e..daba1a7f 100644 --- a/roseau/load_flow/models/potential_refs.py +++ b/roseau/load_flow/models/potential_refs.py @@ -21,9 +21,6 @@ class PotentialRef(Element): can be set on any bus or ground elements. If set on a bus with no neutral and without specifying the phase, the reference will be set as ``Va + Vb + Vc = 0``. For other buses, the default is ``Vn = 0``. - - See Also: - :doc:`Potential reference model documentation ` """ allowed_phases = frozenset({"a", "b", "c", "n"}) @@ -71,7 +68,7 @@ def _res_current_getter(self, warning: bool) -> complex: return self._res_getter(self._res_current, warning) @property - @ureg_wraps("A", (None,), strict=False) + @ureg_wraps("A", (None,)) def res_current(self) -> Q_[complex]: """The sum of the currents (A) of the connection associated to the potential reference. @@ -86,7 +83,7 @@ def res_current(self) -> Q_[complex]: def from_dict(cls, data: JsonDict) -> Self: return cls(data["id"], data["element"], phase=data.get("phases")) - def to_dict(self, include_geometry: bool = True) -> JsonDict: + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: res = {"id": self.id} e = self.element if isinstance(e, Bus): diff --git a/roseau/load_flow/models/sources.py b/roseau/load_flow/models/sources.py index 5da3a7bd..49a5e7bc 100644 --- a/roseau/load_flow/models/sources.py +++ b/roseau/load_flow/models/sources.py @@ -1,5 +1,4 @@ import logging -from collections.abc import Sequence from typing import Any, Optional import numpy as np @@ -9,25 +8,21 @@ from roseau.load_flow.exceptions import RoseauLoadFlowException, RoseauLoadFlowExceptionCode from roseau.load_flow.models.buses import Bus from roseau.load_flow.models.core import Element -from roseau.load_flow.typing import Id, JsonDict +from roseau.load_flow.typing import ComplexArray, ComplexArrayLike1D, Id, JsonDict from roseau.load_flow.units import Q_, ureg_wraps logger = logging.getLogger(__name__) class VoltageSource(Element): - """A voltage source. - - See Also: - :doc:`Voltage source model documentation ` - """ + """A voltage source.""" allowed_phases = Bus.allowed_phases """The allowed phases for a voltage source are the same as for a :attr:`bus`.""" _floating_neutral_allowed: bool = False def __init__( - self, id: Id, bus: Bus, *, voltages: Sequence[complex], phases: Optional[str] = None, **kwargs: Any + self, id: Id, bus: Bus, *, voltages: ComplexArrayLike1D, phases: Optional[str] = None, **kwargs: Any ) -> None: """Voltage source constructor. @@ -39,9 +34,10 @@ def __init__( The bus of the voltage source. voltages: - The voltages of the source. They will be fixed on the connected bus. If the source - has a neutral connection, the voltages are the phase-to-neutral voltages, otherwise - they are the phase-to-phase voltages. + An array-like of the voltages of the source. They will be set on the connected bus. + If the source has a neutral connection, the voltages are considered phase-to-neutral + voltages, otherwise they are the phase-to-phase voltages. Either complex values (V) + or a :class:`Quantity ` of complex values. phases: The phases of the source. A string like ``"abc"`` or ``"an"`` etc. The order of the @@ -78,7 +74,7 @@ def __init__( self.voltages = voltages # Results - self._res_currents: Optional[np.ndarray] = None + self._res_currents: Optional[ComplexArray] = None def __repr__(self) -> str: bus_id = self.bus.id if self.bus is not None else None @@ -88,19 +84,19 @@ def __repr__(self) -> str: ) @property - @ureg_wraps("V", (None,), strict=False) - def voltages(self) -> Q_[np.ndarray]: + @ureg_wraps("V", (None,)) + def voltages(self) -> Q_[ComplexArray]: """The voltages of the source (V).""" return self._voltages @voltages.setter - @ureg_wraps(None, (None, "V"), strict=False) - def voltages(self, voltages: Sequence[complex]) -> None: + @ureg_wraps(None, (None, "V")) + def voltages(self, voltages: ComplexArrayLike1D) -> None: if len(voltages) != self._size: msg = f"Incorrect number of voltages: {len(voltages)} instead of {self._size}" logger.error(msg) raise RoseauLoadFlowException(msg, code=RoseauLoadFlowExceptionCode.BAD_VOLTAGES_SIZE) - self._voltages = np.asarray(voltages, dtype=complex) + self._voltages = np.array(voltages, dtype=np.complex128) self._invalidate_network_results() @property @@ -108,33 +104,33 @@ def voltage_phases(self) -> list[str]: """The phases of the source voltages.""" return calculate_voltage_phases(self.phases) - def _res_currents_getter(self, warning: bool) -> np.ndarray: + def _res_currents_getter(self, warning: bool) -> ComplexArray: return self._res_getter(value=self._res_currents, warning=warning) @property - @ureg_wraps("A", (None,), strict=False) - def res_currents(self) -> Q_[np.ndarray]: + @ureg_wraps("A", (None,)) + def res_currents(self) -> Q_[ComplexArray]: """The load flow result of the source currents (A).""" return self._res_currents_getter(warning=True) - def _res_potentials_getter(self, warning: bool) -> np.ndarray: + def _res_potentials_getter(self, warning: bool) -> ComplexArray: self._raise_disconnected_error() return self.bus._get_potentials_of(self.phases, warning) @property - @ureg_wraps("V", (None,), strict=False) - def res_potentials(self) -> Q_[np.ndarray]: + @ureg_wraps("V", (None,)) + def res_potentials(self) -> Q_[ComplexArray]: """The load flow result of the source potentials (V).""" return self._res_potentials_getter(warning=True) - def _res_powers_getter(self, warning: bool) -> np.ndarray: + def _res_powers_getter(self, warning: bool) -> ComplexArray: curs = self._res_currents_getter(warning) pots = self._res_potentials_getter(warning=False) # we warn on the previous line return pots * curs.conj() @property - @ureg_wraps("VA", (None,), strict=False) - def res_powers(self) -> Q_[np.ndarray]: + @ureg_wraps("VA", (None,)) + def res_powers(self) -> Q_[ComplexArray]: """The load flow result of the source powers (VA).""" return self._res_powers_getter(warning=True) @@ -161,7 +157,7 @@ def from_dict(cls, data: JsonDict) -> Self: voltages = [complex(v[0], v[1]) for v in data["voltages"]] return cls(data["id"], data["bus"], voltages=voltages, phases=data["phases"]) - def to_dict(self, include_geometry: bool = True) -> JsonDict: + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: self._raise_disconnected_error() return { "id": self.id, @@ -171,7 +167,7 @@ def to_dict(self, include_geometry: bool = True) -> JsonDict: } def results_from_dict(self, data: JsonDict) -> None: - self._res_currents = np.array([complex(i[0], i[1]) for i in data["currents"]], dtype=complex) + self._res_currents = np.array([complex(i[0], i[1]) for i in data["currents"]], dtype=np.complex128) def _results_to_dict(self, warning: bool) -> JsonDict: return { diff --git a/roseau/load_flow/models/tests/test_branches.py b/roseau/load_flow/models/tests/test_branches.py index 1b0bebf6..72f7829e 100644 --- a/roseau/load_flow/models/tests/test_branches.py +++ b/roseau/load_flow/models/tests/test_branches.py @@ -225,9 +225,9 @@ def test_powers_equal(network_with_results): def test_lines_results(phases, z_line, y_shunt, len_line, bus_pot, line_cur, ground_pot, expected_pow): bus1 = Bus("bus1", phases=phases["bus1"]) bus2 = Bus("bus2", phases=phases["bus2"]) - y_shunt = np.asarray(y_shunt, dtype=complex) if y_shunt is not None else None + y_shunt = np.array(y_shunt, dtype=np.complex128) if y_shunt is not None else None ground = Ground("gnd") - lp = LineParameters("lp", z_line=np.asarray(z_line, dtype=complex), y_shunt=y_shunt) + lp = LineParameters("lp", z_line=np.array(z_line, dtype=np.complex128), y_shunt=y_shunt) line = Line( "line", bus1, diff --git a/roseau/load_flow/models/tests/test_buses.py b/roseau/load_flow/models/tests/test_buses.py index 298d7564..5fd92ff9 100644 --- a/roseau/load_flow/models/tests/test_buses.py +++ b/roseau/load_flow/models/tests/test_buses.py @@ -1,14 +1,21 @@ import numpy as np +import pandas as pd import pytest from roseau.load_flow import ( + Q_, Bus, ElectricalNetwork, Ground, + Line, + LineParameters, PotentialRef, PowerLoad, RoseauLoadFlowException, RoseauLoadFlowExceptionCode, + Switch, + Transformer, + TransformerParameters, VoltageSource, ) @@ -74,3 +81,277 @@ def test_short_circuit(): bus.add_short_circuit("a", "b") assert "is already connected on bus" in e.value.msg assert e.value.args[1] == RoseauLoadFlowExceptionCode.BAD_SHORT_CIRCUIT + + +def test_voltage_limits(): + # Default values + bus = Bus("bus", phases="abc") + assert bus.min_voltage is None + assert bus.max_voltage is None + + # Passed as arguments + bus = Bus("bus", phases="abc", min_voltage=350, max_voltage=420) + assert bus.min_voltage == Q_(350, "V") + assert bus.max_voltage == Q_(420, "V") + + # Can be set to a real number + bus.min_voltage = 350.0 + bus.max_voltage = 420.0 + assert bus.min_voltage == Q_(350.0, "V") + assert bus.max_voltage == Q_(420.0, "V") + + # Can be reset to None + bus.min_voltage = None + bus.max_voltage = None + assert bus.min_voltage is None + assert bus.max_voltage is None + + # Can be set to a Quantity + bus.min_voltage = Q_(19, "kV") + bus.max_voltage = Q_(21, "kV") + assert bus.min_voltage == Q_(19_000, "V") + assert bus.max_voltage == Q_(21_000, "V") + + # NaNs are converted to None + for na in (np.nan, float("nan"), pd.NA): + bus.min_voltage = na + bus.max_voltage = na + assert bus.min_voltage is None + assert bus.max_voltage is None + + # Bad values + bus.min_voltage = 220 + with pytest.raises(RoseauLoadFlowException) as e: + bus.max_voltage = 200 + assert e.value.code == RoseauLoadFlowExceptionCode.BAD_VOLTAGES + assert e.value.msg == "Cannot set max voltage of bus 'bus' to 200 V as it is lower than its min voltage (220 V)." + bus.max_voltage = 240 + with pytest.raises(RoseauLoadFlowException) as e: + bus.min_voltage = 250 + assert e.value.code == RoseauLoadFlowExceptionCode.BAD_VOLTAGES + assert e.value.msg == "Cannot set min voltage of bus 'bus' to 250 V as it is higher than its max voltage (240 V)." + + +def test_res_violated(): + bus = Bus("bus", phases="abc") + direct_seq = np.exp([0, -2 / 3 * np.pi * 1j, 2 / 3 * np.pi * 1j]) + bus._res_potentials = 230 * direct_seq + + # No limits + assert bus.res_violated is None + + # Only min voltage + bus.min_voltage = 350 + assert bus.res_violated is False + bus.min_voltage = 450 + assert bus.res_violated is True + + # Only max voltage + bus.min_voltage = None + bus.max_voltage = 450 + assert bus.res_violated is False + bus.max_voltage = 350 + assert bus.res_violated is True + + # Both min and max voltage + # min <= v <= max + bus.min_voltage = 350 + bus.max_voltage = 450 + assert bus.res_violated is False + # v < min + bus.min_voltage = 450 + assert bus.res_violated is True + # v > max + bus.min_voltage = 350 + bus.max_voltage = 350 + assert bus.res_violated is True + + +def test_propagate_limits(): # noqa: C901 + b1_mv = Bus("b1_mv", phases="abc") + b2_mv = Bus("b2_mv", phases="abc") + b3_mv = Bus("b3_mv", phases="abc") + b1_lv = Bus("b1_lv", phases="abcn") + b2_lv = Bus("b2_lv", phases="abcn") + + PotentialRef("pref_mv", element=b1_mv) + g = Ground("g") + PotentialRef("pref_lv", element=g) + + lp_mv = LineParameters("lp_mv", z_line=np.eye(3), y_shunt=0.1 * np.eye(3)) + lp_lv = LineParameters("lp_lv", z_line=np.eye(4)) + tp = TransformerParameters.from_catalogue(id="SE_Minera_A0Ak_100kVA", manufacturer="SE") + + Line("l1_mv", b1_mv, b2_mv, length=1.5, parameters=lp_mv, ground=g) + Line("l2_mv", b2_mv, b3_mv, length=2, parameters=lp_mv, ground=g) + Transformer("tr", b3_mv, b1_lv, parameters=tp) + Line("l1_lv", b1_lv, b2_lv, length=1, parameters=lp_lv) + + voltages = 20_000 * np.exp([0, -2 / 3 * np.pi * 1j, 2 / 3 * np.pi * 1j]) + VoltageSource("s_mv", bus=b1_mv, voltages=voltages) + + PowerLoad("pl1_mv", bus=b2_mv, powers=[10e3, 10e3, 10e3]) + PowerLoad("pl2_mv", bus=b3_mv, powers=[10e3, 10e3, 10e3]) + PowerLoad("pl1_lv", bus=b1_lv, powers=[1e3, 1e3, 1e3]) + PowerLoad("pl2_lv", bus=b2_lv, powers=[1e3, 1e3, 1e3]) + + # All buses have None as min and max voltage + for bus in (b1_mv, b2_mv, b3_mv, b1_lv, b2_lv): + assert bus.min_voltage is None + assert bus.max_voltage is None + + # Set min and max voltage of b1_mv + b1_mv.min_voltage = 19_000 + b1_mv.max_voltage = 21_000 + # propagate MV voltage limits + b1_mv.propagate_limits() + for bus in (b1_mv, b2_mv, b3_mv): + assert bus.min_voltage == Q_(19_000, "V") + assert bus.max_voltage == Q_(21_000, "V") + for bus in (b1_lv, b2_lv): + assert bus.min_voltage is None + assert bus.max_voltage is None + + # Set min and max voltage of b1_lv + b1_lv.min_voltage = 217 + b1_lv.max_voltage = 253 + b1_lv.propagate_limits() + for bus in (b1_mv, b2_mv, b3_mv): + assert bus.min_voltage == Q_(19_000, "V") + assert bus.max_voltage == Q_(21_000, "V") + for bus in (b1_lv, b2_lv): + assert bus.min_voltage == Q_(217, "V") + assert bus.max_voltage == Q_(253, "V") + + # Reset min MV voltage limits only + b1_mv.min_voltage = None + b1_mv.propagate_limits() + for bus in (b1_mv, b2_mv, b3_mv): + assert bus.min_voltage is None + assert bus.max_voltage == Q_(21_000, "V") + for bus in (b1_lv, b2_lv): + assert bus.min_voltage == Q_(217, "V") + assert bus.max_voltage == Q_(253, "V") + + # Error, different max voltage limits + b1_mv.max_voltage = 21_005 + with pytest.raises(RoseauLoadFlowException) as e: + b1_mv.propagate_limits() + assert e.value.code == RoseauLoadFlowExceptionCode.BAD_VOLTAGES + assert e.value.msg == ( + "Cannot propagate the maximum voltage (21005 V) of bus 'b1_mv' to bus 'b2_mv' with " + "different maximum voltage (21000 V)." + ) + + # The limits are not changed after the error + for bus in (b2_mv, b3_mv): + assert bus.min_voltage is None + assert bus.max_voltage == Q_(21_000, "V") + for bus in (b1_lv, b2_lv): + assert bus.min_voltage == Q_(217, "V") + assert bus.max_voltage == Q_(253, "V") + + # It is okay to propagate with different limits if force=True + b1_mv.propagate_limits(force=True) + for bus in (b1_mv, b2_mv, b3_mv): + assert bus.min_voltage is None + assert bus.max_voltage == Q_(21_005, "V") + for bus in (b1_lv, b2_lv): + assert bus.min_voltage == Q_(217, "V") + assert bus.max_voltage == Q_(253, "V") + + # What if there is a switch? + b4_mv = Bus("b4_mv", phases="abc") + Switch("sw", b2_mv, b4_mv) + b1_mv.propagate_limits() + for bus in (b1_mv, b2_mv, b3_mv, b4_mv): + assert bus.min_voltage is None + assert bus.max_voltage == Q_(21_005, "V") + for bus in (b1_lv, b2_lv): + assert bus.min_voltage == Q_(217, "V") + assert bus.max_voltage == Q_(253, "V") + + # Let's add a MV loop; does it still work? + Line("l3_mv", b1_mv, b3_mv, length=1, parameters=lp_mv, ground=g) + b1_mv.min_voltage = 19_000 + b1_mv.propagate_limits() + for bus in (b1_mv, b2_mv, b3_mv, b4_mv): + assert bus.min_voltage == Q_(19_000, "V") + assert bus.max_voltage == Q_(21_005, "V") + for bus in (b1_lv, b2_lv): + assert bus.min_voltage == Q_(217, "V") + assert bus.max_voltage == Q_(253, "V") + + +def test_get_connected_buses(): + b1_mv = Bus("b1_mv", phases="abc") + b2_mv = Bus("b2_mv", phases="abc") + b3_mv = Bus("b3_mv", phases="abc") + b4_mv = Bus("b4_mv", phases="abc") + b1_lv = Bus("b1_lv", phases="abcn") + b2_lv = Bus("b2_lv", phases="abcn") + b3_lv = Bus("b3_lv", phases="abcn") + + PotentialRef("pref_mv", element=b1_mv) + g = Ground("g") + PotentialRef("pref_lv", element=g) + + lp_mv = LineParameters("lp_mv", z_line=np.eye(3), y_shunt=0.1 * np.eye(3)) + lp_lv = LineParameters("lp_lv", z_line=np.eye(4)) + tp = TransformerParameters.from_catalogue(id="SE_Minera_A0Ak_100kVA", manufacturer="SE") + + Line("l1_mv", b1_mv, b2_mv, length=1.5, parameters=lp_mv, ground=g) + Line("l2_mv", b2_mv, b3_mv, length=2, parameters=lp_mv, ground=g) + Line("l3_mv", b2_mv, b4_mv, length=0.5, parameters=lp_mv, ground=g) # creates a loop + Switch("sw_mv", b3_mv, b4_mv) + Transformer("tr", b3_mv, b1_lv, parameters=tp) + Line("l1_lv", b1_lv, b2_lv, length=1, parameters=lp_lv) + Switch("sw_lv", b2_lv, b3_lv) + + voltages = 20_000 * np.exp([0, -2 / 3 * np.pi * 1j, 2 / 3 * np.pi * 1j]) + VoltageSource("s_mv", bus=b1_mv, voltages=voltages) + + PowerLoad("pl1_mv", bus=b2_mv, powers=[10e3, 10e3, 10e3]) + PowerLoad("pl2_mv", bus=b3_mv, powers=[10e3, 10e3, 10e3]) + PowerLoad("pl1_lv", bus=b1_lv, powers=[1e3, 1e3, 1e3]) + PowerLoad("pl2_lv", bus=b2_lv, powers=[1e3, 1e3, 1e3]) + + mv_buses = (b1_mv, b2_mv, b3_mv, b4_mv) + mv_bus_ids = sorted(b.id for b in mv_buses) + lv_buses = (b1_lv, b2_lv, b3_lv) + lv_bus_ids = sorted(b.id for b in lv_buses) + for mvb in mv_buses: + assert sorted(mvb.get_connected_buses()) == mv_bus_ids + for lvb in lv_buses: + assert sorted(lvb.get_connected_buses()) == lv_bus_ids + + +def test_res_voltage_unbalance(): + bus = Bus("b3", phases="abc") + + va = 230 + 0j + vb = 230 * np.exp(4j * np.pi / 3) + vc = 230 * np.exp(2j * np.pi / 3) + + # Balanced system + bus._res_potentials = np.array([va, vb, vc]) + assert np.isclose(bus.res_voltage_unbalance().magnitude, 0) + + # Unbalanced system + bus._res_potentials = np.array([va, vb, vb]) + assert np.isclose(bus.res_voltage_unbalance().magnitude, 100) + + # With neutral + bus = Bus("b3n", phases="abcn") + bus._res_potentials = np.array([va, vb, vc, 0]) + assert np.isclose(bus.res_voltage_unbalance().magnitude, 0) + bus._res_potentials = np.array([va, vb, vb, 0]) + assert np.isclose(bus.res_voltage_unbalance().magnitude, 100) + + # Non 3-phase bus + bus = Bus("b1", phases="an") + bus._res_potentials = np.array([va, 0]) + with pytest.raises(RoseauLoadFlowException) as e: + bus.res_voltage_unbalance() + assert e.value.code == RoseauLoadFlowExceptionCode.BAD_PHASE + assert e.value.msg == "Voltage unbalance is only available for 3-phases buses, bus 'b1' has phases 'an'" diff --git a/roseau/load_flow/models/tests/test_flexible_parameters.py b/roseau/load_flow/models/tests/test_flexible_parameters.py index 61f3df91..ba9194a0 100644 --- a/roseau/load_flow/models/tests/test_flexible_parameters.py +++ b/roseau/load_flow/models/tests/test_flexible_parameters.py @@ -1,11 +1,17 @@ import warnings +from contextlib import contextmanager +import numpy as np +import numpy.testing as npt import pytest +from matplotlib import pyplot as plt from roseau.load_flow import ( Q_, Control, + ElectricalNetwork, FlexibleParameter, + PowerLoad, Projection, RoseauLoadFlowException, RoseauLoadFlowExceptionCode, @@ -186,6 +192,7 @@ def test_projection(): def test_flexible_parameter(): + # s_max > 0 with pytest.raises(RoseauLoadFlowException) as e: FlexibleParameter( control_p=Control.constant(), @@ -194,4 +201,175 @@ def test_flexible_parameter(): s_max=Q_(-1e3, "kVA"), ) assert e.value.msg == "'s_max' must be greater than 0 but -1.0 MVA was provided." - assert e.value.code == RoseauLoadFlowExceptionCode.BAD_SMAX_VALUE + assert e.value.code == RoseauLoadFlowExceptionCode.BAD_FLEXIBLE_PARAMETER_VALUE + + # q_min >= -s_max + with pytest.raises(RoseauLoadFlowException) as e: + FlexibleParameter( + control_p=Control.constant(), + control_q=Control.constant(), + projection=Projection(type="euclidean"), + s_max=Q_(1e3, "kVA"), + q_min=Q_(-2e3, "kVAr"), + ) + assert e.value.msg == "'q_min' must be greater than -s_max (-1.0 MVA) but -2.0 MVAr was provided." + assert e.value.code == RoseauLoadFlowExceptionCode.BAD_FLEXIBLE_PARAMETER_VALUE + + # q_max <= s_max + with pytest.raises(RoseauLoadFlowException) as e: + FlexibleParameter( + control_p=Control.constant(), + control_q=Control.constant(), + projection=Projection(type="euclidean"), + s_max=Q_(1e3, "kVA"), + q_max=Q_(2e3, "kVAr"), + ) + assert e.value.msg == "'q_max' must be less than s_max (1.0 MVA) but 2.0 MVAr was provided." + assert e.value.code == RoseauLoadFlowExceptionCode.BAD_FLEXIBLE_PARAMETER_VALUE + + fp = FlexibleParameter( + control_p=Control.constant(), + control_q=Control.constant(), + projection=Projection(type="euclidean"), + s_max=Q_(3e3, "kVA"), + q_max=Q_(2e3, "kVAr"), + q_min=Q_(-2e3, "kVAr"), + ) + fp.s_max = Q_(1e3, "kVA") # reduce q_min and q_max + assert fp.q_max.magnitude == 1e6 + assert fp.q_min.magnitude == -1e6 + + # q_min < q_max + fp = FlexibleParameter( + control_p=Control.constant(), + control_q=Control.constant(), + projection=Projection(type="euclidean"), + s_max=Q_(3e3, "kVA"), + q_max=Q_(2e3, "kVAr"), + q_min=Q_(-2e3, "kVAr"), + ) + + with pytest.raises(RoseauLoadFlowException) as e: + fp.q_max = Q_(-2.5e3, "kVAr") + assert e.value.msg == "'q_max' must be greater than q_min (-2.0 MVAr) but -2.5 MVAr was provided." + assert e.value.code == RoseauLoadFlowExceptionCode.BAD_FLEXIBLE_PARAMETER_VALUE + + with pytest.raises(RoseauLoadFlowException) as e: + fp.q_min = Q_(2.5e3, "kVAr") + assert e.value.msg == "'q_min' must be greater than q_max (2.0 MVAr) but 2.5 MVAr was provided." + assert e.value.code == RoseauLoadFlowExceptionCode.BAD_FLEXIBLE_PARAMETER_VALUE + + +@pytest.fixture(params=["constant", "p_max_u_production", "p_max_u_consumption"]) +def control_p(request) -> Control: + if request.param == "constant": + return Control.constant() + elif request.param == "p_max_u_production": + return Control.p_max_u_production(u_up=Q_(240, "V"), u_max=Q_(250, "V")) + elif request.param == "p_max_u_consumption": + return Control.p_max_u_production(u_up=Q_(210, "V"), u_max=Q_(220, "V")) + raise NotImplementedError(request.param) + + +@pytest.fixture(params=["constant", "q_u"]) +def control_q(request) -> Control: + if request.param == "constant": + return Control.constant() + elif request.param == "q_u": + return Control.q_u(u_min=Q_(210, "V"), u_down=Q_(220, "V"), u_up=Q_(240, "V"), u_max=Q_(250, "V")) + raise NotImplementedError(request.param) + + +@pytest.fixture(params=["keep_p", "keep_q", "euclidean"]) +def projection(request) -> Projection: + return Projection(type=request.param) + + +@pytest.fixture() +def flexible_parameter(control_p, control_q, projection) -> FlexibleParameter: + return FlexibleParameter(control_p=control_p, control_q=control_q, projection=projection, s_max=Q_(5, "kVA")) + + +@pytest.fixture() +def monkeypatch_flexible_parameter_compute_powers(monkeypatch, rg): + @contextmanager + def inner(): + nonlocal monkeypatch + with monkeypatch.context() as m: + m.setattr(target=ElectricalNetwork, name="solve_load_flow", value=lambda *args, **kwargs: 2) + m.setattr( + target=PowerLoad, + name="res_flexible_powers", + value=property( + lambda x: Q_([rg.normal(loc=-2500, scale=1000) + 1j * rg.normal(loc=0, scale=2500)], "VA") + ), + ) + yield m + + return inner + + +def test_plot(flexible_parameter, monkeypatch_flexible_parameter_compute_powers): + voltages = np.array(range(205, 256, 1), dtype=float) + power = Q_(-2.5 + 1j, "kVA") + auth = ("username", "password") + + # + # Test compute powers + # + with monkeypatch_flexible_parameter_compute_powers(): + res_flexible_powers = flexible_parameter.compute_powers(auth=auth, voltages=voltages, power=power) + + # + # Plot control P + # + fig, ax = plt.subplots() + ax, res_flexible_powers_1 = flexible_parameter.plot_control_p( + auth=auth, voltages=voltages, power=power, res_flexible_powers=res_flexible_powers, ax=ax + ) + npt.assert_allclose(res_flexible_powers.m_as("VA"), res_flexible_powers_1.m_as("VA")) + plt.close(fig) + + # The same but do not provide the res_flexible_powers + fig, ax = plt.subplots() + with monkeypatch_flexible_parameter_compute_powers(): + ax, res_flexible_powers_2 = flexible_parameter.plot_control_p(auth=auth, voltages=voltages, power=power, ax=ax) + assert not np.allclose(res_flexible_powers.m_as("VA"), res_flexible_powers_2.m_as("VA")) + plt.close(fig) + + # Plot control Q + ax, res_flexible_powers = flexible_parameter.plot_control_q( + auth=auth, voltages=voltages, power=power, res_flexible_powers=res_flexible_powers, ax=ax + ) + + # The same but do not provide the res_flexible_powers + fig, ax = plt.subplots() + with monkeypatch_flexible_parameter_compute_powers(): + ax, res_flexible_powers_3 = flexible_parameter.plot_control_q(auth=auth, voltages=voltages, power=power, ax=ax) + assert not np.allclose(res_flexible_powers.m_as("VA"), res_flexible_powers_3.m_as("VA")) + plt.close(fig) + + # Plot trajectory in the (P, Q) plane + fig, ax = plt.subplots() + ax, res_flexible_powers_4 = flexible_parameter.plot_pq( + auth=auth, + voltages=voltages, + power=power, + res_flexible_powers=res_flexible_powers, + voltages_labels_mask=np.isin(voltages, [240, 250]), + ax=ax, + ) + npt.assert_allclose(res_flexible_powers.m_as("VA"), res_flexible_powers_4.m_as("VA")) + plt.close(fig) + + # The same but do not provide the res_flexible_powers + fig, ax = plt.subplots() # Create a new ax that is not used directly in the following function call + with monkeypatch_flexible_parameter_compute_powers(): + ax, res_flexible_powers_5 = flexible_parameter.plot_pq( + auth=auth, + voltages=voltages, + power=power, + voltages_labels_mask=np.isin(voltages, [240, 250]), + ) + assert not np.allclose(res_flexible_powers.m_as("VA"), res_flexible_powers_5.m_as("VA")) + plt.close(fig) diff --git a/roseau/load_flow/models/tests/test_line_parameters.py b/roseau/load_flow/models/tests/test_line_parameters.py index 1dcfb110..85b517b7 100644 --- a/roseau/load_flow/models/tests/test_line_parameters.py +++ b/roseau/load_flow/models/tests/test_line_parameters.py @@ -5,6 +5,7 @@ from roseau.load_flow.exceptions import RoseauLoadFlowException, RoseauLoadFlowExceptionCode from roseau.load_flow.models import Bus, Ground, Line, LineParameters +from roseau.load_flow.units import Q_ from roseau.load_flow.utils import ConductorType, InsulatorType, LineType @@ -313,17 +314,19 @@ def test_sym(): def test_from_name_lv(): - with pytest.raises(RoseauLoadFlowException) as e: + with pytest.raises(RoseauLoadFlowException) as e, pytest.warns(FutureWarning): LineParameters.from_name_lv("totoS_Al_150") assert "The line type name does not follow the syntax rule." in e.value.msg assert e.value.code == RoseauLoadFlowExceptionCode.BAD_TYPE_NAME_SYNTAX - lp = LineParameters.from_name_lv("S_AL_150") + with pytest.warns(FutureWarning): + lp = LineParameters.from_name_lv("S_AL_150") assert lp.z_line.shape == (4, 4) assert lp.y_shunt.shape == (4, 4) assert (lp.z_line.real >= 0).all().all() - lp2 = LineParameters.from_name_lv("U_AL_150") + with pytest.warns(FutureWarning): + lp2 = LineParameters.from_name_lv("U_AL_150") npt.assert_allclose(lp2.z_line.m_as("ohm/km"), lp.z_line.m_as("ohm/km")) npt.assert_allclose(lp2.y_shunt.m_as("S/km"), lp.y_shunt.m_as("S/km"), rtol=1e-4) @@ -345,3 +348,20 @@ def test_from_name_mv(): lp = LineParameters.from_name_mv("U_AL_150") npt.assert_allclose(lp.z_line.m_as("ohm/km"), z_line_expected) npt.assert_allclose(lp.y_shunt.m_as("S/km"), y_shunt_expected, rtol=1e-4) + + +def test_max_current(): + lp = LineParameters("test", z_line=np.eye(3)) + assert lp.max_current is None + + lp = LineParameters("test", z_line=np.eye(3), max_current=100) + assert lp.max_current == Q_(100, "A") + + lp.max_current = 200 + assert lp.max_current == Q_(200, "A") + + lp.max_current = None + assert lp.max_current is None + + lp.max_current = Q_(3, "kA") + assert lp.max_current == Q_(3_000, "A") diff --git a/roseau/load_flow/models/tests/test_lines.py b/roseau/load_flow/models/tests/test_lines.py index bcfa954a..307e9af6 100644 --- a/roseau/load_flow/models/tests/test_lines.py +++ b/roseau/load_flow/models/tests/test_lines.py @@ -2,8 +2,8 @@ import pytest from pint import DimensionalityError -from roseau.load_flow import RoseauLoadFlowException, RoseauLoadFlowExceptionCode -from roseau.load_flow.models import Bus, Line, LineParameters +from roseau.load_flow.exceptions import RoseauLoadFlowException, RoseauLoadFlowExceptionCode +from roseau.load_flow.models import Bus, Ground, Line, LineParameters from roseau.load_flow.units import Q_ @@ -61,3 +61,77 @@ def test_lines_units(): line = Line("line", bus1=bus1, bus2=bus2, parameters=lp, length=5) with pytest.raises(DimensionalityError, match=r"Cannot convert from 'ampere' \(\[current\]\) to 'km'"): line.length = Q_(6.5, "A") + + +def test_line_parameters_shortcut(): + bus1 = Bus("bus1", phases="abcn") + bus2 = Bus("bus1", phases="abcn") + + # + # Without shunt + # + lp = LineParameters("lp", z_line=np.eye(4, dtype=complex)) + + # Z + line = Line("line", bus1=bus1, bus2=bus2, parameters=lp, length=Q_(50, "m")) + assert np.allclose(line.z_line.m_as("ohm"), 0.05 * np.eye(4, dtype=complex)) + + # Y + assert not line.with_shunt + assert np.allclose(line.y_shunt.m_as("S"), np.zeros(shape=(4, 4), dtype=complex)) + + # + # With shunt + # + z_line = 0.01 * np.eye(4, dtype=complex) + y_shunt = 1e-5 * np.eye(4, dtype=complex) + lp = LineParameters("lp", z_line=z_line, y_shunt=y_shunt) + + # Z + ground = Ground("ground") + line = Line("line", bus1=bus1, bus2=bus2, parameters=lp, length=Q_(50, "m"), ground=ground) + assert np.allclose(line.z_line.m_as("ohm"), 0.05 * z_line) + + # Y + assert line.with_shunt + assert np.allclose(line.y_shunt.m_as("S"), 0.05 * y_shunt) + + +def test_res_violated(): + bus1 = Bus("bus1", phases="abc") + bus2 = Bus("bus1", phases="abc") + lp = LineParameters("lp", z_line=np.eye(3, dtype=complex)) + line = Line("line", bus1=bus1, bus2=bus2, parameters=lp, length=Q_(50, "m")) + direct_seq = np.exp([0, -2 / 3 * np.pi * 1j, 2 / 3 * np.pi * 1j]) + + bus1._res_potentials = 230 * direct_seq + bus2._res_potentials = 225 * direct_seq + line._res_currents = 10 * direct_seq, -10 * direct_seq + + # No limits + assert line.res_violated is None + + # No constraint violated + lp.max_current = 11 + assert line.res_violated is False + + # Two violations + lp.max_current = 9 + assert line.res_violated is True + + # Side 1 violation + lp.max_current = 11 + line._res_currents = 12 * direct_seq, -10 * direct_seq + assert line.res_violated is True + + # Side 2 violation + lp.max_current = 11 + line._res_currents = 10 * direct_seq, -12 * direct_seq + assert line.res_violated is True + + # A single phase violation + lp.max_current = 11 + line._res_currents = 10 * direct_seq, -10 * direct_seq + line._res_currents[0][0] = 12 * direct_seq[0] + line._res_currents[1][0] = -12 * direct_seq[0] + assert line.res_violated is True diff --git a/roseau/load_flow/models/tests/test_loads.py b/roseau/load_flow/models/tests/test_loads.py index d4244542..4682e3b7 100644 --- a/roseau/load_flow/models/tests/test_loads.py +++ b/roseau/load_flow/models/tests/test_loads.py @@ -163,6 +163,8 @@ def test_flexible_load(): uq_up=240, uq_max=250, s_max=300, + q_min=-200, + q_max=200, alpha_control=100.0, alpha_proj=100.0, epsilon_proj=0.01, @@ -175,6 +177,8 @@ def test_flexible_load(): uq_up=240, uq_max=250, s_max=300, + q_min=-200, + q_max=200, alpha_control=100.0, alpha_proj=100.0, epsilon_proj=0.01, @@ -191,6 +195,18 @@ def test_flexible_load(): assert "The power is greater than the parameter s_max for flexible load" in e.value.msg assert e.value.code == RoseauLoadFlowExceptionCode.BAD_S_VALUE + fp = [fp_pq_prod, fp_const, fp_const] + with pytest.raises(RoseauLoadFlowException) as e: + PowerLoad("flexible load", bus, powers=[10 + 250j, 0, 0j], phases="abcn", flexible_params=fp) + assert "The reactive power is greater than the parameter q_max for flexible load" in e.value.msg + assert e.value.code == RoseauLoadFlowExceptionCode.BAD_S_VALUE + + fp = [fp_pq_prod, fp_const, fp_const] + with pytest.raises(RoseauLoadFlowException) as e: + PowerLoad("flexible load", bus, powers=[10 - 250j, 0, 0j], phases="abcn", flexible_params=fp) + assert "The reactive power is lesser than the parameter q_min for flexible load" in e.value.msg + assert e.value.code == RoseauLoadFlowExceptionCode.BAD_S_VALUE + fp = [fp_pq_prod, fp_const, fp_const] with pytest.raises(RoseauLoadFlowException) as e: PowerLoad("flexible load", bus, powers=[100 + 50j, 0, 0j], phases="abcn", flexible_params=fp) @@ -305,8 +321,8 @@ def test_loads_to_dict(): "control_q": {"type": "constant"}, "projection": { "type": "euclidean", - "alpha": Projection.DEFAULT_ALPHA, - "epsilon": Projection.DEFAULT_EPSILON, + "alpha": Projection._DEFAULT_ALPHA, + "epsilon": Projection._DEFAULT_EPSILON, }, "s_max": 1.0, }, diff --git a/roseau/load_flow/models/tests/test_transformer_parameters.py b/roseau/load_flow/models/tests/test_transformer_parameters.py index 09920c4c..3b259f6d 100644 --- a/roseau/load_flow/models/tests/test_transformer_parameters.py +++ b/roseau/load_flow/models/tests/test_transformer_parameters.py @@ -1,5 +1,6 @@ +import numbers + import numpy as np -import pandas as pd import pytest from pint import DimensionalityError @@ -350,9 +351,12 @@ def test_catalogue_data(): assert np.isclose(tp.psc.m_as("W"), catalogue_data.at[tp.id, "psc"]) assert np.isclose(tp.vsc.m_as(""), catalogue_data.at[tp.id, "vsc"]) - # Check that the transformer can be used - res = tp.to_zyk() - assert all(pd.notna(x) for x in res) + # Check that the parameters are valid + z, y, k, orientation = tp.to_zyk() + assert isinstance(z.m_as("ohm"), numbers.Number) + assert isinstance(y.m_as("S"), numbers.Number) + assert isinstance(k.m_as(""), numbers.Number) + assert orientation in (-1.0, 1.0) # At the end of the process, the found column must be full of True assert catalogue_data["found"].all(), error_message @@ -408,18 +412,18 @@ def test_print_catalogue(): # Print the entire catalogue with console.capture() as capture: TransformerParameters.print_catalogue() - assert len(capture.get().split("\n")) == 138 + assert len(capture.get().split("\n")) == 136 # Filter on a single attribute for field_name, value, expected_lines in ( - ("id", "SE_Minera_A0Ak_50kVA", 9), - ("manufacturer", "SE", 124), - ("range", r"min.*", 64), - ("efficiency", "c0", 37), - ("type", "dy", 134), - ("sn", Q_(160, "kVA"), 18), - ("uhv", Q_(20, "kV"), 138), - ("ulv", 400, 138), + ("id", "SE_Minera_A0Ak_50kVA", 7), + ("manufacturer", "SE", 122), + ("range", r"min.*", 62), + ("efficiency", "c0", 35), + ("type", "dy", 132), + ("sn", Q_(160, "kVA"), 16), + ("uhv", Q_(20, "kV"), 136), + ("ulv", 400, 136), ): with console.capture() as capture: TransformerParameters.print_catalogue(**{field_name: value}) @@ -427,13 +431,13 @@ def test_print_catalogue(): # Filter on two attributes for field_name, value, expected_lines in ( - ("id", "SE_Minera_A0Ak_50kVA", 9), - ("range", "minera", 64), - ("efficiency", "c0", 37), - ("type", r"^d.*11$", 120), - ("sn", Q_(160, "kVA"), 17), - ("uhv", Q_(20, "kV"), 124), - ("ulv", 400, 124), + ("id", "SE_Minera_A0Ak_50kVA", 7), + ("range", "minera", 62), + ("efficiency", "c0", 35), + ("type", r"^d.*11$", 118), + ("sn", Q_(160, "kVA"), 15), + ("uhv", Q_(20, "kV"), 122), + ("ulv", 400, 122), ): with console.capture() as capture: TransformerParameters.print_catalogue(**{field_name: value}, manufacturer="se") @@ -441,12 +445,12 @@ def test_print_catalogue(): # Filter on three attributes for field_name, value, expected_lines in ( - ("id", "se_VEGETA_C0BK_3150kva", 9), - ("efficiency", r"c0[abc]k", 23), - ("type", "dyn", 38), - ("sn", Q_(160, "kVA"), 10), - ("uhv", Q_(20, "kV"), 38), - ("ulv", 400, 38), + ("id", "se_VEGETA_C0BK_3150kva", 7), + ("efficiency", r"c0[abc]k", 21), + ("type", "dyn", 36), + ("sn", Q_(160, "kVA"), 8), + ("uhv", Q_(20, "kV"), 36), + ("ulv", 400, 36), ): with console.capture() as capture: TransformerParameters.print_catalogue(**{field_name: value}, manufacturer="se", range=r"^vegeta$") @@ -456,3 +460,30 @@ def test_print_catalogue(): with console.capture() as capture: TransformerParameters.print_catalogue(ulv=250) assert len(capture.get().split("\n")) == 2 + + +def test_max_power(): + kwds = { + "type": "yzn11", + "psc": 1350.0, + "p0": 145.0, + "i0": 1.8 / 100, + "ulv": 400, + "uhv": 20000, + "sn": 50 * 1e3, + "vsc": 4 / 100, + } + tp = TransformerParameters("test", **kwds) + assert tp.max_power is None + + tp = TransformerParameters("test", **kwds, max_power=60_000) + assert tp.max_power == Q_(60_000, "VA") + + tp.max_power = 55_000 + assert tp.max_power == Q_(55_000, "VA") + + tp.max_power = None + assert tp.max_power is None + + tp.max_power = Q_(65, "kVA") + assert tp.max_power == Q_(65_000, "VA") diff --git a/roseau/load_flow/models/tests/test_transformers.py b/roseau/load_flow/models/tests/test_transformers.py new file mode 100644 index 00000000..c941e18c --- /dev/null +++ b/roseau/load_flow/models/tests/test_transformers.py @@ -0,0 +1,38 @@ +import numpy as np + +from roseau.load_flow.models import Bus, Transformer, TransformerParameters + + +def test_res_violated(): + bus1 = Bus("bus1", phases="abc") + bus2 = Bus("bus1", phases="abcn") + tp = TransformerParameters( + id="tp", psc=1350.0, p0=145.0, i0=1.8 / 100, ulv=400, uhv=20000, sn=50 * 1e3, vsc=4 / 100, type="yzn11" + ) + transformer = Transformer("transformer", bus1=bus1, bus2=bus2, parameters=tp) + direct_seq = np.exp([0, -2 / 3 * np.pi * 1j, 2 / 3 * np.pi * 1j]) + direct_seq_neutral = np.concatenate([direct_seq, [0]]) + + bus1._res_potentials = 20_000 * direct_seq + bus2._res_potentials = 230 * direct_seq_neutral + transformer._res_currents = 0.8 * direct_seq, -65 * direct_seq_neutral + + # No limits + assert transformer.res_violated is None + + # No constraint violated + tp.max_power = 50_000 + assert transformer.res_violated is False + + # Two violations + tp.max_power = 40_000 + assert transformer.res_violated is True + + # Primary side violation + tp.max_power = 47_900 + assert transformer.res_violated is True + + # Secondary side violation + tp.max_power = 50_000 + transformer._res_currents = 0.8 * direct_seq, -80 * direct_seq_neutral + assert transformer.res_violated is True diff --git a/roseau/load_flow/models/transformers/parameters.py b/roseau/load_flow/models/transformers/parameters.py index c3f8acf9..31ec8d07 100644 --- a/roseau/load_flow/models/transformers/parameters.py +++ b/roseau/load_flow/models/transformers/parameters.py @@ -2,6 +2,7 @@ import re import textwrap from importlib import resources +from itertools import cycle from pathlib import Path from typing import NoReturn, Optional, Union @@ -14,17 +15,13 @@ from roseau.load_flow.exceptions import RoseauLoadFlowException, RoseauLoadFlowExceptionCode from roseau.load_flow.typing import Id, JsonDict from roseau.load_flow.units import Q_, ureg_wraps -from roseau.load_flow.utils import CatalogueMixin, Identifiable, JsonMixin, console +from roseau.load_flow.utils import CatalogueMixin, Identifiable, JsonMixin, console, palette logger = logging.getLogger(__name__) class TransformerParameters(Identifiable, JsonMixin, CatalogueMixin[pd.DataFrame]): - """A class to store the parameters of the transformers. - - See Also: - :ref:`Transformer parameters documentation ` - """ + """Parameters that define electrical models of transformers.""" _EXTRACT_WINDINGS_RE = regex.compile( "(?(DEFINE)(?Pyn?)(?Pd)(?Pzn?)(?P[06])" @@ -40,18 +37,19 @@ class TransformerParameters(Identifiable, JsonMixin, CatalogueMixin[pd.DataFrame ) """The pattern to extract the winding of the primary and of the secondary of the transformer.""" - @ureg_wraps(None, (None, None, None, "V", "V", "VA", "W", "", "W", ""), strict=False) + @ureg_wraps(None, (None, None, None, "V", "V", "VA", "W", "", "W", "", "VA")) def __init__( self, id: Id, type: str, - uhv: float, - ulv: float, - sn: float, - p0: float, - i0: float, - psc: float, - vsc: float, + uhv: Union[float, Q_[float]], + ulv: Union[float, Q_[float]], + sn: Union[float, Q_[float]], + p0: Union[float, Q_[float]], + i0: Union[float, Q_[float]], + psc: Union[float, Q_[float]], + vsc: Union[float, Q_[float]], + max_power: Optional[Union[float, Q_[float]]] = None, ) -> None: """TransformerParameters constructor. @@ -84,22 +82,11 @@ def __init__( vsc: Voltages on LV side during short-circuit test (%) + + max_power: + The maximum power loading of the transformer (VA). It is not used in the load flow. """ super().__init__(id) - self._sn = sn - self._uhv = uhv - self._ulv = ulv - self._i0 = i0 - self._p0 = p0 - self._psc = psc - self._vsc = vsc - self.type = type - if type in ("single", "center"): - self.winding1 = None - self.winding2 = None - self.phase_displacement = None - else: - self.winding1, self.winding2, self.phase_displacement = self.extract_windings(string=type) # Check if uhv < ulv: @@ -136,6 +123,22 @@ def __init__( f"imaginary part will be null." ) + self._sn = sn + self._uhv = uhv + self._ulv = ulv + self._i0 = i0 + self._p0 = p0 + self._psc = psc + self._vsc = vsc + self.type = type + if type in ("single", "center"): + self.winding1 = None + self.winding2 = None + self.phase_displacement = None + else: + self.winding1, self.winding2, self.phase_displacement = self.extract_windings(string=type) + self.max_power = max_power + def __eq__(self, other: object) -> bool: if not isinstance(other, TransformerParameters): return NotImplemented @@ -153,48 +156,58 @@ def __eq__(self, other: object) -> bool: ) @property - @ureg_wraps("V", (None,), strict=False) + @ureg_wraps("V", (None,)) def uhv(self) -> Q_[float]: """Phase-to-phase nominal voltages of the high voltages side (V)""" return self._uhv @property - @ureg_wraps("V", (None,), strict=False) + @ureg_wraps("V", (None,)) def ulv(self) -> Q_[float]: """Phase-to-phase nominal voltages of the low voltages side (V)""" return self._ulv @property - @ureg_wraps("VA", (None,), strict=False) + @ureg_wraps("VA", (None,)) def sn(self) -> Q_[float]: """The nominal power of the transformer (VA)""" return self._sn @property - @ureg_wraps("W", (None,), strict=False) + @ureg_wraps("W", (None,)) def p0(self) -> Q_[float]: """Losses during off-load test (W)""" return self._p0 @property - @ureg_wraps("", (None,), strict=False) + @ureg_wraps("", (None,)) def i0(self) -> Q_[float]: """Current during off-load test (%)""" return self._i0 @property - @ureg_wraps("W", (None,), strict=False) + @ureg_wraps("W", (None,)) def psc(self) -> Q_[float]: """Losses during short-circuit test (W)""" return self._psc @property - @ureg_wraps("", (None,), strict=False) + @ureg_wraps("", (None,)) def vsc(self) -> Q_[float]: """Voltages on LV side during short-circuit test (%)""" return self._vsc - @ureg_wraps(("ohm", "S", "", None), (None,), strict=False) + @property + def max_power(self) -> Optional[Q_[float]]: + """The maximum power loading of the transformer (VA) if it is set.""" + return None if self._max_power is None else Q_(self._max_power, "VA") + + @max_power.setter + @ureg_wraps(None, (None, "VA")) + def max_power(self, value: Optional[Union[float, Q_[float]]]) -> None: + self._max_power = value + + @ureg_wraps(("ohm", "S", "", None), (None,)) def to_zyk(self) -> tuple[Q_[complex], Q_[complex], Q_[float], float]: """Compute the transformer parameters ``z2``, ``ym``, ``k`` and ``orientation`` mandatory for some models. @@ -261,10 +274,11 @@ def from_dict(cls, data: JsonDict) -> Self: i0=data["i0"], # Current during off-load test (%) psc=data["psc"], # Losses during short-circuit test (W) vsc=data["vsc"], # Voltages on LV side during short-circuit test (%) + max_power=data.get("max_power"), # Maximum power loading (VA) ) - def to_dict(self, include_geometry: bool = True) -> JsonDict: - return { + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: + res = { "id": self.id, "sn": self._sn, "uhv": self._uhv, @@ -275,6 +289,9 @@ def to_dict(self, include_geometry: bool = True) -> JsonDict: "vsc": self._vsc, "type": self.type, } + if not _lf_only and self.max_power is not None: + res["max_power"] = self.max_power.magnitude + return res def _results_to_dict(self, warning: bool) -> NoReturn: msg = f"The {type(self).__name__} has no results to export." @@ -298,7 +315,7 @@ def catalogue_data(cls) -> pd.DataFrame: return pd.read_csv(cls.catalogue_path() / "Catalogue.csv") @classmethod - @ureg_wraps(None, (None, None, None, None, None, None, "VA", "V", "V"), strict=False) + @ureg_wraps(None, (None, None, None, None, None, None, "VA", "V", "V")) def from_catalogue( cls, id: Optional[Union[str, re.Pattern[str]]] = None, @@ -443,7 +460,7 @@ def from_catalogue( return cls.from_json(path=path) @classmethod - @ureg_wraps(None, (None, None, None, None, None, None, "VA", "V", "V"), strict=False) + @ureg_wraps(None, (None, None, None, None, None, None, "VA", "V", "V")) def print_catalogue( cls, id: Optional[Union[str, re.Pattern[str]]] = None, @@ -487,14 +504,14 @@ def print_catalogue( # Start creating a table to display the results table = Table(title="Available Transformer Parameters") - table.add_column("Id") - table.add_column("Manufacturer", style="color(1)", header_style="color(1)") - table.add_column("Product range", style="color(2)", header_style="color(2)") - table.add_column("Efficiency", style="color(3)", header_style="color(3)") - table.add_column("Type", style="color(4)", header_style="color(4)") - table.add_column("Nominal power (kVA)", justify="right", style="color(5)", header_style="color(5)") - table.add_column("High voltage (kV)", justify="right", style="color(6)", header_style="color(6)") - table.add_column("Low voltage (kV)", justify="right", style="color(9)", header_style="color(9)") + table.add_column("Id", overflow="fold") + table.add_column("Manufacturer", overflow="fold") + table.add_column("Product range", overflow="fold") + table.add_column("Efficiency", overflow="fold") + table.add_column("Type", overflow="fold") + table.add_column("Nominal power (kVA)", justify="right", overflow="fold") + table.add_column("High voltage (kV)", justify="right", overflow="fold") + table.add_column("Low voltage (kV)", justify="right", overflow="fold") empty_table = True # Match on the manufacturer, range, efficiency and type @@ -525,6 +542,7 @@ def print_catalogue( # Iterate over the transformers selected_index = catalogue_mask[catalogue_mask].index + cycler = cycle(palette) for idx in selected_index: empty_table = False table.add_row( @@ -536,6 +554,7 @@ def print_catalogue( f"{catalogue_data.at[idx, 'sn']/1000:.1f}", # VA to kVA f"{catalogue_data.at[idx, 'uhv']/1000:.1f}", # V to kV f"{catalogue_data.at[idx, 'ulv']/1000:.1f}", # V to kV + style=next(cycler), ) # Handle the case of an empty table diff --git a/roseau/load_flow/models/transformers/transformers.py b/roseau/load_flow/models/transformers/transformers.py index 300d8432..3d73ccda 100644 --- a/roseau/load_flow/models/transformers/transformers.py +++ b/roseau/load_flow/models/transformers/transformers.py @@ -8,6 +8,7 @@ from roseau.load_flow.models.buses import Bus from roseau.load_flow.models.transformers.parameters import TransformerParameters from roseau.load_flow.typing import Id, JsonDict +from roseau.load_flow.units import Q_ logger = logging.getLogger(__name__) @@ -15,10 +16,7 @@ class Transformer(AbstractBranch): """A generic transformer model. - The model parameters are defined in the ``parameters``. - - See Also: - :doc:`Transformer models documentation ` + The model parameters are defined using the ``parameters`` argument. """ branch_type = "transformer" @@ -64,7 +62,8 @@ def __init__( The tap of the transformer, for example 1.02. parameters: - The parameters of the transformer. + Parameters defining the electrical model of the transformer. This is an instance of + the :class:`TransformerParameters` class and can be used by multiple transformers. phases1: The phases of the first extremity of the transformer. A string like ``"abc"`` or @@ -130,8 +129,15 @@ def parameters(self, value: TransformerParameters) -> None: self._parameters = value self._invalidate_network_results() - def to_dict(self, include_geometry: bool = True) -> JsonDict: - return {**super().to_dict(include_geometry=include_geometry), "params_id": self.parameters.id, "tap": self.tap} + @property + def max_power(self) -> Optional[Q_[float]]: + """The maximum power loading of the transformer (in VA).""" + # Do not add a setter. The user must know that if they change the max_power, it changes + # for all transformers that share the parameters. It is better to set it on the parameters. + return self.parameters.max_power + + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: + return {**super().to_dict(_lf_only=_lf_only), "params_id": self.parameters.id, "tap": self.tap} def _compute_phases_three( self, @@ -245,3 +251,16 @@ def _check_bus_phases(id: Id, bus: Bus, **kwargs: str) -> None: ) logger.error(msg) raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.BAD_PHASE) + + @property + def res_violated(self) -> Optional[bool]: + """Whether the transformer power exceeds the maximum power (loading > 100%). + + Returns ``None`` if the maximum power is not set. + """ + s_max = self.parameters._max_power + if s_max is None: + return None + powers1, powers2 = self._res_powers_getter(warning=True) + # True if either the primary or secondary is overloaded + return float(max(abs(sum(powers1)), abs(sum(powers2)))) > s_max diff --git a/roseau/load_flow/network.py b/roseau/load_flow/network.py index 01c51c13..72c2d162 100644 --- a/roseau/load_flow/network.py +++ b/roseau/load_flow/network.py @@ -6,10 +6,11 @@ import re import textwrap import warnings -from collections.abc import Sized +from collections.abc import Mapping, Sized from importlib import resources +from itertools import cycle from pathlib import Path -from typing import NoReturn, Optional, TypeVar, Union +from typing import TYPE_CHECKING, NoReturn, Optional, TypeVar, Union from urllib.parse import urljoin import geopandas as gpd @@ -17,7 +18,6 @@ import requests from pyproj import CRS from requests import Response -from requests.auth import HTTPBasicAuth from rich.table import Table from typing_extensions import Self @@ -37,17 +37,16 @@ VoltageSource, ) from roseau.load_flow.solvers import check_solver_params -from roseau.load_flow.typing import Id, JsonDict, Solver, StrPath -from roseau.load_flow.utils import CatalogueMixin, JsonMixin, console +from roseau.load_flow.typing import Authentication, Id, JsonDict, MapOrSeq, Solver, StrPath +from roseau.load_flow.utils import CatalogueMixin, JsonMixin, _optional_deps, console, palette +from roseau.load_flow.utils.types import _DTYPES, VoltagePhaseDtype -logger = logging.getLogger(__name__) +if TYPE_CHECKING: + from networkx import Graph -# Phases dtype for all data frames -_PHASE_DTYPE = pd.CategoricalDtype(categories=["a", "b", "c", "n"], ordered=True) -# Phases dtype for voltage data frames -_VOLTAGE_PHASES_DTYPE = pd.CategoricalDtype(categories=["an", "bn", "cn", "ab", "bc", "ca"], ordered=True) +logger = logging.getLogger(__name__) -_T = TypeVar("_T", bound=Element) +_E = TypeVar("_E", bound=Element) class ElectricalNetwork(JsonMixin, CatalogueMixin[JsonDict]): @@ -92,22 +91,6 @@ class ElectricalNetwork(JsonMixin, CatalogueMixin[JsonDict]): be connected to a bus or to a ground. Attributes: - DEFAULT_TOLERANCE (float): - The default tolerance needed for the convergence of the load flow solver. At each - iteration, the solver computes the residuals of the equations of the problem. When the - maximum of the absolute values of the residuals vector is lower than the provided - tolerance, the solver stops. Default is 1e-6. - - DEFAULT_MAX_ITERATIONS (int): - Maximum number of iterations to perform the load flow analysis. The solver stops when - this number of iterations is reached. Default is 20. - - DEFAULT_BASE_URL (str): - Base URL of the Roseau Load Flow API endpoint. - - DEFAULT_SOLVER (str): - The default solver to compute the load flow. - buses (dict[Id, roseau.load_flow.Bus]): Dictionary of buses of the network indexed by their IDs. Also available as a :attr:`GeoDataFrame`. @@ -148,11 +131,11 @@ class ElectricalNetwork(JsonMixin, CatalogueMixin[JsonDict]): } """ - DEFAULT_TOLERANCE: float = 1e-6 - DEFAULT_MAX_ITERATIONS: int = 20 - DEFAULT_BASE_URL: str = "https://load-flow-api-dev.roseautechnologies.com/" - DEFAULT_WARM_START: bool = True - DEFAULT_SOLVER: Solver = "newton_goldstein" + _DEFAULT_TOLERANCE: float = 1e-6 + _DEFAULT_MAX_ITERATIONS: int = 20 + _DEFAULT_BASE_URL: str = "https://load-flow-api-dev.roseautechnologies.com/" + _DEFAULT_WARM_START: bool = True + _DEFAULT_SOLVER: Solver = "newton_goldstein" # Elements classes (for internal use only) _branch_class = AbstractBranch @@ -170,12 +153,12 @@ class ElectricalNetwork(JsonMixin, CatalogueMixin[JsonDict]): # def __init__( self, - buses: Union[list[Bus], dict[Id, Bus]], - branches: Union[list[AbstractBranch], dict[Id, AbstractBranch]], - loads: Union[list[AbstractLoad], dict[Id, AbstractLoad]], - sources: Union[list[VoltageSource], dict[Id, VoltageSource]], - grounds: Union[list[Ground], dict[Id, Ground]], - potential_refs: Union[list[PotentialRef], dict[Id, PotentialRef]], + buses: MapOrSeq[Bus], + branches: MapOrSeq[AbstractBranch], + loads: MapOrSeq[AbstractLoad], + sources: MapOrSeq[VoltageSource], + grounds: MapOrSeq[Ground], + potential_refs: MapOrSeq[PotentialRef], **kwargs, ) -> None: self.buses = self._elements_as_dict(buses, RoseauLoadFlowExceptionCode.BAD_BUS_ID) @@ -211,25 +194,24 @@ def count_repr(__o: Sized, /, singular: str, plural: Optional[str] = None) -> st ) @staticmethod - def _elements_as_dict( - elements: Union[list[_T], dict[Id, _T]], error_code: RoseauLoadFlowExceptionCode - ) -> dict[Id, _T]: - """Convert a list of elements to a dictionary of elements with their IDs as keys.""" + def _elements_as_dict(elements: MapOrSeq[_E], error_code: RoseauLoadFlowExceptionCode) -> dict[Id, _E]: + """Convert a sequence or a mapping of elements to a dictionary of elements with their IDs as keys.""" typ = error_code.name.removeprefix("BAD_").removesuffix("_ID").replace("_", " ") - if isinstance(elements, dict): + elements_dict: dict[Id, _E] = {} + if isinstance(elements, Mapping): for element_id, element in elements.items(): if element.id != element_id: msg = f"{typ.capitalize()} ID mismatch: {element_id!r} != {element.id!r}." logger.error(msg) raise RoseauLoadFlowException(msg, code=error_code) - return elements - elements_dict: dict[Id, _T] = {} - for element in elements: - if element.id in elements_dict: - msg = f"Duplicate ID for an {typ.lower()} in this network: {element.id!r}." - logger.error(msg) - raise RoseauLoadFlowException(msg, code=error_code) - elements_dict[element.id] = element + elements_dict[element_id] = element + else: + for element in elements: + if element.id in elements_dict: + msg = f"Duplicate ID for an {typ.lower()} in this network: {element.id!r}." + logger.error(msg) + raise RoseauLoadFlowException(msg, code=error_code) + elements_dict[element.id] = element return elements_dict @classmethod @@ -282,10 +264,15 @@ def from_element(cls, initial_bus: Bus) -> Self: @property def buses_frame(self) -> gpd.GeoDataFrame: """The :attr:`buses` of the network as a geo dataframe.""" + data = [] + for bus in self.buses.values(): + min_voltage = bus.min_voltage.magnitude if bus.min_voltage is not None else float("nan") + max_voltage = bus.max_voltage.magnitude if bus.max_voltage is not None else float("nan") + data.append((bus.id, bus.phases, min_voltage, max_voltage, bus.geometry)) return gpd.GeoDataFrame( data=pd.DataFrame.from_records( - data=[(bus_id, bus.phases, bus.geometry) for bus_id, bus in self.buses.items()], - columns=["id", "phases", "geometry"], + data=data, + columns=["id", "phases", "min_voltage", "max_voltage", "geometry"], index="id", ), geometry="geometry", @@ -316,6 +303,94 @@ def branches_frame(self) -> gpd.GeoDataFrame: crs=CRS("EPSG:4326"), ) + @property + def transformers_frame(self) -> gpd.GeoDataFrame: + """The transformers of the network as a geo dataframe. + + This is similar to :attr:`branches_frame` but only contains the transformers. It has a + `max_power` column that contains the maximum power loading (VA) of the transformers. + """ + data = [] + for branch in self.branches.values(): + if not isinstance(branch, Transformer): + continue + max_power = branch.max_power.magnitude if branch.max_power is not None else float("nan") + data.append( + ( + branch.id, + branch.phases1, + branch.phases2, + branch.bus1.id, + branch.bus2.id, + branch.parameters.id, + max_power, + branch.geometry, + ) + ) + return gpd.GeoDataFrame( + data=pd.DataFrame.from_records( + data=data, + columns=["id", "phases1", "phases2", "bus1_id", "bus2_id", "parameters_id", "max_power", "geometry"], + index="id", + ), + geometry="geometry", + crs=CRS("EPSG:4326"), + ) + + @property + def lines_frame(self) -> gpd.GeoDataFrame: + """The lines of the network as a geo dataframe. + + This is similar to :attr:`branches_frame` but only contains the lines. It has a + `max_current` column that contains the maximum current loading (A) of the lines. + """ + data = [] + for branch in self.branches.values(): + if not isinstance(branch, Line): + continue + max_current = branch.max_current.magnitude if branch.max_current is not None else float("nan") + data.append( + ( + branch.id, + branch.phases, + branch.bus1.id, + branch.bus2.id, + branch.parameters.id, + max_current, + branch.geometry, + ) + ) + return gpd.GeoDataFrame( + data=pd.DataFrame.from_records( + data=data, + columns=["id", "phases", "bus1_id", "bus2_id", "parameters_id", "max_current", "geometry"], + index="id", + ), + geometry="geometry", + crs=CRS("EPSG:4326"), + ) + + @property + def switches_frame(self) -> gpd.GeoDataFrame: + """The switches of the network as a geo dataframe. + + This is similar to :attr:`branches_frame` but only contains the switches. + """ + data = [] + for branch in self.branches.values(): + if not isinstance(branch, Switch): + continue + data.append((branch.id, branch.phases, branch.bus1.id, branch.bus2.id, branch.geometry)) + return gpd.GeoDataFrame( + data=pd.DataFrame.from_records( + data=data, + columns=["id", "phases", "bus1_id", "bus2_id", "geometry"], + index="id", + ), + geometry="geometry", + crs=CRS("EPSG:4326"), + ) + @property def loads_frame(self) -> pd.DataFrame: """The :attr:`loads` of the network as a dataframe.""" @@ -368,17 +443,59 @@ def short_circuits_frame(self) -> pd.DataFrame: columns=["bus_id", "phases", "short_circuit", "ground"], ) + # + # Helpers to analyze the network + # + @property + def buses_clusters(self) -> list[set[Id]]: + """Sets of galvanically connected buses, i.e buses connected by lines or a switches. + + This can be useful to isolate parts of the network for localized analysis. For example, to + study a LV subnetwork of a MV feeder. + + See Also: + :meth:`Bus.get_connected_buses() `: Get + the buses in the same galvanically isolated section as a certain bus. + """ + visited: set[Id] = set() + result: list[set[Id]] = [] + for bus in self.buses.values(): + if bus.id in visited: + continue + bus_cluster = set(bus.get_connected_buses()) + visited |= bus_cluster + result.append(bus_cluster) + return result + + def to_graph(self) -> "Graph": + """Create a networkx graph from this electrical network. + + The graph contains the geometries of the buses in the nodes data and the geometries and + branch types in the edges data. + + Note: + This method requires *networkx* to be installed. You can install it with the ``"graph"`` + extra if you are using pip: ``pip install "roseau-load-flow[graph]"``. + """ + nx = _optional_deps.networkx + graph = nx.Graph() + for bus in self.buses.values(): + graph.add_node(bus.id, geom=bus.geometry) + for branch in self.branches.values(): + graph.add_edge(branch.bus1.id, branch.bus2.id, id=branch.id, type=branch.branch_type, geom=branch.geometry) + return graph + # # Method to solve a load flow # def solve_load_flow( self, - auth: Union[tuple[str, str], HTTPBasicAuth], - base_url: str = DEFAULT_BASE_URL, - max_iterations: int = DEFAULT_MAX_ITERATIONS, - tolerance: float = DEFAULT_TOLERANCE, - warm_start: bool = DEFAULT_WARM_START, - solver: Solver = DEFAULT_SOLVER, + auth: Authentication, + base_url: str = _DEFAULT_BASE_URL, + max_iterations: int = _DEFAULT_MAX_ITERATIONS, + tolerance: float = _DEFAULT_TOLERANCE, + warm_start: bool = _DEFAULT_WARM_START, + solver: Solver = _DEFAULT_SOLVER, solver_params: Optional[JsonDict] = None, ) -> int: """Solve the load flow for this network (Requires internet access). @@ -427,7 +544,7 @@ def solve_load_flow( # Get the data data = { - "network": self.to_dict(include_geometry=False), + "network": self.to_dict(_lf_only=True), "solver": { "name": solver, "params": solver_params, @@ -573,17 +690,13 @@ def res_buses(self) -> pd.DataFrame: """ self._warn_invalid_results() res_dict = {"bus_id": [], "phase": [], "potential": []} + dtypes = {c: _DTYPES[c] for c in res_dict} for bus_id, bus in self.buses.items(): for potential, phase in zip(bus._res_potentials_getter(warning=False), bus.phases): res_dict["bus_id"].append(bus_id) res_dict["phase"].append(phase) res_dict["potential"].append(potential) - res_df = ( - pd.DataFrame.from_dict(res_dict, orient="columns") - .astype({"phase": _PHASE_DTYPE, "potential": complex}) - .set_index(["bus_id", "phase"]) - ) - return res_df + return pd.DataFrame(res_dict).astype(dtypes).set_index(["bus_id", "phase"]) @property def res_buses_voltages(self) -> pd.DataFrame: @@ -598,20 +711,42 @@ def res_buses_voltages(self) -> pd.DataFrame: - `phase`: The phase of the bus (in ``{'an', 'bn', 'cn', 'ab', 'bc', 'ca'}``). and the following columns: - `voltage`: The complex voltage of the bus (in Volts) for the given phase. + - `min_voltage`: The minimum voltage of the bus (in Volts). + - `max_voltage`: The maximum voltage of the bus (in Volts). """ self._warn_invalid_results() - voltages_dict = {"bus_id": [], "phase": [], "voltage": []} + voltages_dict = { + "bus_id": [], + "phase": [], + "voltage": [], + "min_voltage": [], + "max_voltage": [], + "violated": [], + } + dtypes = {c: _DTYPES[c] for c in voltages_dict} | {"phase": VoltagePhaseDtype} for bus_id, bus in self.buses.items(): + min_voltage = bus._min_voltage + max_voltage = bus._max_voltage + voltage_limits_set = False + + if min_voltage is None: + min_voltage = float("nan") + else: + voltage_limits_set = True + if max_voltage is None: + max_voltage = float("nan") + else: + voltage_limits_set = True for voltage, phase in zip(bus._res_voltages_getter(warning=False), bus.voltage_phases): + voltage_abs = abs(voltage) + violated = (voltage_abs < min_voltage or voltage_abs > max_voltage) if voltage_limits_set else None voltages_dict["bus_id"].append(bus_id) voltages_dict["phase"].append(phase) voltages_dict["voltage"].append(voltage) - voltages_df = ( - pd.DataFrame.from_dict(voltages_dict, orient="columns") - .astype({"phase": _VOLTAGE_PHASES_DTYPE, "voltage": complex}) - .set_index(["bus_id", "phase"]) - ) - return voltages_df + voltages_dict["min_voltage"].append(min_voltage) + voltages_dict["max_voltage"].append(max_voltage) + voltages_dict["violated"].append(violated) + return pd.DataFrame(voltages_dict).astype(dtypes).set_index(["bus_id", "phase"]) @property def res_branches(self) -> pd.DataFrame: @@ -621,6 +756,7 @@ def res_branches(self) -> pd.DataFrame: - `branch_id`: The id of the branch. - `phase`: The phase of the branch (in ``{'a', 'b', 'c', 'n'}``). and the following columns: + - `branch_type`: The type of the branch, can be ``{'line', 'transformer', 'switch'}``. - `current1`: The complex current of the branch (in Amps) for the given phase at the first bus. - `current2`: The complex current of the branch (in Amps) for the given phase at the @@ -642,6 +778,7 @@ def res_branches(self) -> pd.DataFrame: { "branch_id": branch_id, "phase": phase, + "branch_type": branch.branch_type, "current1": i1, "current2": None, "power1": s1, @@ -655,6 +792,7 @@ def res_branches(self) -> pd.DataFrame: { "branch_id": branch_id, "phase": phase, + "branch_type": branch.branch_type, "current1": None, "current2": i2, "power1": None, @@ -665,28 +803,129 @@ def res_branches(self) -> pd.DataFrame: for i2, s2, v2, phase in zip(currents2, powers2, potentials2, branch.phases2) ) - res_df = ( - pd.DataFrame.from_records(res_list) - .astype( + columns = [ + "branch_id", + "phase", + "branch_type", + "current1", + "current2", + "power1", + "power2", + "potential1", + "potential2", + ] + dtypes = {c: _DTYPES[c] for c in columns} + return ( + pd.DataFrame.from_records(res_list, columns=columns) + .astype(dtypes) + # aggregate x1 and x2 for the same phase for I, V, S, ... + .groupby(["branch_id", "phase", "branch_type"], observed=True) + # there are 2 values of I, V, S, ...; only one is not nan -> keep it + .mean() + # if all values are nan -> drop the row (the phase does not exist) + .dropna(how="all") + .reset_index(level="branch_type") + ) + + @property + def res_transformers(self) -> pd.DataFrame: + """The load flow results of the network transformers. + + This is similar to the :attr:`res_branches` property but provides more information that + only apply to transformers. + + The results are returned as a dataframe with the following index: + - `transformer_id`: The id of the transformer. + - `phase`: The phase of the transformer (in ``{'a', 'b', 'c', 'n'}``). + + and the following columns: + - `current1`: The complex current of the transformer (in Amps) for the given phase at the + first bus. + - `current2`: The complex current of the transformer (in Amps) for the given phase at the + second bus. + - `power1`: The complex power of the transformer (in VoltAmps) for the given phase at the + first bus. + - `power2`: The complex power of the transformer (in VoltAmps) for the given phase at the + second bus. + - `potential1`: The complex potential of the first bus (in Volts) for the given phase. + - `potential2`: The complex potential of the second bus (in Volts) for the given phase. + - `max_power`: The maximum power loading (in VoltAmps) of the transformer. + """ + self._warn_invalid_results() + res_list = [] + for branch in self.branches.values(): + if not isinstance(branch, Transformer): + continue + currents1, currents2 = branch._res_currents_getter(warning=False) + powers1, powers2 = branch._res_powers_getter(warning=False) + potentials1, potentials2 = branch._res_potentials_getter(warning=False) + s_max = branch.parameters._max_power + violated = None + if s_max is not None: + violated = max(abs(sum(powers1)), abs(sum(powers2))) > s_max + res_list.extend( + { + "transformer_id": branch.id, + "phase": phase, + "current1": i1, + "current2": None, + "power1": s1, + "power2": None, + "potential1": v1, + "potential2": None, + "max_power": s_max, + "violated": violated, + } + for i1, s1, v1, phase in zip(currents1, powers1, potentials1, branch.phases1) + ) + res_list.extend( { - "phase": _PHASE_DTYPE, - "current1": complex, - "current2": complex, - "power1": complex, - "power2": complex, - "potential1": complex, - "potential2": complex, + "transformer_id": branch.id, + "phase": phase, + "current1": None, + "current2": i2, + "power1": None, + "power2": s2, + "potential1": None, + "potential2": v2, + "max_power": s_max, + "violated": violated, } + for i2, s2, v2, phase in zip(currents2, powers2, potentials2, branch.phases2) ) - .groupby(["branch_id", "phase"]) # aggregate x1 and x2 for the same phase - .mean() # 2 values; only one is not nan -> keep it - .dropna(how="all") # if all values are nan -> drop the row (the phase does not exist) + + columns = [ + "transformer_id", + "phase", + "current1", + "current2", + "power1", + "power2", + "potential1", + "potential2", + "max_power", + "violated", + ] + dtypes = {c: _DTYPES[c] for c in columns} + res = ( + pd.DataFrame.from_records(res_list, columns=columns) + .astype(dtypes) + # aggregate x1 and x2 for the same phase for I, V, S, ... + .groupby(["transformer_id", "phase", "max_power", "violated"], observed=True) + # there are 2 values of I, V, S, ...; only one is not nan -> keep it + .mean() + # if all values are nan -> drop the row (the phase does not exist) + .dropna(how="all") + .reset_index(level=["max_power", "violated"]) ) - return res_df + # move the max_power and violated columns to the end + res["max_power"] = res.pop("max_power") + res["violated"] = res.pop("violated") + return res @property def res_lines(self) -> pd.DataFrame: - """The load flow results of the the network lines. + """The load flow results of the network lines. This is similar to the :attr:`res_branches` property but provides more information that only apply to lines. This includes currents and complex power losses in the series @@ -735,7 +974,10 @@ def res_lines(self) -> pd.DataFrame: "potential2": [], "series_losses": [], "series_current": [], + "max_current": [], + "violated": [], } + dtypes = {c: _DTYPES[c] for c in res_dict} for branch in self.branches.values(): if not isinstance(branch, Line): continue @@ -744,9 +986,11 @@ def res_lines(self) -> pd.DataFrame: powers = branch._res_powers_getter(warning=False) series_losses = branch._res_series_power_losses_getter(warning=False) series_currents = branch._res_series_currents_getter(warning=False) + i_max = branch.parameters._max_current for i1, i2, s1, s2, v1, v2, s_series, i_series, phase in zip( *currents, *powers, *potentials, series_losses, series_currents, branch.phases ): + violated = None if i_max is None else max(abs(i1), abs(i2)) > i_max res_dict["line_id"].append(branch.id) res_dict["phase"].append(phase) res_dict["current1"].append(i1) @@ -757,16 +1001,60 @@ def res_lines(self) -> pd.DataFrame: res_dict["potential2"].append(v2) res_dict["series_losses"].append(s_series) res_dict["series_current"].append(i_series) - return ( - pd.DataFrame(res_dict) - .astype( - { - "phase": _PHASE_DTYPE, - **{k: complex for k in res_dict if k not in ("phase", "line_id")}, - }, - ) - .set_index(["line_id", "phase"]) - ) + res_dict["max_current"].append(i_max) + res_dict["violated"].append(violated) + res = pd.DataFrame(res_dict).astype(dtypes).set_index(["line_id", "phase"]) + return res + + @property + def res_switches(self) -> pd.DataFrame: + """The load flow results of the network switches. + + This is similar to the :attr:`res_branches` property but only apply to switches. + + The results are returned as a dataframe with the following index: + - `switch_id`: The id of the switch. + - `phase`: The phase of the switch (in ``{'a', 'b', 'c', 'n'}``). + and the following columns: + - `current1`: The complex current of the switch (in Amps) for the given phase at the + first bus. + - `current2`: The complex current of the switch (in Amps) for the given phase at the + second bus. + - `power1`: The complex power of the switch (in VoltAmps) for the given phase at the + first bus. + - `power2`: The complex power of the switch (in VoltAmps) for the given phase at the + second bus. + - `potential1`: The complex potential of the first bus (in Volts) for the given phase. + - `potential2`: The complex potential of the second bus (in Volts) for the given phase. + """ + self._warn_invalid_results() + res_dict = { + "switch_id": [], + "phase": [], + "current1": [], + "current2": [], + "power1": [], + "power2": [], + "potential1": [], + "potential2": [], + } + dtypes = {c: _DTYPES[c] for c in res_dict} + for branch in self.branches.values(): + if not isinstance(branch, Switch): + continue + potentials = branch._res_potentials_getter(warning=False) + currents = branch._res_currents_getter(warning=False) + powers = branch._res_powers_getter(warning=False) + for i1, i2, s1, s2, v1, v2, phase in zip(*currents, *powers, *potentials, branch.phases): + res_dict["switch_id"].append(branch.id) + res_dict["phase"].append(phase) + res_dict["current1"].append(i1) + res_dict["current2"].append(i2) + res_dict["power1"].append(s1) + res_dict["power2"].append(s2) + res_dict["potential1"].append(v1) + res_dict["potential2"].append(v2) + return pd.DataFrame(res_dict).astype(dtypes).set_index(["switch_id", "phase"]) @property def res_loads(self) -> pd.DataFrame: @@ -782,6 +1070,7 @@ def res_loads(self) -> pd.DataFrame: """ self._warn_invalid_results() res_dict = {"load_id": [], "phase": [], "current": [], "power": [], "potential": []} + dtypes = {c: _DTYPES[c] for c in res_dict} for load_id, load in self.loads.items(): currents = load._res_currents_getter(warning=False) powers = load._res_powers_getter(warning=False) @@ -792,12 +1081,7 @@ def res_loads(self) -> pd.DataFrame: res_dict["current"].append(i) res_dict["power"].append(s) res_dict["potential"].append(v) - res_df = ( - pd.DataFrame.from_dict(res_dict, orient="columns") - .astype({"phase": _PHASE_DTYPE, "current": complex, "power": complex, "potential": complex}) - .set_index(["load_id", "phase"]) - ) - return res_df + return pd.DataFrame(res_dict).astype(dtypes).set_index(["load_id", "phase"]) @property def res_loads_voltages(self) -> pd.DataFrame: @@ -812,17 +1096,13 @@ def res_loads_voltages(self) -> pd.DataFrame: """ self._warn_invalid_results() voltages_dict = {"load_id": [], "phase": [], "voltage": []} + dtypes = {c: _DTYPES[c] for c in voltages_dict} | {"phase": VoltagePhaseDtype} for load_id, load in self.loads.items(): for voltage, phase in zip(load._res_voltages_getter(warning=False), load.voltage_phases): voltages_dict["load_id"].append(load_id) voltages_dict["phase"].append(phase) voltages_dict["voltage"].append(voltage) - voltages_df = ( - pd.DataFrame.from_dict(voltages_dict, orient="columns") - .astype({"phase": _VOLTAGE_PHASES_DTYPE, "voltage": complex}) - .set_index(["load_id", "phase"]) - ) - return voltages_df + return pd.DataFrame(voltages_dict).astype(dtypes).set_index(["load_id", "phase"]) @property def res_loads_flexible_powers(self) -> pd.DataFrame: @@ -840,6 +1120,7 @@ def res_loads_flexible_powers(self) -> pd.DataFrame: """ self._warn_invalid_results() loads_dict = {"load_id": [], "phase": [], "power": []} + dtypes = {c: _DTYPES[c] for c in loads_dict} | {"phase": VoltagePhaseDtype} for load_id, load in self.loads.items(): if not (isinstance(load, PowerLoad) and load.is_flexible): continue @@ -847,12 +1128,7 @@ def res_loads_flexible_powers(self) -> pd.DataFrame: loads_dict["load_id"].append(load_id) loads_dict["phase"].append(phase) loads_dict["power"].append(power) - powers_df = ( - pd.DataFrame.from_dict(loads_dict, orient="columns") - .astype({"phase": _VOLTAGE_PHASES_DTYPE, "power": complex}) - .set_index(["load_id", "phase"]) - ) - return powers_df + return pd.DataFrame(loads_dict).astype(dtypes).set_index(["load_id", "phase"]) @property def res_sources(self) -> pd.DataFrame: @@ -868,6 +1144,7 @@ def res_sources(self) -> pd.DataFrame: """ self._warn_invalid_results() res_dict = {"source_id": [], "phase": [], "current": [], "power": [], "potential": []} + dtypes = {c: _DTYPES[c] for c in res_dict} for source_id, source in self.sources.items(): currents = source._res_currents_getter(warning=False) powers = source._res_powers_getter(warning=False) @@ -878,12 +1155,7 @@ def res_sources(self) -> pd.DataFrame: res_dict["current"].append(i) res_dict["power"].append(s) res_dict["potential"].append(v) - res_df = ( - pd.DataFrame.from_dict(res_dict, orient="columns") - .astype({"phase": _PHASE_DTYPE, "current": complex, "power": complex, "potential": complex}) - .set_index(["source_id", "phase"]) - ) - return res_df + return pd.DataFrame(res_dict).astype(dtypes).set_index(["source_id", "phase"]) @property def res_grounds(self) -> pd.DataFrame: @@ -896,14 +1168,12 @@ def res_grounds(self) -> pd.DataFrame: """ self._warn_invalid_results() res_dict = {"ground_id": [], "potential": []} + dtypes = {c: _DTYPES[c] for c in res_dict} for ground in self.grounds.values(): potential = ground._res_potential_getter(warning=False) res_dict["ground_id"].append(ground.id) res_dict["potential"].append(potential) - res_df = ( - pd.DataFrame.from_dict(res_dict, orient="columns").astype({"potential": complex}).set_index(["ground_id"]) - ) - return res_df + return pd.DataFrame(res_dict).astype(dtypes).set_index(["ground_id"]) @property def res_potential_refs(self) -> pd.DataFrame: @@ -917,18 +1187,14 @@ def res_potential_refs(self) -> pd.DataFrame: """ self._warn_invalid_results() res_dict = {"potential_ref_id": [], "current": []} + dtypes = {c: _DTYPES[c] for c in res_dict} for p_ref in self.potential_refs.values(): current = p_ref._res_current_getter(warning=False) res_dict["potential_ref_id"].append(p_ref.id) res_dict["current"].append(current) - res_df = ( - pd.DataFrame.from_dict(res_dict, orient="columns") - .astype({"current": complex}) - .set_index(["potential_ref_id"]) - ) - return res_df + return pd.DataFrame(res_dict).astype(dtypes).set_index(["potential_ref_id"]) - def clear_short_circuits(self): + def clear_short_circuits(self) -> None: """Remove the short-circuits of all the buses.""" for bus in self.buses.values(): bus.clear_short_circuits() @@ -1111,14 +1377,14 @@ def from_dict(cls, data: JsonDict) -> Self: potential_refs=p_refs, ) - def to_dict(self, include_geometry: bool = True) -> JsonDict: + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: """Convert the electrical network to a dictionary. Args: - include_geometry: - If False, the geometry will not be added to the network dictionary. + _lf_only: + Internal argument, please do not use. """ - return network_to_dict(self, include_geometry=include_geometry) + return network_to_dict(self, _lf_only=_lf_only) # # Results saving/loading @@ -1315,14 +1581,14 @@ def print_catalogue( # Start creating a table to display the results table = Table(title="Available Networks") - table.add_column("Name") - table.add_column("Nb buses", justify="right", style="color(1)", header_style="color(1)") - table.add_column("Nb branches", justify="right", style="color(2)", header_style="color(2)") - table.add_column("Nb loads", justify="right", style="color(3)", header_style="color(3)") - table.add_column("Nb sources", justify="right", style="color(4)", header_style="color(4)") - table.add_column("Nb grounds", justify="right", style="color(5)", header_style="color(5)") - table.add_column("Nb potential refs", justify="right", style="color(6)", header_style="color(6)") - table.add_column("Available load points", justify="right", style="color(9)", header_style="color(9)") + table.add_column("Name", overflow="fold") + table.add_column("Nb buses", justify="right", overflow="fold") + table.add_column("Nb branches", justify="right", overflow="fold") + table.add_column("Nb loads", justify="right", overflow="fold") + table.add_column("Nb sources", justify="right", overflow="fold") + table.add_column("Nb grounds", justify="right", overflow="fold") + table.add_column("Nb potential refs", justify="right", overflow="fold") + table.add_column("Available load points", overflow="fold") empty_table = True # Match on the name @@ -1351,6 +1617,7 @@ def match_load_point_function(x: str) -> bool: return x.lower() == load_point_name_pattern # Iterate over the networks + cycler = cycle(palette) for c_name in match_names_list: c_data = catalogue_data[c_name] available_load_points = c_data["load_points"] @@ -1365,6 +1632,7 @@ def match_load_point_function(x: str) -> bool: str(c_data["nb_grounds"]), str(c_data["nb_potential_refs"]), ", ".join(repr(x) for x in sorted(c_data["load_points"])), + style=next(cycler), ) # Handle the case of an empty table diff --git a/roseau/load_flow/solvers.py b/roseau/load_flow/solvers.py index 5071c115..4ef18260 100644 --- a/roseau/load_flow/solvers.py +++ b/roseau/load_flow/solvers.py @@ -1,7 +1,7 @@ import logging from typing import Optional -from roseau.load_flow import RoseauLoadFlowException, RoseauLoadFlowExceptionCode +from roseau.load_flow.exceptions import RoseauLoadFlowException, RoseauLoadFlowExceptionCode from roseau.load_flow.typing import JsonDict, Solver logger = logging.getLogger(__name__) diff --git a/roseau/load_flow/tests/test_converters.py b/roseau/load_flow/tests/test_converters.py index e67d6157..06b262c6 100644 --- a/roseau/load_flow/tests/test_converters.py +++ b/roseau/load_flow/tests/test_converters.py @@ -3,7 +3,7 @@ from pandas.testing import assert_series_equal from roseau.load_flow.converters import phasor_to_sym, series_phasor_to_sym, sym_to_phasor -from roseau.load_flow.network import _PHASE_DTYPE +from roseau.load_flow.utils import PhaseDtype def test_phasor_to_sym(): @@ -13,23 +13,23 @@ def test_phasor_to_sym(): vc = 230 * np.e ** (1j * 2 * np.pi / 3) # Test balanced direct system: positive sequence - expected = np.array([[0], [230], [0]], dtype=complex) + expected = np.array([0, 230, 0], dtype=complex) assert np.allclose(phasor_to_sym([va, vb, vc]), expected) # Also test numpy array input with different shapes assert np.allclose(phasor_to_sym(np.array([va, vb, vc])), expected) - assert np.allclose(phasor_to_sym(np.array([[va], [vb], [vc]])), expected) + assert np.allclose(phasor_to_sym(np.array([[va], [vb], [vc]])), expected.reshape((3, 1))) # Test balanced indirect system: negative sequence - expected = np.array([[0], [0], [230]], dtype=complex) + expected = np.array([0, 0, 230], dtype=complex) assert np.allclose(phasor_to_sym([va, vc, vb]), expected) # Test unbalanced system: zero sequence - expected = np.array([[230], [0], [0]], dtype=complex) + expected = np.array([230, 0, 0], dtype=complex) assert np.allclose(phasor_to_sym([va, va, va]), expected) # Test unbalanced system: general case va = 200 + 0j - expected = np.array([[10 * np.e ** (1j * np.pi)], [220], [10 * np.e ** (1j * np.pi)]], dtype=complex) + expected = np.array([10 * np.exp(1j * np.pi), 220, 10 * np.exp(1j * np.pi)], dtype=complex) assert np.allclose(phasor_to_sym([va, vb, vc]), expected) @@ -40,23 +40,23 @@ def test_sym_to_phasor(): vc = 230 * np.e ** (1j * 2 * np.pi / 3) # Test balanced direct system: positive sequence - expected = np.array([[va], [vb], [vc]], dtype=complex) + expected = np.array([va, vb, vc], dtype=complex) assert np.allclose(sym_to_phasor([0, va, 0]), expected) # Also test numpy array input with different shapes assert np.allclose(sym_to_phasor(np.array([0, va, 0])), expected) - assert np.allclose(sym_to_phasor(np.array([[0], [va], [0]])), expected) + assert np.allclose(sym_to_phasor(np.array([[0], [va], [0]])), expected.reshape((3, 1))) # Test balanced indirect system: negative sequence - expected = np.array([[va], [vc], [vb]], dtype=complex) + expected = np.array([va, vc, vb], dtype=complex) assert np.allclose(sym_to_phasor([0, 0, va]), expected) # Test unbalanced system: zero sequence - expected = np.array([[va], [va], [va]], dtype=complex) + expected = np.array([va, va, va], dtype=complex) assert np.allclose(sym_to_phasor([va, 0, 0]), expected) # Test unbalanced system: general case va = 200 + 0j - expected = np.array([[va], [vb], [vc]], dtype=complex) + expected = np.array([va, vb, vc], dtype=complex) assert np.allclose(sym_to_phasor([10 * np.e ** (1j * np.pi), 220, 10 * np.e ** (1j * np.pi)]), expected) @@ -66,17 +66,17 @@ def test_phasor_sym_roundtrip(): vc = 230 * np.e ** (1j * 2 * np.pi / 3) # Test balanced direct system: positive sequence - assert np.allclose(sym_to_phasor(phasor_to_sym([va, vb, vc])), np.array([[va], [vb], [vc]])) + assert np.allclose(sym_to_phasor(phasor_to_sym([va, vb, vc])), np.array([va, vb, vc])) # Test balanced indirect system: negative sequence - assert np.allclose(sym_to_phasor(phasor_to_sym([va, vc, vb])), np.array([[va], [vc], [vb]])) + assert np.allclose(sym_to_phasor(phasor_to_sym([va, vc, vb])), np.array([va, vc, vb])) # Test unbalanced system: zero sequence - assert np.allclose(sym_to_phasor(phasor_to_sym([va, va, va])), np.array([[va], [va], [va]])) + assert np.allclose(sym_to_phasor(phasor_to_sym([va, va, va])), np.array([va, va, va])) # Test unbalanced system: general case va = 200 + 0j - assert np.allclose(sym_to_phasor(phasor_to_sym([va, vb, vc])), np.array([[va], [vb], [vc]])) + assert np.allclose(sym_to_phasor(phasor_to_sym([va, vb, vc])), np.array([va, vb, vc])) def test_series_phasor_to_sym(): @@ -88,7 +88,7 @@ def test_series_phasor_to_sym(): [("bus1", "a"), ("bus1", "b"), ("bus1", "c"), ("bus2", "a"), ("bus2", "b"), ("bus2", "c")], names=["bus_id", "phase"], ) - index = index.set_levels(index.levels[-1].astype(_PHASE_DTYPE), level=-1) + index = index.set_levels(index.levels[-1].astype(PhaseDtype), level=-1) voltage = pd.Series([va, vb, vc, va / 2, vb / 2, vc / 2], index=index, name="voltage") seq_dtype = pd.CategoricalDtype(categories=["zero", "pos", "neg"], ordered=True) diff --git a/roseau/load_flow/tests/test_electrical_network.py b/roseau/load_flow/tests/test_electrical_network.py index 33331316..0d8ef238 100644 --- a/roseau/load_flow/tests/test_electrical_network.py +++ b/roseau/load_flow/tests/test_electrical_network.py @@ -5,6 +5,7 @@ from urllib.parse import urljoin import geopandas as gpd +import networkx as nx import numpy as np import pandas as pd import pytest @@ -26,9 +27,9 @@ TransformerParameters, VoltageSource, ) -from roseau.load_flow.network import _PHASE_DTYPE, _VOLTAGE_PHASES_DTYPE, ElectricalNetwork +from roseau.load_flow.network import ElectricalNetwork from roseau.load_flow.units import Q_ -from roseau.load_flow.utils import console +from roseau.load_flow.utils import BranchTypeDtype, PhaseDtype, VoltagePhaseDtype, console @pytest.fixture() @@ -520,7 +521,7 @@ def test_solve_load_flow(small_network, good_json_results): # Good result # Request the server - solve_url = urljoin(ElectricalNetwork.DEFAULT_BASE_URL, "solve/") + solve_url = urljoin(ElectricalNetwork._DEFAULT_BASE_URL, "solve/") with requests_mock.Mocker() as m: m.post(solve_url, status_code=200, json=good_json_results, headers={"content-type": "application/json"}) small_network.solve_load_flow(auth=("", "")) @@ -607,7 +608,7 @@ def test_solve_load_flow(small_network, good_json_results): def test_solve_load_flow_error(small_network): # Solve url - solve_url = urljoin(ElectricalNetwork.DEFAULT_BASE_URL, "solve/") + solve_url = urljoin(ElectricalNetwork._DEFAULT_BASE_URL, "solve/") # Parse RLF error json_result = {"msg": "toto", "code": "roseau.load_flow.bad_branch_type"} @@ -647,12 +648,12 @@ def test_solve_load_flow_error(small_network): assert e.value.code == RoseauLoadFlowExceptionCode.BAD_REQUEST -def test_frame(small_network): +def test_frame(small_network: ElectricalNetwork): # Buses buses_gdf = small_network.buses_frame assert isinstance(buses_gdf, gpd.GeoDataFrame) - assert buses_gdf.shape == (2, 2) - assert set(buses_gdf.columns) == {"phases", "geometry"} + assert buses_gdf.shape == (2, 4) + assert set(buses_gdf.columns) == {"phases", "min_voltage", "max_voltage", "geometry"} assert buses_gdf.index.name == "id" # Branches @@ -662,6 +663,33 @@ def test_frame(small_network): assert set(branches_gdf.columns) == {"branch_type", "phases1", "phases2", "bus1_id", "bus2_id", "geometry"} assert branches_gdf.index.name == "id" + # Transformers + transformers_gdf = small_network.transformers_frame + assert isinstance(transformers_gdf, gpd.GeoDataFrame) + assert transformers_gdf.shape == (0, 7) + assert set(transformers_gdf.columns) == { + "phases1", + "phases2", + "bus1_id", + "bus2_id", + "parameters_id", + "max_power", + "geometry", + } + assert transformers_gdf.index.name == "id" + + # Lines + lines_gdf = small_network.lines_frame + assert isinstance(lines_gdf, gpd.GeoDataFrame) + assert lines_gdf.shape == (1, 6) + assert set(lines_gdf.columns) == {"phases", "bus1_id", "bus2_id", "parameters_id", "max_current", "geometry"} + + # Switches + switches_gdf = small_network.switches_frame + assert isinstance(switches_gdf, gpd.GeoDataFrame) + assert switches_gdf.shape == (0, 4) + assert set(switches_gdf.columns) == {"phases", "bus1_id", "bus2_id", "geometry"} + # Loads loads_df = small_network.loads_frame assert isinstance(loads_df, pd.DataFrame) @@ -677,30 +705,169 @@ def test_frame(small_network): assert sources_df.index.name == "id" -def test_buses_voltages(small_network, good_json_results): +def test_frame_empty_network(monkeypatch): + # Test that we can create dataframes even if a certain element is not present in the network + monkeypatch.setattr(ElectricalNetwork, "_check_validity", lambda self, constructed: None) + monkeypatch.setattr(ElectricalNetwork, "_warn_invalid_results", lambda self: None) + empty_network = ElectricalNetwork( + buses={}, + branches={}, + loads={}, + sources={}, + grounds={}, + potential_refs={}, + ) + # Buses + buses = empty_network.buses_frame + assert buses.shape == (0, 4) + assert buses.empty + + # Branches + branches = empty_network.branches_frame + assert branches.shape == (0, 6) + assert branches.empty + + # Transformers + transformers = empty_network.transformers_frame + assert transformers.shape == (0, 7) + assert transformers.empty + + # Lines + lines = empty_network.lines_frame + assert lines.shape == (0, 6) + assert lines.empty + + # Switches + switches = empty_network.switches_frame + assert switches.shape == (0, 4) + assert switches.empty + + # Loads + loads = empty_network.loads_frame + assert loads.shape == (0, 2) + assert loads.empty + + # Sources + sources = empty_network.sources_frame + assert sources.shape == (0, 2) + assert sources.empty + + # Res buses + res_buses = empty_network.res_buses + assert res_buses.shape == (0, 1) + assert res_buses.empty + res_buses_voltages = empty_network.res_buses_voltages + assert res_buses_voltages.shape == (0, 4) + assert res_buses_voltages.empty + + # Res branches + res_branches = empty_network.res_branches + assert res_branches.shape == (0, 7) + assert res_branches.empty + + # Res transformers + res_transformers = empty_network.res_transformers + assert res_transformers.shape == (0, 8) + assert res_transformers.empty + + # Res lines + res_lines = empty_network.res_lines + assert res_lines.shape == (0, 10) + assert res_lines.empty + + # Res switches + res_switches = empty_network.res_switches + assert res_switches.shape == (0, 6) + assert res_switches.empty + + # Res loads + res_loads = empty_network.res_loads + assert res_loads.shape == (0, 3) + assert res_loads.empty + + # Res sources + res_sources = empty_network.res_sources + assert res_sources.shape == (0, 3) + assert res_sources.empty + + +def test_buses_voltages(small_network: ElectricalNetwork, good_json_results): assert isinstance(small_network, ElectricalNetwork) small_network.results_from_dict(good_json_results) + small_network.buses["bus0"].max_voltage = 21_000 + small_network.buses["bus1"].min_voltage = 20_000 voltage_records = [ - {"bus_id": "bus0", "phase": "an", "voltage": 20000.0 + 0.0j}, - {"bus_id": "bus0", "phase": "bn", "voltage": -10000.0 + -17320.508076j}, - {"bus_id": "bus0", "phase": "cn", "voltage": -10000.0 + 17320.508076j}, - {"bus_id": "bus1", "phase": "an", "voltage": 19999.949999875 + 0.0j}, - {"bus_id": "bus1", "phase": "bn", "voltage": -9999.9749999375 + -17320.464774621556j}, - {"bus_id": "bus1", "phase": "cn", "voltage": -9999.9749999375 + 17320.464774621556j}, + { + "bus_id": "bus0", + "phase": "an", + "voltage": 20000.0 + 0.0j, + "min_voltage": np.nan, + "max_voltage": 21000, + "violated": False, + }, + { + "bus_id": "bus0", + "phase": "bn", + "voltage": -10000.0 + -17320.508076j, + "min_voltage": np.nan, + "max_voltage": 21000, + "violated": False, + }, + { + "bus_id": "bus0", + "phase": "cn", + "voltage": -10000.0 + 17320.508076j, + "min_voltage": np.nan, + "max_voltage": 21000, + "violated": False, + }, + { + "bus_id": "bus1", + "phase": "an", + "voltage": 19999.949999875 + 0.0j, + "min_voltage": 20000, + "max_voltage": np.nan, + "violated": True, + }, + { + "bus_id": "bus1", + "phase": "bn", + "voltage": -9999.9749999375 + -17320.464774621556j, + "min_voltage": 20000, + "max_voltage": np.nan, + "violated": True, + }, + { + "bus_id": "bus1", + "phase": "cn", + "voltage": -9999.9749999375 + 17320.464774621556j, + "min_voltage": 20000, + "max_voltage": np.nan, + "violated": True, + }, ] - def set_index_dtype(idx: pd.MultiIndex) -> pd.MultiIndex: - return idx.set_levels(idx.levels[1].astype(_VOLTAGE_PHASES_DTYPE), level=1) - buses_voltages = small_network.res_buses_voltages - expected_buses_voltages = pd.DataFrame.from_records(voltage_records, index=["bus_id", "phase"]) - expected_buses_voltages.index = set_index_dtype(expected_buses_voltages.index) + expected_buses_voltages = ( + pd.DataFrame.from_records(voltage_records) + .astype( + { + "bus_id": str, + "phase": VoltagePhaseDtype, + "voltage": complex, + "min_voltage": float, + "max_voltage": float, + "violated": pd.BooleanDtype(), + } + ) + .set_index(["bus_id", "phase"]) + ) assert isinstance(buses_voltages, pd.DataFrame) - assert buses_voltages.shape == (6, 1) + assert buses_voltages.shape == (6, 4) assert buses_voltages.index.names == ["bus_id", "phase"] - assert list(buses_voltages.columns) == ["voltage"] + assert list(buses_voltages.columns) == ["voltage", "min_voltage", "max_voltage", "violated"] assert_frame_equal(buses_voltages, expected_buses_voltages) @@ -720,6 +887,9 @@ def test_single_phase_network(single_phase_network: ElectricalNetwork): new_net = ElectricalNetwork.from_dict(net_dict) assert_frame_equal(single_phase_network.buses_frame, new_net.buses_frame) assert_frame_equal(single_phase_network.branches_frame, new_net.branches_frame) + assert_frame_equal(single_phase_network.transformers_frame, new_net.transformers_frame) + assert_frame_equal(single_phase_network.lines_frame, new_net.lines_frame) + assert_frame_equal(single_phase_network.switches_frame, new_net.switches_frame) assert_frame_equal(single_phase_network.loads_frame, new_net.loads_frame) assert_frame_equal(single_phase_network.sources_frame, new_net.sources_frame) @@ -778,7 +948,7 @@ def test_single_phase_network(single_phase_network: ElectricalNetwork): {"id": "pref", "current": [-1.2500243895541274e-13, 0.0]}, ], } - solve_url = urljoin(ElectricalNetwork.DEFAULT_BASE_URL, "solve/") + solve_url = urljoin(ElectricalNetwork._DEFAULT_BASE_URL, "solve/") with requests_mock.Mocker() as m: m.post(solve_url, status_code=200, json=json_results, headers={"content-type": "application/json"}) single_phase_network.solve_load_flow(auth=("", "")) @@ -804,7 +974,7 @@ def test_single_phase_network(single_phase_network: ElectricalNetwork): {"bus_id": "bus1", "phase": "n", "potential": 0j}, ] ) - .astype({"phase": _PHASE_DTYPE, "potential": complex}) + .astype({"phase": PhaseDtype, "potential": complex}) .set_index(["bus_id", "phase"]), ) # Buses voltages results @@ -812,11 +982,33 @@ def test_single_phase_network(single_phase_network: ElectricalNetwork): single_phase_network.res_buses_voltages, pd.DataFrame.from_records( [ - {"bus_id": "bus0", "phase": "bn", "voltage": (19999.94999975 + 0j) - (-0.050000250001249996 + 0j)}, - {"bus_id": "bus1", "phase": "bn", "voltage": (19999.899999499998 + 0j) - (0j)}, + { + "bus_id": "bus0", + "phase": "bn", + "voltage": (19999.94999975 + 0j) - (-0.050000250001249996 + 0j), + "min_voltage": np.nan, + "max_voltage": np.nan, + "violated": None, + }, + { + "bus_id": "bus1", + "phase": "bn", + "voltage": (19999.899999499998 + 0j) - (0j), + "min_voltage": np.nan, + "max_voltage": np.nan, + "violated": None, + }, ] ) - .astype({"phase": _VOLTAGE_PHASES_DTYPE, "voltage": complex}) + .astype( + { + "phase": VoltagePhaseDtype, + "voltage": complex, + "min_voltage": float, + "max_voltage": float, + "violated": pd.BooleanDtype(), + } + ) .set_index(["bus_id", "phase"]), ) # Branches results @@ -827,6 +1019,7 @@ def test_single_phase_network(single_phase_network: ElectricalNetwork): { "branch_id": "line", "phase": "b", + "branch_type": "line", "current1": 0.005000025000117603 + 0j, "current2": -0.005000025000117603 - 0j, "power1": (19999.94999975 + 0j) * (0.005000025000117603 + 0j).conjugate(), @@ -837,6 +1030,7 @@ def test_single_phase_network(single_phase_network: ElectricalNetwork): { "branch_id": "line", "phase": "n", + "branch_type": "line", "current1": -0.005000025000125 + 0j, "current2": 0.005000025000125 - 0j, "power1": (-0.050000250001249996 + 0j) * (-0.005000025000125 + 0j).conjugate(), @@ -848,7 +1042,8 @@ def test_single_phase_network(single_phase_network: ElectricalNetwork): ) .astype( { - "phase": _PHASE_DTYPE, + "phase": PhaseDtype, + "branch_type": BranchTypeDtype, "current1": complex, "current2": complex, "power1": complex, @@ -859,8 +1054,43 @@ def test_single_phase_network(single_phase_network: ElectricalNetwork): ) .set_index(["branch_id", "phase"]), ) + + # Transformers results + pd.testing.assert_frame_equal( + single_phase_network.res_transformers, + pd.DataFrame.from_records( + [], + columns=[ + "transformer_id", + "phase", + "current1", + "current2", + "power1", + "power2", + "potential1", + "potential2", + "max_power", + "violated", + ], + ) + .astype( + { + "phase": PhaseDtype, + "current1": complex, + "current2": complex, + "power1": complex, + "power2": complex, + "potential1": complex, + "potential2": complex, + "max_power": float, + "violated": pd.BooleanDtype(), + } + ) + .set_index(["transformer_id", "phase"]), + ) # Lines results - expected_res_lines = ( + pd.testing.assert_frame_equal( + single_phase_network.res_lines, pd.DataFrame.from_records( [ { @@ -877,6 +1107,8 @@ def test_single_phase_network(single_phase_network: ElectricalNetwork): + (19999.899999499998 + 0j) * (-0.005000025000117603 - 0j).conjugate() ), "series_current": 0.005000025000117603 + 0j, + "max_current": np.nan, + "violated": None, }, { "line_id": "line", @@ -892,12 +1124,14 @@ def test_single_phase_network(single_phase_network: ElectricalNetwork): + (0j) * (0.005000025000125 - 0j).conjugate() ), "series_current": -0.005000025000125 + 0j, + "max_current": np.nan, + "violated": None, }, ] ) .astype( { - "phase": _PHASE_DTYPE, + "phase": PhaseDtype, "current1": complex, "current2": complex, "power1": complex, @@ -906,11 +1140,41 @@ def test_single_phase_network(single_phase_network: ElectricalNetwork): "potential2": complex, "series_losses": complex, "series_current": complex, + "max_current": float, + "violated": pd.BooleanDtype(), } ) - .set_index(["line_id", "phase"]) + .set_index(["line_id", "phase"]), + ) + # Switches results + pd.testing.assert_frame_equal( + single_phase_network.res_switches, + pd.DataFrame.from_records( + [], + columns=[ + "switch_id", + "phase", + "current1", + "current2", + "power1", + "power2", + "potential1", + "potential2", + ], + ) + .astype( + { + "phase": PhaseDtype, + "current1": complex, + "current2": complex, + "power1": complex, + "power2": complex, + "potential1": complex, + "potential2": complex, + } + ) + .set_index(["switch_id", "phase"]), ) - pd.testing.assert_frame_equal(single_phase_network.res_lines, expected_res_lines) # Loads results pd.testing.assert_frame_equal( single_phase_network.res_loads, @@ -932,12 +1196,12 @@ def test_single_phase_network(single_phase_network: ElectricalNetwork): }, ] ) - .astype({"phase": _PHASE_DTYPE, "current": complex, "power": complex, "potential": complex}) + .astype({"phase": PhaseDtype, "current": complex, "power": complex, "potential": complex}) .set_index(["load_id", "phase"]), ) -def test_network_elements(small_network): +def test_network_elements(small_network: ElectricalNetwork): # Add a line to the network ("bus2" constructor belongs to the network) bus1 = small_network.buses["bus1"] bus2 = Bus("bus2", phases="abcn") @@ -1036,7 +1300,7 @@ def test_network_results_warning(small_network: ElectricalNetwork, good_json_res assert e.value.args[1] == RoseauLoadFlowExceptionCode.LOAD_FLOW_NOT_RUN # Solve a load flow - solve_url = urljoin(ElectricalNetwork.DEFAULT_BASE_URL, "solve/") + solve_url = urljoin(ElectricalNetwork._DEFAULT_BASE_URL, "solve/") with requests_mock.Mocker() as m: m.post(solve_url, status_code=200, json=good_json_results, headers={"content-type": "application/json"}) small_network.solve_load_flow(auth=("", "")) @@ -1112,159 +1376,447 @@ def test_network_results_warning(small_network: ElectricalNetwork, good_json_res def test_load_flow_results_frames(small_network: ElectricalNetwork, good_json_results: dict): small_network.results_from_dict(good_json_results) + small_network.buses["bus0"].min_voltage = 21_000 - def set_index_dtype(df, dtype): - df.index = df.index.set_levels(df.index.levels[1].astype(dtype), level=1) - - expected_res_buses = pd.DataFrame.from_records( - [ - {"bus_id": "bus0", "phase": "a", "potential": 20000 + 2.89120e-18j}, - {"bus_id": "bus0", "phase": "b", "potential": -10000.00000 - 17320.50807j}, - {"bus_id": "bus0", "phase": "c", "potential": -10000.00000 + 17320.50807j}, - {"bus_id": "bus0", "phase": "n", "potential": -1.34764e-12 + 2.89120e-18j}, - {"bus_id": "bus1", "phase": "a", "potential": 19999.94999 + 2.89119e-18j}, - {"bus_id": "bus1", "phase": "b", "potential": -9999.97499 - 17320.46477j}, - {"bus_id": "bus1", "phase": "c", "potential": -9999.97499 + 17320.46477j}, - {"bus_id": "bus1", "phase": "n", "potential": 0j}, - ], - index=["bus_id", "phase"], + # Buses results + expected_res_buses = ( + pd.DataFrame.from_records( + [ + {"bus_id": "bus0", "phase": "a", "potential": 20000 + 2.89120e-18j}, + {"bus_id": "bus0", "phase": "b", "potential": -10000.00000 - 17320.50807j}, + {"bus_id": "bus0", "phase": "c", "potential": -10000.00000 + 17320.50807j}, + {"bus_id": "bus0", "phase": "n", "potential": -1.34764e-12 + 2.89120e-18j}, + {"bus_id": "bus1", "phase": "a", "potential": 19999.94999 + 2.89119e-18j}, + {"bus_id": "bus1", "phase": "b", "potential": -9999.97499 - 17320.46477j}, + {"bus_id": "bus1", "phase": "c", "potential": -9999.97499 + 17320.46477j}, + {"bus_id": "bus1", "phase": "n", "potential": 0j}, + ] + ) + .astype({"bus_id": object, "phase": PhaseDtype, "potential": complex}) + .set_index(["bus_id", "phase"]) ) - set_index_dtype(expected_res_buses, _PHASE_DTYPE) assert_frame_equal(small_network.res_buses, expected_res_buses, rtol=1e-4) - expected_res_branches = pd.DataFrame.from_records( - [ - { - "branch_id": "line", - "phase": "a", - "current1": 0.00500 + 7.22799e-25j, - "current2": -0.00500 - 7.22799e-25j, - "power1": (20000 + 2.89120e-18j) * (0.00500 + 7.22799e-25j).conjugate(), - "power2": (19999.94999 + 2.89119e-18j) * (-0.00500 - 7.22799e-25j).conjugate(), - "potential1": 20000 + 2.89120e-18j, - "potential2": 19999.94999 + 2.89119e-18j, - }, - { - "branch_id": "line", - "phase": "b", - "current1": -0.00250 - 0.00433j, - "current2": 0.00250 + 0.00433j, - "power1": (-10000.00000 - 17320.50807j) * (-0.00250 - 0.00433j).conjugate(), - "power2": (-9999.97499 - 17320.46477j) * (0.00250 + 0.00433j).conjugate(), - "potential1": -10000.00000 - 17320.50807j, - "potential2": -9999.97499 - 17320.46477j, - }, + # Buses voltages results + expected_res_buses_voltages = ( + pd.DataFrame.from_records( + [ + { + "bus_id": "bus0", + "phase": "an", + "voltage": (20000 + 2.89120e-18j) - (-1.34764e-12 + 2.89120e-18j), + "min_voltage": 21_000, + "max_voltage": np.nan, + "violated": True, + }, + { + "bus_id": "bus0", + "phase": "bn", + "voltage": (-10000.00000 - 17320.50807j) - (-1.34764e-12 + 2.89120e-18j), + "min_voltage": 21_000, + "max_voltage": np.nan, + "violated": True, + }, + { + "bus_id": "bus0", + "phase": "cn", + "voltage": (-10000.00000 + 17320.50807j) - (-1.34764e-12 + 2.89120e-18j), + "min_voltage": 21_000, + "max_voltage": np.nan, + "violated": True, + }, + { + "bus_id": "bus1", + "phase": "an", + "voltage": (19999.94999 + 2.89119e-18j) - (0j), + "min_voltage": np.nan, + "max_voltage": np.nan, + "violated": None, + }, + { + "bus_id": "bus1", + "phase": "bn", + "voltage": (-9999.97499 - 17320.46477j) - (0j), + "min_voltage": np.nan, + "max_voltage": np.nan, + "violated": None, + }, + { + "bus_id": "bus1", + "phase": "cn", + "voltage": (-9999.97499 + 17320.46477j) - (0j), + "min_voltage": np.nan, + "max_voltage": np.nan, + "violated": None, + }, + ] + ) + .astype( { - "branch_id": "line", - "phase": "c", - "current1": -0.00250 + 0.00433j, - "current2": 0.00250 - 0.00433j, - "power1": (-10000.00000 + 17320.50807j) * (-0.00250 + 0.00433j).conjugate(), - "power2": (-9999.97499 + 17320.46477j) * (0.00250 - 0.00433j).conjugate(), - "potential1": -10000.00000 + 17320.50807j, - "potential2": -9999.97499 + 17320.46477j, - }, + "bus_id": object, + "phase": VoltagePhaseDtype, + "voltage": complex, + "min_voltage": float, + "max_voltage": float, + "violated": pd.BooleanDtype(), + } + ) + .set_index(["bus_id", "phase"]) + ) + assert_frame_equal(small_network.res_buses_voltages, expected_res_buses_voltages, rtol=1e-4) + + # Branches results + expected_res_branches = ( + pd.DataFrame.from_records( + [ + { + "branch_id": "line", + "phase": "a", + "branch_type": "line", + "current1": 0.00500 + 7.22799e-25j, + "current2": -0.00500 - 7.22799e-25j, + "power1": (20000 + 2.89120e-18j) * (0.00500 + 7.22799e-25j).conjugate(), + "power2": (19999.94999 + 2.89119e-18j) * (-0.00500 - 7.22799e-25j).conjugate(), + "potential1": 20000 + 2.89120e-18j, + "potential2": 19999.94999 + 2.89119e-18j, + }, + { + "branch_id": "line", + "phase": "b", + "branch_type": "line", + "current1": -0.00250 - 0.00433j, + "current2": 0.00250 + 0.00433j, + "power1": (-10000.00000 - 17320.50807j) * (-0.00250 - 0.00433j).conjugate(), + "power2": (-9999.97499 - 17320.46477j) * (0.00250 + 0.00433j).conjugate(), + "potential1": -10000.00000 - 17320.50807j, + "potential2": -9999.97499 - 17320.46477j, + }, + { + "branch_id": "line", + "phase": "c", + "branch_type": "line", + "current1": -0.00250 + 0.00433j, + "current2": 0.00250 - 0.00433j, + "power1": (-10000.00000 + 17320.50807j) * (-0.00250 + 0.00433j).conjugate(), + "power2": (-9999.97499 + 17320.46477j) * (0.00250 - 0.00433j).conjugate(), + "potential1": -10000.00000 + 17320.50807j, + "potential2": -9999.97499 + 17320.46477j, + }, + { + "branch_id": "line", + "phase": "n", + "branch_type": "line", + "current1": -1.34764e-13 + 2.89120e-19j, + "current2": 1.34764e-13 - 2.89120e-19j, + "power1": (-1.34764e-12 + 2.89120e-18j) * (-1.34764e-13 + 2.89120e-19j).conjugate(), + "power2": (0j) * (1.34764e-13 - 2.89120e-19j).conjugate(), + "potential1": -1.34764e-12 + 2.89120e-18j, + "potential2": 0j, + }, + ], + ) + .astype( { - "branch_id": "line", - "phase": "n", - "current1": -1.34764e-13 + 2.89120e-19j, - "current2": 1.34764e-13 - 2.89120e-19j, - "power1": (-1.34764e-12 + 2.89120e-18j) * (-1.34764e-13 + 2.89120e-19j).conjugate(), - "power2": (0j) * (1.34764e-13 - 2.89120e-19j).conjugate(), - "potential1": -1.34764e-12 + 2.89120e-18j, - "potential2": 0j, - }, - ], - index=["branch_id", "phase"], + "branch_id": object, + "phase": PhaseDtype, + "branch_type": BranchTypeDtype, + "current1": complex, + "current2": complex, + "power1": complex, + "power2": complex, + "potential1": complex, + "potential2": complex, + } + ) + .set_index(["branch_id", "phase"]) ) - set_index_dtype(expected_res_branches, _PHASE_DTYPE) assert_frame_equal(small_network.res_branches, expected_res_branches, rtol=1e-4) - expected_res_loads = pd.DataFrame.from_records( - [ - { - "load_id": "load", - "phase": "a", - "current": 0.00500 + 7.22802e-25j, - "power": (19999.94999 + 2.89119e-18j) * (0.00500 + 7.22802e-25j).conjugate(), - "potential": 19999.94999 + 2.89119e-18j, - }, + # Transformers results + expected_res_transformers = ( + pd.DataFrame.from_records( + [], + columns=[ + "transformer_id", + "phase", + "current1", + "current2", + "power1", + "power2", + "potential1", + "potential2", + "max_power", + "violated", + ], + ) + .astype( { - "load_id": "load", - "phase": "b", - "current": -0.00250 - 0.00433j, - "power": (-9999.97499 - 17320.46477j) * (-0.00250 - 0.00433j).conjugate(), - "potential": -9999.97499 - 17320.46477j, - }, + "transformer_id": object, + "phase": PhaseDtype, + "current1": complex, + "current2": complex, + "power1": complex, + "power2": complex, + "potential1": complex, + "potential2": complex, + "max_power": float, + "violated": pd.BooleanDtype(), + } + ) + .set_index(["transformer_id", "phase"]) + ) + assert_frame_equal(small_network.res_transformers, expected_res_transformers) + + # Lines results + expected_res_lines_records = [ + { + "line_id": "line", + "phase": "a", + "current1": 0.00500 + 7.22799e-25j, + "current2": -0.00500 - 7.22799e-25j, + "power1": (20000 + 2.89120e-18j) * (0.00500 + 7.22799e-25j).conjugate(), + "power2": (19999.94999 + 2.89119e-18j) * (-0.00500 - 7.22799e-25j).conjugate(), + "potential1": 20000 + 2.89120e-18j, + "potential2": 19999.94999 + 2.89119e-18j, + "series_losses": ( + (20000 + 2.89120e-18j) * (0.00500 + 7.22799e-25j).conjugate() + + (19999.94999 + 2.89119e-18j) * (-0.00500 - 7.22799e-25j).conjugate() + ), + "series_current": 0.00500 + 7.22799e-25j, + "max_current": np.nan, + "violated": None, + }, + { + "line_id": "line", + "phase": "b", + "current1": -0.00250 - 0.00433j, + "current2": 0.00250 + 0.00433j, + "power1": (-10000.00000 - 17320.50807j) * (-0.00250 - 0.00433j).conjugate(), + "power2": (-9999.97499 - 17320.46477j) * (0.00250 + 0.00433j).conjugate(), + "potential1": -10000.00000 - 17320.50807j, + "potential2": -9999.97499 - 17320.46477j, + "series_losses": ( + (-10000.00000 - 17320.50807j) * (-0.00250 - 0.00433j).conjugate() + + (-9999.97499 - 17320.46477j) * (0.00250 + 0.00433j).conjugate() + ), + "series_current": -0.00250 - 0.00433j, + "max_current": np.nan, + "violated": None, + }, + { + "line_id": "line", + "phase": "c", + "current1": -0.00250 + 0.00433j, + "current2": 0.00250 - 0.00433j, + "power1": (-10000.00000 + 17320.50807j) * (-0.00250 + 0.00433j).conjugate(), + "power2": (-9999.97499 + 17320.46477j) * (0.00250 - 0.00433j).conjugate(), + "potential1": -10000.00000 + 17320.50807j, + "potential2": -9999.97499 + 17320.46477j, + "series_losses": ( + (-10000.00000 + 17320.50807j) * (-0.00250 + 0.00433j).conjugate() + + (-9999.97499 + 17320.46477j) * (0.00250 - 0.00433j).conjugate() + ), + "series_current": -0.00250 + 0.00433j, + "max_current": np.nan, + "violated": None, + }, + { + "line_id": "line", + "phase": "n", + "current1": -1.34764e-13 + 2.89120e-19j, + "current2": 1.34764e-13 - 2.89120e-19j, + "power1": (-1.34764e-12 + 2.89120e-18j) * (-1.34764e-13 + 2.89120e-19j).conjugate(), + "power2": (0j) * (1.34764e-13 - 2.89120e-19j).conjugate(), + "potential1": -1.34764e-12 + 2.89120e-18j, + "potential2": 0j, + "series_losses": ( + (-1.34764e-12 + 2.89120e-18j) * (-1.34764e-13 + 2.89120e-19j).conjugate() + + (0j) * (1.34764e-13 - 2.89120e-19j).conjugate() + ), + "series_current": -1.34764e-13 + 2.89120e-19j, + "max_current": np.nan, + "violated": None, + }, + ] + expected_res_lines_dtypes = { + "line_id": object, + "phase": PhaseDtype, + "current1": complex, + "current2": complex, + "power1": complex, + "power2": complex, + "potential1": complex, + "potential2": complex, + "series_losses": complex, + "series_current": complex, + "max_current": float, + "violated": pd.BooleanDtype(), + } + expected_res_lines = ( + pd.DataFrame.from_records(expected_res_lines_records) + .astype(expected_res_lines_dtypes) + .set_index(["line_id", "phase"]) + ) + assert_frame_equal(small_network.res_lines, expected_res_lines, rtol=1e-4, atol=1e-5) + + # Lines with violated max current + small_network.branches["line"].parameters.max_current = 0.002 + expected_res_lines_violated_records = [ + d | {"max_current": 0.002, "violated": d["phase"] != "n"} for d in expected_res_lines_records + ] + expected_res_violated_lines = ( + pd.DataFrame.from_records(expected_res_lines_violated_records) + .astype(expected_res_lines_dtypes) + .set_index(["line_id", "phase"]) + ) + assert_frame_equal(small_network.res_lines, expected_res_violated_lines, rtol=1e-4, atol=1e-5) + + # Switches results + expected_res_switches = ( + pd.DataFrame.from_records( + [], + columns=[ + "switch_id", + "phase", + "current1", + "current2", + "power1", + "power2", + "potential1", + "potential2", + ], + ) + .astype( { - "load_id": "load", - "phase": "c", - "current": -0.00250 + 0.00433j, - "power": (-9999.97499 + 17320.46477j) * (-0.00250 + 0.00433j).conjugate(), - "potential": -9999.97499 + 17320.46477j, - }, + "switch_id": object, + "phase": PhaseDtype, + "current1": complex, + "current2": complex, + "power1": complex, + "power2": complex, + "potential1": complex, + "potential2": complex, + } + ) + .set_index(["switch_id", "phase"]) + ) + assert_frame_equal(small_network.res_switches, expected_res_switches) + + # Loads results + expected_res_loads = ( + pd.DataFrame.from_records( + [ + { + "load_id": "load", + "phase": "a", + "current": 0.00500 + 7.22802e-25j, + "power": (19999.94999 + 2.89119e-18j) * (0.00500 + 7.22802e-25j).conjugate(), + "potential": 19999.94999 + 2.89119e-18j, + }, + { + "load_id": "load", + "phase": "b", + "current": -0.00250 - 0.00433j, + "power": (-9999.97499 - 17320.46477j) * (-0.00250 - 0.00433j).conjugate(), + "potential": -9999.97499 - 17320.46477j, + }, + { + "load_id": "load", + "phase": "c", + "current": -0.00250 + 0.00433j, + "power": (-9999.97499 + 17320.46477j) * (-0.00250 + 0.00433j).conjugate(), + "potential": -9999.97499 + 17320.46477j, + }, + { + "load_id": "load", + "phase": "n", + "current": -1.34763e-13 + 0j, + "power": (0j) * (-1.34763e-13 + 0j).conjugate(), + "potential": 0j, + }, + ] + ) + .astype( { - "load_id": "load", - "phase": "n", - "current": -1.34763e-13 + 0j, - "power": (0j) * (-1.34763e-13 + 0j).conjugate(), - "potential": 0j, - }, - ], - index=["load_id", "phase"], + "load_id": object, + "phase": PhaseDtype, + "current": complex, + "power": complex, + "potential": complex, + } + ) + .set_index(["load_id", "phase"]) ) - set_index_dtype(expected_res_loads, _PHASE_DTYPE) assert_frame_equal(small_network.res_loads, expected_res_loads, rtol=1e-4) - expected_res_sources = pd.DataFrame.from_records( - [ - { - "source_id": "vs", - "phase": "a", - "current": -0.00500 + 0j, - "power": (20000 + 2.89120e-18j) * (-0.00500 + 0j).conjugate(), - "potential": 20000 + 2.89120e-18j, - }, - { - "source_id": "vs", - "phase": "b", - "current": 0.00250 + 0.00433j, - "power": (-10000.00000 - 17320.50807j) * (0.00250 + 0.00433j).conjugate(), - "potential": -10000.00000 - 17320.50807j, - }, - { - "source_id": "vs", - "phase": "c", - "current": 0.00250 - 0.00433j, - "power": (-10000.00000 + 17320.50807j) * (0.00250 - 0.00433j).conjugate(), - "potential": -10000.00000 + 17320.50807j, - }, + # Sources results + expected_res_sources = ( + pd.DataFrame.from_records( + [ + { + "source_id": "vs", + "phase": "a", + "current": -0.00500 + 0j, + "power": (20000 + 2.89120e-18j) * (-0.00500 + 0j).conjugate(), + "potential": 20000 + 2.89120e-18j, + }, + { + "source_id": "vs", + "phase": "b", + "current": 0.00250 + 0.00433j, + "power": (-10000.00000 - 17320.50807j) * (0.00250 + 0.00433j).conjugate(), + "potential": -10000.00000 - 17320.50807j, + }, + { + "source_id": "vs", + "phase": "c", + "current": 0.00250 - 0.00433j, + "power": (-10000.00000 + 17320.50807j) * (0.00250 - 0.00433j).conjugate(), + "potential": -10000.00000 + 17320.50807j, + }, + { + "source_id": "vs", + "phase": "n", + "current": 1.34764e-13 - 2.89121e-19j, + "power": (-1.34764e-12 + 2.89120e-18j) * (1.34764e-13 - 2.89121e-19j).conjugate(), + "potential": -1.34764e-12 + 2.89120e-18j, + }, + ] + ) + .astype( { - "source_id": "vs", - "phase": "n", - "current": 1.34764e-13 - 2.89121e-19j, - "power": (-1.34764e-12 + 2.89120e-18j) * (1.34764e-13 - 2.89121e-19j).conjugate(), - "potential": -1.34764e-12 + 2.89120e-18j, - }, - ], - index=["source_id", "phase"], + "source_id": object, + "phase": PhaseDtype, + "current": complex, + "power": complex, + "potential": complex, + } + ) + .set_index(["source_id", "phase"]) ) - set_index_dtype(expected_res_sources, _PHASE_DTYPE) assert_frame_equal(small_network.res_sources, expected_res_sources, rtol=1e-4) - expected_res_grounds = pd.DataFrame.from_records( - [ - {"ground_id": "ground", "potential": 0j}, - ], - index=["ground_id"], + # Grounds results + expected_res_grounds = ( + pd.DataFrame.from_records( + [ + {"ground_id": "ground", "potential": 0j}, + ] + ) + .astype({"ground_id": object, "potential": complex}) + .set_index(["ground_id"]) ) assert_frame_equal(small_network.res_grounds, expected_res_grounds) - expected_res_potential_refs = pd.DataFrame.from_records( - [ - {"potential_ref_id": "pref", "current": 1.08420e-18 - 2.89120e-19j}, - ], - index=["potential_ref_id"], + # Potential refs results + expected_res_potential_refs = ( + pd.DataFrame.from_records( + [ + {"potential_ref_id": "pref", "current": 1.08420e-18 - 2.89120e-19j}, + ] + ) + .astype({"potential_ref_id": object, "current": complex}) + .set_index(["potential_ref_id"]) ) assert_frame_equal(small_network.res_potential_refs, expected_res_potential_refs) @@ -1283,34 +1835,36 @@ def set_index_dtype(df, dtype): [99.99999999999994, 0.0], ] small_network.results_from_dict(good_json_results) - expected_res_flex_powers = pd.DataFrame.from_records( - [ - { - "load_id": "load", - "phase": "an", - "power": 99.99999999999994 + 0j, - }, - { - "load_id": "load", - "phase": "bn", - "power": 99.99999999999994 + 0j, - }, - { - "load_id": "load", - "phase": "cn", - "power": 99.99999999999994 + 0j, - }, - ], - index=["load_id", "phase"], + expected_res_flex_powers = ( + pd.DataFrame.from_records( + [ + { + "load_id": "load", + "phase": "an", + "power": 99.99999999999994 + 0j, + }, + { + "load_id": "load", + "phase": "bn", + "power": 99.99999999999994 + 0j, + }, + { + "load_id": "load", + "phase": "cn", + "power": 99.99999999999994 + 0j, + }, + ] + ) + .astype({"load_id": object, "phase": VoltagePhaseDtype, "power": complex}) + .set_index(["load_id", "phase"]) ) - set_index_dtype(expected_res_flex_powers, _VOLTAGE_PHASES_DTYPE) assert_frame_equal(small_network.res_loads_flexible_powers, expected_res_flex_powers, rtol=1e-4) def test_solver_warm_start(small_network: ElectricalNetwork, good_json_results): load: PowerLoad = small_network.loads["load"] load_bus = small_network.buses["bus1"] - solve_url = urljoin(ElectricalNetwork.DEFAULT_BASE_URL, "solve/") + solve_url = urljoin(ElectricalNetwork._DEFAULT_BASE_URL, "solve/") headers = {"Content-Type": "application/json"} def json_callback(request, context): @@ -1501,37 +2055,37 @@ def test_print_catalogue(): # Print the entire catalogue with console.capture() as capture: ElectricalNetwork.print_catalogue() - assert len(capture.get().split("\n")) == 88 + assert len(capture.get().split("\n")) == 46 # Filter on the network name with console.capture() as capture: ElectricalNetwork.print_catalogue(name="MV") - assert len(capture.get().split("\n")) == 48 + assert len(capture.get().split("\n")) == 26 with console.capture() as capture: ElectricalNetwork.print_catalogue(name=re.compile(r"^MV")) - assert len(capture.get().split("\n")) == 48 + assert len(capture.get().split("\n")) == 26 # Filter on the load point name with console.capture() as capture: ElectricalNetwork.print_catalogue(load_point_name="winter") - assert len(capture.get().split("\n")) == 88 + assert len(capture.get().split("\n")) == 46 with console.capture() as capture: ElectricalNetwork.print_catalogue(load_point_name=re.compile(r"^Winter")) - assert len(capture.get().split("\n")) == 88 + assert len(capture.get().split("\n")) == 46 # Filter on both with console.capture() as capture: ElectricalNetwork.print_catalogue(name="MV", load_point_name="winter") - assert len(capture.get().split("\n")) == 48 + assert len(capture.get().split("\n")) == 26 with console.capture() as capture: ElectricalNetwork.print_catalogue(name="MV", load_point_name=re.compile(r"^Winter")) - assert len(capture.get().split("\n")) == 48 + assert len(capture.get().split("\n")) == 26 with console.capture() as capture: ElectricalNetwork.print_catalogue(name=re.compile(r"^MV"), load_point_name="winter") - assert len(capture.get().split("\n")) == 48 + assert len(capture.get().split("\n")) == 26 with console.capture() as capture: ElectricalNetwork.print_catalogue(name=re.compile(r"^MV"), load_point_name=re.compile(r"^Winter")) - assert len(capture.get().split("\n")) == 48 + assert len(capture.get().split("\n")) == 26 # Regexp error with console.capture() as capture: @@ -1539,4 +2093,19 @@ def test_print_catalogue(): assert len(capture.get().split("\n")) == 2 with console.capture() as capture: ElectricalNetwork.print_catalogue(load_point_name=r"^winter[0-]") - assert len(capture.get().split("\n")) == 3 + assert len(capture.get().split("\n")) == 2 + + +def test_to_graph(small_network: ElectricalNetwork): + g = small_network.to_graph() + assert isinstance(g, nx.Graph) + assert sorted(g.nodes) == sorted(small_network.buses) + assert sorted(g.edges) == sorted((b.bus1.id, b.bus2.id) for b in small_network.branches.values()) + + for bus in small_network.buses.values(): + node_data = g.nodes[bus.id] + assert node_data["geom"] == bus.geometry + + for branch in small_network.branches.values(): + edge_data = g.edges[branch.bus1.id, branch.bus2.id] + assert edge_data == {"id": branch.id, "type": branch.branch_type, "geom": branch.geometry} diff --git a/roseau/load_flow/tests/test_solvers.py b/roseau/load_flow/tests/test_solvers.py index f684d869..ac03fa59 100644 --- a/roseau/load_flow/tests/test_solvers.py +++ b/roseau/load_flow/tests/test_solvers.py @@ -1,6 +1,6 @@ import pytest -from roseau.load_flow import RoseauLoadFlowException, RoseauLoadFlowExceptionCode +from roseau.load_flow.exceptions import RoseauLoadFlowException, RoseauLoadFlowExceptionCode from roseau.load_flow.solvers import check_solver_params diff --git a/roseau/load_flow/tests/test_wrapper.py b/roseau/load_flow/tests/test_wrapper.py new file mode 100644 index 00000000..89f5c9d2 --- /dev/null +++ b/roseau/load_flow/tests/test_wrapper.py @@ -0,0 +1,105 @@ +import operator + +import pytest +from pint import DimensionalityError + +from roseau.load_flow import ureg +from roseau.load_flow.units import ureg_wraps + + +def test_wraps(): + def func(x): + return x + + with pytest.raises(TypeError): + ureg_wraps((3 * ureg.meter, [None])) + with pytest.raises(TypeError): + ureg_wraps((None, [3 * ureg.meter])) + + f0 = ureg_wraps(None, [None])(func) + assert f0(3.0) == 3.0 + + f0 = ureg_wraps(None, None)(func) + assert f0(3.0) == 3.0 + + f1 = ureg_wraps(None, ["meter"])(func) + assert f1(3.0 * ureg.centimeter) == 0.03 + assert f1(3.0 * ureg.meter) == 3.0 + with pytest.raises(DimensionalityError): + f1(3 * ureg.second) + + f1b = ureg_wraps(None, [ureg.meter])(func) + assert f1b(3.0 * ureg.centimeter) == 0.03 + assert f1b(3.0 * ureg.meter) == 3.0 + with pytest.raises(DimensionalityError): + f1b(3 * ureg.second) + + f1c = ureg_wraps("meter", [ureg.meter])(func) + assert f1c(3.0 * ureg.centimeter) == 0.03 * ureg.meter + assert f1c(3.0 * ureg.meter) == 3.0 * ureg.meter + with pytest.raises(DimensionalityError): + f1c(3 * ureg.second) + + f1d = ureg_wraps(ureg.meter, [ureg.meter])(func) + assert f1d(3.0 * ureg.centimeter) == 0.03 * ureg.meter + assert f1d(3.0 * ureg.meter) == 3.0 * ureg.meter + with pytest.raises(DimensionalityError): + f1d(3 * ureg.second) + + f1 = ureg_wraps(None, "meter")(func) + assert f1(3.0 * ureg.centimeter) == 0.03 + assert f1(3.0 * ureg.meter) == 3.0 + with pytest.raises(DimensionalityError): + f1(3 * ureg.second) + + f2 = ureg_wraps("centimeter", ["meter"])(func) + assert f2(3.0 * ureg.centimeter) == 0.03 * ureg.centimeter + assert f2(3.0 * ureg.meter) == 3 * ureg.centimeter + assert f2(3) == 3 * ureg.centimeter + + gfunc = operator.add + + g0 = ureg_wraps(None, [None, None])(gfunc) + assert g0(3, 1) == 4 + + g1 = ureg_wraps(None, ["meter", "centimeter"])(gfunc) + assert g1(3 * ureg.meter, 1 * ureg.centimeter) == 4 + assert g1(3 * ureg.meter, 1 * ureg.meter) == 3 + 100 + + def hfunc(x, y): + return x, y + + h0 = ureg_wraps(None, [None, None])(hfunc) + assert h0(3, 1) == (3, 1) + + h1 = ureg_wraps(["meter", "centimeter"], [None, None])(hfunc) + assert h1(3, 1) == [3 * ureg.meter, 1 * ureg.cm] + + h2 = ureg_wraps(("meter", "centimeter"), [None, None])(hfunc) + assert h2(3, 1) == (3 * ureg.meter, 1 * ureg.cm) + + h3 = ureg_wraps((None,), (None, None))(hfunc) + assert h3(3, 1) == (3, 1) + + def kfunc(a, /, b, c=5, *, d=6): + return a, b, c, d + + k1 = ureg_wraps((None,), (None, None, None, None))(kfunc) + assert k1(1, 2, 3, d=4) == (1, 2, 3, 4) + assert k1(1, 2, c=3, d=4) == (1, 2, 3, 4) + assert k1(1, b=2, c=3, d=4) == (1, 2, 3, 4) + assert k1(1, d=4, b=2, c=3) == (1, 2, 3, 4) + assert k1(1, 2, c=3) == (1, 2, 3, 6) + assert k1(1, 2, d=4) == (1, 2, 5, 4) + assert k1(1, 2) == (1, 2, 5, 6) + + k2 = ureg_wraps((None,), ("meter", "centimeter", "meter", "centimeter"))(kfunc) + assert k2(1 * ureg.meter, 2 * ureg.centimeter, 3 * ureg.meter, d=4 * ureg.centimeter) == (1, 2, 3, 4) + + def lfunc(a): + return a[0] + + l1 = ureg_wraps("centimeter", ("meter",))(lfunc) + assert l1([1, 2]) == 1 * ureg.centimeter + assert l1([1, 2] * ureg.meter) == 1 * ureg.centimeter + assert l1([1 * ureg.meter, 2 * ureg.meter]) == 1 * ureg.centimeter diff --git a/roseau/load_flow/typing.py b/roseau/load_flow/typing.py index b779a96c..fc202af2 100644 --- a/roseau/load_flow/typing.py +++ b/roseau/load_flow/typing.py @@ -1,9 +1,14 @@ """ Type Aliases used by Roseau Load Flow. +.. warning:: + + Types defined in this module are not part of the public API. You can use these types in your + code, but they are not guaranteed to be stable. + .. class:: Id - The type of the identifier of an element. + The type of the identifier of an element. An element's ID can be an integer or a string. .. class:: JsonDict @@ -11,30 +16,54 @@ .. class:: StrPath - The accepted type for files of roseau.load_flow.io. + The accepted type for file paths in roseau.load_flow. This is a string or a path-like object. .. class:: ControlType - Available types of control for flexible loads. + Available control types for flexible loads. .. class:: ProjectionType - Available types of projections for flexible loads control. + Available projections types for flexible loads control. .. class:: Solver Available solvers for the load flow computation. + +.. class:: Authentication + + Valid authentication types used to connect to the Roseau Load Flow solver API. + +.. class:: MapOrSeq + + A mapping from element IDs to elements or a sequence of elements of unique IDs. + +.. class:: ComplexArray + + A numpy array of complex numbers. + +.. class:: ComplexArrayLike1D + + A 1D array-like of complex numbers or a quantity of complex numbers. An array-like is a + sequence or a numpy array. + +.. class:: ComplexArrayLike2D + + A 2D array-like of complex numbers or a quantity of complex numbers. An array-like is a + sequence or a numpy array. """ import os -import sys -from typing import TYPE_CHECKING, Any, Literal, Union +from collections.abc import Mapping, Sequence +from typing import Any, Literal, TypeVar, Union -if sys.version_info >= (3, 10): - from typing import TypeAlias as TypeAlias -elif TYPE_CHECKING: - from typing_extensions import TypeAlias as TypeAlias -else: - TypeAlias = Any +import numpy as np +from numpy.typing import NDArray +from requests.auth import HTTPBasicAuth +from typing_extensions import TypeAlias + +from roseau.load_flow.units import Q_ + +T = TypeVar("T") Id: TypeAlias = Union[int, str] JsonDict: TypeAlias = dict[str, Any] @@ -42,6 +71,34 @@ ControlType: TypeAlias = Literal["constant", "p_max_u_production", "p_max_u_consumption", "q_u"] ProjectionType: TypeAlias = Literal["euclidean", "keep_p", "keep_q"] Solver: TypeAlias = Literal["newton", "newton_goldstein"] - - -__all__ = ["Id", "JsonDict", "StrPath", "ControlType", "ProjectionType", "Solver"] +Authentication: TypeAlias = Union[tuple[str, str], HTTPBasicAuth] +MapOrSeq: TypeAlias = Union[Mapping[Id, T], Sequence[T]] +ComplexArray: TypeAlias = NDArray[np.complex128] +# TODO: improve the types below when shape-typing becomes supported +ComplexArrayLike1D: TypeAlias = Union[ + ComplexArray, + Q_[ComplexArray], + Q_[Sequence[complex]], + Sequence[Union[complex, Q_[complex]]], +] +ComplexArrayLike2D: TypeAlias = Union[ + ComplexArray, + Q_[ComplexArray], + Q_[Sequence[Sequence[complex]]], + Sequence[Sequence[Union[complex, Q_[complex]]]], +] + + +__all__ = [ + "Id", + "JsonDict", + "StrPath", + "ControlType", + "ProjectionType", + "Solver", + "Authentication", + "MapOrSeq", + "ComplexArray", + "ComplexArrayLike1D", + "ComplexArrayLike2D", +] diff --git a/roseau/load_flow/units.py b/roseau/load_flow/units.py index 07c774ba..f580f0e9 100644 --- a/roseau/load_flow/units.py +++ b/roseau/load_flow/units.py @@ -3,21 +3,33 @@ .. class:: ureg - The :class:`~pint.UnitRegistry` object to use in this project. + The :class:`pint.UnitRegistry` object to use in this project. You should not need to use it + directly. .. class:: Q_ - The :class:`~pint.Quantity` class to use in this project. + The :class:`pint.Quantity` class to use in this project. You can use it to provide quantities + in units different from the default ones. For example, to create a constant power load of 1 MVA, + you can do: + + >>> load = lf.PowerLoad("load", bus=bus, powers=Q_([1, 1, 1], "MVA")) + + which is equivalent to: + + >>> load = lf.PowerLoad("load", bus=bus, powers=[1000000, 1000000, 1000000]) # in VA .. _pint: https://pint.readthedocs.io/en/stable/getting/overview.html """ from collections.abc import Callable, Iterable +from types import GenericAlias from typing import TYPE_CHECKING, TypeVar, Union from pint import Unit, UnitRegistry from pint.facets.plain import PlainQuantity from typing_extensions import TypeAlias +from roseau.load_flow._wrapper import wraps + T = TypeVar("T") FuncT = TypeVar("FuncT", bound=Callable) @@ -26,12 +38,13 @@ lambda s: s.replace("%", " percent "), ] ) +ureg.define("volt_ampere_reactive = 1 * volt_ampere = VAr") if TYPE_CHECKING: Q_: TypeAlias = PlainQuantity[T] else: Q_ = ureg.Quantity - Q_.__class_getitem__ = lambda cls, *args: cls + Q_.__class_getitem__ = classmethod(GenericAlias) def ureg_wraps( @@ -39,4 +52,14 @@ def ureg_wraps( args: Union[str, Unit, None, Iterable[Union[str, Unit, None]]], strict: bool = True, ) -> Callable[[FuncT], FuncT]: - return ureg.wraps(ret, args, strict) + """Wraps a function to become pint-aware. + + Args: + ret: + Units of each of the return values. Use `None` to skip argument conversion. + args: + Units of each of the input arguments. Use `None` to skip argument conversion. + strict: + Indicates that only quantities are accepted. (Default value = True) + """ + return wraps(ureg, ret, args) diff --git a/roseau/load_flow/utils/__init__.py b/roseau/load_flow/utils/__init__.py index 99e7cb65..e774d8d0 100644 --- a/roseau/load_flow/utils/__init__.py +++ b/roseau/load_flow/utils/__init__.py @@ -1,10 +1,17 @@ """ This module contains utility classes and functions for Roseau Load Flow. """ -from roseau.load_flow.utils.console import console +from roseau.load_flow.utils.console import console, palette from roseau.load_flow.utils.constants import CX, DELTA_P, EPSILON_0, EPSILON_R, MU_0, MU_R, OMEGA, PI, RHO, TAN_D, F from roseau.load_flow.utils.mixins import CatalogueMixin, Identifiable, JsonMixin -from roseau.load_flow.utils.types import ConductorType, InsulatorType, LineType +from roseau.load_flow.utils.types import ( + BranchTypeDtype, + ConductorType, + InsulatorType, + LineType, + PhaseDtype, + VoltagePhaseDtype, +) __all__ = [ # Constants @@ -27,6 +34,11 @@ "LineType", "ConductorType", "InsulatorType", + # Dtypes + "PhaseDtype", + "VoltagePhaseDtype", + "BranchTypeDtype", # Console "console", + "palette", ] diff --git a/roseau/load_flow/utils/_optional_deps.py b/roseau/load_flow/utils/_optional_deps.py new file mode 100644 index 00000000..99be07f5 --- /dev/null +++ b/roseau/load_flow/utils/_optional_deps.py @@ -0,0 +1,42 @@ +import logging +from typing import TYPE_CHECKING, Any + +from roseau.load_flow.exceptions import RoseauLoadFlowException, RoseauLoadFlowExceptionCode + +if TYPE_CHECKING: + import networkx as networkx + from matplotlib import pyplot as pyplot + +logger = logging.getLogger(__name__) + +__all__ = [ + "pyplot", + "networkx", +] + + +def __getattr__(name: str) -> Any: + if name == "pyplot": + try: + import matplotlib.pyplot + except ImportError as e: + msg = ( + 'matplotlib is required for plotting. Install it with the "plot" extra using ' + '`pip install -U "roseau-load-flow[plot]"`' + ) + logger.error(msg) + raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.IMPORT_ERROR) from e + return matplotlib.pyplot + elif name == "networkx": + try: + import networkx + except ImportError as e: + msg = ( + 'networkx is not installed. Install it with the "graph" extra using ' + '`pip install -U "roseau-load-flow[graph]"`' + ) + logger.error(msg) + raise RoseauLoadFlowException(msg=msg, code=RoseauLoadFlowExceptionCode.IMPORT_ERROR) from e + return networkx + else: + raise AttributeError(f"module {__name__} has no attribute {name!r}") diff --git a/roseau/load_flow/utils/console.py b/roseau/load_flow/utils/console.py index a9463afd..c16e9c7f 100644 --- a/roseau/load_flow/utils/console.py +++ b/roseau/load_flow/utils/console.py @@ -1,3 +1,25 @@ from rich.console import Console console = Console() + +palette = [ + "#4c72b0", + "#dd8452", + "#55a868", + "#c44e52", + "#8172b3", + "#937860", + "#da8bc3", + "#8c8c8c", + "#ccb974", + "#64b5cd", +] +"""Color palette for the catalogue tables. + +This is seaborn's default color palette. Generated with: +```python +import seaborn as sns +sns.set_theme() +list(sns.color_palette().as_hex()) +``` +""" diff --git a/roseau/load_flow/utils/constants.py b/roseau/load_flow/utils/constants.py index eae33041..c1d7b586 100644 --- a/roseau/load_flow/utils/constants.py +++ b/roseau/load_flow/utils/constants.py @@ -38,8 +38,8 @@ ConductorType.CU: Q_(1.2566e-8, "H/m"), ConductorType.AL: Q_(1.2566e-8, "H/m"), ConductorType.AM: Q_(1.2566e-8, "H/m"), - ConductorType.AA: np.nan, # TODO - ConductorType.LA: np.nan, # TODO + ConductorType.AA: Q_(np.nan, "H/m"), # TODO + ConductorType.LA: Q_(np.nan, "H/m"), # TODO } """Magnetic permeability of common conductor materials (H/m).""" @@ -47,8 +47,8 @@ ConductorType.CU: Q_(9.3, "mm"), ConductorType.AL: Q_(112, "mm"), ConductorType.AM: Q_(12.9, "mm"), - ConductorType.AA: np.nan, # TODO - ConductorType.LA: np.nan, # TODO + ConductorType.AA: Q_(np.nan, "mm"), # TODO + ConductorType.LA: Q_(np.nan, "mm"), # TODO } """Skin effect of common conductor materials (mm).""" diff --git a/roseau/load_flow/utils/mixins.py b/roseau/load_flow/utils/mixins.py index 6e5c82d7..9f3c3f7d 100644 --- a/roseau/load_flow/utils/mixins.py +++ b/roseau/load_flow/utils/mixins.py @@ -53,12 +53,12 @@ def from_json(cls, path: StrPath) -> Self: return cls.from_dict(data=data) @abstractmethod - def to_dict(self, include_geometry: bool = True) -> JsonDict: + def to_dict(self, *, _lf_only: bool = False) -> JsonDict: """Return the element information as a dictionary format. Args: - include_geometry: - If False, the geometry will not be added to the result dictionary. + _lf_only: + Internal argument, please do not use. """ raise NotImplementedError diff --git a/roseau/load_flow/utils/types.py b/roseau/load_flow/utils/types.py index 5e2f8be0..e792f510 100644 --- a/roseau/load_flow/utils/types.py +++ b/roseau/load_flow/utils/types.py @@ -1,6 +1,7 @@ import logging from enum import Enum, auto, unique +import pandas as pd from typing_extensions import Self from roseau.load_flow.exceptions import RoseauLoadFlowException, RoseauLoadFlowExceptionCode @@ -9,6 +10,48 @@ logger = logging.getLogger(__name__) +# pandas dtypes used in the data frames +PhaseDtype = pd.CategoricalDtype(categories=["a", "b", "c", "n"], ordered=True) +"""Categorical data type used for the phase of potentials, currents, powers, etc.""" +VoltagePhaseDtype = pd.CategoricalDtype(categories=["an", "bn", "cn", "ab", "bc", "ca"], ordered=True) +"""Categorical data type used for the phase of voltages and flexible powers only.""" +BranchTypeDtype = pd.CategoricalDtype(categories=["line", "transformer", "switch"], ordered=True) +"""Categorical data type used for branch types.""" +_DTYPES = { + "bus_id": object, + "branch_id": object, + "transformer_id": object, + "line_id": object, + "switch_id": object, + "load_id": object, + "source_id": object, + "ground_id": object, + "potential_ref_id": object, + "branch_type": BranchTypeDtype, + "phase": PhaseDtype, + "current": complex, + "current1": complex, + "current2": complex, + "power": complex, + "power1": complex, + "power2": complex, + "potential": complex, + "potential1": complex, + "potential2": complex, + "voltage": complex, + "voltage1": complex, + "voltage2": complex, + "max_power": float, + "series_losses": complex, + "shunt_losses": complex, + "series_current": complex, + "max_current": float, + "min_voltage": float, + "max_voltage": float, + "violated": pd.BooleanDtype(), +} + + @unique class LineType(Enum): """The type of a line."""