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."""